2013-04-22 14:58:53 +00:00
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2012 - 2013, Digium, Inc.
*
* David M. Lee, II <dlee@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*! \file
*
* \brief HTTP binding for the Stasis API
* \author David M. Lee, II <dlee@digium.com>
*
* The API itself is documented using <a
* href="https://developers.helloreverb.com/swagger/">Swagger</a>, a lightweight
* mechanism for documenting RESTful API's using JSON. This allows us to use <a
* href="https://github.com/wordnik/swagger-ui">swagger-ui</a> to provide
* executable documentation for the API, generate client bindings in different
* <a href="https://github.com/asterisk/asterisk_rest_libraries">languages</a>,
* and generate a lot of the boilerplate code for implementing the RESTful
* bindings. The API docs live in the \c rest-api/ directory.
*
* The RESTful bindings are generated from the Swagger API docs using a set of
* <a href="http://mustache.github.io/mustache.5.html">Mustache</a> templates.
* The code generator is written in Python, and uses the Python implementation
* <a href="https://github.com/defunkt/pystache">pystache</a>. Pystache has no
* dependencies, and be installed easily using \c pip. Code generation code
* lives in \c rest-api-templates/.
*
* The generated code reduces a lot of boilerplate when it comes to handling
* HTTP requests. It also helps us have greater consistency in the REST API.
*
* The structure of the generated code is:
*
2013-07-27 23:11:02 +00:00
* - res/ari/resource_{resource}.h
2013-04-22 14:58:53 +00:00
* - For each operation in the resouce, a generated argument structure
* (holding the parsed arguments from the request) and function
2013-07-27 23:11:02 +00:00
* declarations (to implement in res/ari/resource_{resource}.c)
* - res_ari_{resource}.c
2013-04-22 14:58:53 +00:00
* - A set of \ref stasis_rest_callback functions, which glue the two
* together. They parse out path variables and request parameters to
* populate a specific \c *_args which is passed to the specific request
2013-07-27 23:11:02 +00:00
* handler (in res/ari/resource_{resource}.c)
2013-04-22 14:58:53 +00:00
* - A tree of \ref stasis_rest_handlers for routing requests to its
* \ref stasis_rest_callback
*
* The basic flow of an HTTP request is:
*
2013-07-27 23:11:02 +00:00
* - ast_ari_callback()
2013-04-22 14:58:53 +00:00
* 1. Initial request validation
2013-07-27 23:11:02 +00:00
* 2. Routes as either a doc request (ast_ari_get_docs) or API
* request (ast_ari_invoke)
* - ast_ari_invoke()
2013-04-22 14:58:53 +00:00
* 1. Further request validation
* 2. Routes the request through the tree of generated
* \ref stasis_rest_handlers.
* 3. Dispatch to the generated callback
2013-07-27 23:11:02 +00:00
* - \c ast_ari_*_cb
2013-04-22 14:58:53 +00:00
* 1. Populate \c *_args struct with path and get params
* 2. Invoke the request handler
* 3. Validates and sends response
*/
/*** MODULEINFO
2013-07-03 16:32:00 +00:00
<depend type="module">res_http_websocket</depend>
2017-11-02 02:57:52 -04:00
<depend type="module">res_stasis</depend>
2013-04-22 14:58:53 +00:00
<support_level>core</support_level>
***/
/*** DOCUMENTATION
2013-07-27 23:11:02 +00:00
<configInfo name="res_ari" language="en_US">
2013-04-22 14:58:53 +00:00
<synopsis>HTTP binding for the Stasis API</synopsis>
2013-07-03 16:33:13 +00:00
<configFile name="ari.conf">
<configObject name="general">
<synopsis>General configuration settings</synopsis>
2013-04-22 14:58:53 +00:00
<configOption name="enabled">
2013-07-27 23:11:02 +00:00
<synopsis>Enable/disable the ARI module</synopsis>
2014-03-19 02:33:55 +00:00
<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>
2014-03-18 23:32:00 +00:00
</description>
<see-also>
<ref type="filename">http.conf</ref>
<ref type="link">https://wiki.asterisk.org/wiki/display/AST/Asterisk+Builtin+mini-HTTP+Server</ref>
</see-also>
2013-04-22 14:58:53 +00:00
</configOption>
2014-06-26 12:21:14 +00:00
<configOption name="websocket_write_timeout">
<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; default is 100 ms.</para>
</description>
</configOption>
2013-04-22 14:58:53 +00:00
<configOption name="pretty">
2013-07-27 23:11:02 +00:00
<synopsis>Responses from ARI are formatted to be human readable</synopsis>
2013-04-22 14:58:53 +00:00
</configOption>
2013-07-03 16:33:13 +00:00
<configOption name="auth_realm">
<synopsis>Realm to use for authentication. Defaults to Asterisk REST Interface.</synopsis>
</configOption>
2013-07-15 21:22:12 +00:00
<configOption name="allowed_origins">
<synopsis>Comma separated list of allowed origins, for Cross-Origin Resource Sharing. May be set to * to allow all origins.</synopsis>
</configOption>
2016-11-11 11:45:37 -05:00
<configOption name="channelvars">
<synopsis>Comma separated list of channel variables to display in channel json.</synopsis>
</configOption>
2013-07-03 16:33:13 +00:00
</configObject>
<configObject name="user">
<synopsis>Per-user configuration settings</synopsis>
2013-07-11 14:39:55 +00:00
<configOption name="type">
<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>
2013-07-03 16:33:13 +00:00
<configOption name="read_only">
<synopsis>When set to yes, user is only authorized for read-only requests</synopsis>
</configOption>
<configOption name="password">
<synopsis>Crypted or plaintext password (see password_format)</synopsis>
</configOption>
<configOption name="password_format">
<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>
2013-04-22 14:58:53 +00:00
</configObject>
</configFile>
</configInfo>
***/
# include "asterisk.h"
2013-08-30 13:40:27 +00:00
# include "ari/internal.h"
# include "asterisk/ari.h"
2013-07-03 16:33:13 +00:00
# include "asterisk/astobj2.h"
2013-04-22 14:58:53 +00:00
# include "asterisk/module.h"
# include "asterisk/paths.h"
2017-01-19 08:05:36 -07:00
# include "asterisk/stasis_app.h"
2013-04-22 14:58:53 +00:00
# include <string.h>
# include <sys/stat.h>
# include <unistd.h>
/*! \brief Helper function to check if module is enabled. */
2013-07-03 16:33:13 +00:00
static int is_enabled ( void )
2013-04-22 14:58:53 +00:00
{
2013-07-27 23:11:02 +00:00
RAII_VAR ( struct ast_ari_conf * , cfg , ast_ari_config_get ( ) , ao2_cleanup ) ;
2013-07-03 16:33:13 +00:00
return cfg & & cfg - > general & & cfg - > general - > enabled ;
2013-04-22 14:58:53 +00:00
}
/*! Lock for \ref root_handler */
static ast_mutex_t root_handler_lock ;
/*! Handler for root RESTful resource. */
static struct stasis_rest_handlers * root_handler ;
/*! Pre-defined message for allocation failures. */
2013-07-03 16:32:00 +00:00
static struct ast_json * oom_json ;
2013-07-27 23:11:02 +00:00
struct ast_json * ast_ari_oom_json ( void )
2013-07-03 16:32:00 +00:00
{
return oom_json ;
}
2013-04-22 14:58:53 +00:00
2013-07-27 23:11:02 +00:00
int ast_ari_add_handler ( struct stasis_rest_handlers * handler )
2013-04-22 14:58:53 +00:00
{
RAII_VAR ( struct stasis_rest_handlers * , new_handler , NULL , ao2_cleanup ) ;
size_t old_size , new_size ;
SCOPED_MUTEX ( lock , & root_handler_lock ) ;
2015-08-18 14:46:46 -05:00
old_size = sizeof ( * new_handler ) + root_handler - > num_children * sizeof ( handler ) ;
2013-04-22 14:58:53 +00:00
new_size = old_size + sizeof ( handler ) ;
new_handler = ao2_alloc ( new_size , NULL ) ;
if ( ! new_handler ) {
return - 1 ;
}
memcpy ( new_handler , root_handler , old_size ) ;
new_handler - > children [ new_handler - > num_children + + ] = handler ;
ao2_cleanup ( root_handler ) ;
ao2_ref ( new_handler , + 1 ) ;
root_handler = new_handler ;
return 0 ;
}
2013-07-27 23:11:02 +00:00
int ast_ari_remove_handler ( struct stasis_rest_handlers * handler )
2013-04-22 14:58:53 +00:00
{
2015-08-18 14:46:46 -05:00
struct stasis_rest_handlers * new_handler ;
size_t size ;
size_t i ;
size_t j ;
2013-04-22 14:58:53 +00:00
2013-05-10 17:12:57 +00:00
ast_assert ( root_handler ! = NULL ) ;
2013-04-30 13:37:09 +00:00
ast_mutex_lock ( & root_handler_lock ) ;
2015-08-18 14:46:46 -05:00
size = sizeof ( * new_handler ) + root_handler - > num_children * sizeof ( handler ) ;
2013-04-22 14:58:53 +00:00
2013-04-30 13:37:09 +00:00
new_handler = ao2_alloc ( size , NULL ) ;
2013-04-22 14:58:53 +00:00
if ( ! new_handler ) {
2015-08-18 14:46:46 -05:00
ast_mutex_unlock ( & root_handler_lock ) ;
2013-04-22 14:58:53 +00:00
return - 1 ;
}
2015-08-18 14:46:46 -05:00
/* Create replacement root_handler less the handler to remove. */
memcpy ( new_handler , root_handler , sizeof ( * new_handler ) ) ;
2013-04-22 14:58:53 +00:00
for ( i = 0 , j = 0 ; i < root_handler - > num_children ; + + i ) {
if ( root_handler - > children [ i ] = = handler ) {
continue ;
}
new_handler - > children [ j + + ] = root_handler - > children [ i ] ;
}
new_handler - > num_children = j ;
2015-08-18 14:46:46 -05:00
/* Replace the old root_handler with the new. */
2013-04-22 14:58:53 +00:00
ao2_cleanup ( root_handler ) ;
root_handler = new_handler ;
2015-08-18 14:46:46 -05:00
2013-04-30 13:37:09 +00:00
ast_mutex_unlock ( & root_handler_lock ) ;
2013-04-22 14:58:53 +00:00
return 0 ;
}
static struct stasis_rest_handlers * get_root_handler ( void )
{
SCOPED_MUTEX ( lock , & root_handler_lock ) ;
ao2_ref ( root_handler , + 1 ) ;
return root_handler ;
}
static struct stasis_rest_handlers * root_handler_create ( void )
{
RAII_VAR ( struct stasis_rest_handlers * , handler , NULL , ao2_cleanup ) ;
handler = ao2_alloc ( sizeof ( * handler ) , NULL ) ;
if ( ! handler ) {
return NULL ;
}
2013-07-03 16:32:00 +00:00
handler - > path_segment = " ari " ;
2013-04-22 14:58:53 +00:00
ao2_ref ( handler , + 1 ) ;
return handler ;
}
2013-07-27 23:11:02 +00:00
void ast_ari_response_error ( struct ast_ari_response * response ,
2013-04-22 14:58:53 +00:00
int response_code ,
const char * response_text ,
const char * message_fmt , . . . )
{
RAII_VAR ( struct ast_json * , message , NULL , ast_json_unref ) ;
va_list ap ;
va_start ( ap , message_fmt ) ;
message = ast_json_vstringf ( message_fmt , ap ) ;
2014-07-03 17:16:55 +00:00
va_end ( ap ) ;
2013-04-22 14:58:53 +00:00
response - > message = ast_json_pack ( " {s: o} " ,
" message " , ast_json_ref ( message ) ) ;
response - > response_code = response_code ;
response - > response_text = response_text ;
}
2013-07-27 23:11:02 +00:00
void ast_ari_response_ok ( struct ast_ari_response * response ,
2013-04-22 14:58:53 +00:00
struct ast_json * message )
{
2014-01-12 22:24:27 +00:00
response - > message = message ;
2013-04-22 14:58:53 +00:00
response - > response_code = 200 ;
response - > response_text = " OK " ;
}
2013-07-27 23:11:02 +00:00
void ast_ari_response_no_content ( struct ast_ari_response * response )
2013-04-22 14:58:53 +00:00
{
2013-07-03 16:32:41 +00:00
response - > message = ast_json_null ( ) ;
2013-04-22 14:58:53 +00:00
response - > response_code = 204 ;
response - > response_text = " No Content " ;
}
2015-07-14 13:12:32 -05:00
void ast_ari_response_accepted ( struct ast_ari_response * response )
{
response - > message = ast_json_null ( ) ;
response - > response_code = 202 ;
response - > response_text = " Accepted " ;
}
2013-07-27 23:11:02 +00:00
void ast_ari_response_alloc_failed ( struct ast_ari_response * response )
2013-04-22 14:58:53 +00:00
{
2013-07-03 16:32:00 +00:00
response - > message = ast_json_ref ( oom_json ) ;
2013-04-22 14:58:53 +00:00
response - > response_code = 500 ;
response - > response_text = " Internal Server Error " ;
}
2013-07-27 23:11:02 +00:00
void ast_ari_response_created ( struct ast_ari_response * response ,
2013-05-23 21:46:38 +00:00
const char * url , struct ast_json * message )
2013-05-23 20:11:35 +00:00
{
2016-05-14 13:29:09 -04:00
RAII_VAR ( struct stasis_rest_handlers * , root , get_root_handler ( ) , ao2_cleanup ) ;
2014-01-12 22:24:27 +00:00
response - > message = message ;
2013-05-23 20:11:35 +00:00
response - > response_code = 201 ;
response - > response_text = " Created " ;
2016-05-14 13:29:09 -04:00
ast_str_append ( & response - > headers , 0 , " Location: /%s%s \r \n " , root - > path_segment , url ) ;
2013-05-23 20:11:35 +00:00
}
2013-04-22 14:58:53 +00:00
static void add_allow_header ( struct stasis_rest_handlers * handler ,
2013-07-27 23:11:02 +00:00
struct ast_ari_response * response )
2013-04-22 14:58:53 +00:00
{
enum ast_http_method m ;
ast_str_append ( & response - > headers , 0 ,
" Allow: OPTIONS " ) ;
for ( m = 0 ; m < AST_HTTP_MAX_METHOD ; + + m ) {
if ( handler - > callbacks [ m ] ! = NULL ) {
ast_str_append ( & response - > headers , 0 ,
" ,%s " , ast_get_http_method ( m ) ) ;
}
}
ast_str_append ( & response - > headers , 0 , " \r \n " ) ;
}
2013-07-12 17:52:52 +00:00
static int origin_allowed ( const char * origin )
{
2013-07-27 23:11:02 +00:00
RAII_VAR ( struct ast_ari_conf * , cfg , ast_ari_config_get ( ) , ao2_cleanup ) ;
2013-07-12 17:52:52 +00:00
char * allowed = ast_strdupa ( cfg - > general - > allowed_origins ) ;
char * current ;
while ( ( current = strsep ( & allowed , " , " ) ) ) {
if ( ! strcmp ( current , " * " ) ) {
return 1 ;
}
if ( ! strcmp ( current , origin ) ) {
return 1 ;
}
}
return 0 ;
}
2013-04-22 14:58:53 +00:00
# define ACR_METHOD "Access-Control-Request-Method"
# define ACR_HEADERS "Access-Control-Request-Headers"
# define ACA_METHODS "Access-Control-Allow-Methods"
# define ACA_HEADERS "Access-Control-Allow-Headers"
/*!
* \brief Handle OPTIONS request, mainly for CORS preflight requests.
*
* Some browsers will send this prior to non-simple methods (i.e. DELETE).
* See http://www.w3.org/TR/cors/ for the spec. Especially section 6.2.
*/
static void handle_options ( struct stasis_rest_handlers * handler ,
struct ast_variable * headers ,
2013-07-27 23:11:02 +00:00
struct ast_ari_response * response )
2013-04-22 14:58:53 +00:00
{
struct ast_variable * header ;
char const * acr_method = NULL ;
char const * acr_headers = NULL ;
char const * origin = NULL ;
RAII_VAR ( struct ast_str * , allow , NULL , ast_free ) ;
enum ast_http_method m ;
int allowed = 0 ;
/* Regular OPTIONS response */
add_allow_header ( handler , response ) ;
2013-07-27 23:11:02 +00:00
ast_ari_response_no_content ( response ) ;
2013-04-22 14:58:53 +00:00
/* Parse CORS headers */
for ( header = headers ; header ! = NULL ; header = header - > next ) {
if ( strcmp ( ACR_METHOD , header - > name ) = = 0 ) {
acr_method = header - > value ;
} else if ( strcmp ( ACR_HEADERS , header - > name ) = = 0 ) {
acr_headers = header - > value ;
} else if ( strcmp ( " Origin " , header - > name ) = = 0 ) {
origin = header - > value ;
}
}
/* CORS 6.2, #1 - "If the Origin header is not present terminate this
2013-07-12 17:52:52 +00:00
* set of steps."
2013-04-22 14:58:53 +00:00
*/
if ( origin = = NULL ) {
return ;
}
/* CORS 6.2, #2 - "If the value of the Origin header is not a
* case-sensitive match for any of the values in list of origins do not
* set any additional headers and terminate this set of steps.
*
2013-07-12 17:52:52 +00:00
* Always matching is acceptable since the list of origins can be
2013-04-22 14:58:53 +00:00
* unbounded.
*
2013-07-12 17:52:52 +00:00
* The Origin header can only contain a single origin as the user agent
* will not follow redirects."
2013-04-22 14:58:53 +00:00
*/
2013-07-12 17:52:52 +00:00
if ( ! origin_allowed ( origin ) ) {
ast_log ( LOG_NOTICE , " Origin header '%s' does not match an allowed origin. \n " , origin ) ;
return ;
}
2013-04-22 14:58:53 +00:00
/* CORS 6.2, #3 - "If there is no Access-Control-Request-Method header
* or if parsing failed, do not set any additional headers and terminate
* this set of steps."
*/
if ( acr_method = = NULL ) {
return ;
}
/* CORS 6.2, #4 - "If there are no Access-Control-Request-Headers
* headers let header field-names be the empty list."
*/
if ( acr_headers = = NULL ) {
acr_headers = " " ;
}
/* CORS 6.2, #5 - "If method is not a case-sensitive match for any of
* the values in list of methods do not set any additional headers and
* terminate this set of steps."
*/
allow = ast_str_create ( 20 ) ;
if ( ! allow ) {
2013-07-27 23:11:02 +00:00
ast_ari_response_alloc_failed ( response ) ;
2013-04-22 14:58:53 +00:00
return ;
}
/* Go ahead and build the ACA_METHODS header at the same time */
for ( m = 0 ; m < AST_HTTP_MAX_METHOD ; + + m ) {
if ( handler - > callbacks [ m ] ! = NULL ) {
char const * m_str = ast_get_http_method ( m ) ;
if ( strcmp ( m_str , acr_method ) = = 0 ) {
allowed = 1 ;
}
ast_str_append ( & allow , 0 , " ,%s " , m_str ) ;
}
}
if ( ! allowed ) {
return ;
}
/* CORS 6.2 #6 - "If any of the header field-names is not a ASCII
* case-insensitive match for any of the values in list of headers do
* not set any additional headers and terminate this set of steps.
*
2013-07-12 17:52:52 +00:00
* Note: Always matching is acceptable since the list of headers can be
2013-04-22 14:58:53 +00:00
* unbounded."
*/
/* CORS 6.2 #7 - "If the resource supports credentials add a single
* Access-Control-Allow-Origin header, with the value of the Origin
* header as value, and add a single Access-Control-Allow-Credentials
* header with the case-sensitive string "true" as value."
*
* Added by process_cors_request() earlier in the request.
*/
/* CORS 6.2 #8 - "Optionally add a single Access-Control-Max-Age
* header..."
*/
/* CORS 6.2 #9 - "Add one or more Access-Control-Allow-Methods headers
* consisting of (a subset of) the list of methods."
*/
2013-10-21 18:59:22 +00:00
ast_str_append ( & response - > headers , 0 , " %s: OPTIONS%s \r \n " ,
2013-04-22 14:58:53 +00:00
ACA_METHODS , ast_str_buffer ( allow ) ) ;
/* CORS 6.2, #10 - "Add one or more Access-Control-Allow-Headers headers
* consisting of (a subset of) the list of headers.
*
2013-07-12 17:52:52 +00:00
* Since the list of headers can be unbounded simply returning headers
2013-04-22 14:58:53 +00:00
* can be enough."
*/
if ( ! ast_strlen_zero ( acr_headers ) ) {
ast_str_append ( & response - > headers , 0 , " %s: %s \r \n " ,
ACA_HEADERS , acr_headers ) ;
}
}
2013-07-27 23:11:02 +00:00
void ast_ari_invoke ( struct ast_tcptls_session_instance * ser ,
2013-07-03 16:32:00 +00:00
const char * uri , enum ast_http_method method ,
struct ast_variable * get_params , struct ast_variable * headers ,
2017-01-19 08:05:36 -07:00
struct ast_json * body , struct ast_ari_response * response )
2013-04-22 14:58:53 +00:00
{
RAII_VAR ( struct stasis_rest_handlers * , root , NULL , ao2_cleanup ) ;
struct stasis_rest_handlers * handler ;
2014-01-12 22:24:27 +00:00
RAII_VAR ( struct ast_variable * , path_vars , NULL , ast_variables_destroy ) ;
2013-04-22 14:58:53 +00:00
char * path = ast_strdupa ( uri ) ;
2013-06-24 13:49:20 +00:00
char * path_segment ;
2013-04-22 14:58:53 +00:00
stasis_rest_callback callback ;
root = handler = get_root_handler ( ) ;
ast_assert ( root ! = NULL ) ;
while ( ( path_segment = strsep ( & path , " / " ) ) & & ( strlen ( path_segment ) > 0 ) ) {
struct stasis_rest_handlers * found_handler = NULL ;
int i ;
2017-01-19 08:05:36 -07:00
2013-06-24 13:49:20 +00:00
ast_uri_decode ( path_segment , ast_uri_http_legacy ) ;
2013-04-22 14:58:53 +00:00
ast_debug ( 3 , " Finding handler for %s \n " , path_segment ) ;
2017-01-19 08:05:36 -07:00
2013-04-22 14:58:53 +00:00
for ( i = 0 ; found_handler = = NULL & & i < handler - > num_children ; + + i ) {
struct stasis_rest_handlers * child = handler - > children [ i ] ;
ast_debug ( 3 , " Checking %s \n " , child - > path_segment ) ;
if ( child - > is_wildcard ) {
/* Record the path variable */
struct ast_variable * path_var = ast_variable_new ( child - > path_segment , path_segment , __FILE__ ) ;
path_var - > next = path_vars ;
path_vars = path_var ;
found_handler = child ;
} else if ( strcmp ( child - > path_segment , path_segment ) = = 0 ) {
found_handler = child ;
}
}
if ( found_handler = = NULL ) {
/* resource not found */
ast_debug ( 3 , " Handler not found \n " ) ;
2013-07-27 23:11:02 +00:00
ast_ari_response_error (
2013-04-22 14:58:53 +00:00
response , 404 , " Not Found " ,
" Resource not found " ) ;
return ;
} else {
ast_debug ( 3 , " Got it! \n " ) ;
handler = found_handler ;
}
}
ast_assert ( handler ! = NULL ) ;
if ( method = = AST_HTTP_OPTIONS ) {
handle_options ( handler , headers , response ) ;
return ;
}
if ( method < 0 | | method > = AST_HTTP_MAX_METHOD ) {
add_allow_header ( handler , response ) ;
2013-07-27 23:11:02 +00:00
ast_ari_response_error (
2013-04-22 14:58:53 +00:00
response , 405 , " Method Not Allowed " ,
" Invalid method " ) ;
return ;
}
2013-07-03 16:32:00 +00:00
if ( handler - > ws_server & & method = = AST_HTTP_GET ) {
/* WebSocket! */
2013-08-30 13:40:27 +00:00
ari_handle_websocket ( handler - > ws_server , ser , uri , method ,
get_params , headers ) ;
2013-07-03 16:32:00 +00:00
/* Since the WebSocket code handles the connection, we shouldn't
* do anything else; setting no_response */
response - > no_response = 1 ;
return ;
}
2013-04-22 14:58:53 +00:00
callback = handler - > callbacks [ method ] ;
if ( callback = = NULL ) {
add_allow_header ( handler , response ) ;
2013-07-27 23:11:02 +00:00
ast_ari_response_error (
2013-04-22 14:58:53 +00:00
response , 405 , " Method Not Allowed " ,
" Invalid method " ) ;
return ;
}
2017-01-19 08:05:36 -07:00
callback ( ser , get_params , path_vars , headers , body , response ) ;
2013-04-22 14:58:53 +00:00
if ( response - > message = = NULL & & response - > response_code = = 0 ) {
/* Really should not happen */
2013-07-23 14:57:03 +00:00
ast_log ( LOG_ERROR , " ARI %s %s not implemented \n " ,
ast_get_http_method ( method ) , uri ) ;
2013-07-27 23:11:02 +00:00
ast_ari_response_error (
2013-07-23 14:57:03 +00:00
response , 501 , " Not Implemented " ,
2013-04-22 14:58:53 +00:00
" Method not implemented " ) ;
}
}
2016-08-17 15:10:54 +02:00
void ast_ari_get_docs ( const char * uri , const char * prefix , struct ast_variable * headers ,
2013-07-27 23:11:02 +00:00
struct ast_ari_response * response )
2013-04-22 14:58:53 +00:00
{
RAII_VAR ( struct ast_str * , absolute_path_builder , NULL , ast_free ) ;
2013-10-02 17:12:49 +00:00
RAII_VAR ( char * , absolute_api_dirname , NULL , ast_std_free ) ;
RAII_VAR ( char * , absolute_filename , NULL , ast_std_free ) ;
2013-04-22 14:58:53 +00:00
struct ast_json * obj = NULL ;
struct ast_variable * host = NULL ;
struct ast_json_error error = { } ;
struct stat file_stat ;
ast_debug ( 3 , " %s(%s) \n " , __func__ , uri ) ;
absolute_path_builder = ast_str_create ( 80 ) ;
if ( absolute_path_builder = = NULL ) {
2013-07-27 23:11:02 +00:00
ast_ari_response_alloc_failed ( response ) ;
2013-04-22 14:58:53 +00:00
return ;
}
/* absolute path to the rest-api directory */
ast_str_append ( & absolute_path_builder , 0 , " %s " , ast_config_AST_DATA_DIR ) ;
ast_str_append ( & absolute_path_builder , 0 , " /rest-api/ " ) ;
absolute_api_dirname = realpath ( ast_str_buffer ( absolute_path_builder ) , NULL ) ;
if ( absolute_api_dirname = = NULL ) {
ast_log ( LOG_ERROR , " Error determining real directory for rest-api \n " ) ;
2013-07-27 23:11:02 +00:00
ast_ari_response_error (
2013-04-22 14:58:53 +00:00
response , 500 , " Internal Server Error " ,
" Cannot find rest-api directory " ) ;
return ;
}
/* absolute path to the requested file */
ast_str_append ( & absolute_path_builder , 0 , " %s " , uri ) ;
absolute_filename = realpath ( ast_str_buffer ( absolute_path_builder ) , NULL ) ;
if ( absolute_filename = = NULL ) {
switch ( errno ) {
case ENAMETOOLONG :
case ENOENT :
case ENOTDIR :
2013-07-27 23:11:02 +00:00
ast_ari_response_error (
2013-04-22 14:58:53 +00:00
response , 404 , " Not Found " ,
" Resource not found " ) ;
break ;
case EACCES :
2013-07-27 23:11:02 +00:00
ast_ari_response_error (
2013-04-22 14:58:53 +00:00
response , 403 , " Forbidden " ,
" Permission denied " ) ;
break ;
default :
ast_log ( LOG_ERROR ,
" Error determining real path for uri '%s': %s \n " ,
uri , strerror ( errno ) ) ;
2013-07-27 23:11:02 +00:00
ast_ari_response_error (
2013-04-22 14:58:53 +00:00
response , 500 , " Internal Server Error " ,
" Cannot find file " ) ;
break ;
}
return ;
}
if ( ! ast_begins_with ( absolute_filename , absolute_api_dirname ) ) {
/* HACKERZ! */
ast_log ( LOG_ERROR ,
" Invalid attempt to access '%s' (not in %s) \n " ,
absolute_filename , absolute_api_dirname ) ;
2013-07-27 23:11:02 +00:00
ast_ari_response_error (
2013-04-22 14:58:53 +00:00
response , 404 , " Not Found " ,
" Resource not found " ) ;
return ;
}
if ( stat ( absolute_filename , & file_stat ) = = 0 ) {
if ( ! ( file_stat . st_mode & S_IFREG ) ) {
/* Not a file */
2013-07-27 23:11:02 +00:00
ast_ari_response_error (
2013-04-22 14:58:53 +00:00
response , 403 , " Forbidden " ,
" Invalid access " ) ;
return ;
}
} else {
/* Does not exist */
2013-07-27 23:11:02 +00:00
ast_ari_response_error (
2013-04-22 14:58:53 +00:00
response , 404 , " Not Found " ,
" Resource not found " ) ;
return ;
}
/* Load resource object from file */
obj = ast_json_load_new_file ( absolute_filename , & error ) ;
if ( obj = = NULL ) {
ast_log ( LOG_ERROR , " Error parsing resource file: %s:%d(%d) %s \n " ,
error . source , error . line , error . column , error . text ) ;
2013-07-27 23:11:02 +00:00
ast_ari_response_error (
2013-04-22 14:58:53 +00:00
response , 500 , " Internal Server Error " ,
" Yikes! Cannot parse resource " ) ;
return ;
}
/* Update the basePath properly */
if ( ast_json_object_get ( obj , " basePath " ) ! = NULL ) {
for ( host = headers ; host ; host = host - > next ) {
if ( strcasecmp ( host - > name , " Host " ) = = 0 ) {
break ;
}
}
if ( host ! = NULL ) {
2016-08-17 15:10:54 +02:00
if ( prefix ! = NULL & & strlen ( prefix ) > 0 ) {
ast_json_object_set (
obj , " basePath " ,
ast_json_stringf ( " http://%s%s/ari " , host - > value , prefix ) ) ;
} else {
ast_json_object_set (
obj , " basePath " ,
ast_json_stringf ( " http://%s/ari " , host - > value ) ) ;
}
2013-04-22 14:58:53 +00:00
} else {
/* Without the host, we don't have the basePath */
ast_json_object_del ( obj , " basePath " ) ;
}
}
2013-07-27 23:11:02 +00:00
ast_ari_response_ok ( response , obj ) ;
2013-04-22 14:58:53 +00:00
}
static void remove_trailing_slash ( const char * uri ,
2013-07-27 23:11:02 +00:00
struct ast_ari_response * response )
2013-04-22 14:58:53 +00:00
{
char * slashless = ast_strdupa ( uri ) ;
slashless [ strlen ( slashless ) - 1 ] = ' \0 ' ;
2013-06-28 01:07:32 +00:00
/* While it's tempting to redirect the client to the slashless URL,
* that is problematic. A 302 Found is the most appropriate response,
* but most clients issue a GET on the location you give them,
* regardless of the method of the original request.
*
* While there are some ways around this, it gets into a lot of client
* specific behavior and corner cases in the HTTP standard. There's also
* very little practical benefit of redirecting; only GET and HEAD can
* be redirected automagically; all other requests "MUST NOT
* automatically redirect the request unless it can be confirmed by the
* user, since this might change the conditions under which the request
* was issued."
*
* Given all of that, a 404 with a nice message telling them what to do
* is probably our best bet.
*/
2013-07-27 23:11:02 +00:00
ast_ari_response_error ( response , 404 , " Not Found " ,
2013-07-03 16:32:00 +00:00
" ARI URLs do not end with a slash. Try /ari/%s " , slashless ) ;
2013-04-22 14:58:53 +00:00
}
/*!
* \brief Handle CORS headers for simple requests.
*
* See http://www.w3.org/TR/cors/ for the spec. Especially section 6.1.
*/
static void process_cors_request ( struct ast_variable * headers ,
2013-07-27 23:11:02 +00:00
struct ast_ari_response * response )
2013-04-22 14:58:53 +00:00
{
char const * origin = NULL ;
struct ast_variable * header ;
/* Parse CORS headers */
for ( header = headers ; header ! = NULL ; header = header - > next ) {
if ( strcmp ( " Origin " , header - > name ) = = 0 ) {
origin = header - > value ;
}
}
/* CORS 6.1, #1 - "If the Origin header is not present terminate this
* set of steps."
*/
if ( origin = = NULL ) {
return ;
}
/* CORS 6.1, #2 - "If the value of the Origin header is not a
* case-sensitive match for any of the values in list of origins, do not
* set any additional headers and terminate this set of steps.
*
2013-07-12 17:52:52 +00:00
* Note: Always matching is acceptable since the list of origins can be
2013-04-22 14:58:53 +00:00
* unbounded."
*/
2013-07-12 17:52:52 +00:00
if ( ! origin_allowed ( origin ) ) {
ast_log ( LOG_NOTICE , " Origin header '%s' does not match an allowed origin. \n " , origin ) ;
return ;
}
2013-04-22 14:58:53 +00:00
/* CORS 6.1, #3 - "If the resource supports credentials add a single
* Access-Control-Allow-Origin header, with the value of the Origin
* header as value, and add a single Access-Control-Allow-Credentials
* header with the case-sensitive string "true" as value.
*
2013-07-12 17:52:52 +00:00
* Otherwise, add a single Access-Control-Allow-Origin header, with
2013-04-22 14:58:53 +00:00
* either the value of the Origin header or the string "*" as value."
*/
ast_str_append ( & response - > headers , 0 ,
" Access-Control-Allow-Origin: %s \r \n " , origin ) ;
2013-07-12 17:52:52 +00:00
ast_str_append ( & response - > headers , 0 ,
" Access-Control-Allow-Credentials: true \r \n " ) ;
2013-04-22 14:58:53 +00:00
/* CORS 6.1, #4 - "If the list of exposed headers is not empty add one
* or more Access-Control-Expose-Headers headers, with as values the
* header field names given in the list of exposed headers."
*
* No exposed headers; skipping
*/
}
2013-07-27 23:11:02 +00:00
enum ast_json_encoding_format ast_ari_json_format ( void )
2013-07-03 16:32:41 +00:00
{
2013-07-27 23:11:02 +00:00
RAII_VAR ( struct ast_ari_conf * , cfg , NULL , ao2_cleanup ) ;
cfg = ast_ari_config_get ( ) ;
2013-07-03 16:33:13 +00:00
return cfg - > general - > format ;
}
/*!
* \brief Authenticate a <code>?api_key=userid:password</code>
*
* \param api_key API key query parameter
* \return User object for the authenticated user.
* \return \c NULL if authentication failed.
*/
2013-07-27 23:11:02 +00:00
static struct ast_ari_conf_user * authenticate_api_key ( const char * api_key )
2013-07-03 16:33:13 +00:00
{
RAII_VAR ( char * , copy , NULL , ast_free ) ;
char * username ;
char * password ;
password = copy = ast_strdup ( api_key ) ;
if ( ! copy ) {
return NULL ;
}
username = strsep ( & password , " : " ) ;
if ( ! password ) {
ast_log ( LOG_WARNING , " Invalid api_key \n " ) ;
return NULL ;
}
2013-07-27 23:11:02 +00:00
return ast_ari_config_validate_user ( username , password ) ;
2013-07-03 16:33:13 +00:00
}
/*!
* \brief Authenticate an HTTP request.
*
* \param get_params GET parameters of the request.
* \param header HTTP headers.
* \return User object for the authenticated user.
* \return \c NULL if authentication failed.
*/
2013-07-27 23:11:02 +00:00
static struct ast_ari_conf_user * authenticate_user ( struct ast_variable * get_params ,
2013-07-03 16:33:13 +00:00
struct ast_variable * headers )
{
RAII_VAR ( struct ast_http_auth * , http_auth , NULL , ao2_cleanup ) ;
struct ast_variable * v ;
/* HTTP Basic authentication */
http_auth = ast_http_get_auth ( headers ) ;
if ( http_auth ) {
2013-07-27 23:11:02 +00:00
return ast_ari_config_validate_user ( http_auth - > userid ,
2013-07-03 16:33:13 +00:00
http_auth - > password ) ;
}
/* ?api_key authentication */
for ( v = get_params ; v ; v = v - > next ) {
if ( strcasecmp ( " api_key " , v - > name ) = = 0 ) {
return authenticate_api_key ( v - > value ) ;
}
}
return NULL ;
2013-07-03 16:32:41 +00:00
}
2013-04-22 14:58:53 +00:00
/*!
* \internal
2013-07-27 23:11:02 +00:00
* \brief ARI HTTP handler.
2013-04-22 14:58:53 +00:00
*
* This handler takes the HTTP request and turns it into the appropriate
* RESTful request (conversion to JSON, routing, etc.)
*
* \param ser TCP session.
* \param urih URI handler.
* \param uri URI requested.
* \param method HTTP method.
* \param get_params HTTP \c GET params.
* \param headers HTTP headers.
*/
2013-07-27 23:11:02 +00:00
static int ast_ari_callback ( struct ast_tcptls_session_instance * ser ,
2013-04-22 14:58:53 +00:00
const struct ast_http_uri * urih ,
const char * uri ,
enum ast_http_method method ,
struct ast_variable * get_params ,
struct ast_variable * headers )
{
2013-07-27 23:11:02 +00:00
RAII_VAR ( struct ast_ari_conf * , conf , NULL , ao2_cleanup ) ;
2013-04-22 14:58:53 +00:00
RAII_VAR ( struct ast_str * , response_body , ast_str_create ( 256 ) , ast_free ) ;
2013-07-27 23:11:02 +00:00
RAII_VAR ( struct ast_ari_conf_user * , user , NULL , ao2_cleanup ) ;
2016-05-18 06:19:58 -05:00
struct ast_ari_response response = { . fd = - 1 , 0 } ;
2013-11-08 17:29:53 +00:00
RAII_VAR ( struct ast_variable * , post_vars , NULL , ast_variables_destroy ) ;
2017-01-19 08:05:36 -07:00
struct ast_variable * var ;
const char * app_name = NULL ;
2017-10-08 21:05:56 +02:00
RAII_VAR ( struct ast_json * , body , ast_json_null ( ) , ast_json_unref ) ;
2017-01-19 08:05:36 -07:00
int debug_app = 0 ;
2013-04-22 14:58:53 +00:00
2014-01-12 22:24:27 +00:00
if ( ! response_body ) {
2014-07-03 19:06:12 +00:00
ast_http_request_close_on_completion ( ser ) ;
ast_http_error ( ser , 500 , " Server Error " , " Out of memory " ) ;
return 0 ;
2013-04-22 14:58:53 +00:00
}
response . headers = ast_str_create ( 40 ) ;
2013-07-03 16:33:13 +00:00
if ( ! response . headers ) {
2014-07-03 19:06:12 +00:00
ast_http_request_close_on_completion ( ser ) ;
ast_http_error ( ser , 500 , " Server Error " , " Out of memory " ) ;
return 0 ;
2013-07-03 16:33:13 +00:00
}
2013-07-27 23:11:02 +00:00
conf = ast_ari_config_get ( ) ;
2013-07-03 16:33:13 +00:00
if ( ! conf | | ! conf - > general ) {
2014-01-12 22:24:27 +00:00
ast_free ( response . headers ) ;
2014-07-03 19:06:12 +00:00
ast_http_request_close_on_completion ( ser ) ;
ast_http_error ( ser , 500 , " Server Error " , " URI handler config missing " ) ;
return 0 ;
2013-07-03 16:33:13 +00:00
}
2013-04-22 14:58:53 +00:00
process_cors_request ( headers , & response ) ;
2013-11-08 17:29:53 +00:00
/* Process form data from a POST. It could be mixed with query
* parameters, which seems a bit odd. But it's allowed, so that's okay
* with us.
*/
post_vars = ast_http_get_post_vars ( ser , headers ) ;
2014-07-03 17:16:55 +00:00
if ( ! post_vars ) {
2013-11-08 17:29:53 +00:00
switch ( errno ) {
case EFBIG :
ast_ari_response_error ( & response , 413 ,
" Request Entity Too Large " ,
" Request body too large " ) ;
2014-07-03 17:16:55 +00:00
goto request_failed ;
2013-11-08 17:29:53 +00:00
case ENOMEM :
2014-07-03 19:06:12 +00:00
ast_http_request_close_on_completion ( ser ) ;
2013-11-08 17:29:53 +00:00
ast_ari_response_error ( & response , 500 ,
" Internal Server Error " ,
2014-07-03 19:06:12 +00:00
" Out of memory " ) ;
2014-07-03 17:16:55 +00:00
goto request_failed ;
2013-11-08 17:29:53 +00:00
case EIO :
ast_ari_response_error ( & response , 400 ,
" Bad Request " , " Error parsing request body " ) ;
2014-07-03 17:16:55 +00:00
goto request_failed ;
2013-11-08 17:29:53 +00:00
}
2017-01-19 08:05:36 -07:00
/* Look for a JSON request entity only if there were no post_vars.
* If there were post_vars, then the request body would already have
* been consumed and can not be read again.
*/
body = ast_http_get_json ( ser , headers ) ;
if ( ! body ) {
switch ( errno ) {
case EFBIG :
ast_ari_response_error ( & response , 413 , " Request Entity Too Large " , " Request body too large " ) ;
goto request_failed ;
case ENOMEM :
ast_ari_response_error ( & response , 500 , " Internal Server Error " , " Error processing request " ) ;
goto request_failed ;
case EIO :
ast_ari_response_error ( & response , 400 , " Bad Request " , " Error parsing request body " ) ;
goto request_failed ;
}
}
2014-07-03 17:16:55 +00:00
}
if ( get_params = = NULL ) {
2013-11-08 17:29:53 +00:00
get_params = post_vars ;
} else if ( get_params & & post_vars ) {
/* Has both post_vars and get_params */
struct ast_variable * last_var = post_vars ;
while ( last_var - > next ) {
last_var = last_var - > next ;
}
/* The duped get_params will get freed when post_vars gets
* ast_variables_destroyed.
*/
last_var - > next = ast_variables_dup ( get_params ) ;
get_params = post_vars ;
}
2017-01-19 08:05:36 -07:00
/* At this point, get_params will contain post_vars (if any) */
app_name = ast_variable_find_in_list ( get_params , " app " ) ;
if ( ! app_name ) {
struct ast_json * app = ast_json_object_get ( body , " app " ) ;
app_name = ( app ? ast_json_string_get ( app ) : NULL ) ;
}
/* stasis_app_get_debug_by_name returns an "||" of the app's debug flag
* and the global debug flag.
*/
debug_app = stasis_app_get_debug_by_name ( app_name ) ;
if ( debug_app ) {
struct ast_str * buf = ast_str_create ( 512 ) ;
char * str = ast_json_dump_string_format ( body , ast_ari_json_format ( ) ) ;
2018-05-03 11:34:32 +00:00
if ( ! buf | | ( body & & ! str ) ) {
2017-01-19 08:05:36 -07:00
ast_http_request_close_on_completion ( ser ) ;
2018-05-03 11:34:32 +00:00
ast_ari_response_error ( & response , 500 , " Server Error " , " Out of memory " ) ;
2017-11-01 17:47:57 -05:00
ast_json_free ( str ) ;
ast_free ( buf ) ;
2017-01-19 08:05:36 -07:00
goto request_failed ;
}
ast_str_append ( & buf , 0 , " <--- ARI request received from: %s ---> \n " ,
ast_sockaddr_stringify ( & ser - > remote_address ) ) ;
for ( var = headers ; var ; var = var - > next ) {
ast_str_append ( & buf , 0 , " %s: %s \n " , var - > name , var - > value ) ;
}
for ( var = get_params ; var ; var = var - > next ) {
ast_str_append ( & buf , 0 , " %s: %s \n " , var - > name , var - > value ) ;
}
2018-05-03 11:34:32 +00:00
ast_verbose ( " %sbody: \n %s \n \n " , ast_str_buffer ( buf ) , S_OR ( str , " " ) ) ;
2017-01-19 08:05:36 -07:00
ast_json_free ( str ) ;
ast_free ( buf ) ;
}
2013-07-03 16:33:13 +00:00
user = authenticate_user ( get_params , headers ) ;
2013-11-08 17:29:53 +00:00
if ( response . response_code > 0 ) {
/* POST parameter processing error. Do nothing. */
} else if ( ! user ) {
2013-07-03 16:33:13 +00:00
/* Per RFC 2617, section 1.2: The 401 (Unauthorized) response
* message is used by an origin server to challenge the
* authorization of a user agent. This response MUST include a
* WWW-Authenticate header field containing at least one
* challenge applicable to the requested resource.
*/
2014-02-26 17:04:38 +00:00
ast_ari_response_error ( & response , 401 , " Unauthorized " , " Authentication required " ) ;
2013-07-03 16:33:13 +00:00
/* Section 1.2:
* realm = "realm" "=" realm-value
* realm-value = quoted-string
* Section 2:
* challenge = "Basic" realm
*/
ast_str_append ( & response . headers , 0 ,
" WWW-Authenticate: Basic realm= \" %s \" \r \n " ,
conf - > general - > auth_realm ) ;
2013-07-05 22:08:27 +00:00
} else if ( ! ast_fully_booted ) {
2014-07-03 19:06:12 +00:00
ast_http_request_close_on_completion ( ser ) ;
2014-02-26 17:04:38 +00:00
ast_ari_response_error ( & response , 503 , " Service Unavailable " , " Asterisk not booted " ) ;
2013-07-03 16:33:13 +00:00
} else if ( user - > read_only & & method ! = AST_HTTP_GET & & method ! = AST_HTTP_OPTIONS ) {
2014-02-26 17:04:38 +00:00
ast_ari_response_error ( & response , 403 , " Forbidden " , " Write access denied " ) ;
2013-07-03 16:33:13 +00:00
} else if ( ast_ends_with ( uri , " / " ) ) {
2013-04-22 14:58:53 +00:00
remove_trailing_slash ( uri , & response ) ;
} else if ( ast_begins_with ( uri , " api-docs/ " ) ) {
/* Serving up API docs */
if ( method ! = AST_HTTP_GET ) {
2014-02-26 17:04:38 +00:00
ast_ari_response_error ( & response , 405 , " Method Not Allowed " , " Unsupported method " ) ;
2013-04-22 14:58:53 +00:00
} else {
/* Skip the api-docs prefix */
2016-08-17 15:10:54 +02:00
ast_ari_get_docs ( strchr ( uri , ' / ' ) + 1 , urih - > prefix , headers , & response ) ;
2013-04-22 14:58:53 +00:00
}
} else {
/* Other RESTful resources */
2017-01-19 08:05:36 -07:00
ast_ari_invoke ( ser , uri , method , get_params , headers , body ,
2013-07-03 16:32:00 +00:00
& response ) ;
}
if ( response . no_response ) {
/* The handler indicates no further response is necessary.
* Probably because it already handled it */
2014-01-12 22:24:27 +00:00
ast_free ( response . headers ) ;
2013-07-03 16:32:00 +00:00
return 0 ;
2013-04-22 14:58:53 +00:00
}
2014-07-03 17:16:55 +00:00
request_failed :
2017-01-19 08:05:36 -07:00
2013-07-03 16:32:41 +00:00
/* If you explicitly want to have no content, set message to
* ast_json_null().
2013-04-22 14:58:53 +00:00
*/
2013-07-03 16:32:41 +00:00
ast_assert ( response . message ! = NULL ) ;
2013-04-22 14:58:53 +00:00
ast_assert ( response . response_code > 0 ) ;
/* response.message could be NULL, in which case the empty response_body
* is correct
*/
if ( response . message & & ! ast_json_is_null ( response . message ) ) {
2014-01-12 22:24:27 +00:00
ast_str_append ( & response . headers , 0 ,
2013-04-22 14:58:53 +00:00
" Content-type: application/json \r \n " ) ;
2013-07-03 16:33:13 +00:00
if ( ast_json_dump_str_format ( response . message , & response_body ,
conf - > general - > format ) ! = 0 ) {
2013-04-22 14:58:53 +00:00
/* Error encoding response */
response . response_code = 500 ;
response . response_text = " Internal Server Error " ;
ast_str_set ( & response_body , 0 , " %s " , " " ) ;
2014-01-12 22:24:27 +00:00
ast_str_set ( & response . headers , 0 , " %s " , " " ) ;
2013-04-22 14:58:53 +00:00
}
}
2017-01-19 08:05:36 -07:00
if ( debug_app ) {
ast_verbose ( " <--- Sending ARI response to %s ---> \n %d %s \n %s%s \n \n " ,
ast_sockaddr_stringify ( & ser - > remote_address ) , response . response_code ,
response . response_text , ast_str_buffer ( response . headers ) ,
ast_str_buffer ( response_body ) ) ;
}
2013-04-22 14:58:53 +00:00
ast_http_send ( ser , method , response . response_code ,
2014-01-12 22:24:27 +00:00
response . response_text , response . headers , response_body ,
2016-05-18 06:19:58 -05:00
response . fd ! = - 1 ? response . fd : 0 , 0 ) ;
2013-04-22 14:58:53 +00:00
/* ast_http_send takes ownership, so we don't have to free them */
response_body = NULL ;
ast_json_unref ( response . message ) ;
2016-05-18 06:19:58 -05:00
if ( response . fd > = 0 ) {
close ( response . fd ) ;
}
2014-07-03 19:06:12 +00:00
return 0 ;
2013-04-22 14:58:53 +00:00
}
static struct ast_http_uri http_uri = {
2013-07-27 23:11:02 +00:00
. callback = ast_ari_callback ,
2013-04-22 14:58:53 +00:00
. description = " Asterisk RESTful API " ,
2013-07-03 16:32:00 +00:00
. uri = " ari " ,
2013-04-22 14:58:53 +00:00
. has_subtree = 1 ,
. data = NULL ,
. key = __FILE__ ,
2013-06-24 13:49:20 +00:00
. no_decode_uri = 1 ,
2013-04-22 14:58:53 +00:00
} ;
2017-04-11 10:07:39 -06:00
static int unload_module ( void )
{
ast_ari_cli_unregister ( ) ;
if ( is_enabled ( ) ) {
ast_debug ( 3 , " Disabling ARI \n " ) ;
ast_http_uri_unlink ( & http_uri ) ;
}
ast_ari_config_destroy ( ) ;
ao2_cleanup ( root_handler ) ;
root_handler = NULL ;
ast_mutex_destroy ( & root_handler_lock ) ;
ast_json_unref ( oom_json ) ;
oom_json = NULL ;
return 0 ;
}
2013-04-22 14:58:53 +00:00
static int load_module ( void )
{
ast_mutex_init ( & root_handler_lock ) ;
2013-05-10 17:12:57 +00:00
2013-07-03 16:33:13 +00:00
/* root_handler may have been built during a declined load */
if ( ! root_handler ) {
root_handler = root_handler_create ( ) ;
}
2013-04-22 14:58:53 +00:00
if ( ! root_handler ) {
2017-04-11 10:07:39 -06:00
return AST_MODULE_LOAD_DECLINE ;
2013-04-22 14:58:53 +00:00
}
2013-07-03 16:33:13 +00:00
/* oom_json may have been built during a declined load */
if ( ! oom_json ) {
oom_json = ast_json_pack (
" {s: s} " , " error " , " Allocation failed " ) ;
}
if ( ! oom_json ) {
/* Ironic */
2017-04-11 10:07:39 -06:00
unload_module ( ) ;
return AST_MODULE_LOAD_DECLINE ;
2013-04-22 14:58:53 +00:00
}
2013-07-27 23:11:02 +00:00
if ( ast_ari_config_init ( ) ! = 0 ) {
2017-04-11 10:07:39 -06:00
unload_module ( ) ;
2013-04-22 14:58:53 +00:00
return AST_MODULE_LOAD_DECLINE ;
}
if ( is_enabled ( ) ) {
2013-07-03 16:33:13 +00:00
ast_debug ( 3 , " ARI enabled \n " ) ;
2013-04-22 14:58:53 +00:00
ast_http_uri_link ( & http_uri ) ;
2013-07-03 16:33:13 +00:00
} else {
ast_debug ( 3 , " ARI disabled \n " ) ;
}
2013-07-27 23:11:02 +00:00
if ( ast_ari_cli_register ( ) ! = 0 ) {
2017-04-11 10:07:39 -06:00
unload_module ( ) ;
return AST_MODULE_LOAD_DECLINE ;
2013-04-22 14:58:53 +00:00
}
return AST_MODULE_LOAD_SUCCESS ;
}
static int reload_module ( void )
{
char was_enabled = is_enabled ( ) ;
2013-07-27 23:11:02 +00:00
if ( ast_ari_config_reload ( ) ! = 0 ) {
2013-04-22 14:58:53 +00:00
return AST_MODULE_LOAD_DECLINE ;
}
if ( was_enabled & & ! is_enabled ( ) ) {
2013-07-03 16:33:13 +00:00
ast_debug ( 3 , " Disabling ARI \n " ) ;
2013-04-22 14:58:53 +00:00
ast_http_uri_unlink ( & http_uri ) ;
} else if ( ! was_enabled & & is_enabled ( ) ) {
2013-07-03 16:33:13 +00:00
ast_debug ( 3 , " Enabling ARI \n " ) ;
2013-04-22 14:58:53 +00:00
ast_http_uri_link ( & http_uri ) ;
}
return AST_MODULE_LOAD_SUCCESS ;
}
2013-07-03 16:32:00 +00:00
AST_MODULE_INFO ( ASTERISK_GPL_KEY , AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER , " Asterisk RESTful Interface " ,
2014-07-25 16:47:17 +00:00
. support_level = AST_MODULE_SUPPORT_CORE ,
2013-04-22 14:58:53 +00:00
. load = load_module ,
. unload = unload_module ,
. reload = reload_module ,
2017-11-19 17:30:49 -05:00
. optional_modules = " res_http_websocket " ,
2018-02-16 22:11:42 -05:00
. requires = " http,res_stasis " ,
2013-04-22 14:58:53 +00:00
. load_pri = AST_MODPRI_APP_DEPEND ,
2015-05-05 20:49:04 -04:00
) ;