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 ;
2019-08-20 12:04:56 -06:00
struct stasis_rest_handlers * wildcard_handler = NULL ;
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 );
2019-08-20 12:04:56 -06:00
ast_debug ( 3 , "Finding handler for %s \n " , path );
2013-04-22 14:58:53 +00:00
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 );
2019-08-20 12:04:56 -06: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 ];
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 ;
2019-08-20 12:04:56 -06:00
wildcard_handler = child ;
ast_debug ( 3 , " Checking %s %s: Matched wildcard. \n " , handler -> path_segment , child -> path_segment );
2013-04-22 14:58:53 +00:00
} else if ( strcmp ( child -> path_segment , path_segment ) == 0 ) {
found_handler = child ;
2019-08-20 12:04:56 -06:00
ast_debug ( 3 , " Checking %s %s: Explicit match with %s \n " , handler -> path_segment , child -> path_segment , path_segment );
} else {
ast_debug ( 3 , " Checking %s %s: Didn't match %s \n " , handler -> path_segment , child -> path_segment , path_segment );
2013-04-22 14:58:53 +00:00
}
}
2019-08-20 12:04:56 -06:00
if ( ! found_handler && wildcard_handler ) {
ast_debug ( 3 , " No explicit handler found for %s. Using wildcard %s. \n " ,
path_segment , wildcard_handler -> path_segment );
found_handler = wildcard_handler ;
wildcard_handler = NULL ;
}
2013-04-22 14:58:53 +00:00
if ( found_handler == NULL ) {
/* resource not found */
2019-08-20 12:04:56 -06:00
ast_debug ( 3 , " Handler not found for %s \n " , path_segment );
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 {
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
);