mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-11-04 05:15:22 +00:00 
			
		
		
		
	ARI Outbound Websockets
Asterisk can now establish websocket sessions _to_ your ARI applications
as well as accepting websocket sessions _from_ them.
Full details: http://s.asterisk.net/ari-outbound-ws
Code change summary:
* Added an ast_vector_string_join() function,
* Added ApplicationRegistered and ApplicationUnregistered ARI events.
* Converted res/ari/config.c to use sorcery to process ari.conf.
* Added the "outbound-websocket" ARI config object.
* Refactored res/ari/ari_websockets.c to handle outbound websockets.
* Refactored res/ari/cli.c for the sorcery changeover.
* Updated res/res_stasis.c for the sorcery changeover.
* Updated apps/app_stasis.c to allow initiating per-call outbound websockets.
* Added CLI commands to manage ARI websockets.
* Added the new "outbound-websocket" object to ari.conf.sample.
* Moved the ARI XML documentation out of res_ari.c into res/ari/ari_doc.xml
UserNote: Asterisk can now establish websocket sessions _to_ your ARI applications
as well as accepting websocket sessions _from_ them.
Full details: http://s.asterisk.net/ari-outbound-ws
(cherry picked from commit 1c0d552155)
			
			
This commit is contained in:
		@@ -25,12 +25,14 @@
 | 
			
		||||
 | 
			
		||||
/*** MODULEINFO
 | 
			
		||||
	<depend>res_stasis</depend>
 | 
			
		||||
	<depend>res_ari</depend>
 | 
			
		||||
	<support_level>core</support_level>
 | 
			
		||||
 ***/
 | 
			
		||||
 | 
			
		||||
#include "asterisk.h"
 | 
			
		||||
 | 
			
		||||
#include "asterisk/app.h"
 | 
			
		||||
#include "asterisk/ari.h"
 | 
			
		||||
#include "asterisk/module.h"
 | 
			
		||||
#include "asterisk/pbx.h"
 | 
			
		||||
#include "asterisk/stasis.h"
 | 
			
		||||
@@ -86,6 +88,7 @@ static const char *stasis = "Stasis";
 | 
			
		||||
static int app_exec(struct ast_channel *chan, const char *data)
 | 
			
		||||
{
 | 
			
		||||
	char *parse = NULL;
 | 
			
		||||
	char *connection_id;
 | 
			
		||||
	int ret = -1;
 | 
			
		||||
 | 
			
		||||
	AST_DECLARE_APP_ARGS(args,
 | 
			
		||||
@@ -104,13 +107,35 @@ static int app_exec(struct ast_channel *chan, const char *data)
 | 
			
		||||
 | 
			
		||||
	if (args.argc < 1) {
 | 
			
		||||
		ast_log(LOG_WARNING, "Stasis app_name argument missing\n");
 | 
			
		||||
	} else {
 | 
			
		||||
		ret = stasis_app_exec(chan,
 | 
			
		||||
		                      args.app_name,
 | 
			
		||||
		                      args.argc - 1,
 | 
			
		||||
		                      args.app_argv);
 | 
			
		||||
		goto done;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (stasis_app_is_registered(args.app_name)) {
 | 
			
		||||
		ast_debug(3, "%s: App '%s' is already registered\n",
 | 
			
		||||
			ast_channel_name(chan), args.app_name);
 | 
			
		||||
		ret = stasis_app_exec(chan, args.app_name, args.argc - 1, args.app_argv);
 | 
			
		||||
		goto done;
 | 
			
		||||
	}
 | 
			
		||||
	ast_debug(3, "%s: App '%s' is NOT already registered\n",
 | 
			
		||||
		ast_channel_name(chan), args.app_name);
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * The app isn't registered so we need to see if we have a
 | 
			
		||||
	 * per-call outbound websocket config we can use.
 | 
			
		||||
	 * connection_id will be freed by ast_ari_close_per_call_websocket().
 | 
			
		||||
	 */
 | 
			
		||||
	connection_id = ast_ari_create_per_call_websocket(args.app_name, chan);
 | 
			
		||||
	if (ast_strlen_zero(connection_id)) {
 | 
			
		||||
		ast_log(LOG_WARNING,
 | 
			
		||||
			"%s: Stasis app '%s' doesn't exist\n",
 | 
			
		||||
			ast_channel_name(chan), args.app_name);
 | 
			
		||||
		goto done;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ret = stasis_app_exec(chan, connection_id, args.argc - 1, args.app_argv);
 | 
			
		||||
	ast_ari_close_per_call_websocket(connection_id);
 | 
			
		||||
 | 
			
		||||
done:
 | 
			
		||||
	if (ret) {
 | 
			
		||||
		/* set ret to 0 so pbx_core doesnt hangup the channel */
 | 
			
		||||
		if (!ast_check_hangup(chan)) {
 | 
			
		||||
@@ -140,5 +165,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Stasis dialplan applicat
 | 
			
		||||
	.support_level = AST_MODULE_SUPPORT_CORE,
 | 
			
		||||
	.load = load_module,
 | 
			
		||||
	.unload = unload_module,
 | 
			
		||||
	.requires = "res_stasis",
 | 
			
		||||
	.requires = "res_stasis,res_ari",
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -35,3 +35,24 @@ enabled = yes       ; When set to no, ARI support is disabled.
 | 
			
		||||
; When set to plain, the password is in plaintext.
 | 
			
		||||
;
 | 
			
		||||
;password_format = plain
 | 
			
		||||
 | 
			
		||||
; Outbound Websocket Connections
 | 
			
		||||
;
 | 
			
		||||
;[connection1]                 ; The connection name
 | 
			
		||||
;type = outbound_websocket     ; Must be "outbound_websocket"
 | 
			
		||||
;websocket_client_id = myid    ; The id of a websocket client defined in
 | 
			
		||||
                               ; websocket_client.conf.
 | 
			
		||||
                               ; Default: none
 | 
			
		||||
;apps = app1, app2             ; A comma-separated list of Stasis applications
 | 
			
		||||
                               ; that will be served by this connection.
 | 
			
		||||
                               ; No other connection may serve these apps.
 | 
			
		||||
                               ; Default: none
 | 
			
		||||
;subscribe_all = no            ; If set to "yes", the server will receive all
 | 
			
		||||
                               ; events just as though "subscribeAll=true" was
 | 
			
		||||
                               ; specified on an incoming websocket connection.
 | 
			
		||||
                               ; Default: no
 | 
			
		||||
;local_ari_user = local_user   ; The name of a local ARI user defined above.
 | 
			
		||||
                               ; This controls whether this connection can make
 | 
			
		||||
                               ; read/write requests or is read-only.
 | 
			
		||||
                               ; Default: none
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -244,4 +244,51 @@ void ast_ari_response_created(struct ast_ari_response *response,
 | 
			
		||||
 */
 | 
			
		||||
void ast_ari_response_alloc_failed(struct ast_ari_response *response);
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Create a per-call outbound websocket connection.
 | 
			
		||||
 *
 | 
			
		||||
 * \param app_name The app name.
 | 
			
		||||
 * \param channel The channel to create the websocket for.
 | 
			
		||||
 *
 | 
			
		||||
 * This function should really only be called by app_stasis.
 | 
			
		||||
 *
 | 
			
		||||
 * A "per_call" websocket configuration must already exist in
 | 
			
		||||
 * ari.conf that has 'app_name' in its 'apps' parameter.
 | 
			
		||||
 *
 | 
			
		||||
 * The channel uniqueid is used to create a unique app_id
 | 
			
		||||
 * composed of "<app_name>-<channel_uniqueid>" which will be
 | 
			
		||||
 * returned from this call.  This ID will be used to register
 | 
			
		||||
 * an ephemeral Stasis application and should be used as the
 | 
			
		||||
 * app_name for the call to stasis_app_exec().  When
 | 
			
		||||
 * stasis_app_exec() returns, ast_ari_close_per_call_websocket()
 | 
			
		||||
 * must be called with the app_id to close the websocket.
 | 
			
		||||
 *
 | 
			
		||||
 * The channel unique id is also used to detect when the
 | 
			
		||||
 * StasisEnd event is sent for the channel.  It's how
 | 
			
		||||
 * ast_ari_close_per_call_websocket() knows that all
 | 
			
		||||
 * messages for the channel have been sent and it's safe
 | 
			
		||||
 * to close the websocket.
 | 
			
		||||
 *
 | 
			
		||||
 * \retval The ephemeral application id or NULL if one could
 | 
			
		||||
 *         not be created. This pointer will be freed by
 | 
			
		||||
 *         ast_ari_close_per_call_websocket().  Do not free
 | 
			
		||||
 *         it yourself.
 | 
			
		||||
 */
 | 
			
		||||
char *ast_ari_create_per_call_websocket(const char *app_name,
 | 
			
		||||
	struct ast_channel *channel);
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Close a per-call outbound websocket connection.
 | 
			
		||||
 *
 | 
			
		||||
 * \param app_id The ephemeral application id returned by
 | 
			
		||||
 *               ast_ari_create_per_call_websocket().
 | 
			
		||||
 *
 | 
			
		||||
 * This function should really only be called by app_stasis.
 | 
			
		||||
 *
 | 
			
		||||
 * \note This call will block until all messages for the
 | 
			
		||||
 *       channel have been sent or 5 seconds has elapsed.
 | 
			
		||||
 *       After that, the websocket will be closed.
 | 
			
		||||
 */
 | 
			
		||||
void ast_ari_close_per_call_websocket(char *app_id);
 | 
			
		||||
 | 
			
		||||
#endif /* _ASTERISK_ARI_H */
 | 
			
		||||
 
 | 
			
		||||
@@ -84,6 +84,17 @@ int ast_vector_string_split(struct ast_vector_string *dest,
 | 
			
		||||
	const char *input, const char *delim, int flags,
 | 
			
		||||
	int (*excludes_cmp)(const char *s1, const char *s2));
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Join the elements of a string vector into a single string.
 | 
			
		||||
 *
 | 
			
		||||
 * \param vec Pointer to the vector.
 | 
			
		||||
 * \param delim String to separate elements with.
 | 
			
		||||
 *
 | 
			
		||||
 * \retval Resulting string.  Must be freed with ast_free.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
char *ast_vector_string_join(struct ast_vector_string *vec, const char *delim);
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Define a vector structure with a read/write lock
 | 
			
		||||
 *
 | 
			
		||||
 
 | 
			
		||||
@@ -403,6 +403,25 @@ char *ast_read_line_from_buffer(char **buffer)
 | 
			
		||||
	return start;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
char *ast_vector_string_join(struct ast_vector_string *vec, const char *delim)
 | 
			
		||||
{
 | 
			
		||||
	struct ast_str *buf = ast_str_create(256);
 | 
			
		||||
	char *rtn;
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	if (!buf) {
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < AST_VECTOR_SIZE(vec); i++) {
 | 
			
		||||
		ast_str_append(&buf, 0, "%s%s", AST_VECTOR_GET(vec, i), delim);
 | 
			
		||||
	}
 | 
			
		||||
	ast_str_truncate(buf, -strlen(delim));
 | 
			
		||||
	rtn = ast_strdup(ast_str_buffer(buf));
 | 
			
		||||
	ast_free(buf);
 | 
			
		||||
	return rtn;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ast_vector_string_split(struct ast_vector_string *dest,
 | 
			
		||||
	const char *input, const char *delim, int flags,
 | 
			
		||||
	int (*excludes_cmp)(const char *s1, const char *s2))
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										157
									
								
								res/ari/ari_doc.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								res/ari/ari_doc.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
			
		||||
<!DOCTYPE docs SYSTEM "appdocsxml.dtd">
 | 
			
		||||
<?xml-stylesheet type="text/xsl" href="appdocsxml.xslt"?>
 | 
			
		||||
<docs xmlns:xi="http://www.w3.org/2001/XInclude">
 | 
			
		||||
	<configInfo name="res_ari" language="en_US">
 | 
			
		||||
		<synopsis>HTTP binding for the Stasis API</synopsis>
 | 
			
		||||
		<configFile name="ari.conf">
 | 
			
		||||
			<configObject name="general">
 | 
			
		||||
				<since>
 | 
			
		||||
					<version>12.0.0</version>
 | 
			
		||||
				</since>
 | 
			
		||||
				<synopsis>General configuration settings</synopsis>
 | 
			
		||||
				<configOption name="enabled">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>12.0.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Enable/disable the ARI module</synopsis>
 | 
			
		||||
					<description>
 | 
			
		||||
						<para>This option enables or disables the ARI module.</para>
 | 
			
		||||
						<note>
 | 
			
		||||
							<para>ARI uses Asterisk's HTTP server, which must also be enabled in <filename>http.conf</filename>.</para>
 | 
			
		||||
						</note>
 | 
			
		||||
					</description>
 | 
			
		||||
					<see-also>
 | 
			
		||||
						<ref type="filename">http.conf</ref>
 | 
			
		||||
						<ref type="link">https://docs.asterisk.org/Configuration/Core-Configuration/Asterisk-Builtin-mini-HTTP-Server/</ref>
 | 
			
		||||
					</see-also>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="websocket_write_timeout" default="100">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>11.11.0</version>
 | 
			
		||||
						<version>12.4.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>The timeout (in milliseconds) to set on WebSocket connections.</synopsis>
 | 
			
		||||
					<description>
 | 
			
		||||
						<para>If a websocket connection accepts input slowly, the timeout
 | 
			
		||||
						for writes to it can be increased to keep it from being disconnected.
 | 
			
		||||
						Value is in milliseconds.</para>
 | 
			
		||||
					</description>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="pretty">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>12.0.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Responses from ARI are formatted to be human readable</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="auth_realm">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>12.0.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Realm to use for authentication. Defaults to Asterisk REST Interface.</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="allowed_origins">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>12.0.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Comma separated list of allowed origins, for Cross-Origin Resource Sharing. May be set to * to allow all origins.</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="channelvars">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>14.2.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Comma separated list of channel variables to display in channel json.</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
			</configObject>
 | 
			
		||||
 | 
			
		||||
			<configObject name="user">
 | 
			
		||||
				<since>
 | 
			
		||||
					<version>12.0.0</version>
 | 
			
		||||
				</since>
 | 
			
		||||
				<synopsis>Per-user configuration settings</synopsis>
 | 
			
		||||
				<configOption name="type">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>13.30.0</version>
 | 
			
		||||
						<version>16.7.0</version>
 | 
			
		||||
						<version>17.1.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Define this configuration section as a user.</synopsis>
 | 
			
		||||
					<description>
 | 
			
		||||
						<enumlist>
 | 
			
		||||
							<enum name="user"><para>Configure this section as a <replaceable>user</replaceable></para></enum>
 | 
			
		||||
						</enumlist>
 | 
			
		||||
					</description>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="read_only">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>13.30.0</version>
 | 
			
		||||
						<version>16.7.0</version>
 | 
			
		||||
						<version>17.1.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>When set to yes, user is only authorized for read-only requests</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="password">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>13.30.0</version>
 | 
			
		||||
						<version>16.7.0</version>
 | 
			
		||||
						<version>17.1.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Crypted or plaintext password (see password_format)</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="password_format">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>12.0.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>password_format may be set to plain (the default) or crypt. When set to crypt, crypt(3) is used to validate the password. A crypted password can be generated using mkpasswd -m sha-512. When set to plain, the password is in plaintext</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
			</configObject>
 | 
			
		||||
			<configObject name="outbound_websocket">
 | 
			
		||||
				<since>
 | 
			
		||||
					<version>20.15.0</version>
 | 
			
		||||
					<version>21.10.0</version>
 | 
			
		||||
					<version>22.5.0</version>
 | 
			
		||||
				</since>
 | 
			
		||||
				<synopsis>Outbound websocket configuration</synopsis>
 | 
			
		||||
				<configOption name="type">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>20.15.0</version>
 | 
			
		||||
						<version>21.10.0</version>
 | 
			
		||||
						<version>22.5.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Must be "outbound_websocket".</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="websocket_client_id">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>20.15.0</version>
 | 
			
		||||
						<version>21.10.0</version>
 | 
			
		||||
						<version>22.5.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>The ID of a connection defined in websocket_client.conf.</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="apps">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>20.15.0</version>
 | 
			
		||||
						<version>21.10.0</version>
 | 
			
		||||
						<version>22.5.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Comma separated list of stasis applications that will use this websocket.</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="local_ari_user">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>20.15.0</version>
 | 
			
		||||
						<version>21.10.0</version>
 | 
			
		||||
						<version>22.5.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>The local ARI user to act as.</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="subscribe_all" default="no">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>20.15.0</version>
 | 
			
		||||
						<version>21.10.0</version>
 | 
			
		||||
						<version>22.5.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Subscribe applications to all event</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
			</configObject>
 | 
			
		||||
		</configFile>
 | 
			
		||||
	</configInfo>
 | 
			
		||||
</docs>
 | 
			
		||||
@@ -2610,6 +2610,85 @@ ari_validator ast_ari_validate_application_move_failed_fn(void)
 | 
			
		||||
	return ast_ari_validate_application_move_failed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ast_ari_validate_application_registered(struct ast_json *json)
 | 
			
		||||
{
 | 
			
		||||
	int res = 1;
 | 
			
		||||
	struct ast_json_iter *iter;
 | 
			
		||||
	int has_type = 0;
 | 
			
		||||
	int has_application = 0;
 | 
			
		||||
	int has_timestamp = 0;
 | 
			
		||||
 | 
			
		||||
	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
 | 
			
		||||
		if (strcmp("asterisk_id", ast_json_object_iter_key(iter)) == 0) {
 | 
			
		||||
			int prop_is_valid;
 | 
			
		||||
			prop_is_valid = ast_ari_validate_string(
 | 
			
		||||
				ast_json_object_iter_value(iter));
 | 
			
		||||
			if (!prop_is_valid) {
 | 
			
		||||
				ast_log(LOG_ERROR, "ARI ApplicationRegistered field asterisk_id failed validation\n");
 | 
			
		||||
				res = 0;
 | 
			
		||||
			}
 | 
			
		||||
		} else
 | 
			
		||||
		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
 | 
			
		||||
			int prop_is_valid;
 | 
			
		||||
			has_type = 1;
 | 
			
		||||
			prop_is_valid = ast_ari_validate_string(
 | 
			
		||||
				ast_json_object_iter_value(iter));
 | 
			
		||||
			if (!prop_is_valid) {
 | 
			
		||||
				ast_log(LOG_ERROR, "ARI ApplicationRegistered field type failed validation\n");
 | 
			
		||||
				res = 0;
 | 
			
		||||
			}
 | 
			
		||||
		} else
 | 
			
		||||
		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
 | 
			
		||||
			int prop_is_valid;
 | 
			
		||||
			has_application = 1;
 | 
			
		||||
			prop_is_valid = ast_ari_validate_string(
 | 
			
		||||
				ast_json_object_iter_value(iter));
 | 
			
		||||
			if (!prop_is_valid) {
 | 
			
		||||
				ast_log(LOG_ERROR, "ARI ApplicationRegistered field application failed validation\n");
 | 
			
		||||
				res = 0;
 | 
			
		||||
			}
 | 
			
		||||
		} else
 | 
			
		||||
		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
 | 
			
		||||
			int prop_is_valid;
 | 
			
		||||
			has_timestamp = 1;
 | 
			
		||||
			prop_is_valid = ast_ari_validate_date(
 | 
			
		||||
				ast_json_object_iter_value(iter));
 | 
			
		||||
			if (!prop_is_valid) {
 | 
			
		||||
				ast_log(LOG_ERROR, "ARI ApplicationRegistered field timestamp failed validation\n");
 | 
			
		||||
				res = 0;
 | 
			
		||||
			}
 | 
			
		||||
		} else
 | 
			
		||||
		{
 | 
			
		||||
			ast_log(LOG_ERROR,
 | 
			
		||||
				"ARI ApplicationRegistered has undocumented field %s\n",
 | 
			
		||||
				ast_json_object_iter_key(iter));
 | 
			
		||||
			res = 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!has_type) {
 | 
			
		||||
		ast_log(LOG_ERROR, "ARI ApplicationRegistered missing required field type\n");
 | 
			
		||||
		res = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!has_application) {
 | 
			
		||||
		ast_log(LOG_ERROR, "ARI ApplicationRegistered missing required field application\n");
 | 
			
		||||
		res = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!has_timestamp) {
 | 
			
		||||
		ast_log(LOG_ERROR, "ARI ApplicationRegistered missing required field timestamp\n");
 | 
			
		||||
		res = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ari_validator ast_ari_validate_application_registered_fn(void)
 | 
			
		||||
{
 | 
			
		||||
	return ast_ari_validate_application_registered;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ast_ari_validate_application_replaced(struct ast_json *json)
 | 
			
		||||
{
 | 
			
		||||
	int res = 1;
 | 
			
		||||
@@ -2689,6 +2768,85 @@ ari_validator ast_ari_validate_application_replaced_fn(void)
 | 
			
		||||
	return ast_ari_validate_application_replaced;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ast_ari_validate_application_unregistered(struct ast_json *json)
 | 
			
		||||
{
 | 
			
		||||
	int res = 1;
 | 
			
		||||
	struct ast_json_iter *iter;
 | 
			
		||||
	int has_type = 0;
 | 
			
		||||
	int has_application = 0;
 | 
			
		||||
	int has_timestamp = 0;
 | 
			
		||||
 | 
			
		||||
	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
 | 
			
		||||
		if (strcmp("asterisk_id", ast_json_object_iter_key(iter)) == 0) {
 | 
			
		||||
			int prop_is_valid;
 | 
			
		||||
			prop_is_valid = ast_ari_validate_string(
 | 
			
		||||
				ast_json_object_iter_value(iter));
 | 
			
		||||
			if (!prop_is_valid) {
 | 
			
		||||
				ast_log(LOG_ERROR, "ARI ApplicationUnregistered field asterisk_id failed validation\n");
 | 
			
		||||
				res = 0;
 | 
			
		||||
			}
 | 
			
		||||
		} else
 | 
			
		||||
		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
 | 
			
		||||
			int prop_is_valid;
 | 
			
		||||
			has_type = 1;
 | 
			
		||||
			prop_is_valid = ast_ari_validate_string(
 | 
			
		||||
				ast_json_object_iter_value(iter));
 | 
			
		||||
			if (!prop_is_valid) {
 | 
			
		||||
				ast_log(LOG_ERROR, "ARI ApplicationUnregistered field type failed validation\n");
 | 
			
		||||
				res = 0;
 | 
			
		||||
			}
 | 
			
		||||
		} else
 | 
			
		||||
		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
 | 
			
		||||
			int prop_is_valid;
 | 
			
		||||
			has_application = 1;
 | 
			
		||||
			prop_is_valid = ast_ari_validate_string(
 | 
			
		||||
				ast_json_object_iter_value(iter));
 | 
			
		||||
			if (!prop_is_valid) {
 | 
			
		||||
				ast_log(LOG_ERROR, "ARI ApplicationUnregistered field application failed validation\n");
 | 
			
		||||
				res = 0;
 | 
			
		||||
			}
 | 
			
		||||
		} else
 | 
			
		||||
		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
 | 
			
		||||
			int prop_is_valid;
 | 
			
		||||
			has_timestamp = 1;
 | 
			
		||||
			prop_is_valid = ast_ari_validate_date(
 | 
			
		||||
				ast_json_object_iter_value(iter));
 | 
			
		||||
			if (!prop_is_valid) {
 | 
			
		||||
				ast_log(LOG_ERROR, "ARI ApplicationUnregistered field timestamp failed validation\n");
 | 
			
		||||
				res = 0;
 | 
			
		||||
			}
 | 
			
		||||
		} else
 | 
			
		||||
		{
 | 
			
		||||
			ast_log(LOG_ERROR,
 | 
			
		||||
				"ARI ApplicationUnregistered has undocumented field %s\n",
 | 
			
		||||
				ast_json_object_iter_key(iter));
 | 
			
		||||
			res = 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!has_type) {
 | 
			
		||||
		ast_log(LOG_ERROR, "ARI ApplicationUnregistered missing required field type\n");
 | 
			
		||||
		res = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!has_application) {
 | 
			
		||||
		ast_log(LOG_ERROR, "ARI ApplicationUnregistered missing required field application\n");
 | 
			
		||||
		res = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!has_timestamp) {
 | 
			
		||||
		ast_log(LOG_ERROR, "ARI ApplicationUnregistered missing required field timestamp\n");
 | 
			
		||||
		res = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ari_validator ast_ari_validate_application_unregistered_fn(void)
 | 
			
		||||
{
 | 
			
		||||
	return ast_ari_validate_application_unregistered;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ast_ari_validate_bridge_attended_transfer(struct ast_json *json)
 | 
			
		||||
{
 | 
			
		||||
	int res = 1;
 | 
			
		||||
@@ -5990,9 +6148,15 @@ int ast_ari_validate_event(struct ast_json *json)
 | 
			
		||||
	if (strcmp("ApplicationMoveFailed", discriminator) == 0) {
 | 
			
		||||
		return ast_ari_validate_application_move_failed(json);
 | 
			
		||||
	} else
 | 
			
		||||
	if (strcmp("ApplicationRegistered", discriminator) == 0) {
 | 
			
		||||
		return ast_ari_validate_application_registered(json);
 | 
			
		||||
	} else
 | 
			
		||||
	if (strcmp("ApplicationReplaced", discriminator) == 0) {
 | 
			
		||||
		return ast_ari_validate_application_replaced(json);
 | 
			
		||||
	} else
 | 
			
		||||
	if (strcmp("ApplicationUnregistered", discriminator) == 0) {
 | 
			
		||||
		return ast_ari_validate_application_unregistered(json);
 | 
			
		||||
	} else
 | 
			
		||||
	if (strcmp("BridgeAttendedTransfer", discriminator) == 0) {
 | 
			
		||||
		return ast_ari_validate_bridge_attended_transfer(json);
 | 
			
		||||
	} else
 | 
			
		||||
@@ -6203,9 +6367,15 @@ int ast_ari_validate_message(struct ast_json *json)
 | 
			
		||||
	if (strcmp("ApplicationMoveFailed", discriminator) == 0) {
 | 
			
		||||
		return ast_ari_validate_application_move_failed(json);
 | 
			
		||||
	} else
 | 
			
		||||
	if (strcmp("ApplicationRegistered", discriminator) == 0) {
 | 
			
		||||
		return ast_ari_validate_application_registered(json);
 | 
			
		||||
	} else
 | 
			
		||||
	if (strcmp("ApplicationReplaced", discriminator) == 0) {
 | 
			
		||||
		return ast_ari_validate_application_replaced(json);
 | 
			
		||||
	} else
 | 
			
		||||
	if (strcmp("ApplicationUnregistered", discriminator) == 0) {
 | 
			
		||||
		return ast_ari_validate_application_unregistered(json);
 | 
			
		||||
	} else
 | 
			
		||||
	if (strcmp("BridgeAttendedTransfer", discriminator) == 0) {
 | 
			
		||||
		return ast_ari_validate_bridge_attended_transfer(json);
 | 
			
		||||
	} else
 | 
			
		||||
 
 | 
			
		||||
@@ -603,6 +603,22 @@ int ast_ari_validate_application_move_failed(struct ast_json *json);
 | 
			
		||||
 */
 | 
			
		||||
ari_validator ast_ari_validate_application_move_failed_fn(void);
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Validator for ApplicationRegistered.
 | 
			
		||||
 *
 | 
			
		||||
 * Notification that a Stasis app has been registered.
 | 
			
		||||
 *
 | 
			
		||||
 * \param json JSON object to validate.
 | 
			
		||||
 * \retval True (non-zero) if valid.
 | 
			
		||||
 * \retval False (zero) if invalid.
 | 
			
		||||
 */
 | 
			
		||||
int ast_ari_validate_application_registered(struct ast_json *json);
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Function pointer to ast_ari_validate_application_registered().
 | 
			
		||||
 */
 | 
			
		||||
ari_validator ast_ari_validate_application_registered_fn(void);
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Validator for ApplicationReplaced.
 | 
			
		||||
 *
 | 
			
		||||
@@ -621,6 +637,22 @@ int ast_ari_validate_application_replaced(struct ast_json *json);
 | 
			
		||||
 */
 | 
			
		||||
ari_validator ast_ari_validate_application_replaced_fn(void);
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Validator for ApplicationUnregistered.
 | 
			
		||||
 *
 | 
			
		||||
 * Notification that a Stasis app has been unregistered.
 | 
			
		||||
 *
 | 
			
		||||
 * \param json JSON object to validate.
 | 
			
		||||
 * \retval True (non-zero) if valid.
 | 
			
		||||
 * \retval False (zero) if invalid.
 | 
			
		||||
 */
 | 
			
		||||
int ast_ari_validate_application_unregistered(struct ast_json *json);
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Function pointer to ast_ari_validate_application_unregistered().
 | 
			
		||||
 */
 | 
			
		||||
ari_validator ast_ari_validate_application_unregistered_fn(void);
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Validator for BridgeAttendedTransfer.
 | 
			
		||||
 *
 | 
			
		||||
@@ -1596,11 +1628,21 @@ ari_validator ast_ari_validate_application_fn(void);
 | 
			
		||||
 * - args: List[string] (required)
 | 
			
		||||
 * - channel: Channel (required)
 | 
			
		||||
 * - destination: string (required)
 | 
			
		||||
 * ApplicationRegistered
 | 
			
		||||
 * - asterisk_id: string
 | 
			
		||||
 * - type: string (required)
 | 
			
		||||
 * - application: string (required)
 | 
			
		||||
 * - timestamp: Date (required)
 | 
			
		||||
 * ApplicationReplaced
 | 
			
		||||
 * - asterisk_id: string
 | 
			
		||||
 * - type: string (required)
 | 
			
		||||
 * - application: string (required)
 | 
			
		||||
 * - timestamp: Date (required)
 | 
			
		||||
 * ApplicationUnregistered
 | 
			
		||||
 * - asterisk_id: string
 | 
			
		||||
 * - type: string (required)
 | 
			
		||||
 * - application: string (required)
 | 
			
		||||
 * - timestamp: Date (required)
 | 
			
		||||
 * BridgeAttendedTransfer
 | 
			
		||||
 * - asterisk_id: string
 | 
			
		||||
 * - type: string (required)
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -28,6 +28,7 @@
 | 
			
		||||
#include "asterisk/http.h"
 | 
			
		||||
#include "asterisk/json.h"
 | 
			
		||||
#include "asterisk/vector.h"
 | 
			
		||||
#include "asterisk/websocket_client.h"
 | 
			
		||||
 | 
			
		||||
struct ast_ari_events_event_websocket_args;
 | 
			
		||||
 | 
			
		||||
@@ -35,19 +36,45 @@ struct ast_ari_events_event_websocket_args;
 | 
			
		||||
 * which causes optional_api stuff to happen, which makes optional_api more
 | 
			
		||||
 * difficult to debug. */
 | 
			
		||||
 | 
			
		||||
//struct ast_websocket_server;
 | 
			
		||||
struct ast_websocket;
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Since we create a "stasis-<appname>" dialplan context for each
 | 
			
		||||
 * stasis app, we need to make sure that the total length will be
 | 
			
		||||
 * <= AST_MAX_CONTEXT
 | 
			
		||||
 */
 | 
			
		||||
#define STASIS_CONTEXT_PREFIX "stasis-"
 | 
			
		||||
#define STASIS_CONTEXT_PREFIX_LEN (sizeof(STASIS_CONTEXT_PREFIX) - 1)
 | 
			
		||||
#define ARI_MAX_APP_NAME_LEN (AST_MAX_CONTEXT - STASIS_CONTEXT_PREFIX_LEN)
 | 
			
		||||
 | 
			
		||||
struct ari_ws_session {
 | 
			
		||||
	enum ast_websocket_type type;                   /*!< The type of websocket session. */
 | 
			
		||||
	struct ast_websocket *ast_ws_session;           /*!< The parent websocket session. */
 | 
			
		||||
	int (*validator)(struct ast_json *);            /*!< The message validator. */
 | 
			
		||||
	struct ao2_container *websocket_apps;           /*!< List of Stasis apps registered to
 | 
			
		||||
	struct ast_vector_string websocket_apps;        /*!< List of Stasis apps registered to
 | 
			
		||||
	                                                     the websocket session. */
 | 
			
		||||
	int subscribe_all;                              /*!< Flag indicating if all events are subscribed to. */
 | 
			
		||||
	AST_VECTOR(, struct ast_json *) message_queue;  /*!< Container for holding delayed messages. */
 | 
			
		||||
	char *app_name;                                 /*!< The name of the Stasis application. */
 | 
			
		||||
	char *remote_addr;                              /*!< The remote address. */
 | 
			
		||||
	struct ari_conf_outbound_websocket *owc;           /*!< The outbound websocket configuration. */
 | 
			
		||||
	pthread_t thread;                               /*!< The thread that handles the websocket. */
 | 
			
		||||
	char *channel_id;                               /*!< The channel id for per-call websocket. */
 | 
			
		||||
	char *channel_name;                             /*!< The channel name for per-call websocket. */
 | 
			
		||||
	int stasis_end_sent;                            /*!< Flag indicating if the StasisEnd message was sent. */
 | 
			
		||||
	int connected;                                  /*!< Flag indicating if the websocket is connected. */
 | 
			
		||||
	int closing;                                    /*!< Flag indicating if the session is closing. */
 | 
			
		||||
	char session_id[];                              /*!< The id for the websocket session. */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ao2_container* ari_websocket_get_sessions(void);
 | 
			
		||||
struct ari_ws_session *ari_websocket_get_session(const char *session_id);
 | 
			
		||||
struct ari_ws_session *ari_websocket_get_session_by_app(const char *app_name);
 | 
			
		||||
const char *ari_websocket_type_to_str(enum ast_websocket_type type);
 | 
			
		||||
void ari_websocket_shutdown(struct ari_ws_session *session);
 | 
			
		||||
void ari_websocket_shutdown_all(void);
 | 
			
		||||
int ari_outbound_websocket_start(struct ari_conf_outbound_websocket *owc);
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \internal
 | 
			
		||||
 * \brief Send a JSON event to a websocket.
 | 
			
		||||
@@ -91,6 +118,6 @@ void ari_handle_websocket(struct ast_tcptls_session_instance *ser,
 | 
			
		||||
	struct ast_variable *headers);
 | 
			
		||||
 | 
			
		||||
int ari_websocket_unload_module(void);
 | 
			
		||||
int ari_websocket_load_module(void);
 | 
			
		||||
int ari_websocket_load_module(int is_enabled);
 | 
			
		||||
 | 
			
		||||
#endif /* ARI_WEBSOCKETS_H_ */
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										498
									
								
								res/ari/cli.c
									
									
									
									
									
								
							
							
						
						
									
										498
									
								
								res/ari/cli.c
									
									
									
									
									
								
							@@ -27,11 +27,13 @@
 | 
			
		||||
#include "asterisk/astobj2.h"
 | 
			
		||||
#include "asterisk/cli.h"
 | 
			
		||||
#include "asterisk/stasis_app.h"
 | 
			
		||||
#include "asterisk/uuid.h"
 | 
			
		||||
#include "internal.h"
 | 
			
		||||
#include "ari_websockets.h"
 | 
			
		||||
 | 
			
		||||
static char *ari_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | 
			
		||||
{
 | 
			
		||||
	RAII_VAR(struct ast_ari_conf *, conf, NULL, ao2_cleanup);
 | 
			
		||||
	RAII_VAR(struct ari_conf_general *, general, NULL, ao2_cleanup);
 | 
			
		||||
 | 
			
		||||
	switch (cmd) {
 | 
			
		||||
	case CLI_INIT:
 | 
			
		||||
@@ -50,43 +52,42 @@ static char *ari_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | 
			
		||||
		return CLI_SHOWUSAGE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	conf = ast_ari_config_get();
 | 
			
		||||
	general = ari_conf_get_general();
 | 
			
		||||
 | 
			
		||||
	if (!conf) {
 | 
			
		||||
	if (!general) {
 | 
			
		||||
		ast_cli(a->fd, "Error getting ARI configuration\n");
 | 
			
		||||
		return CLI_FAILURE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ast_cli(a->fd, "ARI Status:\n");
 | 
			
		||||
	ast_cli(a->fd, "Enabled: %s\n", AST_CLI_YESNO(conf->general->enabled));
 | 
			
		||||
	ast_cli(a->fd, "Enabled: %s\n", AST_CLI_YESNO(general->enabled));
 | 
			
		||||
	ast_cli(a->fd, "Output format: ");
 | 
			
		||||
	if (conf->general->format & AST_JSON_PRETTY) {
 | 
			
		||||
	if (general->format & AST_JSON_PRETTY) {
 | 
			
		||||
		ast_cli(a->fd, "pretty");
 | 
			
		||||
	} else {
 | 
			
		||||
		ast_cli(a->fd, "compact");
 | 
			
		||||
	}
 | 
			
		||||
	ast_cli(a->fd, "\n");
 | 
			
		||||
	ast_cli(a->fd, "Auth realm: %s\n", conf->general->auth_realm);
 | 
			
		||||
	ast_cli(a->fd, "Allowed Origins: %s\n", conf->general->allowed_origins);
 | 
			
		||||
	ast_cli(a->fd, "User count: %d\n", ao2_container_count(conf->users));
 | 
			
		||||
	ast_cli(a->fd, "Auth realm: %s\n", general->auth_realm);
 | 
			
		||||
	ast_cli(a->fd, "Allowed Origins: %s\n", general->allowed_origins);
 | 
			
		||||
	return CLI_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int show_users_cb(void *obj, void *arg, int flags)
 | 
			
		||||
{
 | 
			
		||||
	struct ast_ari_conf_user *user = obj;
 | 
			
		||||
	struct ari_conf_user *user = obj;
 | 
			
		||||
	struct ast_cli_args *a = arg;
 | 
			
		||||
 | 
			
		||||
	ast_cli(a->fd, "%-4s  %s\n",
 | 
			
		||||
		AST_CLI_YESNO(user->read_only),
 | 
			
		||||
		user->username);
 | 
			
		||||
		ast_sorcery_object_get_id(user));
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *ari_show_users(struct ast_cli_entry *e, int cmd,
 | 
			
		||||
	struct ast_cli_args *a)
 | 
			
		||||
{
 | 
			
		||||
	RAII_VAR(struct ast_ari_conf *, conf, NULL, ao2_cleanup);
 | 
			
		||||
	RAII_VAR(struct ao2_container *, users, NULL, ao2_cleanup);
 | 
			
		||||
 | 
			
		||||
	switch (cmd) {
 | 
			
		||||
	case CLI_INIT:
 | 
			
		||||
@@ -105,8 +106,8 @@ static char *ari_show_users(struct ast_cli_entry *e, int cmd,
 | 
			
		||||
		return CLI_SHOWUSAGE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	conf = ast_ari_config_get();
 | 
			
		||||
	if (!conf) {
 | 
			
		||||
	users = ari_conf_get_users();
 | 
			
		||||
	if (!users) {
 | 
			
		||||
		ast_cli(a->fd, "Error getting ARI configuration\n");
 | 
			
		||||
		return CLI_FAILURE;
 | 
			
		||||
	}
 | 
			
		||||
@@ -114,63 +115,37 @@ static char *ari_show_users(struct ast_cli_entry *e, int cmd,
 | 
			
		||||
	ast_cli(a->fd, "r/o?  Username\n");
 | 
			
		||||
	ast_cli(a->fd, "----  --------\n");
 | 
			
		||||
 | 
			
		||||
	ao2_callback(conf->users, OBJ_NODATA, show_users_cb, a);
 | 
			
		||||
	ao2_callback(users, OBJ_NODATA, show_users_cb, a);
 | 
			
		||||
 | 
			
		||||
	return CLI_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct user_complete {
 | 
			
		||||
	/*! Nth user to search for */
 | 
			
		||||
	int state;
 | 
			
		||||
	/*! Which user currently on */
 | 
			
		||||
	int which;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int complete_ari_user_search(void *obj, void *arg, void *data, int flags)
 | 
			
		||||
static void complete_sorcery_object(struct ao2_container *container,
 | 
			
		||||
	const char *word)
 | 
			
		||||
{
 | 
			
		||||
	struct user_complete *search = data;
 | 
			
		||||
	size_t wordlen = strlen(word);
 | 
			
		||||
	void *object;
 | 
			
		||||
	struct ao2_iterator i = ao2_iterator_init(container, 0);
 | 
			
		||||
 | 
			
		||||
	if (++search->which > search->state) {
 | 
			
		||||
		return CMP_MATCH;
 | 
			
		||||
	while ((object = ao2_iterator_next(&i))) {
 | 
			
		||||
		const char *id = ast_sorcery_object_get_id(object);
 | 
			
		||||
		if (!strncasecmp(word, id, wordlen)) {
 | 
			
		||||
			ast_cli_completion_add(ast_strdup(id));
 | 
			
		||||
		}
 | 
			
		||||
		ao2_ref(object, -1);
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *complete_ari_user(struct ast_cli_args *a)
 | 
			
		||||
{
 | 
			
		||||
	RAII_VAR(struct ast_ari_conf *, conf, NULL, ao2_cleanup);
 | 
			
		||||
	RAII_VAR(struct ast_ari_conf_user *, user, NULL, ao2_cleanup);
 | 
			
		||||
 | 
			
		||||
	struct user_complete search = {
 | 
			
		||||
		.state = a->n,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	conf = ast_ari_config_get();
 | 
			
		||||
	if (!conf) {
 | 
			
		||||
		ast_cli(a->fd, "Error getting ARI configuration\n");
 | 
			
		||||
		return CLI_FAILURE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	user = ao2_callback_data(conf->users,
 | 
			
		||||
		ast_strlen_zero(a->word) ? 0 : OBJ_PARTIAL_KEY,
 | 
			
		||||
		complete_ari_user_search, (char*)a->word, &search);
 | 
			
		||||
 | 
			
		||||
	return user ? ast_strdup(user->username) : NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *complete_ari_show_user(struct ast_cli_args *a)
 | 
			
		||||
{
 | 
			
		||||
	if (a->pos == 3) {
 | 
			
		||||
		return complete_ari_user(a);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return NULL;
 | 
			
		||||
	ao2_iterator_destroy(&i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *ari_show_user(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | 
			
		||||
{
 | 
			
		||||
	RAII_VAR(struct ast_ari_conf *, conf, NULL, ao2_cleanup);
 | 
			
		||||
	RAII_VAR(struct ast_ari_conf_user *, user, NULL, ao2_cleanup);
 | 
			
		||||
	RAII_VAR(struct ari_conf_user *, user, NULL, ao2_cleanup);
 | 
			
		||||
	RAII_VAR(struct ao2_container *, users, ari_conf_get_users(), ao2_cleanup);
 | 
			
		||||
 | 
			
		||||
	if (!users) {
 | 
			
		||||
		ast_cli(a->fd, "Error getting ARI configuration\n");
 | 
			
		||||
		return CLI_FAILURE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch (cmd) {
 | 
			
		||||
	case CLI_INIT:
 | 
			
		||||
@@ -180,7 +155,8 @@ static char *ari_show_user(struct ast_cli_entry *e, int cmd, struct ast_cli_args
 | 
			
		||||
			"       Shows a specific ARI user\n";
 | 
			
		||||
		return NULL;
 | 
			
		||||
	case CLI_GENERATE:
 | 
			
		||||
		return complete_ari_show_user(a);
 | 
			
		||||
		complete_sorcery_object(users, a->word);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
@@ -189,20 +165,13 @@ static char *ari_show_user(struct ast_cli_entry *e, int cmd, struct ast_cli_args
 | 
			
		||||
		return CLI_SHOWUSAGE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	conf = ast_ari_config_get();
 | 
			
		||||
 | 
			
		||||
	if (!conf) {
 | 
			
		||||
		ast_cli(a->fd, "Error getting ARI configuration\n");
 | 
			
		||||
		return CLI_FAILURE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	user = ao2_find(conf->users, a->argv[3], OBJ_KEY);
 | 
			
		||||
	user = ari_conf_get_user(a->argv[3]);
 | 
			
		||||
	if (!user) {
 | 
			
		||||
		ast_cli(a->fd, "User '%s' not found\n", a->argv[3]);
 | 
			
		||||
		return CLI_SUCCESS;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ast_cli(a->fd, "Username: %s\n", user->username);
 | 
			
		||||
	ast_cli(a->fd, "Username: %s\n", ast_sorcery_object_get_id(user));
 | 
			
		||||
	ast_cli(a->fd, "Read only?: %s\n", AST_CLI_YESNO(user->read_only));
 | 
			
		||||
 | 
			
		||||
	return CLI_SUCCESS;
 | 
			
		||||
@@ -281,7 +250,7 @@ static char *ari_show_apps(struct ast_cli_entry *e, int cmd, struct ast_cli_args
 | 
			
		||||
	ast_cli(a->fd, "=========================\n");
 | 
			
		||||
	it_apps = ao2_iterator_init(apps, 0);
 | 
			
		||||
	while ((app = ao2_iterator_next(&it_apps))) {
 | 
			
		||||
		ast_cli(a->fd, "%-25.25s\n", app);
 | 
			
		||||
		ast_cli(a->fd, "%s\n", app);
 | 
			
		||||
		ao2_ref(app, -1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -291,55 +260,31 @@ static char *ari_show_apps(struct ast_cli_entry *e, int cmd, struct ast_cli_args
 | 
			
		||||
	return CLI_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct app_complete {
 | 
			
		||||
	/*! Nth app to search for */
 | 
			
		||||
	int state;
 | 
			
		||||
	/*! Which app currently on */
 | 
			
		||||
	int which;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int complete_ari_app_search(void *obj, void *arg, void *data, int flags)
 | 
			
		||||
static void complete_app(struct ao2_container *container,
 | 
			
		||||
	const char *word)
 | 
			
		||||
{
 | 
			
		||||
	struct app_complete *search = data;
 | 
			
		||||
	size_t wordlen = strlen(word);
 | 
			
		||||
	void *object;
 | 
			
		||||
	struct ao2_iterator i = ao2_iterator_init(container, 0);
 | 
			
		||||
 | 
			
		||||
	if (++search->which > search->state) {
 | 
			
		||||
		return CMP_MATCH;
 | 
			
		||||
	while ((object = ao2_iterator_next(&i))) {
 | 
			
		||||
		if (!strncasecmp(word, object, wordlen)) {
 | 
			
		||||
			ast_cli_completion_add(ast_strdup(object));
 | 
			
		||||
		}
 | 
			
		||||
		ao2_ref(object, -1);
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *complete_ari_app(struct ast_cli_args *a, int include_all)
 | 
			
		||||
{
 | 
			
		||||
	RAII_VAR(struct ao2_container *, apps, stasis_app_get_all(), ao2_cleanup);
 | 
			
		||||
	RAII_VAR(char *, app, NULL, ao2_cleanup);
 | 
			
		||||
 | 
			
		||||
	struct app_complete search = {
 | 
			
		||||
		.state = a->n,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (a->pos != 3) {
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!apps) {
 | 
			
		||||
		ast_cli(a->fd, "Error getting ARI applications\n");
 | 
			
		||||
		return CLI_FAILURE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (include_all && ast_strlen_zero(a->word)) {
 | 
			
		||||
		ast_str_container_add(apps, " all");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	app = ao2_callback_data(apps,
 | 
			
		||||
		ast_strlen_zero(a->word) ? 0 : OBJ_SEARCH_PARTIAL_KEY,
 | 
			
		||||
		complete_ari_app_search, (char*)a->word, &search);
 | 
			
		||||
 | 
			
		||||
	return app ? ast_strdup(app) : NULL;
 | 
			
		||||
	ao2_iterator_destroy(&i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *ari_show_app(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | 
			
		||||
{
 | 
			
		||||
	void *app;
 | 
			
		||||
	RAII_VAR(struct ao2_container *, apps, stasis_app_get_all(), ao2_cleanup);
 | 
			
		||||
 | 
			
		||||
	if (!apps) {
 | 
			
		||||
		ast_cli(a->fd, "Error getting ARI applications\n");
 | 
			
		||||
		return CLI_FAILURE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch (cmd) {
 | 
			
		||||
	case CLI_INIT:
 | 
			
		||||
@@ -350,7 +295,8 @@ static char *ari_show_app(struct ast_cli_entry *e, int cmd, struct ast_cli_args
 | 
			
		||||
			;
 | 
			
		||||
		return NULL;
 | 
			
		||||
	case CLI_GENERATE:
 | 
			
		||||
		return complete_ari_app(a, 0);
 | 
			
		||||
		complete_app(apps, a->word);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
@@ -373,9 +319,15 @@ static char *ari_show_app(struct ast_cli_entry *e, int cmd, struct ast_cli_args
 | 
			
		||||
 | 
			
		||||
static char *ari_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | 
			
		||||
{
 | 
			
		||||
	RAII_VAR(struct ao2_container *, apps, stasis_app_get_all(), ao2_cleanup);
 | 
			
		||||
	void *app;
 | 
			
		||||
	int debug;
 | 
			
		||||
 | 
			
		||||
	if (!apps) {
 | 
			
		||||
		ast_cli(a->fd, "Error getting ARI applications\n");
 | 
			
		||||
		return CLI_FAILURE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch (cmd) {
 | 
			
		||||
	case CLI_INIT:
 | 
			
		||||
		e->command = "ari set debug";
 | 
			
		||||
@@ -385,7 +337,14 @@ static char *ari_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args
 | 
			
		||||
			;
 | 
			
		||||
		return NULL;
 | 
			
		||||
	case CLI_GENERATE:
 | 
			
		||||
		return complete_ari_app(a, 1);
 | 
			
		||||
		if (a->argc == 3) {
 | 
			
		||||
			ast_cli_completion_add(ast_strdup("all"));
 | 
			
		||||
			complete_app(apps, a->word);
 | 
			
		||||
		} else if (a->argc == 4) {
 | 
			
		||||
			ast_cli_completion_add(ast_strdup("on"));
 | 
			
		||||
			ast_cli_completion_add(ast_strdup("off"));
 | 
			
		||||
		}
 | 
			
		||||
		return NULL;
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
@@ -418,6 +377,309 @@ static char *ari_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args
 | 
			
		||||
	return CLI_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int show_owc_cb(void *obj, void *arg, int flags)
 | 
			
		||||
{
 | 
			
		||||
	struct ari_conf_outbound_websocket *owc = obj;
 | 
			
		||||
	const char *id = ast_sorcery_object_get_id(owc);
 | 
			
		||||
	enum ari_conf_owc_fields invalid_fields = ari_conf_owc_get_invalid_fields(id);
 | 
			
		||||
	struct ast_cli_args *a = arg;
 | 
			
		||||
 | 
			
		||||
	ast_cli(a->fd, "%-32s %-15s %-32s %-7s %s\n",
 | 
			
		||||
		id,
 | 
			
		||||
		ari_websocket_type_to_str(owc->websocket_client->connection_type),
 | 
			
		||||
		owc->apps,
 | 
			
		||||
		invalid_fields == ARI_OWC_FIELD_NONE ? "valid" : "INVALID",
 | 
			
		||||
		owc->websocket_client->uri);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define DASHES "----------------------------------------------------------------------"
 | 
			
		||||
static char *ari_show_owcs(struct ast_cli_entry *e, int cmd,
 | 
			
		||||
	struct ast_cli_args *a)
 | 
			
		||||
{
 | 
			
		||||
	RAII_VAR(struct ao2_container *, owcs, NULL, ao2_cleanup);
 | 
			
		||||
 | 
			
		||||
	switch (cmd) {
 | 
			
		||||
	case CLI_INIT:
 | 
			
		||||
		e->command = "ari show outbound-websockets";
 | 
			
		||||
		e->usage =
 | 
			
		||||
			"Usage: ari show outbound-websockets\n"
 | 
			
		||||
			"       Shows all ARI outbound-websockets\n";
 | 
			
		||||
		return NULL;
 | 
			
		||||
	case CLI_GENERATE:
 | 
			
		||||
		return NULL;
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (a->argc != 3) {
 | 
			
		||||
		return CLI_SHOWUSAGE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	owcs = ari_conf_get_owcs();
 | 
			
		||||
	if (!owcs) {
 | 
			
		||||
		ast_cli(a->fd, "Error getting ARI configuration\n");
 | 
			
		||||
		return CLI_FAILURE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ast_cli(a->fd, "%-32s %-15s %-32s %-7s %s\n", "Name", "Type", "Apps", "Status", "URI");
 | 
			
		||||
	ast_cli(a->fd, "%.*s %.*s %.*s %.*s %.*s\n", 32, DASHES, 15, DASHES, 32, DASHES, 7, DASHES, 64, DASHES);
 | 
			
		||||
 | 
			
		||||
	ao2_callback(owcs, OBJ_NODATA, show_owc_cb, a);
 | 
			
		||||
 | 
			
		||||
	return CLI_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *ari_show_owc(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | 
			
		||||
{
 | 
			
		||||
	RAII_VAR(struct ari_conf_outbound_websocket *, owc, NULL, ao2_cleanup);
 | 
			
		||||
	RAII_VAR(struct ao2_container *, owcs, ari_conf_get_owcs(), ao2_cleanup);
 | 
			
		||||
	const char *id = NULL;
 | 
			
		||||
	enum ari_conf_owc_fields invalid_fields;
 | 
			
		||||
 | 
			
		||||
	switch (cmd) {
 | 
			
		||||
	case CLI_INIT:
 | 
			
		||||
		e->command = "ari show outbound-websocket";
 | 
			
		||||
		e->usage =
 | 
			
		||||
			"Usage: ari show outbound-websocket <connection id>\n"
 | 
			
		||||
			"       Shows a specific ARI outbound websocket\n";
 | 
			
		||||
		return NULL;
 | 
			
		||||
	case CLI_GENERATE:
 | 
			
		||||
		complete_sorcery_object(owcs, a->word);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (a->argc != 4) {
 | 
			
		||||
		return CLI_SHOWUSAGE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	owc = ari_conf_get_owc(a->argv[3]);
 | 
			
		||||
	if (!owc) {
 | 
			
		||||
		ast_cli(a->fd, "Error getting ARI configuration\n");
 | 
			
		||||
		return CLI_FAILURE;
 | 
			
		||||
	}
 | 
			
		||||
	id = ast_sorcery_object_get_id(owc);
 | 
			
		||||
	invalid_fields = ari_conf_owc_get_invalid_fields(id);
 | 
			
		||||
 | 
			
		||||
	ast_cli(a->fd, "[%s] %s\n", id,
 | 
			
		||||
		invalid_fields == ARI_OWC_FIELD_NONE ? "" : "**INVALID**");
 | 
			
		||||
	ast_cli(a->fd, "uri =                    %s\n", owc->websocket_client->uri);
 | 
			
		||||
	ast_cli(a->fd, "protocols =              %s\n", owc->websocket_client->protocols);
 | 
			
		||||
	ast_cli(a->fd, "apps =                   %s%s\n", owc->apps,
 | 
			
		||||
		invalid_fields & ARI_OWC_FIELD_APPS ? " (invalid)" : "");
 | 
			
		||||
	ast_cli(a->fd, "username =               %s\n", owc->websocket_client->username);
 | 
			
		||||
	ast_cli(a->fd, "password =               %s\n", S_COR(owc->websocket_client->password, "********", ""));
 | 
			
		||||
	ast_cli(a->fd, "local_ari_user =         %s%s\n", owc->local_ari_user,
 | 
			
		||||
		invalid_fields & ARI_OWC_FIELD_LOCAL_ARI_USER ? " (invalid)" : "");
 | 
			
		||||
	ast_cli(a->fd, "connection_type =        %s\n", ari_websocket_type_to_str(owc->websocket_client->connection_type));
 | 
			
		||||
	ast_cli(a->fd, "subscribe_all =          %s\n", AST_CLI_YESNO(owc->subscribe_all));
 | 
			
		||||
	ast_cli(a->fd, "connec_timeout =         %d\n", owc->websocket_client->connect_timeout);
 | 
			
		||||
	ast_cli(a->fd, "reconnect_attempts =     %d\n", owc->websocket_client->reconnect_attempts);
 | 
			
		||||
	ast_cli(a->fd, "reconnect_interval =     %d\n", owc->websocket_client->reconnect_interval);
 | 
			
		||||
	ast_cli(a->fd, "tls_enabled =            %s\n", AST_CLI_YESNO(owc->websocket_client->tls_enabled));
 | 
			
		||||
	ast_cli(a->fd, "ca_list_file =           %s\n", owc->websocket_client->ca_list_file);
 | 
			
		||||
	ast_cli(a->fd, "ca_list_path =           %s\n", owc->websocket_client->ca_list_path);
 | 
			
		||||
	ast_cli(a->fd, "cert_file =              %s\n", owc->websocket_client->cert_file);
 | 
			
		||||
	ast_cli(a->fd, "priv_key_file =          %s\n", owc->websocket_client->priv_key_file);
 | 
			
		||||
	ast_cli(a->fd, "verify_server =          %s\n", AST_CLI_YESNO(owc->websocket_client->verify_server_cert));
 | 
			
		||||
	ast_cli(a->fd, "verify_server_hostname = %s\n", AST_CLI_YESNO(owc->websocket_client->verify_server_hostname));
 | 
			
		||||
	ast_cli(a->fd, "\n");
 | 
			
		||||
 | 
			
		||||
	return CLI_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *ari_start_owc(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 | 
			
		||||
{
 | 
			
		||||
	RAII_VAR(struct ari_conf_outbound_websocket *, owc, NULL, ao2_cleanup);
 | 
			
		||||
	RAII_VAR(struct ao2_container *, owcs, ari_conf_get_owcs(), ao2_cleanup);
 | 
			
		||||
 | 
			
		||||
	if (!owcs) {
 | 
			
		||||
		ast_cli(a->fd, "Error getting ARI configuration\n");
 | 
			
		||||
		return CLI_FAILURE ;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch (cmd) {
 | 
			
		||||
	case CLI_INIT:
 | 
			
		||||
		e->command = "ari start outbound-websocket";
 | 
			
		||||
		e->usage =
 | 
			
		||||
			"Usage: ari start outbound-websocket <connection id>\n"
 | 
			
		||||
			"       Starts a specific ARI outbound websocket\n";
 | 
			
		||||
		return NULL;
 | 
			
		||||
	case CLI_GENERATE:
 | 
			
		||||
		complete_sorcery_object(owcs, a->word);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (a->argc != 4) {
 | 
			
		||||
		return CLI_SHOWUSAGE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	owc = ari_conf_get_owc(a->argv[3]);
 | 
			
		||||
	if (!owc) {
 | 
			
		||||
		ast_cli(a->fd, "Error getting ARI configuration\n");
 | 
			
		||||
		return CLI_FAILURE;
 | 
			
		||||
	}
 | 
			
		||||
	ast_cli(a->fd, "Starting websocket session for outbound-websocket '%s'\n", a->argv[3]);
 | 
			
		||||
 | 
			
		||||
	if (ari_outbound_websocket_start(owc) != 0) {
 | 
			
		||||
		ast_cli(a->fd, "Error starting outbound websocket\n");
 | 
			
		||||
		return CLI_FAILURE ;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return CLI_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int show_sessions_cb(void *obj, void *arg, int flags)
 | 
			
		||||
{
 | 
			
		||||
	struct ari_ws_session *session = obj;
 | 
			
		||||
	struct ast_cli_args *a = arg;
 | 
			
		||||
	char *apps = ast_vector_string_join(&session->websocket_apps, ",");
 | 
			
		||||
 | 
			
		||||
	ast_cli(a->fd, "%-*s %-15s %-32s %-5s %s\n",
 | 
			
		||||
		AST_UUID_STR_LEN,
 | 
			
		||||
		session->session_id,
 | 
			
		||||
		ari_websocket_type_to_str(session->type),
 | 
			
		||||
		S_OR(session->remote_addr, "N/A"),
 | 
			
		||||
		session->type == AST_WS_TYPE_CLIENT_PER_CALL_CONFIG
 | 
			
		||||
			? "N/A" : (session->connected ? "Up" : "Down"),
 | 
			
		||||
		S_OR(apps, ""));
 | 
			
		||||
 | 
			
		||||
	ast_free(apps);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define DASHES "----------------------------------------------------------------------"
 | 
			
		||||
static char *ari_show_sessions(struct ast_cli_entry *e, int cmd,
 | 
			
		||||
	struct ast_cli_args *a)
 | 
			
		||||
{
 | 
			
		||||
	RAII_VAR(struct ao2_container *, sessions, NULL, ao2_cleanup);
 | 
			
		||||
 | 
			
		||||
	switch (cmd) {
 | 
			
		||||
	case CLI_INIT:
 | 
			
		||||
		e->command = "ari show websocket sessions";
 | 
			
		||||
		e->usage =
 | 
			
		||||
			"Usage: ari show websocket sessions\n"
 | 
			
		||||
			"       Shows all ARI websocket sessions\n";
 | 
			
		||||
		return NULL;
 | 
			
		||||
	case CLI_GENERATE:
 | 
			
		||||
		return NULL;
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (a->argc != 4) {
 | 
			
		||||
		return CLI_SHOWUSAGE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sessions = ari_websocket_get_sessions();
 | 
			
		||||
	if (!sessions) {
 | 
			
		||||
		ast_cli(a->fd, "Error getting websocket sessions\n");
 | 
			
		||||
		return CLI_FAILURE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ast_cli(a->fd, "%-*.*s %-15.15s %-32.32s %-5.5s %-16.16s\n",
 | 
			
		||||
		AST_UUID_STR_LEN, AST_UUID_STR_LEN,
 | 
			
		||||
		"Connection ID",
 | 
			
		||||
		"Type",
 | 
			
		||||
		"RemoteAddr",
 | 
			
		||||
		"State",
 | 
			
		||||
		"Apps"
 | 
			
		||||
		);
 | 
			
		||||
	ast_cli(a->fd, "%-*.*s %-15.15s %-32.32s %-5.5s %-16.16s\n",
 | 
			
		||||
		AST_UUID_STR_LEN, AST_UUID_STR_LEN, DASHES, DASHES, DASHES, DASHES, DASHES);
 | 
			
		||||
 | 
			
		||||
	ao2_callback(sessions, OBJ_NODATA, show_sessions_cb, a);
 | 
			
		||||
 | 
			
		||||
	return CLI_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *ari_shut_sessions(struct ast_cli_entry *e, int cmd,
 | 
			
		||||
	struct ast_cli_args *a)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
	switch (cmd) {
 | 
			
		||||
	case CLI_INIT:
 | 
			
		||||
		e->command = "ari shutdown websocket sessions";
 | 
			
		||||
		e->usage =
 | 
			
		||||
			"Usage: ari shutdown websocket sessions\n"
 | 
			
		||||
			"       Shuts down all ARI websocket sessions\n";
 | 
			
		||||
		return NULL;
 | 
			
		||||
	case CLI_GENERATE:
 | 
			
		||||
		return NULL;
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (a->argc != 4) {
 | 
			
		||||
		return CLI_SHOWUSAGE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ast_cli(a->fd, "Shutting down all websocket sessions\n");
 | 
			
		||||
	ari_websocket_shutdown_all();
 | 
			
		||||
 | 
			
		||||
	return CLI_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void complete_session(struct ao2_container *container,
 | 
			
		||||
	const char *word)
 | 
			
		||||
{
 | 
			
		||||
	size_t wordlen = strlen(word);
 | 
			
		||||
	struct ari_ws_session *session;
 | 
			
		||||
	struct ao2_iterator i = ao2_iterator_init(container, 0);
 | 
			
		||||
 | 
			
		||||
	while ((session = ao2_iterator_next(&i))) {
 | 
			
		||||
		if (!strncasecmp(word, session->session_id, wordlen)) {
 | 
			
		||||
			ast_cli_completion_add(ast_strdup(session->session_id));
 | 
			
		||||
		}
 | 
			
		||||
		ao2_ref(session, -1);
 | 
			
		||||
	}
 | 
			
		||||
	ao2_iterator_destroy(&i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *ari_shut_session(struct ast_cli_entry *e, int cmd,
 | 
			
		||||
	struct ast_cli_args *a)
 | 
			
		||||
{
 | 
			
		||||
	RAII_VAR(struct ari_ws_session *, session, NULL, ao2_cleanup);
 | 
			
		||||
	RAII_VAR(struct ao2_container *, sessions, ari_websocket_get_sessions(), ao2_cleanup);
 | 
			
		||||
 | 
			
		||||
	if (!sessions) {
 | 
			
		||||
		ast_cli(a->fd, "Error getting ARI configuration\n");
 | 
			
		||||
		return CLI_FAILURE ;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch (cmd) {
 | 
			
		||||
	case CLI_INIT:
 | 
			
		||||
		e->command = "ari shutdown websocket session";
 | 
			
		||||
		e->usage =
 | 
			
		||||
			"Usage: ari shutdown websocket session <id>\n"
 | 
			
		||||
			"       Shuts down ARI websocket session\n";
 | 
			
		||||
		return NULL;
 | 
			
		||||
	case CLI_GENERATE:
 | 
			
		||||
		complete_session(sessions, a->word);
 | 
			
		||||
		return NULL;
 | 
			
		||||
	default:
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (a->argc != 5) {
 | 
			
		||||
		return CLI_SHOWUSAGE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	session = ari_websocket_get_session(a->argv[4]);
 | 
			
		||||
	if (!session) {
 | 
			
		||||
		ast_cli(a->fd, "Websocket session '%s' not found\n", a->argv[4]);
 | 
			
		||||
		return CLI_FAILURE ;
 | 
			
		||||
	}
 | 
			
		||||
	ast_cli(a->fd, "Shutting down websocket session '%s'\n", a->argv[4]);
 | 
			
		||||
	ari_websocket_shutdown(session);
 | 
			
		||||
 | 
			
		||||
	return CLI_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct ast_cli_entry cli_ari[] = {
 | 
			
		||||
	AST_CLI_DEFINE(ari_show, "Show ARI settings"),
 | 
			
		||||
	AST_CLI_DEFINE(ari_show_users, "List ARI users"),
 | 
			
		||||
@@ -426,12 +688,18 @@ static struct ast_cli_entry cli_ari[] = {
 | 
			
		||||
	AST_CLI_DEFINE(ari_show_apps, "List registered ARI applications"),
 | 
			
		||||
	AST_CLI_DEFINE(ari_show_app, "Display details of a registered ARI application"),
 | 
			
		||||
	AST_CLI_DEFINE(ari_set_debug, "Enable/disable debugging of an ARI application"),
 | 
			
		||||
	AST_CLI_DEFINE(ari_show_owcs, "List outbound websocket connections"),
 | 
			
		||||
	AST_CLI_DEFINE(ari_show_owc, "Show outbound websocket connection"),
 | 
			
		||||
	AST_CLI_DEFINE(ari_start_owc, "Start outbound websocket connection"),
 | 
			
		||||
	AST_CLI_DEFINE(ari_show_sessions, "Show websocket sessions"),
 | 
			
		||||
	AST_CLI_DEFINE(ari_shut_session, "Shutdown websocket session"),
 | 
			
		||||
	AST_CLI_DEFINE(ari_shut_sessions, "Shutdown websocket sessions"),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int ast_ari_cli_register(void) {
 | 
			
		||||
int ari_cli_register(void) {
 | 
			
		||||
	return ast_cli_register_multiple(cli_ari, ARRAY_LEN(cli_ari));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ast_ari_cli_unregister(void) {
 | 
			
		||||
void ari_cli_unregister(void) {
 | 
			
		||||
	ast_cli_unregister_multiple(cli_ari, ARRAY_LEN(cli_ari));
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										935
									
								
								res/ari/config.c
									
									
									
									
									
								
							
							
						
						
									
										935
									
								
								res/ari/config.c
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -27,7 +27,12 @@
 | 
			
		||||
 | 
			
		||||
#include "asterisk/http.h"
 | 
			
		||||
#include "asterisk/json.h"
 | 
			
		||||
#include "asterisk/md5.h"
 | 
			
		||||
#include "asterisk/sorcery.h"
 | 
			
		||||
#include "asterisk/stringfields.h"
 | 
			
		||||
#include "asterisk/websocket_client.h"
 | 
			
		||||
#include "ari_websockets.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*! @{ */
 | 
			
		||||
 | 
			
		||||
@@ -37,98 +42,139 @@
 | 
			
		||||
 * \return 0 on success.
 | 
			
		||||
 * \return Non-zero on error.
 | 
			
		||||
 */
 | 
			
		||||
int ast_ari_cli_register(void);
 | 
			
		||||
int ari_cli_register(void);
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Unregister CLI commands for ARI.
 | 
			
		||||
 */
 | 
			
		||||
void ast_ari_cli_unregister(void);
 | 
			
		||||
void ari_cli_unregister(void);
 | 
			
		||||
 | 
			
		||||
/*! @} */
 | 
			
		||||
 | 
			
		||||
/*! @{ */
 | 
			
		||||
 | 
			
		||||
struct ast_ari_conf_general;
 | 
			
		||||
 | 
			
		||||
/*! \brief All configuration options for ARI. */
 | 
			
		||||
struct ast_ari_conf {
 | 
			
		||||
	/*! The general section configuration options. */
 | 
			
		||||
	struct ast_ari_conf_general *general;
 | 
			
		||||
	/*! Configured users */
 | 
			
		||||
	struct ao2_container *users;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*! Max length for auth_realm field */
 | 
			
		||||
#define ARI_AUTH_REALM_LEN 256
 | 
			
		||||
 | 
			
		||||
/*! \brief Global configuration options for ARI. */
 | 
			
		||||
struct ast_ari_conf_general {
 | 
			
		||||
struct ari_conf_general {
 | 
			
		||||
	SORCERY_OBJECT(details);
 | 
			
		||||
	AST_DECLARE_STRING_FIELDS(
 | 
			
		||||
		/*! Allowed CORS origins */
 | 
			
		||||
		AST_STRING_FIELD(allowed_origins);
 | 
			
		||||
		/*! Authentication realm */
 | 
			
		||||
		AST_STRING_FIELD(auth_realm);
 | 
			
		||||
		/*! Channel variables */
 | 
			
		||||
		AST_STRING_FIELD(channelvars);
 | 
			
		||||
	);
 | 
			
		||||
	/*! Enabled by default, disabled if false. */
 | 
			
		||||
	int enabled;
 | 
			
		||||
	/*! Write timeout for websocket connections */
 | 
			
		||||
	int write_timeout;
 | 
			
		||||
	/*! Encoding format used during output (default compact). */
 | 
			
		||||
	enum ast_json_encoding_format format;
 | 
			
		||||
	/*! Authentication realm */
 | 
			
		||||
	char auth_realm[ARI_AUTH_REALM_LEN];
 | 
			
		||||
 | 
			
		||||
	AST_DECLARE_STRING_FIELDS(
 | 
			
		||||
		AST_STRING_FIELD(allowed_origins);
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*! \brief Password format */
 | 
			
		||||
enum ast_ari_password_format {
 | 
			
		||||
enum ari_user_password_format {
 | 
			
		||||
	/*! \brief Plaintext password */
 | 
			
		||||
	ARI_PASSWORD_FORMAT_PLAIN,
 | 
			
		||||
	/*! crypt(3) password */
 | 
			
		||||
	ARI_PASSWORD_FORMAT_CRYPT,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief User's password mx length.
 | 
			
		||||
 *
 | 
			
		||||
 * If 256 seems like a lot, a crypt SHA-512 has over 106 characters.
 | 
			
		||||
 */
 | 
			
		||||
#define ARI_PASSWORD_LEN 256
 | 
			
		||||
 | 
			
		||||
/*! \brief Per-user configuration options */
 | 
			
		||||
struct ast_ari_conf_user {
 | 
			
		||||
	/*! Username for authentication */
 | 
			
		||||
	char *username;
 | 
			
		||||
	/*! User's password. */
 | 
			
		||||
	char password[ARI_PASSWORD_LEN];
 | 
			
		||||
struct ari_conf_user {
 | 
			
		||||
	SORCERY_OBJECT(details);
 | 
			
		||||
	AST_DECLARE_STRING_FIELDS(
 | 
			
		||||
		/*! User's password. */
 | 
			
		||||
		AST_STRING_FIELD(password);
 | 
			
		||||
	);
 | 
			
		||||
	/*! Format for the password field */
 | 
			
		||||
	enum ast_ari_password_format password_format;
 | 
			
		||||
	enum ari_user_password_format password_format;
 | 
			
		||||
	/*! If true, user cannot execute change operations */
 | 
			
		||||
	int read_only;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Initialize the ARI configuration
 | 
			
		||||
 */
 | 
			
		||||
int ast_ari_config_init(void);
 | 
			
		||||
enum ari_conf_owc_fields {
 | 
			
		||||
	ARI_OWC_FIELD_NONE =                    0,
 | 
			
		||||
	ARI_OWC_FIELD_WEBSOCKET_CONNECTION_ID = (1 << AST_WS_CLIENT_FIELD_USER_START),
 | 
			
		||||
	ARI_OWC_FIELD_APPS =                    (1 << (AST_WS_CLIENT_FIELD_USER_START + 1)),
 | 
			
		||||
	ARI_OWC_FIELD_LOCAL_ARI_USER =          (1 << (AST_WS_CLIENT_FIELD_USER_START + 2)),
 | 
			
		||||
	ARI_OWC_FIELD_LOCAL_ARI_PASSWORD =      (1 << (AST_WS_CLIENT_FIELD_USER_START + 3)),
 | 
			
		||||
	ARI_OWC_FIELD_SUBSCRIBE_ALL =           (1 << (AST_WS_CLIENT_FIELD_USER_START + 4)),
 | 
			
		||||
	ARI_OWC_NEEDS_RECONNECT = AST_WS_CLIENT_NEEDS_RECONNECT
 | 
			
		||||
	| ARI_OWC_FIELD_WEBSOCKET_CONNECTION_ID | ARI_OWC_FIELD_LOCAL_ARI_USER
 | 
			
		||||
	| ARI_OWC_FIELD_LOCAL_ARI_PASSWORD,
 | 
			
		||||
	ARI_OWC_NEEDS_REREGISTER = ARI_OWC_FIELD_APPS | ARI_OWC_FIELD_SUBSCRIBE_ALL,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct ari_conf_outbound_websocket {
 | 
			
		||||
	SORCERY_OBJECT(details);
 | 
			
		||||
	AST_DECLARE_STRING_FIELDS(
 | 
			
		||||
		AST_STRING_FIELD(websocket_client_id);  /*!< The ID of the websocket client to use */
 | 
			
		||||
		AST_STRING_FIELD(apps);          /*!< Stasis apps using this connection */
 | 
			
		||||
		AST_STRING_FIELD(local_ari_user);/*!< The ARI user to act as */
 | 
			
		||||
		AST_STRING_FIELD(local_ari_password);   /*!< The password for the ARI user */
 | 
			
		||||
	);
 | 
			
		||||
	int invalid;                         /*!< Invalid configuration */
 | 
			
		||||
	int subscribe_all;                   /*!< Subscribe to all events */
 | 
			
		||||
	struct ast_websocket_client *websocket_client; /*!< The websocket client */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Reload the ARI configuration
 | 
			
		||||
 * \brief Detect changes between two outbound websocket configurations.
 | 
			
		||||
 *
 | 
			
		||||
 * \param old_owc The old outbound websocket configuration.
 | 
			
		||||
 * \param new_owc The new outbound websocket configuration.
 | 
			
		||||
 * \return A bitmask of changed fields.
 | 
			
		||||
 */
 | 
			
		||||
int ast_ari_config_reload(void);
 | 
			
		||||
enum ari_conf_owc_fields ari_conf_owc_detect_changes(
 | 
			
		||||
	struct ari_conf_outbound_websocket *old_owc,
 | 
			
		||||
	struct ari_conf_outbound_websocket *new_owc);
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Get the outbound websocket configuration for a Stasis app.
 | 
			
		||||
 *
 | 
			
		||||
 * \param app_name The application name to search for.
 | 
			
		||||
 * \param ws_type An OR'd list of ari_websocket_types or ARI_WS_TYPE_ANY.
 | 
			
		||||
 *
 | 
			
		||||
 * \retval ARI outbound websocket configuration object.
 | 
			
		||||
 * \retval NULL if not found.
 | 
			
		||||
 */
 | 
			
		||||
struct ari_conf_outbound_websocket *ari_conf_get_owc_for_app(
 | 
			
		||||
	const char *app_name, unsigned int ws_type);
 | 
			
		||||
 | 
			
		||||
enum ari_conf_load_flags {
 | 
			
		||||
	ARI_CONF_INIT =              (1 << 0), /*!< Initialize sorcery */
 | 
			
		||||
	ARI_CONF_RELOAD =            (1 << 1), /*!< Reload sorcery */
 | 
			
		||||
	ARI_CONF_LOAD_GENERAL = (1 << 2), /*!< Load general config */
 | 
			
		||||
	ARI_CONF_LOAD_USER =    (1 << 3), /*!< Load user config */
 | 
			
		||||
	ARI_CONF_LOAD_OWC =     (1 << 4), /*!< Load outbound websocket config */
 | 
			
		||||
	ARI_CONF_LOAD_ALL =     (         /*!< Load all configs */
 | 
			
		||||
		ARI_CONF_LOAD_GENERAL
 | 
			
		||||
		| ARI_CONF_LOAD_USER
 | 
			
		||||
		| ARI_CONF_LOAD_OWC),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief (Re)load the ARI configuration
 | 
			
		||||
 */
 | 
			
		||||
int ari_conf_load(enum ari_conf_load_flags flags);
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Destroy the ARI configuration
 | 
			
		||||
 */
 | 
			
		||||
void ast_ari_config_destroy(void);
 | 
			
		||||
void ari_conf_destroy(void);
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Get the current ARI configuration.
 | 
			
		||||
 *
 | 
			
		||||
 * This is an immutable object, so don't modify it. It is AO2 managed, so
 | 
			
		||||
 * ao2_cleanup() when you're done with it.
 | 
			
		||||
 *
 | 
			
		||||
 * \return ARI configuration object.
 | 
			
		||||
 * \retval NULL on error.
 | 
			
		||||
 */
 | 
			
		||||
struct ast_ari_conf *ast_ari_config_get(void);
 | 
			
		||||
struct ari_conf_general* ari_conf_get_general(void);
 | 
			
		||||
struct ao2_container *ari_conf_get_users(void);
 | 
			
		||||
struct ari_conf_user *ari_conf_get_user(const char *username);
 | 
			
		||||
struct ao2_container *ari_conf_get_owcs(void);
 | 
			
		||||
struct ari_conf_outbound_websocket *ari_conf_get_owc(const char *id);
 | 
			
		||||
enum ari_conf_owc_fields ari_conf_owc_get_invalid_fields(const char *id);
 | 
			
		||||
const char *ari_websocket_type_to_str(enum ast_websocket_type type);
 | 
			
		||||
int ari_sorcery_observer_add(const char *object_type,
 | 
			
		||||
	const struct ast_sorcery_observer *callbacks);
 | 
			
		||||
int ari_sorcery_observer_remove(const char *object_type,
 | 
			
		||||
	const struct ast_sorcery_observer *callbacks);
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
 * \brief Validated a user's credentials.
 | 
			
		||||
@@ -138,7 +184,7 @@ struct ast_ari_conf *ast_ari_config_get(void);
 | 
			
		||||
 * \return User object.
 | 
			
		||||
 * \retval NULL if username or password is invalid.
 | 
			
		||||
 */
 | 
			
		||||
struct ast_ari_conf_user *ast_ari_config_validate_user(const char *username,
 | 
			
		||||
struct ari_conf_user *ari_conf_validate_user(const char *username,
 | 
			
		||||
	const char *password);
 | 
			
		||||
 | 
			
		||||
/*! @} */
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										185
									
								
								res/res_ari.c
									
									
									
									
									
								
							
							
						
						
									
										185
									
								
								res/res_ari.c
									
									
									
									
									
								
							@@ -74,117 +74,10 @@
 | 
			
		||||
/*** MODULEINFO
 | 
			
		||||
	<depend type="module">res_http_websocket</depend>
 | 
			
		||||
	<depend type="module">res_stasis</depend>
 | 
			
		||||
	<depend type="module">res_websocket_client</depend>
 | 
			
		||||
	<support_level>core</support_level>
 | 
			
		||||
 ***/
 | 
			
		||||
 | 
			
		||||
/*** DOCUMENTATION
 | 
			
		||||
	<configInfo name="res_ari" language="en_US">
 | 
			
		||||
		<synopsis>HTTP binding for the Stasis API</synopsis>
 | 
			
		||||
		<configFile name="ari.conf">
 | 
			
		||||
			<configObject name="general">
 | 
			
		||||
				<since>
 | 
			
		||||
					<version>12.0.0</version>
 | 
			
		||||
				</since>
 | 
			
		||||
				<synopsis>General configuration settings</synopsis>
 | 
			
		||||
				<configOption name="enabled">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>12.0.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Enable/disable the ARI module</synopsis>
 | 
			
		||||
					<description>
 | 
			
		||||
						<para>This option enables or disables the ARI module.</para>
 | 
			
		||||
						<note>
 | 
			
		||||
							<para>ARI uses Asterisk's HTTP server, which must also be enabled in <filename>http.conf</filename>.</para>
 | 
			
		||||
						</note>
 | 
			
		||||
					</description>
 | 
			
		||||
					<see-also>
 | 
			
		||||
						<ref type="filename">http.conf</ref>
 | 
			
		||||
						<ref type="link">https://docs.asterisk.org/Configuration/Core-Configuration/Asterisk-Builtin-mini-HTTP-Server/</ref>
 | 
			
		||||
					</see-also>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="websocket_write_timeout" default="100">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>11.11.0</version>
 | 
			
		||||
						<version>12.4.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>The timeout (in milliseconds) to set on WebSocket connections.</synopsis>
 | 
			
		||||
					<description>
 | 
			
		||||
						<para>If a websocket connection accepts input slowly, the timeout
 | 
			
		||||
						for writes to it can be increased to keep it from being disconnected.
 | 
			
		||||
						Value is in milliseconds.</para>
 | 
			
		||||
					</description>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="pretty">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>12.0.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Responses from ARI are formatted to be human readable</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="auth_realm">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>12.0.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Realm to use for authentication. Defaults to Asterisk REST Interface.</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="allowed_origins">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>12.0.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Comma separated list of allowed origins, for Cross-Origin Resource Sharing. May be set to * to allow all origins.</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="channelvars">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>14.2.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Comma separated list of channel variables to display in channel json.</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
			</configObject>
 | 
			
		||||
 | 
			
		||||
			<configObject name="user">
 | 
			
		||||
				<since>
 | 
			
		||||
					<version>12.0.0</version>
 | 
			
		||||
				</since>
 | 
			
		||||
				<synopsis>Per-user configuration settings</synopsis>
 | 
			
		||||
				<configOption name="type">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>13.30.0</version>
 | 
			
		||||
						<version>16.7.0</version>
 | 
			
		||||
						<version>17.1.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Define this configuration section as a user.</synopsis>
 | 
			
		||||
					<description>
 | 
			
		||||
						<enumlist>
 | 
			
		||||
							<enum name="user"><para>Configure this section as a <replaceable>user</replaceable></para></enum>
 | 
			
		||||
						</enumlist>
 | 
			
		||||
					</description>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="read_only">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>13.30.0</version>
 | 
			
		||||
						<version>16.7.0</version>
 | 
			
		||||
						<version>17.1.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>When set to yes, user is only authorized for read-only requests</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="password">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>13.30.0</version>
 | 
			
		||||
						<version>16.7.0</version>
 | 
			
		||||
						<version>17.1.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>Crypted or plaintext password (see password_format)</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
				<configOption name="password_format">
 | 
			
		||||
					<since>
 | 
			
		||||
						<version>12.0.0</version>
 | 
			
		||||
					</since>
 | 
			
		||||
					<synopsis>password_format may be set to plain (the default) or crypt. When set to crypt, crypt(3) is used to validate the password. A crypted password can be generated using mkpasswd -m sha-512. When set to plain, the password is in plaintext</synopsis>
 | 
			
		||||
				</configOption>
 | 
			
		||||
			</configObject>
 | 
			
		||||
		</configFile>
 | 
			
		||||
	</configInfo>
 | 
			
		||||
***/
 | 
			
		||||
 | 
			
		||||
#include "asterisk.h"
 | 
			
		||||
 | 
			
		||||
#include "ari/internal.h"
 | 
			
		||||
@@ -202,8 +95,8 @@
 | 
			
		||||
/*! \brief Helper function to check if module is enabled. */
 | 
			
		||||
static int is_enabled(void)
 | 
			
		||||
{
 | 
			
		||||
	RAII_VAR(struct ast_ari_conf *, cfg, ast_ari_config_get(), ao2_cleanup);
 | 
			
		||||
	return cfg && cfg->general && cfg->general->enabled;
 | 
			
		||||
	RAII_VAR(struct ari_conf_general *, general, ari_conf_get_general(), ao2_cleanup);
 | 
			
		||||
	return general && general->enabled;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*! Lock for \ref root_handler */
 | 
			
		||||
@@ -389,9 +282,9 @@ static void add_allow_header(struct stasis_rest_handlers *handler,
 | 
			
		||||
 | 
			
		||||
static int origin_allowed(const char *origin)
 | 
			
		||||
{
 | 
			
		||||
	RAII_VAR(struct ast_ari_conf *, cfg, ast_ari_config_get(), ao2_cleanup);
 | 
			
		||||
	RAII_VAR(struct ari_conf_general *, general, ari_conf_get_general(), ao2_cleanup);
 | 
			
		||||
 | 
			
		||||
	char *allowed = ast_strdupa(cfg->general->allowed_origins);
 | 
			
		||||
	char *allowed = ast_strdupa(general ? general->allowed_origins : "");
 | 
			
		||||
	char *current;
 | 
			
		||||
 | 
			
		||||
	while ((current = strsep(&allowed, ","))) {
 | 
			
		||||
@@ -555,7 +448,7 @@ static void handle_options(struct stasis_rest_handlers *handler,
 | 
			
		||||
 * \return User object for the authenticated user.
 | 
			
		||||
 * \retval NULL if authentication failed.
 | 
			
		||||
 */
 | 
			
		||||
static struct ast_ari_conf_user *authenticate_api_key(const char *api_key)
 | 
			
		||||
static struct ari_conf_user *authenticate_api_key(const char *api_key)
 | 
			
		||||
{
 | 
			
		||||
	RAII_VAR(char *, copy, NULL, ast_free);
 | 
			
		||||
	char *username;
 | 
			
		||||
@@ -572,7 +465,7 @@ static struct ast_ari_conf_user *authenticate_api_key(const char *api_key)
 | 
			
		||||
		return NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ast_ari_config_validate_user(username, password);
 | 
			
		||||
	return ari_conf_validate_user(username, password);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
@@ -583,7 +476,7 @@ static struct ast_ari_conf_user *authenticate_api_key(const char *api_key)
 | 
			
		||||
 * \return User object for the authenticated user.
 | 
			
		||||
 * \retval NULL if authentication failed.
 | 
			
		||||
 */
 | 
			
		||||
static struct ast_ari_conf_user *authenticate_user(struct ast_variable *get_params,
 | 
			
		||||
static struct ari_conf_user *authenticate_user(struct ast_variable *get_params,
 | 
			
		||||
	struct ast_variable *headers)
 | 
			
		||||
{
 | 
			
		||||
	RAII_VAR(struct ast_http_auth *, http_auth, NULL, ao2_cleanup);
 | 
			
		||||
@@ -592,7 +485,7 @@ static struct ast_ari_conf_user *authenticate_user(struct ast_variable *get_para
 | 
			
		||||
	/* HTTP Basic authentication */
 | 
			
		||||
	http_auth = ast_http_get_auth(headers);
 | 
			
		||||
	if (http_auth) {
 | 
			
		||||
		return ast_ari_config_validate_user(http_auth->userid,
 | 
			
		||||
		return ari_conf_validate_user(http_auth->userid,
 | 
			
		||||
			http_auth->password);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -642,8 +535,8 @@ enum ast_ari_invoke_result ast_ari_invoke(struct ast_tcptls_session_instance *se
 | 
			
		||||
	struct stasis_rest_handlers *handler = NULL;
 | 
			
		||||
	struct stasis_rest_handlers *wildcard_handler = NULL;
 | 
			
		||||
	RAII_VAR(struct ast_variable *, path_vars, NULL, ast_variables_destroy);
 | 
			
		||||
	RAII_VAR(struct ast_ari_conf_user *, user, NULL, ao2_cleanup);
 | 
			
		||||
	RAII_VAR(struct ast_ari_conf *, conf, ast_ari_config_get(), ao2_cleanup);
 | 
			
		||||
	RAII_VAR(struct ari_conf_user *, user, NULL, ao2_cleanup);
 | 
			
		||||
	RAII_VAR(struct ari_conf_general *, general, ari_conf_get_general(), ao2_cleanup);
 | 
			
		||||
 | 
			
		||||
	char *path = ast_strdupa(uri);
 | 
			
		||||
	char *path_segment = NULL;
 | 
			
		||||
@@ -651,7 +544,7 @@ enum ast_ari_invoke_result ast_ari_invoke(struct ast_tcptls_session_instance *se
 | 
			
		||||
	SCOPE_ENTER(3, "Request: %s %s, path:%s\n", ast_get_http_method(method), uri, path);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	if (!conf || !conf->general) {
 | 
			
		||||
	if (!general) {
 | 
			
		||||
		if (ser && source == ARI_INVOKE_SOURCE_REST) {
 | 
			
		||||
			ast_http_request_close_on_completion(ser);
 | 
			
		||||
		}
 | 
			
		||||
@@ -679,7 +572,7 @@ enum ast_ari_invoke_result ast_ari_invoke(struct ast_tcptls_session_instance *se
 | 
			
		||||
		 */
 | 
			
		||||
		ast_str_append(&response->headers, 0,
 | 
			
		||||
			"WWW-Authenticate: Basic realm=\"%s\"\r\n",
 | 
			
		||||
			conf->general->auth_realm);
 | 
			
		||||
			general->auth_realm);
 | 
			
		||||
		SCOPE_EXIT_RTN_VALUE(ARI_INVOKE_RESULT_ERROR_CONTINUE, "Response: %d : %s\n",
 | 
			
		||||
			response->response_code, response->response_text);
 | 
			
		||||
	} else if (!ast_fully_booted) {
 | 
			
		||||
@@ -1014,9 +907,8 @@ static void process_cors_request(struct ast_variable *headers,
 | 
			
		||||
 | 
			
		||||
enum ast_json_encoding_format ast_ari_json_format(void)
 | 
			
		||||
{
 | 
			
		||||
	RAII_VAR(struct ast_ari_conf *, cfg, NULL, ao2_cleanup);
 | 
			
		||||
	cfg = ast_ari_config_get();
 | 
			
		||||
	return cfg->general->format;
 | 
			
		||||
	RAII_VAR(struct ari_conf_general *, general, ari_conf_get_general(), ao2_cleanup);
 | 
			
		||||
	return general ? general->format : AST_JSON_COMPACT;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*!
 | 
			
		||||
@@ -1230,14 +1122,14 @@ static int unload_module(void)
 | 
			
		||||
{
 | 
			
		||||
	ari_websocket_unload_module();
 | 
			
		||||
 | 
			
		||||
	ast_ari_cli_unregister();
 | 
			
		||||
	ari_cli_unregister();
 | 
			
		||||
 | 
			
		||||
	if (is_enabled()) {
 | 
			
		||||
		ast_debug(3, "Disabling ARI\n");
 | 
			
		||||
		ast_http_uri_unlink(&http_uri);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ast_ari_config_destroy();
 | 
			
		||||
	ari_conf_destroy();
 | 
			
		||||
 | 
			
		||||
	ao2_cleanup(root_handler);
 | 
			
		||||
	root_handler = NULL;
 | 
			
		||||
@@ -1272,12 +1164,33 @@ static int load_module(void)
 | 
			
		||||
		return AST_MODULE_LOAD_DECLINE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (ast_ari_config_init() != 0) {
 | 
			
		||||
	/*
 | 
			
		||||
	 * ari_websocket_load_module() needs to know if ARI is enabled
 | 
			
		||||
	 * globally so it needs the "general" config to be loaded but it
 | 
			
		||||
	 * also needs to register a sorcery object observer for
 | 
			
		||||
	 * "outbound_websocket" BEFORE the outbound_websocket configs are loaded.
 | 
			
		||||
	 * outbound_websocket in turn needs the users to be loaded so we'll
 | 
			
		||||
	 * initialize sorcery and load "general" and "user" configs first, then
 | 
			
		||||
	 * load the websocket module, then load the "outbound_websocket" configs
 | 
			
		||||
	 * which will fire the observers.
 | 
			
		||||
	 */
 | 
			
		||||
	if (ari_conf_load(ARI_CONF_INIT | ARI_CONF_LOAD_GENERAL | ARI_CONF_LOAD_USER) != 0) {
 | 
			
		||||
		unload_module();
 | 
			
		||||
		return AST_MODULE_LOAD_DECLINE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (ari_websocket_load_module() != AST_MODULE_LOAD_SUCCESS) {
 | 
			
		||||
	if (ari_websocket_load_module(is_enabled()) != AST_MODULE_LOAD_SUCCESS) {
 | 
			
		||||
		unload_module();
 | 
			
		||||
		return AST_MODULE_LOAD_DECLINE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * Now we can load the outbound_websocket configs which will
 | 
			
		||||
	 * fire the observers.
 | 
			
		||||
	 */
 | 
			
		||||
	ari_conf_load(ARI_CONF_LOAD_OWC);
 | 
			
		||||
 | 
			
		||||
	if (ari_cli_register() != 0) {
 | 
			
		||||
		unload_module();
 | 
			
		||||
		return AST_MODULE_LOAD_DECLINE;
 | 
			
		||||
	}
 | 
			
		||||
@@ -1289,26 +1202,22 @@ static int load_module(void)
 | 
			
		||||
		ast_debug(3, "ARI disabled\n");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (ast_ari_cli_register() != 0) {
 | 
			
		||||
		unload_module();
 | 
			
		||||
		return AST_MODULE_LOAD_DECLINE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return AST_MODULE_LOAD_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int reload_module(void)
 | 
			
		||||
{
 | 
			
		||||
	char was_enabled = is_enabled();
 | 
			
		||||
	int is_now_enabled = 0;
 | 
			
		||||
 | 
			
		||||
	if (ast_ari_config_reload() != 0) {
 | 
			
		||||
		return AST_MODULE_LOAD_DECLINE;
 | 
			
		||||
	}
 | 
			
		||||
	ari_conf_load(ARI_CONF_RELOAD | ARI_CONF_LOAD_ALL);
 | 
			
		||||
 | 
			
		||||
	if (was_enabled && !is_enabled()) {
 | 
			
		||||
	is_now_enabled = is_enabled();
 | 
			
		||||
 | 
			
		||||
	if (was_enabled && !is_now_enabled) {
 | 
			
		||||
		ast_debug(3, "Disabling ARI\n");
 | 
			
		||||
		ast_http_uri_unlink(&http_uri);
 | 
			
		||||
	} else if (!was_enabled && is_enabled()) {
 | 
			
		||||
	} else if (!was_enabled && is_now_enabled) {
 | 
			
		||||
		ast_debug(3, "Enabling ARI\n");
 | 
			
		||||
		ast_http_uri_link(&http_uri);
 | 
			
		||||
	}
 | 
			
		||||
@@ -1321,6 +1230,6 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_
 | 
			
		||||
	.load = load_module,
 | 
			
		||||
	.unload = unload_module,
 | 
			
		||||
	.reload = reload_module,
 | 
			
		||||
	.requires = "http,res_stasis,res_http_websocket",
 | 
			
		||||
	.requires = "http,res_stasis,res_http_websocket,res_websocket_client",
 | 
			
		||||
	.load_pri = AST_MODPRI_APP_DEPEND,
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -169,6 +169,8 @@
 | 
			
		||||
				"RecordingFinished",
 | 
			
		||||
				"RecordingFailed",
 | 
			
		||||
				"ApplicationMoveFailed",
 | 
			
		||||
				"ApplicationRegistered",
 | 
			
		||||
				"ApplicationUnregistered",
 | 
			
		||||
				"ApplicationReplaced",
 | 
			
		||||
				"BridgeCreated",
 | 
			
		||||
				"BridgeDestroyed",
 | 
			
		||||
@@ -366,6 +368,16 @@
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		"ApplicationRegistered": {
 | 
			
		||||
			"id": "ApplicationRegistered",
 | 
			
		||||
			"description": "Notification that a Stasis app has been registered.",
 | 
			
		||||
			"properties": {}
 | 
			
		||||
		},
 | 
			
		||||
		"ApplicationUnregistered": {
 | 
			
		||||
			"id": "ApplicationUnregistered",
 | 
			
		||||
			"description": "Notification that a Stasis app has been unregistered.",
 | 
			
		||||
			"properties": {}
 | 
			
		||||
		},
 | 
			
		||||
		"ApplicationReplaced": {
 | 
			
		||||
			"id": "ApplicationReplaced",
 | 
			
		||||
			"description": "Notification that another WebSocket has taken over for an application.\n\nAn application may only be subscribed to by a single WebSocket at a time. If multiple WebSockets attempt to subscribe to the same application, the newer WebSocket wins, and the older one receives this event.",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user