summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--configs/ari.conf.sample23
-rw-r--r--configs/stasis_http.conf.sample25
-rw-r--r--main/Makefile2
-rw-r--r--main/http.c87
-rw-r--r--main/utils.c173
-rw-r--r--makeopts.in3
-rw-r--r--res/Makefile4
-rw-r--r--res/res_stasis_http.c281
-rw-r--r--res/stasis_http/cli.c266
-rw-r--r--res/stasis_http/config.c341
-rw-r--r--res/stasis_http/internal.h139
11 files changed, 1187 insertions, 157 deletions
diff --git a/configs/ari.conf.sample b/configs/ari.conf.sample
new file mode 100644
index 000000000..11e2b065e
--- /dev/null
+++ b/configs/ari.conf.sample
@@ -0,0 +1,23 @@
+[general]
+enabled = yes ; When set to no, stasis-http support is disabled.
+;pretty = no ; When set to yes, responses from stasis-http are
+; ; formatted to be human readable.
+;allowed_origins = ; Comma separated list of allowed origins, for
+; ; Cross-Origin Resource Sharing. May be set to * to
+; ; allow all origins.
+;auth_realm = ; Realm to use for authentication. Defaults to Asterisk
+; ; REST Interface.
+
+;[user-username]
+;read_only = no ; When set to yes, user is only authorized for
+; ; read-only requests.
+;
+;password = ; Crypted or plaintext password (see password_format).
+;
+; 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.
+;
+;password_format = plain
diff --git a/configs/stasis_http.conf.sample b/configs/stasis_http.conf.sample
deleted file mode 100644
index 1527a32be..000000000
--- a/configs/stasis_http.conf.sample
+++ /dev/null
@@ -1,25 +0,0 @@
-[general]
-enabled = yes ; When set to no, stasis-http support is disabled
-;pretty = no ; When set to yes, responses from stasis-http are
-; ; formatted to be human readable
-;allowed_origins = ; Comma separated list of allowed origins, for
-; ; Cross-Origin Resource Sharing. May be set to * to allow
-; ; all origins.
-
-;[user-username]
-;read_only = no ; When set to yes, user is only authorized for
-; ; read-only requests
-;
-; If a password is specified, user must authenticate using HTTP Basic
-; authentication. If no password is specified, then the user may authenticate
-; simply by adding ?api_key=username to their requests.
-;
-;password = ; Crypted or plaintext password (see crypt_password)
-;
-; crypt_password may be set to crypt (the default) or plain. When set to crypt,
-; crypt(3) is used to encrypt the password. A crypted password can be generated
-; using mkpasswd -m sha-512.
-;
-; When set to plain, the password is in plaintext
-;
-;crypt_password = plain
diff --git a/main/Makefile b/main/Makefile
index 294965365..62ae6d4fb 100644
--- a/main/Makefile
+++ b/main/Makefile
@@ -37,6 +37,7 @@ AST_LIBS+=$(SQLITE3_LIB)
AST_LIBS+=$(ASTSSL_LIBS)
AST_LIBS+=$(JANSSON_LIB)
AST_LIBS+=$(UUID_LIB)
+AST_LIBS+=$(CRYPT_LIB)
ifneq ($(findstring $(OSARCH), linux-gnu uclinux linux-uclibc kfreebsd-gnu),)
ifneq ($(findstring LOADABLE_MODULES,$(MENUSELECT_CFLAGS)),)
@@ -153,6 +154,7 @@ db.o: _ASTCFLAGS+=$(SQLITE3_INCLUDE)
asterisk.o: _ASTCFLAGS+=$(LIBEDIT_INCLUDE)
cli.o: _ASTCFLAGS+=$(LIBEDIT_INCLUDE)
json.o: _ASTCFLAGS+=$(JANSSON_INCLUDE)
+util.o: _ASTCFLAGS+=$(CRYPT_INCLUDE)
uuid.o: _ASTCFLAGS+=$(UUID_INCLUDE)
ifneq ($(findstring ENABLE_UPLOADS,$(MENUSELECT_CFLAGS)),)
diff --git a/main/http.c b/main/http.c
index c7dc623a5..d459eb184 100644
--- a/main/http.c
+++ b/main/http.c
@@ -867,6 +867,93 @@ struct ast_variable *ast_http_get_cookies(struct ast_variable *headers)
return cookies;
}
+static struct ast_http_auth *auth_create(const char *userid,
+ const char *password)
+{
+ RAII_VAR(struct ast_http_auth *, auth, NULL, ao2_cleanup);
+ size_t userid_len;
+ size_t password_len;
+
+ if (!userid || !password) {
+ ast_log(LOG_ERROR, "Invalid userid/password\n");
+ return NULL;
+ }
+
+ userid_len = strlen(userid) + 1;
+ password_len = strlen(password) + 1;
+
+ /* Allocate enough room to store everything in one memory block */
+ auth = ao2_alloc(sizeof(*auth) + userid_len + password_len, NULL);
+ if (!auth) {
+ return NULL;
+ }
+
+ /* Put the userid right after the struct */
+ auth->userid = (char *)(auth + 1);
+ strcpy(auth->userid, userid);
+
+ /* Put the password right after the userid */
+ auth->password = auth->userid + userid_len;
+ strcpy(auth->password, password);
+
+ ao2_ref(auth, +1);
+ return auth;
+}
+
+#define BASIC_PREFIX "Basic "
+#define BASIC_LEN 6 /*!< strlen(BASIC_PREFIX) */
+
+struct ast_http_auth *ast_http_get_auth(struct ast_variable *headers)
+{
+ struct ast_variable *v;
+
+ for (v = headers; v; v = v->next) {
+ const char *base64;
+ char decoded[256] = {};
+ char *username;
+ char *password;
+ int cnt;
+
+ if (strcasecmp("Authorization", v->name) != 0) {
+ continue;
+ }
+
+ if (!ast_begins_with(v->value, BASIC_PREFIX)) {
+ ast_log(LOG_DEBUG,
+ "Unsupported Authorization scheme\n");
+ continue;
+ }
+
+ /* Basic auth header parsing. RFC 2617, section 2.
+ * credentials = "Basic" basic-credentials
+ * basic-credentials = base64-user-pass
+ * base64-user-pass = <base64 encoding of user-pass,
+ * except not limited to 76 char/line>
+ * user-pass = userid ":" password
+ */
+
+ base64 = v->value + BASIC_LEN;
+
+ /* This will truncate "userid:password" lines to
+ * sizeof(decoded). The array is long enough that this shouldn't
+ * be a problem */
+ cnt = ast_base64decode((unsigned char*)decoded, base64,
+ sizeof(decoded) - 1);
+ ast_assert(cnt < sizeof(decoded));
+
+ /* Split the string at the colon */
+ password = decoded;
+ username = strsep(&password, ":");
+ if (!password) {
+ ast_log(LOG_WARNING, "Invalid Authorization header\n");
+ return NULL;
+ }
+
+ return auth_create(username, password);
+ }
+
+ return NULL;
+}
static void *httpd_helper_thread(void *data)
{
diff --git a/main/utils.c b/main/utils.c
index 100725487..208a4d326 100644
--- a/main/utils.c
+++ b/main/utils.c
@@ -32,12 +32,14 @@
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include <ctype.h>
+#include <fcntl.h>
#include <sys/stat.h>
#include <sys/stat.h>
-
-#include <fcntl.h>
-
#include <sys/syscall.h>
+#include <unistd.h>
+#if defined(HAVE_CRYPT_R)
+#include <crypt.h>
+#endif
#if defined(__APPLE__)
#include <mach/mach.h>
#elif defined(HAVE_SYS_THR_H)
@@ -2271,6 +2273,171 @@ int ast_get_tid(void)
return ret;
}
+/*!
+ * \brief Max length of a salt string.
+ *
+ * $[1,5,6]$[a–zA–Z0–9./]{1,16}$, plus null terminator
+ */
+#define MAX_SALT_LEN 21
+
+static char salt_chars[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789"
+ "./";
+
+/*! Randomly select a character for a salt string */
+static char gen_salt_char(void)
+{
+ int which = ast_random_double() * 64;
+ return salt_chars[which];
+}
+
+/*!
+ * \brief Generates a salt to try with crypt.
+ *
+ * If given an empty string, will generate a salt for the most secure algorithm
+ * to try with crypt(). If given a previously generated salt, the algorithm will
+ * be lowered by one level of security.
+ *
+ * \param[out] current_salt Output string in which to generate the salt.
+ * This can be an empty string, or the results of a
+ * prior gen_salt call.
+ * \param max_len Length of \a current_salt.
+ * \return 0 on success.
+ * \return Non-zero on error.
+ */
+static int gen_salt(char *current_salt, size_t maxlen)
+{
+ int i;
+
+ if (maxlen < MAX_SALT_LEN || current_salt == NULL) {
+ return -1;
+ }
+
+ switch (current_salt[0]) {
+ case '\0':
+ /* Initial generation; $6$ = SHA-512 */
+ *current_salt++ = '$';
+ *current_salt++ = '6';
+ *current_salt++ = '$';
+ for (i = 0; i < 16; ++i) {
+ *current_salt++ = gen_salt_char();
+ }
+ *current_salt++ = '$';
+ *current_salt++ = '\0';
+ return 0;
+ case '$':
+ switch (current_salt[1]) {
+ case '6':
+ /* Downgrade to SHA-256 */
+ current_salt[1] = '5';
+ return 0;
+ case '5':
+ /* Downgrade to MD5 */
+ current_salt[1] = '1';
+ return 0;
+ case '1':
+ /* Downgrade to traditional crypt */
+ *current_salt++ = gen_salt_char();
+ *current_salt++ = gen_salt_char();
+ *current_salt++ = '\0';
+ return 0;
+ default:
+ /* Unrecognized algorithm */
+ return -1;
+ }
+ default:
+ /* Was already as insecure as it gets */
+ return -1;
+ }
+
+}
+
+#if defined(HAVE_CRYPT_R)
+
+char *ast_crypt(const char *key, const char *salt)
+{
+ struct crypt_data data = {};
+ const char *crypted = crypt_r(key, salt, &data);
+
+ /* Crypt may return success even if it doesn't recognize the salt. But
+ * in those cases it always mangles the salt in some way.
+ */
+ if (!crypted || !ast_begins_with(crypted, salt)) {
+ return NULL;
+ }
+
+ return ast_strdup(crypted);
+}
+
+int ast_crypt_validate(const char *key, const char *expected)
+{
+ struct crypt_data data = {};
+ return strcmp(expected, crypt_r(key, expected, &data)) == 0;
+}
+
+#elif defined(HAVE_CRYPT)
+
+/* crypt is not reentrant. A global mutex is neither ideal nor perfect, but good
+ * enough if crypt_r support is unavailable
+ */
+AST_MUTEX_DEFINE_STATIC(crypt_mutex);
+
+char *ast_crypt(const char *key, const char *salt)
+{
+ const char *crypted;
+ SCOPED_MUTEX(lock, &crypt_mutex);
+
+ crypted = crypt(key, salt);
+
+ /* Crypt may return success even if it doesn't recognize the salt. But
+ * in those cases it always mangles the salt in some way.
+ */
+ if (!crypted || !ast_begins_with(crypted, salt)) {
+ return NULL;
+ }
+
+ return ast_strdup(crypted);
+}
+
+int ast_crypt_validate(const char *key, const char *expected)
+{
+ SCOPED_MUTEX(lock, &crypt_mutex);
+ return strcmp(expected, crypt(key, expected)) == 0;
+}
+
+#else /* No crypt support */
+
+char *ast_crypt(const char *key, const char *salt)
+{
+ ast_log(LOG_WARNING,
+ "crypt() support not available; cannot encrypt password\n");
+ return NULL;
+}
+
+int ast_crypt_validate(const char *key, const char *expected)
+{
+ ast_log(LOG_WARNING,
+ "crypt() support not available; cannot validate password\n");
+ return 0;
+}
+
+#endif /* No crypt support */
+
+char *ast_crypt_encrypt(const char *key)
+{
+ char salt[MAX_SALT_LEN] = {};
+ while (gen_salt(salt, sizeof(salt)) == 0) {
+ char *crypted = ast_crypt(key, salt);
+ if (crypted) {
+ return crypted;
+ }
+ }
+ return NULL;
+}
+
+
char *ast_utils_which(const char *binary, char *fullpath, size_t fullpath_size)
{
const char *envPATH = getenv("PATH");
diff --git a/makeopts.in b/makeopts.in
index 95e69b817..401e8f1af 100644
--- a/makeopts.in
+++ b/makeopts.in
@@ -281,6 +281,9 @@ SRTP_INCLUDE=@SRTP_INCLUDE@
OPENSSL_INCLUDE=@OPENSSL_INCLUDE@
OPENSSL_LIB=@OPENSSL_LIB@
+CRYPT_INCLUDE=@CRYPT_INCLUDE@
+CRYPT_LIB=@CRYPT_LIB@
+
CRYPTO_INCLUDE=@CRYPTO_INCLUDE@
CRYPTO_LIB=@CRYPTO_LIB@
diff --git a/res/Makefile b/res/Makefile
index 1310dae3a..588bc2e72 100644
--- a/res/Makefile
+++ b/res/Makefile
@@ -80,8 +80,8 @@ clean::
$(if $(filter res_parking,$(EMBEDDED_MODS)),modules.link,res_parking.so): $(subst .c,.o,$(wildcard parking/*.c))
$(subst .c,.o,$(wildcard parking/*.c)): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_parking)
-res_stasis_http.so: stasis_http/ari_websockets.o
-stasis_http/ari_websockets.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http_asterisk)
+res_stasis_http.so: stasis_http/cli.o stasis_http/config.o stasis_http/ari_websockets.o
+stasis_http/cli.o stasis_http/config.o stasis_http/ari_websockets.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_stasis_http)
res_ari_model.so: stasis_http/ari_model_validators.o
stasis_http/ari_model_validators.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ari_model)
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);
}
diff --git a/res/stasis_http/cli.c b/res/stasis_http/cli.c
new file mode 100644
index 000000000..98d082b2c
--- /dev/null
+++ b/res/stasis_http/cli.c
@@ -0,0 +1,266 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 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 Command line for ARI.
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/astobj2.h"
+#include "asterisk/cli.h"
+#include "internal.h"
+
+static char *ari_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ RAII_VAR(struct ari_conf *, conf, NULL, ao2_cleanup);
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "ari show status";
+ e->usage =
+ "Usage: ari show status\n"
+ " Shows all ARI settings\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ default:
+ break;
+ }
+
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+
+ conf = ari_config_get();
+
+ if (!conf) {
+ ast_cli(a->fd, "Error getting ARI configuration\n");
+ return CLI_FAILURE;
+ }
+
+ ast_cli(a->fd, "ARI Status:\n");
+ ast_cli(a->fd, "Enabled: %s\n", AST_CLI_YESNO(conf->general->enabled));
+ ast_cli(a->fd, "Output format: ");
+ switch (conf->general->format) {
+ case AST_JSON_COMPACT:
+ ast_cli(a->fd, "compact");
+ break;
+ case AST_JSON_PRETTY:
+ ast_cli(a->fd, "pretty");
+ break;
+ }
+ ast_cli(a->fd, "\n");
+ ast_cli(a->fd, "Auth realm: %s\n", conf->general->auth_realm);
+ ast_cli(a->fd, "User count: %d\n", ao2_container_count(conf->users));
+ return CLI_SUCCESS;
+}
+
+static int show_users_cb(void *obj, void *arg, int flags)
+{
+ struct ari_conf_user *user = obj;
+ struct ast_cli_args *a = arg;
+
+ ast_cli(a->fd, "%-4s %s\n",
+ AST_CLI_YESNO(user->read_only),
+ user->username);
+ return 0;
+}
+
+static char *ari_show_users(struct ast_cli_entry *e, int cmd,
+ struct ast_cli_args *a)
+{
+ RAII_VAR(struct ari_conf *, conf, NULL, ao2_cleanup);
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "ari show users";
+ e->usage =
+ "Usage: ari show users\n"
+ " Shows all ARI users\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ default:
+ break;
+ }
+
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+
+ conf = ari_config_get();
+ if (!conf) {
+ ast_cli(a->fd, "Error getting ARI configuration\n");
+ return CLI_FAILURE;
+ }
+
+ ast_cli(a->fd, "r/o? Username\n");
+ ast_cli(a->fd, "---- --------\n");
+
+ ao2_callback(conf->users, OBJ_NODATA, show_users_cb, a);
+
+ return CLI_SUCCESS;
+}
+
+struct user_complete {
+ /*! Nth user to search for */
+ int state;
+ /*! Which user currently on */
+ int which;
+};
+
+static int complete_ari_user_search(void *obj, void *arg, void *data, int flags)
+{
+ struct user_complete *search = data;
+
+ if (++search->which > search->state) {
+ return CMP_MATCH;
+ }
+ return 0;
+}
+
+static char *complete_ari_user(struct ast_cli_args *a)
+{
+ RAII_VAR(struct ari_conf *, conf, NULL, ao2_cleanup);
+ RAII_VAR(struct ari_conf_user *, user, NULL, ao2_cleanup);
+
+ struct user_complete search = {
+ .state = a->n,
+ };
+
+ conf = ari_config_get();
+ if (!conf) {
+ ast_cli(a->fd, "Error getting ARI configuration\n");
+ return CLI_FAILURE;
+ }
+
+ user = ao2_callback_data(conf->users,
+ ast_strlen_zero(a->word) ? 0 : OBJ_PARTIAL_KEY,
+ complete_ari_user_search, (char*)a->word, &search);
+
+ return user ? ast_strdup(user->username) : NULL;
+}
+
+static char *complete_ari_show_user(struct ast_cli_args *a)
+{
+ if (a->pos == 3) {
+ return complete_ari_user(a);
+ }
+
+ return NULL;
+}
+
+static char *ari_show_user(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ RAII_VAR(struct ari_conf *, conf, NULL, ao2_cleanup);
+ RAII_VAR(struct ari_conf_user *, user, NULL, ao2_cleanup);
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "ari show user";
+ e->usage =
+ "Usage: ari show user <username>\n"
+ " Shows a specific ARI user\n";
+ return NULL;
+ case CLI_GENERATE:
+ return complete_ari_show_user(a);
+ default:
+ break;
+ }
+
+ if (a->argc != 4) {
+ return CLI_SHOWUSAGE;
+ }
+
+ conf = ari_config_get();
+
+ if (!conf) {
+ ast_cli(a->fd, "Error getting ARI configuration\n");
+ return CLI_FAILURE;
+ }
+
+ user = ao2_find(conf->users, a->argv[3], OBJ_KEY);
+ if (!user) {
+ ast_cli(a->fd, "User '%s' not found\n", a->argv[3]);
+ return CLI_SUCCESS;
+ }
+
+ ast_cli(a->fd, "Username: %s\n", user->username);
+ ast_cli(a->fd, "Read only?: %s\n", AST_CLI_YESNO(user->read_only));
+
+ return CLI_SUCCESS;
+}
+
+static char *ari_mkpasswd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ RAII_VAR(char *, crypted, NULL, ast_free);
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "ari mkpasswd";
+ e->usage =
+ "Usage: ari mkpasswd <password>\n"
+ " Encrypts a password for use in ari.conf\n"
+ " Be aware that the password will be shown in the\n"
+ " command line history. The mkpasswd shell command\n"
+ " may be preferable.\n"
+ ;
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ default:
+ break;
+ }
+
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+
+ crypted = ast_crypt_encrypt(a->argv[2]);
+ if (!crypted) {
+ ast_cli(a->fd, "Failed to encrypt password\n");
+ return CLI_FAILURE;
+ }
+
+ ast_cli(a->fd,
+ "; Copy the following two lines into ari.conf\n");
+ ast_cli(a->fd, "password_format = crypt\n");
+ ast_cli(a->fd, "password = %s\n", crypted);
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_ari[] = {
+ AST_CLI_DEFINE(ari_show, "Show ARI settings"),
+ AST_CLI_DEFINE(ari_show_users, "List ARI users"),
+ AST_CLI_DEFINE(ari_show_user, "List single ARI user"),
+ AST_CLI_DEFINE(ari_mkpasswd, "Encrypts a password"),
+};
+
+int ari_cli_register(void) {
+ return ast_cli_register_multiple(cli_ari, ARRAY_LEN(cli_ari));
+}
+
+void ari_cli_unregister(void) {
+ ast_cli_unregister_multiple(cli_ari, ARRAY_LEN(cli_ari));
+}
diff --git a/res/stasis_http/config.c b/res/stasis_http/config.c
new file mode 100644
index 000000000..f02fabea4
--- /dev/null
+++ b/res/stasis_http/config.c
@@ -0,0 +1,341 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 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 Config framework stuffz for ARI.
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/config_options.h"
+#include "internal.h"
+
+/*! \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 general_option = {
+ .type = ACO_GLOBAL,
+ .name = "general",
+ .item_offset = offsetof(struct ari_conf, general),
+ .category = "^general$",
+ .category_match = ACO_WHITELIST,
+};
+
+static struct aco_type *general_options[] = ACO_TYPES(&general_option);
+
+/*! \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 ari_conf_general *general = obj;
+
+ if (!strcasecmp(var->name, "pretty")) {
+ general->format = ast_true(var->value) ?
+ AST_JSON_PRETTY : AST_JSON_COMPACT;
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*! \brief Parses the ari_password_format enum from a config file */
+static int password_format_handler(const struct aco_option *opt,
+ struct ast_variable *var, void *obj)
+{
+ struct ari_conf_user *user = obj;
+
+ if (strcasecmp(var->value, "plain") == 0) {
+ user->password_format = ARI_PASSWORD_FORMAT_PLAIN;
+ } else if (strcasecmp(var->value, "crypt") == 0) {
+ user->password_format = ARI_PASSWORD_FORMAT_CRYPT;
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*! \brief Destructor for \ref ari_conf_user */
+static void user_dtor(void *obj)
+{
+ struct ari_conf_user *user = obj;
+ ast_debug(3, "Disposing of user %s\n", user->username);
+ ast_free(user->username);
+}
+
+/*! \brief Allocate an \ref ari_conf_user for config parsing */
+static void *user_alloc(const char *cat)
+{
+ RAII_VAR(struct ari_conf_user *, user, NULL, ao2_cleanup);
+ const char *username;
+
+ if (!cat) {
+ return NULL;
+ }
+
+ username = strchr(cat, '-') + 1;
+
+ if (!username) {
+ ast_log(LOG_ERROR, "Invalid user category '%s'\n", cat);
+ return NULL;
+ }
+
+ ast_debug(3, "Allocating user %s\n", cat);
+
+ user = ao2_alloc_options(sizeof(*user), user_dtor,
+ AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!user) {
+ return NULL;
+ }
+
+ user->username = ast_strdup(username);
+ if (!user->username) {
+ return NULL;
+ }
+
+ ao2_ref(user, +1);
+ return user;
+}
+
+/*! \brief Sorting function for use with red/black tree */
+static int user_sort_cmp(const void *obj_left, const void *obj_right, int flags)
+{
+ const struct ari_conf_user *user_left = obj_left;
+
+ if (flags & OBJ_PARTIAL_KEY) {
+ const char *key_right = obj_right;
+ return strncasecmp(user_left->username, key_right,
+ strlen(key_right));
+ } else if (flags & OBJ_KEY) {
+ const char *key_right = obj_right;
+ return strcasecmp(user_left->username, key_right);
+ } else {
+ const struct ari_conf_user *user_right = obj_right;
+ const char *key_right = user_right->username;
+ return strcasecmp(user_left->username, key_right);
+ }
+}
+
+/*! \brief \ref aco_type item_find function */
+static void *user_find(struct ao2_container *tmp_container, const char *cat)
+{
+ const char *username;
+
+ if (!cat) {
+ return NULL;
+ }
+
+ username = strchr(cat, '-') + 1;
+ return ao2_find(tmp_container, username, OBJ_KEY);
+}
+
+static struct aco_type user_option = {
+ .type = ACO_ITEM,
+ .name = "user",
+ .category_match = ACO_WHITELIST,
+ .category = "^user-.+$",
+ .item_alloc = user_alloc,
+ .item_find = user_find,
+ .item_offset = offsetof(struct ari_conf, users),
+};
+
+static struct aco_type *user[] = ACO_TYPES(&user_option);
+
+/*! \brief \ref ari_conf destructor. */
+static void conf_destructor(void *obj)
+{
+ struct ari_conf *cfg = obj;
+ ao2_cleanup(cfg->general);
+ ao2_cleanup(cfg->users);
+}
+
+/*! \brief Allocate an \ref ari_conf for config parsing */
+static void *conf_alloc(void)
+{
+ RAII_VAR(struct ari_conf *, cfg, NULL, ao2_cleanup);
+
+ cfg = ao2_alloc_options(sizeof(*cfg), conf_destructor,
+ AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!cfg) {
+ return NULL;
+ }
+
+ cfg->general = ao2_alloc_options(sizeof(*cfg->general), NULL,
+ AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!cfg->general) {
+ return NULL;
+ }
+
+ cfg->users = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK,
+ AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, user_sort_cmp, NULL);
+
+ ao2_ref(cfg, +1);
+ return cfg;
+}
+
+#define CONF_FILENAME "ari.conf"
+
+/*! \brief The conf file that's processed for the module. */
+static struct aco_file conf_file = {
+ /*! The config file name. */
+ .filename = CONF_FILENAME,
+ /*! The mapping object types to be processed. */
+ .types = ACO_TYPES(&general_option, &user_option),
+};
+
+CONFIG_INFO_STANDARD(cfg_info, confs, conf_alloc,
+ .files = ACO_FILES(&conf_file));
+
+struct ari_conf *ari_config_get(void)
+{
+ struct ari_conf *res = ao2_global_obj_ref(confs);
+ if (!res) {
+ ast_log(LOG_ERROR,
+ "Error obtaining config from " CONF_FILENAME "\n");
+ }
+ return res;
+}
+
+struct ari_conf_user *ari_config_validate_user(const char *username,
+ const char *password)
+{
+ RAII_VAR(struct ari_conf *, conf, NULL, ao2_cleanup);
+ RAII_VAR(struct ari_conf_user *, user, NULL, ao2_cleanup);
+ int is_valid = 0;
+
+ conf = ari_config_get();
+ if (!conf) {
+ return NULL;
+ }
+
+ user = ao2_find(conf->users, username, OBJ_KEY);
+ if (!user) {
+ return NULL;
+ }
+
+ if (ast_strlen_zero(user->password)) {
+ ast_log(LOG_WARNING,
+ "User '%s' missing password; authentication failed\n",
+ user->username);
+ return NULL;
+ }
+
+ switch (user->password_format) {
+ case ARI_PASSWORD_FORMAT_PLAIN:
+ is_valid = strcmp(password, user->password) == 0;
+ break;
+ case ARI_PASSWORD_FORMAT_CRYPT:
+ is_valid = ast_crypt_validate(password, user->password);
+ break;
+ }
+
+ if (!is_valid) {
+ return NULL;
+ }
+
+ ao2_ref(user, +1);
+ return user;
+}
+
+/*! \brief Callback to validate a user object */
+static int validate_user_cb(void *obj, void *arg, int flags)
+{
+ struct ari_conf_user *user = obj;
+
+ if (ast_strlen_zero(user->password)) {
+ ast_log(LOG_WARNING, "User '%s' missing password\n",
+ user->username);
+ }
+
+ return 0;
+}
+
+/*! \brief Load (or reload) configuration. */
+static int process_config(int reload)
+{
+ RAII_VAR(struct ari_conf *, conf, NULL, ao2_cleanup);
+
+ switch (aco_process_config(&cfg_info, reload)) {
+ case ACO_PROCESS_ERROR:
+ return -1;
+ case ACO_PROCESS_OK:
+ case ACO_PROCESS_UNCHANGED:
+ break;
+ }
+
+ conf = ari_config_get();
+ if (!conf) {
+ ast_assert(0); /* We just configured; it should be there */
+ return -1;
+ }
+
+ if (ao2_container_count(conf->users) == 0) {
+ ast_log(LOG_ERROR, "No configured users for ARI\n");
+ }
+
+ ao2_callback(conf->users, OBJ_NODATA, validate_user_cb, NULL);
+
+ return 0;
+}
+
+int ari_config_init(void)
+{
+ if (aco_info_init(&cfg_info)) {
+ aco_info_destroy(&cfg_info);
+ return -1;
+ }
+
+ aco_option_register(&cfg_info, "enabled", ACO_EXACT, general_options,
+ "yes", OPT_BOOL_T, 1,
+ FLDSET(struct ari_conf_general, enabled));
+ aco_option_register_custom(&cfg_info, "pretty", ACO_EXACT,
+ general_options, "no", encoding_format_handler, 0);
+ aco_option_register(&cfg_info, "auth_realm", ACO_EXACT, general_options,
+ "Asterisk REST Interface", OPT_CHAR_ARRAY_T, 0,
+ FLDSET(struct ari_conf_general, auth_realm),
+ ARI_AUTH_REALM_LEN);
+
+ aco_option_register(&cfg_info, "read_only", ACO_EXACT, user,
+ "no", OPT_BOOL_T, 1,
+ FLDSET(struct ari_conf_user, read_only));
+ aco_option_register(&cfg_info, "password", ACO_EXACT, user,
+ "", OPT_CHAR_ARRAY_T, 0,
+ FLDSET(struct ari_conf_user, password), ARI_PASSWORD_LEN);
+ aco_option_register_custom(&cfg_info, "password_format", ACO_EXACT,
+ user, "plain", password_format_handler, 0);
+
+ return process_config(0);
+}
+
+int ari_config_reload(void)
+{
+ return process_config(1);
+}
+
+void ari_config_destroy(void)
+{
+ aco_info_destroy(&cfg_info);
+ ao2_global_obj_release(confs);
+}
diff --git a/res/stasis_http/internal.h b/res/stasis_http/internal.h
new file mode 100644
index 000000000..659f4a2ae
--- /dev/null
+++ b/res/stasis_http/internal.h
@@ -0,0 +1,139 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 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.
+ */
+
+#ifndef STASIS_HTTP_INTERNAL_H_
+#define STASIS_HTTP_INTERNAL_H_
+
+/*! \file
+ *
+ * \brief Internal API's for res_stasis_http.
+ * \author David M. Lee, II <dlee@digium.com>
+ */
+
+#include "asterisk/json.h"
+
+/*! @{ */
+
+/*!
+ * \brief Register CLI commands for ARI.
+ *
+ * \return 0 on success.
+ * \return Non-zero on error.
+ */
+int ari_cli_register(void);
+
+/*!
+ * \brief Unregister CLI commands for ARI.
+ */
+void ari_cli_unregister(void);
+
+/*! @} */
+
+/*! @{ */
+
+struct ari_conf_general;
+
+/*! \brief All configuration options for stasis http. */
+struct ari_conf {
+ /*! The general section configuration options. */
+ struct ari_conf_general *general;
+ /*! Configured users */
+ struct ao2_container *users;
+};
+
+/*! Max length for auth_realm field */
+#define ARI_AUTH_REALM_LEN 80
+
+/*! \brief Global configuration options for stasis http. */
+struct ari_conf_general {
+ /*! Enabled by default, disabled if false. */
+ int enabled;
+ /*! Encoding format used during output (default compact). */
+ enum ast_json_encoding_format format;
+ /*! Authentication realm */
+ char auth_realm[ARI_AUTH_REALM_LEN];
+};
+
+/*! \brief Password format */
+enum ari_password_format {
+ /*! \brief Plaintext password */
+ ARI_PASSWORD_FORMAT_PLAIN,
+ /*! crypt(3) password */
+ ARI_PASSWORD_FORMAT_CRYPT,
+};
+
+/*!
+ * \brief User's password mx length.
+ *
+ * If 256 seems like a lot, a crypt SHA-512 has over 106 characters.
+ */
+#define ARI_PASSWORD_LEN 256
+
+/*! \brief Per-user configuration options */
+struct ari_conf_user {
+ /*! Username for authentication */
+ char *username;
+ /*! User's password. */
+ char password[ARI_PASSWORD_LEN];
+ /*! Format for the password field */
+ enum ari_password_format password_format;
+ /*! If true, user cannot execute change operations */
+ int read_only;
+};
+
+/*!
+ * \brief Initialize the ARI configuration
+ */
+int ari_config_init(void);
+
+/*!
+ * \brief Reload the ARI configuration
+ */
+int ari_config_reload(void);
+
+/*!
+ * \brief Destroy the ARI configuration
+ */
+void ari_config_destroy(void);
+
+/*!
+ * \brief Get the current ARI configuration.
+ *
+ * This is an immutable object, so don't modify it. It is AO2 managed, so
+ * ao2_cleanup() when you're done with it.
+ *
+ * \return ARI configuration object.
+ * \return \c NULL on error.
+ */
+struct ari_conf *ari_config_get(void);
+
+/*!
+ * \brief Validated a user's credentials.
+ *
+ * \param username Name of the user.
+ * \param password User's password.
+ * \return User object.
+ * \return \c NULL if username or password is invalid.
+ */
+struct ari_conf_user *ari_config_validate_user(const char *username,
+ const char *password);
+
+/*! @} */
+
+
+#endif /* STASIS_HTTP_INTERNAL_H_ */