2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2012 - 2013, Digium, Inc.
6 * David M. Lee, II <dlee@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief HTTP binding for the Stasis API
22 * \author David M. Lee, II <dlee@digium.com>
24 * The API itself is documented using <a
25 * href="https://developers.helloreverb.com/swagger/">Swagger</a>, a lightweight
26 * mechanism for documenting RESTful API's using JSON. This allows us to use <a
27 * href="https://github.com/wordnik/swagger-ui">swagger-ui</a> to provide
28 * executable documentation for the API, generate client bindings in different
29 * <a href="https://github.com/asterisk/asterisk_rest_libraries">languages</a>,
30 * and generate a lot of the boilerplate code for implementing the RESTful
31 * bindings. The API docs live in the \c rest-api/ directory.
33 * The RESTful bindings are generated from the Swagger API docs using a set of
34 * <a href="http://mustache.github.io/mustache.5.html">Mustache</a> templates.
35 * The code generator is written in Python, and uses the Python implementation
36 * <a href="https://github.com/defunkt/pystache">pystache</a>. Pystache has no
37 * dependencies, and be installed easily using \c pip. Code generation code
38 * lives in \c rest-api-templates/.
40 * The generated code reduces a lot of boilerplate when it comes to handling
41 * HTTP requests. It also helps us have greater consistency in the REST API.
43 * The structure of the generated code is:
45 * - res/ari/resource_{resource}.h
46 * - For each operation in the resouce, a generated argument structure
47 * (holding the parsed arguments from the request) and function
48 * declarations (to implement in res/ari/resource_{resource}.c)
49 * - res_ari_{resource}.c
50 * - A set of \ref stasis_rest_callback functions, which glue the two
51 * together. They parse out path variables and request parameters to
52 * populate a specific \c *_args which is passed to the specific request
53 * handler (in res/ari/resource_{resource}.c)
54 * - A tree of \ref stasis_rest_handlers for routing requests to its
55 * \ref stasis_rest_callback
57 * The basic flow of an HTTP request is:
59 * - ast_ari_callback()
60 * 1. Initial request validation
61 * 2. Routes as either a doc request (ast_ari_get_docs) or API
62 * request (ast_ari_invoke)
64 * 1. Further request validation
65 * 2. Routes the request through the tree of generated
66 * \ref stasis_rest_handlers.
67 * 3. Dispatch to the generated callback
69 * 1. Populate \c *_args struct with path and get params
70 * 2. Invoke the request handler
71 * 3. Validates and sends response
75 <depend type="module">res_http_websocket</depend>
76 <support_level>core</support_level>
80 <configInfo name="res_ari" language="en_US">
81 <synopsis>HTTP binding for the Stasis API</synopsis>
82 <configFile name="ari.conf">
83 <configObject name="general">
84 <synopsis>General configuration settings</synopsis>
85 <configOption name="enabled">
86 <synopsis>Enable/disable the ARI module</synopsis>
88 <para>This option enables or disables the ARI module.</para>
90 <para>ARI uses Asterisk's HTTP server, which must also be enabled in <filename>http.conf</filename>.</para>
94 <ref type="filename">http.conf</ref>
95 <ref type="link">https://wiki.asterisk.org/wiki/display/AST/Asterisk+Builtin+mini-HTTP+Server</ref>
98 <configOption name="pretty">
99 <synopsis>Responses from ARI are formatted to be human readable</synopsis>
101 <configOption name="auth_realm">
102 <synopsis>Realm to use for authentication. Defaults to Asterisk REST Interface.</synopsis>
104 <configOption name="allowed_origins">
105 <synopsis>Comma separated list of allowed origins, for Cross-Origin Resource Sharing. May be set to * to allow all origins.</synopsis>
109 <configObject name="user">
110 <synopsis>Per-user configuration settings</synopsis>
111 <configOption name="type">
112 <synopsis>Define this configuration section as a user.</synopsis>
115 <enum name="user"><para>Configure this section as a <replaceable>user</replaceable></para></enum>
119 <configOption name="read_only">
120 <synopsis>When set to yes, user is only authorized for read-only requests</synopsis>
122 <configOption name="password">
123 <synopsis>Crypted or plaintext password (see password_format)</synopsis>
125 <configOption name="password_format">
126 <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>
133 #include "asterisk.h"
135 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
137 #include "ari/internal.h"
138 #include "asterisk/ari.h"
139 #include "asterisk/astobj2.h"
140 #include "asterisk/module.h"
141 #include "asterisk/paths.h"
144 #include <sys/stat.h>
147 /*! \brief Helper function to check if module is enabled. */
148 static int is_enabled(void)
150 RAII_VAR(struct ast_ari_conf *, cfg, ast_ari_config_get(), ao2_cleanup);
151 return cfg && cfg->general && cfg->general->enabled;
154 /*! Lock for \ref root_handler */
155 static ast_mutex_t root_handler_lock;
157 /*! Handler for root RESTful resource. */
158 static struct stasis_rest_handlers *root_handler;
160 /*! Pre-defined message for allocation failures. */
161 static struct ast_json *oom_json;
163 struct ast_json *ast_ari_oom_json(void)
168 int ast_ari_add_handler(struct stasis_rest_handlers *handler)
170 RAII_VAR(struct stasis_rest_handlers *, new_handler, NULL, ao2_cleanup);
171 size_t old_size, new_size;
173 SCOPED_MUTEX(lock, &root_handler_lock);
175 old_size = sizeof(*new_handler) +
176 root_handler->num_children * sizeof(handler);
177 new_size = old_size + sizeof(handler);
179 new_handler = ao2_alloc(new_size, NULL);
183 memcpy(new_handler, root_handler, old_size);
184 new_handler->children[new_handler->num_children++] = handler;
186 ao2_cleanup(root_handler);
187 ao2_ref(new_handler, +1);
188 root_handler = new_handler;
189 ast_module_ref(ast_module_info->self);
193 int ast_ari_remove_handler(struct stasis_rest_handlers *handler)
195 RAII_VAR(struct stasis_rest_handlers *, new_handler, NULL, ao2_cleanup);
198 ast_assert(root_handler != NULL);
200 ast_mutex_lock(&root_handler_lock);
201 size = sizeof(*new_handler) +
202 root_handler->num_children * sizeof(handler);
204 new_handler = ao2_alloc(size, NULL);
208 memcpy(new_handler, root_handler, sizeof(*new_handler));
210 for (i = 0, j = 0; i < root_handler->num_children; ++i) {
211 if (root_handler->children[i] == handler) {
212 ast_module_unref(ast_module_info->self);
215 new_handler->children[j++] = root_handler->children[i];
217 new_handler->num_children = j;
219 ao2_cleanup(root_handler);
220 ao2_ref(new_handler, +1);
221 root_handler = new_handler;
222 ast_mutex_unlock(&root_handler_lock);
226 static struct stasis_rest_handlers *get_root_handler(void)
228 SCOPED_MUTEX(lock, &root_handler_lock);
229 ao2_ref(root_handler, +1);
233 static struct stasis_rest_handlers *root_handler_create(void)
235 RAII_VAR(struct stasis_rest_handlers *, handler, NULL, ao2_cleanup);
237 handler = ao2_alloc(sizeof(*handler), NULL);
241 handler->path_segment = "ari";
243 ao2_ref(handler, +1);
247 void ast_ari_response_error(struct ast_ari_response *response,
249 const char *response_text,
250 const char *message_fmt, ...)
252 RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
255 va_start(ap, message_fmt);
256 message = ast_json_vstringf(message_fmt, ap);
257 response->message = ast_json_pack("{s: o}",
258 "message", ast_json_ref(message));
259 response->response_code = response_code;
260 response->response_text = response_text;
263 void ast_ari_response_ok(struct ast_ari_response *response,
264 struct ast_json *message)
266 response->message = message;
267 response->response_code = 200;
268 response->response_text = "OK";
271 void ast_ari_response_no_content(struct ast_ari_response *response)
273 response->message = ast_json_null();
274 response->response_code = 204;
275 response->response_text = "No Content";
278 void ast_ari_response_alloc_failed(struct ast_ari_response *response)
280 response->message = ast_json_ref(oom_json);
281 response->response_code = 500;
282 response->response_text = "Internal Server Error";
285 void ast_ari_response_created(struct ast_ari_response *response,
286 const char *url, struct ast_json *message)
288 response->message = message;
289 response->response_code = 201;
290 response->response_text = "Created";
291 ast_str_append(&response->headers, 0, "Location: %s\r\n", url);
294 static void add_allow_header(struct stasis_rest_handlers *handler,
295 struct ast_ari_response *response)
297 enum ast_http_method m;
298 ast_str_append(&response->headers, 0,
300 for (m = 0; m < AST_HTTP_MAX_METHOD; ++m) {
301 if (handler->callbacks[m] != NULL) {
302 ast_str_append(&response->headers, 0,
303 ",%s", ast_get_http_method(m));
306 ast_str_append(&response->headers, 0, "\r\n");
309 static int origin_allowed(const char *origin)
311 RAII_VAR(struct ast_ari_conf *, cfg, ast_ari_config_get(), ao2_cleanup);
313 char *allowed = ast_strdupa(cfg->general->allowed_origins);
316 while ((current = strsep(&allowed, ","))) {
317 if (!strcmp(current, "*")) {
321 if (!strcmp(current, origin)) {
329 #define ACR_METHOD "Access-Control-Request-Method"
330 #define ACR_HEADERS "Access-Control-Request-Headers"
331 #define ACA_METHODS "Access-Control-Allow-Methods"
332 #define ACA_HEADERS "Access-Control-Allow-Headers"
335 * \brief Handle OPTIONS request, mainly for CORS preflight requests.
337 * Some browsers will send this prior to non-simple methods (i.e. DELETE).
338 * See http://www.w3.org/TR/cors/ for the spec. Especially section 6.2.
340 static void handle_options(struct stasis_rest_handlers *handler,
341 struct ast_variable *headers,
342 struct ast_ari_response *response)
344 struct ast_variable *header;
345 char const *acr_method = NULL;
346 char const *acr_headers = NULL;
347 char const *origin = NULL;
349 RAII_VAR(struct ast_str *, allow, NULL, ast_free);
350 enum ast_http_method m;
353 /* Regular OPTIONS response */
354 add_allow_header(handler, response);
355 ast_ari_response_no_content(response);
357 /* Parse CORS headers */
358 for (header = headers; header != NULL; header = header->next) {
359 if (strcmp(ACR_METHOD, header->name) == 0) {
360 acr_method = header->value;
361 } else if (strcmp(ACR_HEADERS, header->name) == 0) {
362 acr_headers = header->value;
363 } else if (strcmp("Origin", header->name) == 0) {
364 origin = header->value;
368 /* CORS 6.2, #1 - "If the Origin header is not present terminate this
371 if (origin == NULL) {
375 /* CORS 6.2, #2 - "If the value of the Origin header is not a
376 * case-sensitive match for any of the values in list of origins do not
377 * set any additional headers and terminate this set of steps.
379 * Always matching is acceptable since the list of origins can be
382 * The Origin header can only contain a single origin as the user agent
383 * will not follow redirects."
385 if (!origin_allowed(origin)) {
386 ast_log(LOG_NOTICE, "Origin header '%s' does not match an allowed origin.\n", origin);
390 /* CORS 6.2, #3 - "If there is no Access-Control-Request-Method header
391 * or if parsing failed, do not set any additional headers and terminate
392 * this set of steps."
394 if (acr_method == NULL) {
398 /* CORS 6.2, #4 - "If there are no Access-Control-Request-Headers
399 * headers let header field-names be the empty list."
401 if (acr_headers == NULL) {
405 /* CORS 6.2, #5 - "If method is not a case-sensitive match for any of
406 * the values in list of methods do not set any additional headers and
407 * terminate this set of steps."
409 allow = ast_str_create(20);
412 ast_ari_response_alloc_failed(response);
416 /* Go ahead and build the ACA_METHODS header at the same time */
417 for (m = 0; m < AST_HTTP_MAX_METHOD; ++m) {
418 if (handler->callbacks[m] != NULL) {
419 char const *m_str = ast_get_http_method(m);
420 if (strcmp(m_str, acr_method) == 0) {
423 ast_str_append(&allow, 0, ",%s", m_str);
431 /* CORS 6.2 #6 - "If any of the header field-names is not a ASCII
432 * case-insensitive match for any of the values in list of headers do
433 * not set any additional headers and terminate this set of steps.
435 * Note: Always matching is acceptable since the list of headers can be
439 /* CORS 6.2 #7 - "If the resource supports credentials add a single
440 * Access-Control-Allow-Origin header, with the value of the Origin
441 * header as value, and add a single Access-Control-Allow-Credentials
442 * header with the case-sensitive string "true" as value."
444 * Added by process_cors_request() earlier in the request.
447 /* CORS 6.2 #8 - "Optionally add a single Access-Control-Max-Age
451 /* CORS 6.2 #9 - "Add one or more Access-Control-Allow-Methods headers
452 * consisting of (a subset of) the list of methods."
454 ast_str_append(&response->headers, 0, "%s: OPTIONS%s\r\n",
455 ACA_METHODS, ast_str_buffer(allow));
458 /* CORS 6.2, #10 - "Add one or more Access-Control-Allow-Headers headers
459 * consisting of (a subset of) the list of headers.
461 * Since the list of headers can be unbounded simply returning headers
464 if (!ast_strlen_zero(acr_headers)) {
465 ast_str_append(&response->headers, 0, "%s: %s\r\n",
466 ACA_HEADERS, acr_headers);
470 void ast_ari_invoke(struct ast_tcptls_session_instance *ser,
471 const char *uri, enum ast_http_method method,
472 struct ast_variable *get_params, struct ast_variable *headers,
473 struct ast_ari_response *response)
475 RAII_VAR(struct stasis_rest_handlers *, root, NULL, ao2_cleanup);
476 struct stasis_rest_handlers *handler;
477 RAII_VAR(struct ast_variable *, path_vars, NULL, ast_variables_destroy);
478 char *path = ast_strdupa(uri);
480 stasis_rest_callback callback;
482 root = handler = get_root_handler();
483 ast_assert(root != NULL);
485 while ((path_segment = strsep(&path, "/")) && (strlen(path_segment) > 0)) {
486 struct stasis_rest_handlers *found_handler = NULL;
488 ast_uri_decode(path_segment, ast_uri_http_legacy);
489 ast_debug(3, "Finding handler for %s\n", path_segment);
490 for (i = 0; found_handler == NULL && i < handler->num_children; ++i) {
491 struct stasis_rest_handlers *child = handler->children[i];
493 ast_debug(3, " Checking %s\n", child->path_segment);
494 if (child->is_wildcard) {
495 /* Record the path variable */
496 struct ast_variable *path_var = ast_variable_new(child->path_segment, path_segment, __FILE__);
497 path_var->next = path_vars;
498 path_vars = path_var;
499 found_handler = child;
500 } else if (strcmp(child->path_segment, path_segment) == 0) {
501 found_handler = child;
505 if (found_handler == NULL) {
506 /* resource not found */
507 ast_debug(3, " Handler not found\n");
508 ast_ari_response_error(
509 response, 404, "Not Found",
510 "Resource not found");
513 ast_debug(3, " Got it!\n");
514 handler = found_handler;
518 ast_assert(handler != NULL);
519 if (method == AST_HTTP_OPTIONS) {
520 handle_options(handler, headers, response);
524 if (method < 0 || method >= AST_HTTP_MAX_METHOD) {
525 add_allow_header(handler, response);
526 ast_ari_response_error(
527 response, 405, "Method Not Allowed",
532 if (handler->ws_server && method == AST_HTTP_GET) {
534 ari_handle_websocket(handler->ws_server, ser, uri, method,
535 get_params, headers);
536 /* Since the WebSocket code handles the connection, we shouldn't
537 * do anything else; setting no_response */
538 response->no_response = 1;
542 callback = handler->callbacks[method];
543 if (callback == NULL) {
544 add_allow_header(handler, response);
545 ast_ari_response_error(
546 response, 405, "Method Not Allowed",
551 callback(ser, get_params, path_vars, headers, response);
552 if (response->message == NULL && response->response_code == 0) {
553 /* Really should not happen */
554 ast_log(LOG_ERROR, "ARI %s %s not implemented\n",
555 ast_get_http_method(method), uri);
556 ast_ari_response_error(
557 response, 501, "Not Implemented",
558 "Method not implemented");
562 void ast_ari_get_docs(const char *uri, struct ast_variable *headers,
563 struct ast_ari_response *response)
565 RAII_VAR(struct ast_str *, absolute_path_builder, NULL, ast_free);
566 RAII_VAR(char *, absolute_api_dirname, NULL, ast_std_free);
567 RAII_VAR(char *, absolute_filename, NULL, ast_std_free);
568 struct ast_json *obj = NULL;
569 struct ast_variable *host = NULL;
570 struct ast_json_error error = {};
571 struct stat file_stat;
573 ast_debug(3, "%s(%s)\n", __func__, uri);
575 absolute_path_builder = ast_str_create(80);
576 if (absolute_path_builder == NULL) {
577 ast_ari_response_alloc_failed(response);
581 /* absolute path to the rest-api directory */
582 ast_str_append(&absolute_path_builder, 0, "%s", ast_config_AST_DATA_DIR);
583 ast_str_append(&absolute_path_builder, 0, "/rest-api/");
584 absolute_api_dirname = realpath(ast_str_buffer(absolute_path_builder), NULL);
585 if (absolute_api_dirname == NULL) {
586 ast_log(LOG_ERROR, "Error determining real directory for rest-api\n");
587 ast_ari_response_error(
588 response, 500, "Internal Server Error",
589 "Cannot find rest-api directory");
593 /* absolute path to the requested file */
594 ast_str_append(&absolute_path_builder, 0, "%s", uri);
595 absolute_filename = realpath(ast_str_buffer(absolute_path_builder), NULL);
596 if (absolute_filename == NULL) {
601 ast_ari_response_error(
602 response, 404, "Not Found",
603 "Resource not found");
606 ast_ari_response_error(
607 response, 403, "Forbidden",
608 "Permission denied");
612 "Error determining real path for uri '%s': %s\n",
613 uri, strerror(errno));
614 ast_ari_response_error(
615 response, 500, "Internal Server Error",
622 if (!ast_begins_with(absolute_filename, absolute_api_dirname)) {
625 "Invalid attempt to access '%s' (not in %s)\n",
626 absolute_filename, absolute_api_dirname);
627 ast_ari_response_error(
628 response, 404, "Not Found",
629 "Resource not found");
633 if (stat(absolute_filename, &file_stat) == 0) {
634 if (!(file_stat.st_mode & S_IFREG)) {
636 ast_ari_response_error(
637 response, 403, "Forbidden",
643 ast_ari_response_error(
644 response, 404, "Not Found",
645 "Resource not found");
649 /* Load resource object from file */
650 obj = ast_json_load_new_file(absolute_filename, &error);
652 ast_log(LOG_ERROR, "Error parsing resource file: %s:%d(%d) %s\n",
653 error.source, error.line, error.column, error.text);
654 ast_ari_response_error(
655 response, 500, "Internal Server Error",
656 "Yikes! Cannot parse resource");
660 /* Update the basePath properly */
661 if (ast_json_object_get(obj, "basePath") != NULL) {
662 for (host = headers; host; host = host->next) {
663 if (strcasecmp(host->name, "Host") == 0) {
670 ast_json_stringf("http://%s/ari", host->value));
672 /* Without the host, we don't have the basePath */
673 ast_json_object_del(obj, "basePath");
677 ast_ari_response_ok(response, obj);
680 static void remove_trailing_slash(const char *uri,
681 struct ast_ari_response *response)
683 char *slashless = ast_strdupa(uri);
684 slashless[strlen(slashless) - 1] = '\0';
686 /* While it's tempting to redirect the client to the slashless URL,
687 * that is problematic. A 302 Found is the most appropriate response,
688 * but most clients issue a GET on the location you give them,
689 * regardless of the method of the original request.
691 * While there are some ways around this, it gets into a lot of client
692 * specific behavior and corner cases in the HTTP standard. There's also
693 * very little practical benefit of redirecting; only GET and HEAD can
694 * be redirected automagically; all other requests "MUST NOT
695 * automatically redirect the request unless it can be confirmed by the
696 * user, since this might change the conditions under which the request
699 * Given all of that, a 404 with a nice message telling them what to do
700 * is probably our best bet.
702 ast_ari_response_error(response, 404, "Not Found",
703 "ARI URLs do not end with a slash. Try /ari/%s", slashless);
707 * \brief Handle CORS headers for simple requests.
709 * See http://www.w3.org/TR/cors/ for the spec. Especially section 6.1.
711 static void process_cors_request(struct ast_variable *headers,
712 struct ast_ari_response *response)
714 char const *origin = NULL;
715 struct ast_variable *header;
717 /* Parse CORS headers */
718 for (header = headers; header != NULL; header = header->next) {
719 if (strcmp("Origin", header->name) == 0) {
720 origin = header->value;
724 /* CORS 6.1, #1 - "If the Origin header is not present terminate this
727 if (origin == NULL) {
731 /* CORS 6.1, #2 - "If the value of the Origin header is not a
732 * case-sensitive match for any of the values in list of origins, do not
733 * set any additional headers and terminate this set of steps.
735 * Note: Always matching is acceptable since the list of origins can be
738 if (!origin_allowed(origin)) {
739 ast_log(LOG_NOTICE, "Origin header '%s' does not match an allowed origin.\n", origin);
743 /* CORS 6.1, #3 - "If the resource supports credentials add a single
744 * Access-Control-Allow-Origin header, with the value of the Origin
745 * header as value, and add a single Access-Control-Allow-Credentials
746 * header with the case-sensitive string "true" as value.
748 * Otherwise, add a single Access-Control-Allow-Origin header, with
749 * either the value of the Origin header or the string "*" as value."
751 ast_str_append(&response->headers, 0,
752 "Access-Control-Allow-Origin: %s\r\n", origin);
753 ast_str_append(&response->headers, 0,
754 "Access-Control-Allow-Credentials: true\r\n");
756 /* CORS 6.1, #4 - "If the list of exposed headers is not empty add one
757 * or more Access-Control-Expose-Headers headers, with as values the
758 * header field names given in the list of exposed headers."
760 * No exposed headers; skipping
764 enum ast_json_encoding_format ast_ari_json_format(void)
766 RAII_VAR(struct ast_ari_conf *, cfg, NULL, ao2_cleanup);
767 cfg = ast_ari_config_get();
768 return cfg->general->format;
772 * \brief Authenticate a <code>?api_key=userid:password</code>
774 * \param api_key API key query parameter
775 * \return User object for the authenticated user.
776 * \return \c NULL if authentication failed.
778 static struct ast_ari_conf_user *authenticate_api_key(const char *api_key)
780 RAII_VAR(char *, copy, NULL, ast_free);
784 password = copy = ast_strdup(api_key);
789 username = strsep(&password, ":");
791 ast_log(LOG_WARNING, "Invalid api_key\n");
795 return ast_ari_config_validate_user(username, password);
799 * \brief Authenticate an HTTP request.
801 * \param get_params GET parameters of the request.
802 * \param header HTTP headers.
803 * \return User object for the authenticated user.
804 * \return \c NULL if authentication failed.
806 static struct ast_ari_conf_user *authenticate_user(struct ast_variable *get_params,
807 struct ast_variable *headers)
809 RAII_VAR(struct ast_http_auth *, http_auth, NULL, ao2_cleanup);
810 struct ast_variable *v;
812 /* HTTP Basic authentication */
813 http_auth = ast_http_get_auth(headers);
815 return ast_ari_config_validate_user(http_auth->userid,
816 http_auth->password);
819 /* ?api_key authentication */
820 for (v = get_params; v; v = v->next) {
821 if (strcasecmp("api_key", v->name) == 0) {
822 return authenticate_api_key(v->value);
831 * \brief ARI HTTP handler.
833 * This handler takes the HTTP request and turns it into the appropriate
834 * RESTful request (conversion to JSON, routing, etc.)
836 * \param ser TCP session.
837 * \param urih URI handler.
838 * \param uri URI requested.
839 * \param method HTTP method.
840 * \param get_params HTTP \c GET params.
841 * \param headers HTTP headers.
843 static int ast_ari_callback(struct ast_tcptls_session_instance *ser,
844 const struct ast_http_uri *urih,
846 enum ast_http_method method,
847 struct ast_variable *get_params,
848 struct ast_variable *headers)
850 RAII_VAR(struct ast_ari_conf *, conf, NULL, ao2_cleanup);
851 RAII_VAR(struct ast_str *, response_body, ast_str_create(256), ast_free);
852 RAII_VAR(struct ast_ari_conf_user *, user, NULL, ao2_cleanup);
853 struct ast_ari_response response = {};
855 RAII_VAR(struct ast_variable *, post_vars, NULL, ast_variables_destroy);
857 if (!response_body) {
861 response.headers = ast_str_create(40);
862 if (!response.headers) {
866 conf = ast_ari_config_get();
867 if (!conf || !conf->general) {
868 ast_free(response.headers);
872 process_cors_request(headers, &response);
874 /* Process form data from a POST. It could be mixed with query
875 * parameters, which seems a bit odd. But it's allowed, so that's okay
878 post_vars = ast_http_get_post_vars(ser, headers);
879 if (get_params == NULL) {
882 ast_ari_response_error(&response, 413,
883 "Request Entity Too Large",
884 "Request body too large");
887 ast_ari_response_error(&response, 500,
888 "Internal Server Error",
889 "Error processing request");
892 ast_ari_response_error(&response, 400,
893 "Bad Request", "Error parsing request body");
896 get_params = post_vars;
897 } else if (get_params && post_vars) {
898 /* Has both post_vars and get_params */
899 struct ast_variable *last_var = post_vars;
900 while (last_var->next) {
901 last_var = last_var->next;
903 /* The duped get_params will get freed when post_vars gets
904 * ast_variables_destroyed.
906 last_var->next = ast_variables_dup(get_params);
907 get_params = post_vars;
910 user = authenticate_user(get_params, headers);
911 if (response.response_code > 0) {
912 /* POST parameter processing error. Do nothing. */
914 /* Per RFC 2617, section 1.2: The 401 (Unauthorized) response
915 * message is used by an origin server to challenge the
916 * authorization of a user agent. This response MUST include a
917 * WWW-Authenticate header field containing at least one
918 * challenge applicable to the requested resource.
920 ast_ari_response_error(&response, 401, "Unauthorized", "Authentication required");
923 * realm = "realm" "=" realm-value
924 * realm-value = quoted-string
926 * challenge = "Basic" realm
928 ast_str_append(&response.headers, 0,
929 "WWW-Authenticate: Basic realm=\"%s\"\r\n",
930 conf->general->auth_realm);
931 } else if (!ast_fully_booted) {
932 ast_ari_response_error(&response, 503, "Service Unavailable", "Asterisk not booted");
933 } else if (user->read_only && method != AST_HTTP_GET && method != AST_HTTP_OPTIONS) {
934 ast_ari_response_error(&response, 403, "Forbidden", "Write access denied");
935 } else if (ast_ends_with(uri, "/")) {
936 remove_trailing_slash(uri, &response);
937 } else if (ast_begins_with(uri, "api-docs/")) {
938 /* Serving up API docs */
939 if (method != AST_HTTP_GET) {
940 ast_ari_response_error(&response, 405, "Method Not Allowed", "Unsupported method");
942 /* Skip the api-docs prefix */
943 ast_ari_get_docs(strchr(uri, '/') + 1, headers, &response);
946 /* Other RESTful resources */
947 ast_ari_invoke(ser, uri, method, get_params, headers,
951 if (response.no_response) {
952 /* The handler indicates no further response is necessary.
953 * Probably because it already handled it */
954 ast_free(response.headers);
958 /* If you explicitly want to have no content, set message to
961 ast_assert(response.message != NULL);
962 ast_assert(response.response_code > 0);
964 /* response.message could be NULL, in which case the empty response_body
967 if (response.message && !ast_json_is_null(response.message)) {
968 ast_str_append(&response.headers, 0,
969 "Content-type: application/json\r\n");
970 if (ast_json_dump_str_format(response.message, &response_body,
971 conf->general->format) != 0) {
972 /* Error encoding response */
973 response.response_code = 500;
974 response.response_text = "Internal Server Error";
975 ast_str_set(&response_body, 0, "%s", "");
976 ast_str_set(&response.headers, 0, "%s", "");
981 ast_debug(3, "Examining ARI response:\n%d %s\n%s\n%s\n", response.response_code,
982 response.response_text, ast_str_buffer(response.headers), ast_str_buffer(response_body));
983 ast_http_send(ser, method, response.response_code,
984 response.response_text, response.headers, response_body,
986 /* ast_http_send takes ownership, so we don't have to free them */
987 response_body = NULL;
989 ast_json_unref(response.message);
993 static struct ast_http_uri http_uri = {
994 .callback = ast_ari_callback,
995 .description = "Asterisk RESTful API",
1004 static int load_module(void)
1006 ast_mutex_init(&root_handler_lock);
1008 /* root_handler may have been built during a declined load */
1009 if (!root_handler) {
1010 root_handler = root_handler_create();
1012 if (!root_handler) {
1013 return AST_MODULE_LOAD_FAILURE;
1016 /* oom_json may have been built during a declined load */
1018 oom_json = ast_json_pack(
1019 "{s: s}", "error", "Allocation failed");
1023 return AST_MODULE_LOAD_FAILURE;
1026 if (ast_ari_config_init() != 0) {
1027 return AST_MODULE_LOAD_DECLINE;
1031 ast_debug(3, "ARI enabled\n");
1032 ast_http_uri_link(&http_uri);
1034 ast_debug(3, "ARI disabled\n");
1037 if (ast_ari_cli_register() != 0) {
1038 return AST_MODULE_LOAD_FAILURE;
1041 return AST_MODULE_LOAD_SUCCESS;
1044 static int unload_module(void)
1046 ast_ari_cli_unregister();
1049 ast_debug(3, "Disabling ARI\n");
1050 ast_http_uri_unlink(&http_uri);
1053 ast_ari_config_destroy();
1055 ao2_cleanup(root_handler);
1056 root_handler = NULL;
1057 ast_mutex_destroy(&root_handler_lock);
1059 ast_json_unref(oom_json);
1065 static int reload_module(void)
1067 char was_enabled = is_enabled();
1069 if (ast_ari_config_reload() != 0) {
1070 return AST_MODULE_LOAD_DECLINE;
1073 if (was_enabled && !is_enabled()) {
1074 ast_debug(3, "Disabling ARI\n");
1075 ast_http_uri_unlink(&http_uri);
1076 } else if (!was_enabled && is_enabled()) {
1077 ast_debug(3, "Enabling ARI\n");
1078 ast_http_uri_link(&http_uri);
1081 return AST_MODULE_LOAD_SUCCESS;
1084 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Asterisk RESTful Interface",
1085 .load = load_module,
1086 .unload = unload_module,
1087 .reload = reload_module,
1088 .nonoptreq = "res_http_websocket",
1089 .load_pri = AST_MODPRI_APP_DEPEND,