4b2e1ccd75c25b32abe2264c9be8abaeed45aab3
[asterisk/asterisk.git] / res / res_stasis_http.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2012 - 2013, Digium, Inc.
5  *
6  * David M. Lee, II <dlee@digium.com>
7  *
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.
13  *
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.
17  */
18
19 /*! \file
20  *
21  * \brief HTTP binding for the Stasis API
22  * \author David M. Lee, II <dlee@digium.com>
23  *
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.
32  *
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/.
39  *
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.
42  *
43  * The structure of the generated code is:
44  *
45  *  - res/stasis_http/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/stasis_http/resource_{resource}.c)
49  *  - res_stasis_http_{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/stasis_http/resource_{resource}.c)
54  *    - A tree of \ref stasis_rest_handlers for routing requests to its
55  *      \ref stasis_rest_callback
56  *
57  * The basic flow of an HTTP request is:
58  *
59  *  - stasis_http_callback()
60  *    1. Initial request validation
61  *    2. Routes as either a doc request (stasis_http_get_docs) or API
62  *       request (stasis_http_invoke)
63  *       - stasis_http_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
68  *            - \c stasis_http_*_cb
69  *              1. Populate \c *_args struct with path and get params
70  *              2. Invoke the request handler
71  *    3. Validates and sends response
72  */
73
74 /*** MODULEINFO
75         <depend type="module">res_http_websocket</depend>
76         <support_level>core</support_level>
77  ***/
78
79 /*** DOCUMENTATION
80         <configInfo name="res_stasis_http" 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 stasis-http module</synopsis>
87                                 </configOption>
88                                 <configOption name="pretty">
89                                         <synopsis>Responses from stasis-http are formatted to be human readable</synopsis>
90                                 </configOption>
91                                 <configOption name="auth_realm">
92                                         <synopsis>Realm to use for authentication. Defaults to Asterisk REST Interface.</synopsis>
93                                 </configOption>
94                         </configObject>
95
96                         <configObject name="user">
97                                 <synopsis>Per-user configuration settings</synopsis>
98                                 <configOption name="read_only">
99                                         <synopsis>When set to yes, user is only authorized for read-only requests</synopsis>
100                                 </configOption>
101                                 <configOption name="password">
102                                         <synopsis>Crypted or plaintext password (see password_format)</synopsis>
103                                 </configOption>
104                                 <configOption name="password_format">
105                                         <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>
106                                 </configOption>
107                         </configObject>
108                 </configFile>
109         </configInfo>
110 ***/
111
112 #include "asterisk.h"
113
114 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
115
116 #include "asterisk/astobj2.h"
117 #include "asterisk/module.h"
118 #include "asterisk/paths.h"
119 #include "asterisk/stasis_http.h"
120 #include "stasis_http/internal.h"
121
122 #include <string.h>
123 #include <sys/stat.h>
124 #include <unistd.h>
125
126 /*! \brief Helper function to check if module is enabled. */
127 static int is_enabled(void)
128 {
129         RAII_VAR(struct ari_conf *, cfg, ari_config_get(), ao2_cleanup);
130         return cfg && cfg->general && cfg->general->enabled;
131 }
132
133 /*! Lock for \ref root_handler */
134 static ast_mutex_t root_handler_lock;
135
136 /*! Handler for root RESTful resource. */
137 static struct stasis_rest_handlers *root_handler;
138
139 /*! Pre-defined message for allocation failures. */
140 static struct ast_json *oom_json;
141
142 struct ast_json *ari_oom_json(void)
143 {
144         return oom_json;
145 }
146
147 int stasis_http_add_handler(struct stasis_rest_handlers *handler)
148 {
149         RAII_VAR(struct stasis_rest_handlers *, new_handler, NULL, ao2_cleanup);
150         size_t old_size, new_size;
151
152         SCOPED_MUTEX(lock, &root_handler_lock);
153
154         old_size = sizeof(*new_handler) +
155                 root_handler->num_children * sizeof(handler);
156         new_size = old_size + sizeof(handler);
157
158         new_handler = ao2_alloc(new_size, NULL);
159         if (!new_handler) {
160                 return -1;
161         }
162         memcpy(new_handler, root_handler, old_size);
163         new_handler->children[new_handler->num_children++] = handler;
164
165         ao2_cleanup(root_handler);
166         ao2_ref(new_handler, +1);
167         root_handler = new_handler;
168         ast_module_ref(ast_module_info->self);
169         return 0;
170 }
171
172 int stasis_http_remove_handler(struct stasis_rest_handlers *handler)
173 {
174         RAII_VAR(struct stasis_rest_handlers *, new_handler, NULL, ao2_cleanup);
175         size_t size, i, j;
176
177         ast_assert(root_handler != NULL);
178
179         ast_mutex_lock(&root_handler_lock);
180         size = sizeof(*new_handler) +
181                 root_handler->num_children * sizeof(handler);
182
183         new_handler = ao2_alloc(size, NULL);
184         if (!new_handler) {
185                 return -1;
186         }
187         memcpy(new_handler, root_handler, sizeof(*new_handler));
188
189         for (i = 0, j = 0; i < root_handler->num_children; ++i) {
190                 if (root_handler->children[i] == handler) {
191                         ast_module_unref(ast_module_info->self);
192                         continue;
193                 }
194                 new_handler->children[j++] = root_handler->children[i];
195         }
196         new_handler->num_children = j;
197
198         ao2_cleanup(root_handler);
199         ao2_ref(new_handler, +1);
200         root_handler = new_handler;
201         ast_mutex_unlock(&root_handler_lock);
202         return 0;
203 }
204
205 static struct stasis_rest_handlers *get_root_handler(void)
206 {
207         SCOPED_MUTEX(lock, &root_handler_lock);
208         ao2_ref(root_handler, +1);
209         return root_handler;
210 }
211
212 static struct stasis_rest_handlers *root_handler_create(void)
213 {
214         RAII_VAR(struct stasis_rest_handlers *, handler, NULL, ao2_cleanup);
215
216         handler = ao2_alloc(sizeof(*handler), NULL);
217         if (!handler) {
218                 return NULL;
219         }
220         handler->path_segment = "ari";
221
222         ao2_ref(handler, +1);
223         return handler;
224 }
225
226 void stasis_http_response_error(struct stasis_http_response *response,
227                                 int response_code,
228                                 const char *response_text,
229                                 const char *message_fmt, ...)
230 {
231         RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
232         va_list ap;
233
234         va_start(ap, message_fmt);
235         message = ast_json_vstringf(message_fmt, ap);
236         response->message = ast_json_pack("{s: o}",
237                                           "message", ast_json_ref(message));
238         response->response_code = response_code;
239         response->response_text = response_text;
240 }
241
242 void stasis_http_response_ok(struct stasis_http_response *response,
243                              struct ast_json *message)
244 {
245         response->message = ast_json_ref(message);
246         response->response_code = 200;
247         response->response_text = "OK";
248 }
249
250 void stasis_http_response_no_content(struct stasis_http_response *response)
251 {
252         response->message = ast_json_null();
253         response->response_code = 204;
254         response->response_text = "No Content";
255 }
256
257 void stasis_http_response_alloc_failed(struct stasis_http_response *response)
258 {
259         response->message = ast_json_ref(oom_json);
260         response->response_code = 500;
261         response->response_text = "Internal Server Error";
262 }
263
264 void stasis_http_response_created(struct stasis_http_response *response,
265         const char *url, struct ast_json *message)
266 {
267         response->message = ast_json_ref(message);
268         response->response_code = 201;
269         response->response_text = "Created";
270         ast_str_append(&response->headers, 0, "Location: %s\r\n", url);
271 }
272
273 static void add_allow_header(struct stasis_rest_handlers *handler,
274                              struct stasis_http_response *response)
275 {
276         enum ast_http_method m;
277         ast_str_append(&response->headers, 0,
278                        "Allow: OPTIONS");
279         for (m = 0; m < AST_HTTP_MAX_METHOD; ++m) {
280                 if (handler->callbacks[m] != NULL) {
281                         ast_str_append(&response->headers, 0,
282                                        ",%s", ast_get_http_method(m));
283                 }
284         }
285         ast_str_append(&response->headers, 0, "\r\n");
286 }
287
288 #define ACR_METHOD "Access-Control-Request-Method"
289 #define ACR_HEADERS "Access-Control-Request-Headers"
290 #define ACA_METHODS "Access-Control-Allow-Methods"
291 #define ACA_HEADERS "Access-Control-Allow-Headers"
292
293 /*!
294  * \brief Handle OPTIONS request, mainly for CORS preflight requests.
295  *
296  * Some browsers will send this prior to non-simple methods (i.e. DELETE).
297  * See http://www.w3.org/TR/cors/ for the spec. Especially section 6.2.
298  */
299 static void handle_options(struct stasis_rest_handlers *handler,
300                            struct ast_variable *headers,
301                            struct stasis_http_response *response)
302 {
303         struct ast_variable *header;
304         char const *acr_method = NULL;
305         char const *acr_headers = NULL;
306         char const *origin = NULL;
307
308         RAII_VAR(struct ast_str *, allow, NULL, ast_free);
309         enum ast_http_method m;
310         int allowed = 0;
311
312         /* Regular OPTIONS response */
313         add_allow_header(handler, response);
314         stasis_http_response_no_content(response);
315
316         /* Parse CORS headers */
317         for (header = headers; header != NULL; header = header->next) {
318                 if (strcmp(ACR_METHOD, header->name) == 0) {
319                         acr_method = header->value;
320                 } else if (strcmp(ACR_HEADERS, header->name) == 0) {
321                         acr_headers = header->value;
322                 } else if (strcmp("Origin", header->name) == 0) {
323                         origin = header->value;
324                 }
325         }
326
327         /* CORS 6.2, #1 - "If the Origin header is not present terminate this
328          * set of steps.
329          */
330         if (origin == NULL) {
331                 return;
332         }
333
334         /* CORS 6.2, #2 - "If the value of the Origin header is not a
335          * case-sensitive match for any of the values in list of origins do not
336          * set any additional headers and terminate this set of steps.
337          *
338          * "Always matching is acceptable since the list of origins can be
339          * unbounded.
340          *
341          * "The Origin header can only contain a single origin as the user agent
342          * will not follow redirects.
343          *
344          * TODO - pull list of allowed origins from config
345          */
346
347         /* CORS 6.2, #3 - "If there is no Access-Control-Request-Method header
348          * or if parsing failed, do not set any additional headers and terminate
349          * this set of steps."
350          */
351         if (acr_method == NULL) {
352                 return;
353         }
354
355         /* CORS 6.2, #4 - "If there are no Access-Control-Request-Headers
356          * headers let header field-names be the empty list."
357          */
358         if (acr_headers == NULL) {
359                 acr_headers = "";
360         }
361
362         /* CORS 6.2, #5 - "If method is not a case-sensitive match for any of
363          * the values in list of methods do not set any additional headers and
364          * terminate this set of steps."
365          */
366         allow = ast_str_create(20);
367
368         if (!allow) {
369                 stasis_http_response_alloc_failed(response);
370                 return;
371         }
372
373         /* Go ahead and build the ACA_METHODS header at the same time */
374         for (m = 0; m < AST_HTTP_MAX_METHOD; ++m) {
375                 if (handler->callbacks[m] != NULL) {
376                         char const *m_str = ast_get_http_method(m);
377                         if (strcmp(m_str, acr_method) == 0) {
378                                 allowed = 1;
379                         }
380                         ast_str_append(&allow, 0, ",%s", m_str);
381                 }
382         }
383
384         if (!allowed) {
385                 return;
386         }
387
388         /* CORS 6.2 #6 - "If any of the header field-names is not a ASCII
389          * case-insensitive match for any of the values in list of headers do
390          * not set any additional headers and terminate this set of steps.
391          *
392          * "Note: Always matching is acceptable since the list of headers can be
393          * unbounded."
394          */
395
396         /* CORS 6.2 #7 - "If the resource supports credentials add a single
397          * Access-Control-Allow-Origin header, with the value of the Origin
398          * header as value, and add a single Access-Control-Allow-Credentials
399          * header with the case-sensitive string "true" as value."
400          *
401          * Added by process_cors_request() earlier in the request.
402          */
403
404         /* CORS 6.2 #8 - "Optionally add a single Access-Control-Max-Age
405          * header..."
406          */
407
408         /* CORS 6.2 #9 - "Add one or more Access-Control-Allow-Methods headers
409          * consisting of (a subset of) the list of methods."
410          */
411         ast_str_append(&response->headers, 0, "%s: OPTIONS,%s\r\n",
412                        ACA_METHODS, ast_str_buffer(allow));
413
414
415         /* CORS 6.2, #10 - "Add one or more Access-Control-Allow-Headers headers
416          * consisting of (a subset of) the list of headers.
417          *
418          * "Since the list of headers can be unbounded simply returning headers
419          * can be enough."
420          */
421         if (!ast_strlen_zero(acr_headers)) {
422                 ast_str_append(&response->headers, 0, "%s: %s\r\n",
423                                ACA_HEADERS, acr_headers);
424         }
425 }
426
427 void stasis_http_invoke(struct ast_tcptls_session_instance *ser,
428         const char *uri, enum ast_http_method method,
429         struct ast_variable *get_params, struct ast_variable *headers,
430         struct stasis_http_response *response)
431 {
432         RAII_VAR(char *, response_text, NULL, ast_free);
433         RAII_VAR(struct stasis_rest_handlers *, root, NULL, ao2_cleanup);
434         struct stasis_rest_handlers *handler;
435         struct ast_variable *path_vars = NULL;
436         char *path = ast_strdupa(uri);
437         char *path_segment;
438         stasis_rest_callback callback;
439
440         root = handler = get_root_handler();
441         ast_assert(root != NULL);
442
443         while ((path_segment = strsep(&path, "/")) && (strlen(path_segment) > 0)) {
444                 struct stasis_rest_handlers *found_handler = NULL;
445                 int i;
446                 ast_uri_decode(path_segment, ast_uri_http_legacy);
447                 ast_debug(3, "Finding handler for %s\n", path_segment);
448                 for (i = 0; found_handler == NULL && i < handler->num_children; ++i) {
449                         struct stasis_rest_handlers *child = handler->children[i];
450
451                         ast_debug(3, "  Checking %s\n", child->path_segment);
452                         if (child->is_wildcard) {
453                                 /* Record the path variable */
454                                 struct ast_variable *path_var = ast_variable_new(child->path_segment, path_segment, __FILE__);
455                                 path_var->next = path_vars;
456                                 path_vars = path_var;
457                                 found_handler = child;
458                         } else if (strcmp(child->path_segment, path_segment) == 0) {
459                                 found_handler = child;
460                         }
461                 }
462
463                 if (found_handler == NULL) {
464                         /* resource not found */
465                         ast_debug(3, "  Handler not found\n");
466                         stasis_http_response_error(
467                                 response, 404, "Not Found",
468                                 "Resource not found");
469                         return;
470                 } else {
471                         ast_debug(3, "  Got it!\n");
472                         handler = found_handler;
473                 }
474         }
475
476         ast_assert(handler != NULL);
477         if (method == AST_HTTP_OPTIONS) {
478                 handle_options(handler, headers, response);
479                 return;
480         }
481
482         if (method < 0 || method >= AST_HTTP_MAX_METHOD) {
483                 add_allow_header(handler, response);
484                 stasis_http_response_error(
485                         response, 405, "Method Not Allowed",
486                         "Invalid method");
487                 return;
488         }
489
490         if (handler->ws_server && method == AST_HTTP_GET) {
491                 /* WebSocket! */
492                 struct ast_http_uri fake_urih = {
493                         .data = handler->ws_server,
494                 };
495                 ast_websocket_uri_cb(ser, &fake_urih, uri, method, get_params,
496                         headers);
497                 /* Since the WebSocket code handles the connection, we shouldn't
498                  * do anything else; setting no_response */
499                 response->no_response = 1;
500                 return;
501         }
502
503         callback = handler->callbacks[method];
504         if (callback == NULL) {
505                 add_allow_header(handler, response);
506                 stasis_http_response_error(
507                         response, 405, "Method Not Allowed",
508                         "Invalid method");
509                 return;
510         }
511
512         callback(get_params, path_vars, headers, response);
513         if (response->message == NULL && response->response_code == 0) {
514                 /* Really should not happen */
515                 ast_assert(0);
516                 stasis_http_response_error(
517                         response, 418, "I'm a teapot",
518                         "Method not implemented");
519         }
520 }
521
522 void stasis_http_get_docs(const char *uri, struct ast_variable *headers,
523                           struct stasis_http_response *response)
524 {
525         RAII_VAR(struct ast_str *, absolute_path_builder, NULL, ast_free);
526         RAII_VAR(char *, absolute_api_dirname, NULL, free);
527         RAII_VAR(char *, absolute_filename, NULL, free);
528         struct ast_json *obj = NULL;
529         struct ast_variable *host = NULL;
530         struct ast_json_error error = {};
531         struct stat file_stat;
532
533         ast_debug(3, "%s(%s)\n", __func__, uri);
534
535         absolute_path_builder = ast_str_create(80);
536         if (absolute_path_builder == NULL) {
537                 stasis_http_response_alloc_failed(response);
538                 return;
539         }
540
541         /* absolute path to the rest-api directory */
542         ast_str_append(&absolute_path_builder, 0, "%s", ast_config_AST_DATA_DIR);
543         ast_str_append(&absolute_path_builder, 0, "/rest-api/");
544         absolute_api_dirname = realpath(ast_str_buffer(absolute_path_builder), NULL);
545         if (absolute_api_dirname == NULL) {
546                 ast_log(LOG_ERROR, "Error determining real directory for rest-api\n");
547                 stasis_http_response_error(
548                         response, 500, "Internal Server Error",
549                         "Cannot find rest-api directory");
550                 return;
551         }
552
553         /* absolute path to the requested file */
554         ast_str_append(&absolute_path_builder, 0, "%s", uri);
555         absolute_filename = realpath(ast_str_buffer(absolute_path_builder), NULL);
556         if (absolute_filename == NULL) {
557                 switch (errno) {
558                 case ENAMETOOLONG:
559                 case ENOENT:
560                 case ENOTDIR:
561                         stasis_http_response_error(
562                                 response, 404, "Not Found",
563                                 "Resource not found");
564                         break;
565                 case EACCES:
566                         stasis_http_response_error(
567                                 response, 403, "Forbidden",
568                                 "Permission denied");
569                         break;
570                 default:
571                         ast_log(LOG_ERROR,
572                                 "Error determining real path for uri '%s': %s\n",
573                                 uri, strerror(errno));
574                         stasis_http_response_error(
575                                 response, 500, "Internal Server Error",
576                                 "Cannot find file");
577                         break;
578                 }
579                 return;
580         }
581
582         if (!ast_begins_with(absolute_filename, absolute_api_dirname)) {
583                 /* HACKERZ! */
584                 ast_log(LOG_ERROR,
585                         "Invalid attempt to access '%s' (not in %s)\n",
586                         absolute_filename, absolute_api_dirname);
587                 stasis_http_response_error(
588                         response, 404, "Not Found",
589                         "Resource not found");
590                 return;
591         }
592
593         if (stat(absolute_filename, &file_stat) == 0) {
594                 if (!(file_stat.st_mode & S_IFREG)) {
595                         /* Not a file */
596                         stasis_http_response_error(
597                                 response, 403, "Forbidden",
598                                 "Invalid access");
599                         return;
600                 }
601         } else {
602                 /* Does not exist */
603                 stasis_http_response_error(
604                         response, 404, "Not Found",
605                         "Resource not found");
606                 return;
607         }
608
609         /* Load resource object from file */
610         obj = ast_json_load_new_file(absolute_filename, &error);
611         if (obj == NULL) {
612                 ast_log(LOG_ERROR, "Error parsing resource file: %s:%d(%d) %s\n",
613                         error.source, error.line, error.column, error.text);
614                 stasis_http_response_error(
615                         response, 500, "Internal Server Error",
616                         "Yikes! Cannot parse resource");
617                 return;
618         }
619
620         /* Update the basePath properly */
621         if (ast_json_object_get(obj, "basePath") != NULL) {
622                 for (host = headers; host; host = host->next) {
623                         if (strcasecmp(host->name, "Host") == 0) {
624                                 break;
625                         }
626                 }
627                 if (host != NULL) {
628                         ast_json_object_set(
629                                 obj, "basePath",
630                                 ast_json_stringf("http://%s/ari", host->value));
631                 } else {
632                         /* Without the host, we don't have the basePath */
633                         ast_json_object_del(obj, "basePath");
634                 }
635         }
636
637         stasis_http_response_ok(response, obj);
638 }
639
640 static void remove_trailing_slash(const char *uri,
641                                   struct stasis_http_response *response)
642 {
643         char *slashless = ast_strdupa(uri);
644         slashless[strlen(slashless) - 1] = '\0';
645
646         /* While it's tempting to redirect the client to the slashless URL,
647          * that is problematic. A 302 Found is the most appropriate response,
648          * but most clients issue a GET on the location you give them,
649          * regardless of the method of the original request.
650          *
651          * While there are some ways around this, it gets into a lot of client
652          * specific behavior and corner cases in the HTTP standard. There's also
653          * very little practical benefit of redirecting; only GET and HEAD can
654          * be redirected automagically; all other requests "MUST NOT
655          * automatically redirect the request unless it can be confirmed by the
656          * user, since this might change the conditions under which the request
657          * was issued."
658          *
659          * Given all of that, a 404 with a nice message telling them what to do
660          * is probably our best bet.
661          */
662         stasis_http_response_error(response, 404, "Not Found",
663                 "ARI URLs do not end with a slash. Try /ari/%s", slashless);
664 }
665
666 /*!
667  * \brief Handle CORS headers for simple requests.
668  *
669  * See http://www.w3.org/TR/cors/ for the spec. Especially section 6.1.
670  */
671 static void process_cors_request(struct ast_variable *headers,
672                                  struct stasis_http_response *response)
673 {
674         char const *origin = NULL;
675         struct ast_variable *header;
676
677         /* Parse CORS headers */
678         for (header = headers; header != NULL; header = header->next) {
679                 if (strcmp("Origin", header->name) == 0) {
680                         origin = header->value;
681                 }
682         }
683
684         /* CORS 6.1, #1 - "If the Origin header is not present terminate this
685          * set of steps."
686          */
687         if (origin == NULL) {
688                 return;
689         }
690
691         /* CORS 6.1, #2 - "If the value of the Origin header is not a
692          * case-sensitive match for any of the values in list of origins, do not
693          * set any additional headers and terminate this set of steps.
694          *
695          * "Note: Always matching is acceptable since the list of origins can be
696          * unbounded."
697          *
698          * TODO - pull list of allowed origins from config
699          */
700
701         /* CORS 6.1, #3 - "If the resource supports credentials add a single
702          * Access-Control-Allow-Origin header, with the value of the Origin
703          * header as value, and add a single Access-Control-Allow-Credentials
704          * header with the case-sensitive string "true" as value.
705          *
706          * "Otherwise, add a single Access-Control-Allow-Origin header, with
707          * either the value of the Origin header or the string "*" as value."
708          *
709          * TODO - when we add authentication, this will change to
710          * Access-Control-Allow-Credentials.
711          */
712         ast_str_append(&response->headers, 0,
713                        "Access-Control-Allow-Origin: %s\r\n", origin);
714
715         /* CORS 6.1, #4 - "If the list of exposed headers is not empty add one
716          * or more Access-Control-Expose-Headers headers, with as values the
717          * header field names given in the list of exposed headers."
718          *
719          * No exposed headers; skipping
720          */
721 }
722
723 enum ast_json_encoding_format stasis_http_json_format(void)
724 {
725         RAII_VAR(struct ari_conf *, cfg, NULL, ao2_cleanup);
726         cfg = ari_config_get();
727         return cfg->general->format;
728 }
729
730 /*!
731  * \brief Authenticate a <code>?api_key=userid:password</code>
732  *
733  * \param api_key API key query parameter
734  * \return User object for the authenticated user.
735  * \return \c NULL if authentication failed.
736  */
737 static struct ari_conf_user *authenticate_api_key(const char *api_key)
738 {
739         RAII_VAR(char *, copy, NULL, ast_free);
740         char *username;
741         char *password;
742
743         password = copy = ast_strdup(api_key);
744         if (!copy) {
745                 return NULL;
746         }
747
748         username = strsep(&password, ":");
749         if (!password) {
750                 ast_log(LOG_WARNING, "Invalid api_key\n");
751                 return NULL;
752         }
753
754         return ari_config_validate_user(username, password);
755 }
756
757 /*!
758  * \brief Authenticate an HTTP request.
759  *
760  * \param get_params GET parameters of the request.
761  * \param header HTTP headers.
762  * \return User object for the authenticated user.
763  * \return \c NULL if authentication failed.
764  */
765 static struct ari_conf_user *authenticate_user(struct ast_variable *get_params,
766         struct ast_variable *headers)
767 {
768         RAII_VAR(struct ast_http_auth *, http_auth, NULL, ao2_cleanup);
769         struct ast_variable *v;
770
771         /* HTTP Basic authentication */
772         http_auth = ast_http_get_auth(headers);
773         if (http_auth) {
774                 return ari_config_validate_user(http_auth->userid,
775                         http_auth->password);
776         }
777
778         /* ?api_key authentication */
779         for (v = get_params; v; v = v->next) {
780                 if (strcasecmp("api_key", v->name) == 0) {
781                         return authenticate_api_key(v->value);
782                 }
783         }
784
785         return NULL;
786 }
787
788 /*!
789  * \internal
790  * \brief Stasis HTTP handler.
791  *
792  * This handler takes the HTTP request and turns it into the appropriate
793  * RESTful request (conversion to JSON, routing, etc.)
794  *
795  * \param ser TCP session.
796  * \param urih URI handler.
797  * \param uri URI requested.
798  * \param method HTTP method.
799  * \param get_params HTTP \c GET params.
800  * \param headers HTTP headers.
801  */
802 static int stasis_http_callback(struct ast_tcptls_session_instance *ser,
803                                 const struct ast_http_uri *urih,
804                                 const char *uri,
805                                 enum ast_http_method method,
806                                 struct ast_variable *get_params,
807                                 struct ast_variable *headers)
808 {
809         RAII_VAR(struct ari_conf *, conf, NULL, ao2_cleanup);
810         RAII_VAR(struct ast_str *, response_headers, ast_str_create(40), ast_free);
811         RAII_VAR(struct ast_str *, response_body, ast_str_create(256), ast_free);
812         RAII_VAR(struct ari_conf_user *, user, NULL, ao2_cleanup);
813         struct stasis_http_response response = {};
814         int ret = 0;
815
816         if (!response_headers || !response_body) {
817                 return -1;
818         }
819
820         response.headers = ast_str_create(40);
821         if (!response.headers) {
822                 return -1;
823         }
824
825         conf = ari_config_get();
826         if (!conf || !conf->general) {
827                 return -1;
828         }
829
830         process_cors_request(headers, &response);
831
832         user = authenticate_user(get_params, headers);
833         if (!user) {
834                 /* Per RFC 2617, section 1.2: The 401 (Unauthorized) response
835                  * message is used by an origin server to challenge the
836                  * authorization of a user agent. This response MUST include a
837                  * WWW-Authenticate header field containing at least one
838                  * challenge applicable to the requested resource.
839                  */
840                 response.response_code = 401;
841                 response.response_text = "Unauthorized";
842
843                 /* Section 1.2:
844                  *   realm       = "realm" "=" realm-value
845                  *   realm-value = quoted-string
846                  * Section 2:
847                  *   challenge   = "Basic" realm
848                  */
849                 ast_str_append(&response.headers, 0,
850                         "WWW-Authenticate: Basic realm=\"%s\"\r\n",
851                         conf->general->auth_realm);
852                 response.message = ast_json_pack("{s: s}",
853                         "error", "Authentication required");
854         } else if (user->read_only && method != AST_HTTP_GET && method != AST_HTTP_OPTIONS) {
855                 response.message = ast_json_pack("{s: s}",
856                         "error", "Write access denied");
857                 response.response_code = 403;
858                 response.response_text = "Forbidden";
859         } else if (ast_ends_with(uri, "/")) {
860                 remove_trailing_slash(uri, &response);
861         } else if (ast_begins_with(uri, "api-docs/")) {
862                 /* Serving up API docs */
863                 if (method != AST_HTTP_GET) {
864                         response.message =
865                                 ast_json_pack("{s: s}",
866                                               "message", "Unsupported method");
867                         response.response_code = 405;
868                         response.response_text = "Method Not Allowed";
869                 } else {
870                         /* Skip the api-docs prefix */
871                         stasis_http_get_docs(strchr(uri, '/') + 1, headers, &response);
872                 }
873         } else {
874                 /* Other RESTful resources */
875                 stasis_http_invoke(ser, uri, method, get_params, headers,
876                         &response);
877         }
878
879         if (response.no_response) {
880                 /* The handler indicates no further response is necessary.
881                  * Probably because it already handled it */
882                 return 0;
883         }
884
885         /* If you explicitly want to have no content, set message to
886          * ast_json_null().
887          */
888         ast_assert(response.message != NULL);
889         ast_assert(response.response_code > 0);
890
891         ast_str_append(&response_headers, 0, "%s", ast_str_buffer(response.headers));
892
893         /* response.message could be NULL, in which case the empty response_body
894          * is correct
895          */
896         if (response.message && !ast_json_is_null(response.message)) {
897                 ast_str_append(&response_headers, 0,
898                                "Content-type: application/json\r\n");
899                 if (ast_json_dump_str_format(response.message, &response_body,
900                                 conf->general->format) != 0) {
901                         /* Error encoding response */
902                         response.response_code = 500;
903                         response.response_text = "Internal Server Error";
904                         ast_str_set(&response_body, 0, "%s", "");
905                         ast_str_set(&response_headers, 0, "%s", "");
906                         ret = -1;
907                 }
908         }
909
910         ast_http_send(ser, method, response.response_code,
911                       response.response_text, response_headers, response_body,
912                       0, 0);
913         /* ast_http_send takes ownership, so we don't have to free them */
914         response_headers = NULL;
915         response_body = NULL;
916
917         ast_json_unref(response.message);
918         return ret;
919 }
920
921 static struct ast_http_uri http_uri = {
922         .callback = stasis_http_callback,
923         .description = "Asterisk RESTful API",
924         .uri = "ari",
925
926         .has_subtree = 1,
927         .data = NULL,
928         .key = __FILE__,
929         .no_decode_uri = 1,
930 };
931
932 static int load_module(void)
933 {
934         ast_mutex_init(&root_handler_lock);
935
936         /* root_handler may have been built during a declined load */
937         if (!root_handler) {
938                 root_handler = root_handler_create();
939         }
940         if (!root_handler) {
941                 return AST_MODULE_LOAD_FAILURE;
942         }
943
944         /* oom_json may have been built during a declined load */
945         if (!oom_json) {
946                 oom_json = ast_json_pack(
947                         "{s: s}", "error", "Allocation failed");
948         }
949         if (!oom_json) {
950                 /* Ironic */
951                 return AST_MODULE_LOAD_FAILURE;
952         }
953
954         if (ari_config_init() != 0) {
955                 return AST_MODULE_LOAD_DECLINE;
956         }
957
958         if (is_enabled()) {
959                 ast_debug(3, "ARI enabled\n");
960                 ast_http_uri_link(&http_uri);
961         } else {
962                 ast_debug(3, "ARI disabled\n");
963         }
964
965         if (ari_cli_register() != 0) {
966                 return AST_MODULE_LOAD_FAILURE;
967         }
968
969         return AST_MODULE_LOAD_SUCCESS;
970 }
971
972 static int unload_module(void)
973 {
974         ari_cli_unregister();
975
976         if (is_enabled()) {
977                 ast_debug(3, "Disabling ARI\n");
978                 ast_http_uri_unlink(&http_uri);
979         }
980
981         ari_config_destroy();
982
983         ao2_cleanup(root_handler);
984         root_handler = NULL;
985         ast_mutex_destroy(&root_handler_lock);
986
987         ast_json_unref(oom_json);
988         oom_json = NULL;
989
990         return 0;
991 }
992
993 static int reload_module(void)
994 {
995         char was_enabled = is_enabled();
996
997         if (ari_config_reload() != 0) {
998                 return AST_MODULE_LOAD_DECLINE;
999         }
1000
1001         if (was_enabled && !is_enabled()) {
1002                 ast_debug(3, "Disabling ARI\n");
1003                 ast_http_uri_unlink(&http_uri);
1004         } else if (!was_enabled && is_enabled()) {
1005                 ast_debug(3, "Enabling ARI\n");
1006                 ast_http_uri_link(&http_uri);
1007         }
1008
1009         return AST_MODULE_LOAD_SUCCESS;
1010 }
1011
1012 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Asterisk RESTful Interface",
1013         .load = load_module,
1014         .unload = unload_module,
1015         .reload = reload_module,
1016         .nonoptreq = "res_stasis,res_http_websocket",
1017         .load_pri = AST_MODPRI_APP_DEPEND,
1018         );