diff options
Diffstat (limited to 'res/res_stasis_http.c')
-rw-r--r-- | res/res_stasis_http.c | 281 |
1 files changed, 154 insertions, 127 deletions
diff --git a/res/res_stasis_http.c b/res/res_stasis_http.c index 3ff6482b5..4b2e1ccd7 100644 --- a/res/res_stasis_http.c +++ b/res/res_stasis_http.c @@ -79,15 +79,31 @@ /*** DOCUMENTATION <configInfo name="res_stasis_http" language="en_US"> <synopsis>HTTP binding for the Stasis API</synopsis> - <configFile name="stasis_http.conf"> - <configObject name="global"> - <synopsis>Global configuration settings</synopsis> + <configFile name="ari.conf"> + <configObject name="general"> + <synopsis>General configuration settings</synopsis> <configOption name="enabled"> <synopsis>Enable/disable the stasis-http module</synopsis> </configOption> <configOption name="pretty"> <synopsis>Responses from stasis-http are formatted to be human readable</synopsis> </configOption> + <configOption name="auth_realm"> + <synopsis>Realm to use for authentication. Defaults to Asterisk REST Interface.</synopsis> + </configOption> + </configObject> + + <configObject name="user"> + <synopsis>Per-user configuration settings</synopsis> + <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> </configObject> </configFile> </configInfo> @@ -97,112 +113,21 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") +#include "asterisk/astobj2.h" #include "asterisk/module.h" #include "asterisk/paths.h" #include "asterisk/stasis_http.h" -#include "asterisk/config_options.h" +#include "stasis_http/internal.h" #include <string.h> #include <sys/stat.h> #include <unistd.h> -/*! \brief Global configuration options for stasis http. */ -struct conf_global_options { - /*! Enabled by default, disabled if false. */ - int enabled:1; - /*! Encoding format used during output (default compact). */ - enum ast_json_encoding_format format; -}; - -/*! \brief All configuration options for stasis http. */ -struct conf { - /*! The general section configuration options. */ - struct conf_global_options *global; -}; - -/*! \brief Locking container for safe configuration access. */ -static AO2_GLOBAL_OBJ_STATIC(confs); - -/*! \brief Mapping of the stasis http conf struct's globals to the - * general context in the config file. */ -static struct aco_type global_option = { - .type = ACO_GLOBAL, - .name = "global", - .item_offset = offsetof(struct conf, global), - .category = "^general$", - .category_match = ACO_WHITELIST -}; - -static struct aco_type *global_options[] = ACO_TYPES(&global_option); - -/*! \brief Disposes of the stasis http conf object */ -static void conf_destructor(void *obj) -{ - struct conf *cfg = obj; - ao2_cleanup(cfg->global); -} - -/*! \brief Creates the statis http conf object. */ -static void *conf_alloc(void) -{ - struct conf *cfg; - - if (!(cfg = ao2_alloc(sizeof(*cfg), conf_destructor))) { - return NULL; - } - - if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), NULL))) { - ao2_ref(cfg, -1); - return NULL; - } - return cfg; -} - -/*! \brief The conf file that's processed for the module. */ -static struct aco_file conf_file = { - /*! The config file name. */ - .filename = "stasis_http.conf", - /*! The mapping object types to be processed. */ - .types = ACO_TYPES(&global_option), -}; - -CONFIG_INFO_STANDARD(cfg_info, confs, conf_alloc, - .files = ACO_FILES(&conf_file)); - -/*! \brief Bitfield handler since it is not possible to take address. */ -static int conf_bitfield_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) -{ - struct conf_global_options *global = obj; - - if (!strcasecmp(var->name, "enabled")) { - global->enabled = ast_true(var->value); - } else { - return -1; - } - - return 0; -} - -/*! \brief Encoding format handler converts from boolean to enum. */ -static int encoding_format_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) -{ - struct conf_global_options *global = obj; - - if (!strcasecmp(var->name, "pretty")) { - global->format = ast_true(var->value) ? AST_JSON_PRETTY : AST_JSON_COMPACT; - } else { - return -1; - } - - return 0; -} - /*! \brief Helper function to check if module is enabled. */ -static char is_enabled(void) +static int is_enabled(void) { - RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup); - - return cfg->global->enabled; + RAII_VAR(struct ari_conf *, cfg, ari_config_get(), ao2_cleanup); + return cfg && cfg->general && cfg->general->enabled; } /*! Lock for \ref root_handler */ @@ -797,8 +722,67 @@ static void process_cors_request(struct ast_variable *headers, enum ast_json_encoding_format stasis_http_json_format(void) { - RAII_VAR(struct conf *, cfg, ao2_global_obj_ref(confs), ao2_cleanup); - return cfg->global->format; + RAII_VAR(struct ari_conf *, cfg, NULL, ao2_cleanup); + cfg = ari_config_get(); + 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. + */ +static struct ari_conf_user *authenticate_api_key(const char *api_key) +{ + 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; + } + + return ari_config_validate_user(username, password); +} + +/*! + * \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. + */ +static struct ari_conf_user *authenticate_user(struct ast_variable *get_params, + struct ast_variable *headers) +{ + RAII_VAR(struct ast_http_auth *, http_auth, NULL, ao2_cleanup); + struct ast_variable *v; + + /* HTTP Basic authentication */ + http_auth = ast_http_get_auth(headers); + if (http_auth) { + return ari_config_validate_user(http_auth->userid, + 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; } /*! @@ -822,8 +806,10 @@ static int stasis_http_callback(struct ast_tcptls_session_instance *ser, struct ast_variable *get_params, struct ast_variable *headers) { + RAII_VAR(struct ari_conf *, conf, NULL, ao2_cleanup); RAII_VAR(struct ast_str *, response_headers, ast_str_create(40), ast_free); RAII_VAR(struct ast_str *, response_body, ast_str_create(256), ast_free); + RAII_VAR(struct ari_conf_user *, user, NULL, ao2_cleanup); struct stasis_http_response response = {}; int ret = 0; @@ -832,10 +818,45 @@ static int stasis_http_callback(struct ast_tcptls_session_instance *ser, } response.headers = ast_str_create(40); + if (!response.headers) { + return -1; + } + + conf = ari_config_get(); + if (!conf || !conf->general) { + return -1; + } process_cors_request(headers, &response); - if (ast_ends_with(uri, "/")) { + user = authenticate_user(get_params, headers); + if (!user) { + /* 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. + */ + response.response_code = 401; + response.response_text = "Unauthorized"; + + /* 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); + response.message = ast_json_pack("{s: s}", + "error", "Authentication required"); + } else if (user->read_only && method != AST_HTTP_GET && method != AST_HTTP_OPTIONS) { + response.message = ast_json_pack("{s: s}", + "error", "Write access denied"); + response.response_code = 403; + response.response_text = "Forbidden"; + } else if (ast_ends_with(uri, "/")) { remove_trailing_slash(uri, &response); } else if (ast_begins_with(uri, "api-docs/")) { /* Serving up API docs */ @@ -875,7 +896,8 @@ static int stasis_http_callback(struct ast_tcptls_session_instance *ser, if (response.message && !ast_json_is_null(response.message)) { ast_str_append(&response_headers, 0, "Content-type: application/json\r\n"); - if (ast_json_dump_str_format(response.message, &response_body, stasis_http_json_format()) != 0) { + if (ast_json_dump_str_format(response.message, &response_body, + conf->general->format) != 0) { /* Error encoding response */ response.response_code = 500; response.response_text = "Internal Server Error"; @@ -909,38 +931,39 @@ static struct ast_http_uri http_uri = { static int load_module(void) { - oom_json = ast_json_pack( - "{s: s}", "error", "AllocationFailed"); - - if (!oom_json) { - /* Ironic */ - return AST_MODULE_LOAD_FAILURE; - } - ast_mutex_init(&root_handler_lock); - root_handler = root_handler_create(); + /* root_handler may have been built during a declined load */ + if (!root_handler) { + root_handler = root_handler_create(); + } if (!root_handler) { return AST_MODULE_LOAD_FAILURE; } - if (aco_info_init(&cfg_info)) { - aco_info_destroy(&cfg_info); - return AST_MODULE_LOAD_DECLINE; + /* 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 */ + return AST_MODULE_LOAD_FAILURE; } - aco_option_register_custom(&cfg_info, "enabled", ACO_EXACT, global_options, - "yes", conf_bitfield_handler, 0); - aco_option_register_custom(&cfg_info, "pretty", ACO_EXACT, global_options, - "no", encoding_format_handler, 0); - - if (aco_process_config(&cfg_info, 0)) { - aco_info_destroy(&cfg_info); + if (ari_config_init() != 0) { return AST_MODULE_LOAD_DECLINE; } if (is_enabled()) { + ast_debug(3, "ARI enabled\n"); ast_http_uri_link(&http_uri); + } else { + ast_debug(3, "ARI disabled\n"); + } + + if (ari_cli_register() != 0) { + return AST_MODULE_LOAD_FAILURE; } return AST_MODULE_LOAD_SUCCESS; @@ -948,20 +971,22 @@ static int load_module(void) static int unload_module(void) { - ast_json_unref(oom_json); - oom_json = NULL; + ari_cli_unregister(); if (is_enabled()) { + ast_debug(3, "Disabling ARI\n"); ast_http_uri_unlink(&http_uri); } - aco_info_destroy(&cfg_info); - ao2_global_obj_release(confs); + 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; } @@ -969,13 +994,15 @@ static int reload_module(void) { char was_enabled = is_enabled(); - if (aco_process_config(&cfg_info, 1)) { + if (ari_config_reload() != 0) { return AST_MODULE_LOAD_DECLINE; } if (was_enabled && !is_enabled()) { + ast_debug(3, "Disabling ARI\n"); ast_http_uri_unlink(&http_uri); } else if (!was_enabled && is_enabled()) { + ast_debug(3, "Enabling ARI\n"); ast_http_uri_link(&http_uri); } |