More public API name changes to use an appropriate ast_ prefix
[asterisk-bristuff.git] / main / http.c
blob11db1779c87d969af55a08010ade9ce31190690c
1 /*
2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, Digium, Inc.
6 * Mark Spencer <markster@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.
19 /*!
20 * \file
21 * \brief http server for AMI access
23 * \author Mark Spencer <markster@digium.com>
25 * This program implements a tiny http server
26 * and was inspired by micro-httpd by Jef Poskanzer
28 * \ref AstHTTP - AMI over the http protocol
31 #include "asterisk.h"
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
35 #include "asterisk/paths.h" /* use ast_config_AST_DATA_DIR */
36 #include "asterisk/network.h"
37 #include <time.h>
38 #include <sys/time.h>
39 #include <sys/stat.h>
40 #include <sys/signal.h>
41 #include <fcntl.h>
43 #include "minimime/mm.h"
45 #include "asterisk/cli.h"
46 #include "asterisk/tcptls.h"
47 #include "asterisk/http.h"
48 #include "asterisk/utils.h"
49 #include "asterisk/strings.h"
50 #include "asterisk/config.h"
51 #include "asterisk/stringfields.h"
52 #include "asterisk/ast_version.h"
53 #include "asterisk/manager.h"
55 #define MAX_PREFIX 80
56 #define DEFAULT_PREFIX "/asterisk"
58 /* See http.h for more information about the SSL implementation */
59 #if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE))
60 #define DO_SSL /* comment in/out if you want to support ssl */
61 #endif
63 static struct ast_tls_config http_tls_cfg;
65 static void *httpd_helper_thread(void *arg);
67 /*!
68 * we have up to two accepting threads, one for http, one for https
70 static struct server_args http_desc = {
71 .accept_fd = -1,
72 .master = AST_PTHREADT_NULL,
73 .tls_cfg = NULL,
74 .poll_timeout = -1,
75 .name = "http server",
76 .accept_fn = ast_tcptls_server_root,
77 .worker_fn = httpd_helper_thread,
80 static struct server_args https_desc = {
81 .accept_fd = -1,
82 .master = AST_PTHREADT_NULL,
83 .tls_cfg = &http_tls_cfg,
84 .poll_timeout = -1,
85 .name = "https server",
86 .accept_fn = ast_tcptls_server_root,
87 .worker_fn = httpd_helper_thread,
90 static AST_RWLIST_HEAD_STATIC(uris, ast_http_uri); /*!< list of supported handlers */
92 struct ast_http_post_mapping {
93 AST_RWLIST_ENTRY(ast_http_post_mapping) entry;
94 char *from;
95 char *to;
98 static AST_RWLIST_HEAD_STATIC(post_mappings, ast_http_post_mapping);
100 /* all valid URIs must be prepended by the string in prefix. */
101 static char prefix[MAX_PREFIX];
102 static int enablestatic;
104 /*! \brief Limit the kinds of files we're willing to serve up */
105 static struct {
106 const char *ext;
107 const char *mtype;
108 } mimetypes[] = {
109 { "png", "image/png" },
110 { "jpg", "image/jpeg" },
111 { "js", "application/x-javascript" },
112 { "wav", "audio/x-wav" },
113 { "mp3", "audio/mpeg" },
114 { "svg", "image/svg+xml" },
115 { "svgz", "image/svg+xml" },
116 { "gif", "image/gif" },
119 struct http_uri_redirect {
120 AST_LIST_ENTRY(http_uri_redirect) entry;
121 char *dest;
122 char target[0];
125 static AST_RWLIST_HEAD_STATIC(uri_redirects, http_uri_redirect);
127 static const char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
129 int x;
130 if (ftype) {
131 for (x=0;x<sizeof(mimetypes) / sizeof(mimetypes[0]); x++) {
132 if (!strcasecmp(ftype, mimetypes[x].ext))
133 return mimetypes[x].mtype;
136 snprintf(wkspace, wkspacelen, "text/%s", ftype ? ftype : "plain");
137 return wkspace;
140 static struct ast_str *static_callback(struct ast_tcptls_server_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
142 char *path;
143 char *ftype;
144 const char *mtype;
145 char wkspace[80];
146 struct stat st;
147 int len;
148 int fd;
149 struct timeval tv = ast_tvnow();
150 char buf[256];
151 struct ast_tm tm;
153 /* Yuck. I'm not really sold on this, but if you don't deliver static content it makes your configuration
154 substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
155 if (!enablestatic || ast_strlen_zero(uri))
156 goto out403;
157 /* Disallow any funny filenames at all */
158 if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0]))
159 goto out403;
160 if (strstr(uri, "/.."))
161 goto out403;
163 if ((ftype = strrchr(uri, '.')))
164 ftype++;
165 mtype = ftype2mtype(ftype, wkspace, sizeof(wkspace));
167 /* Cap maximum length */
168 len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5;
169 if (len > 1024)
170 goto out403;
172 path = alloca(len);
173 sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
174 if (stat(path, &st))
175 goto out404;
176 if (S_ISDIR(st.st_mode))
177 goto out404;
178 fd = open(path, O_RDONLY);
179 if (fd < 0)
180 goto out403;
182 ast_strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
183 fprintf(ser->f, "HTTP/1.1 200 OK\r\n"
184 "Server: Asterisk/%s\r\n"
185 "Date: %s\r\n"
186 "Connection: close\r\n"
187 "Cache-Control: no-cache, no-store\r\n"
188 "Content-Length: %d\r\n"
189 "Content-type: %s\r\n\r\n",
190 ast_get_version(), buf, (int) st.st_size, mtype);
192 while ((len = read(fd, buf, sizeof(buf))) > 0)
193 fwrite(buf, 1, len, ser->f);
195 close(fd);
196 return NULL;
198 out404:
199 *status = 404;
200 *title = ast_strdup("Not Found");
201 return ast_http_error(404, "Not Found", NULL, "Nothing to see here. Move along.");
203 out403:
204 *status = 403;
205 *title = ast_strdup("Access Denied");
206 return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
210 static struct ast_str *httpstatus_callback(struct ast_tcptls_server_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
212 struct ast_str *out = ast_str_create(512);
213 struct ast_variable *v;
215 if (out == NULL)
216 return out;
218 ast_str_append(&out, 0,
219 "\r\n"
220 "<title>Asterisk HTTP Status</title>\r\n"
221 "<body bgcolor=\"#ffffff\">\r\n"
222 "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
223 "<h2>&nbsp;&nbsp;Asterisk&trade; HTTP Status</h2></td></tr>\r\n");
225 ast_str_append(&out, 0, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
226 ast_str_append(&out, 0, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
227 ast_inet_ntoa(http_desc.oldsin.sin_addr));
228 ast_str_append(&out, 0, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
229 ntohs(http_desc.oldsin.sin_port));
230 if (http_tls_cfg.enabled)
231 ast_str_append(&out, 0, "<tr><td><i>SSL Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
232 ntohs(https_desc.oldsin.sin_port));
233 ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
234 for (v = vars; v; v = v->next) {
235 if (strncasecmp(v->name, "cookie_", 7))
236 ast_str_append(&out, 0, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
238 ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
239 for (v = vars; v; v = v->next) {
240 if (!strncasecmp(v->name, "cookie_", 7))
241 ast_str_append(&out, 0, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
243 ast_str_append(&out, 0, "</table><center><font size=\"-1\"><i>Asterisk and Digium are registered trademarks of Digium, Inc.</i></font></center></body>\r\n");
244 return out;
247 static struct ast_http_uri statusuri = {
248 .callback = httpstatus_callback,
249 .description = "Asterisk HTTP General Status",
250 .uri = "httpstatus",
251 .has_subtree = 0,
254 static struct ast_http_uri staticuri = {
255 .callback = static_callback,
256 .description = "Asterisk HTTP Static Delivery",
257 .uri = "static",
258 .has_subtree = 1,
259 .static_content = 1,
262 struct ast_str *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
264 struct ast_str *out = ast_str_create(512);
265 if (out == NULL)
266 return out;
267 ast_str_set(&out, 0,
268 "Content-type: text/html\r\n"
269 "%s"
270 "\r\n"
271 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
272 "<html><head>\r\n"
273 "<title>%d %s</title>\r\n"
274 "</head><body>\r\n"
275 "<h1>%s</h1>\r\n"
276 "<p>%s</p>\r\n"
277 "<hr />\r\n"
278 "<address>Asterisk Server</address>\r\n"
279 "</body></html>\r\n",
280 (extra_header ? extra_header : ""), status, title, title, text);
281 return out;
284 /*! \brief
285 * Link the new uri into the list.
287 * They are sorted by length of
288 * the string, not alphabetically. Duplicate entries are not replaced,
289 * but the insertion order (using <= and not just <) makes sure that
290 * more recent insertions hide older ones.
291 * On a lookup, we just scan the list and stop at the first matching entry.
293 int ast_http_uri_link(struct ast_http_uri *urih)
295 struct ast_http_uri *uri;
296 int len = strlen(urih->uri);
298 AST_RWLIST_WRLOCK(&uris);
300 if ( AST_RWLIST_EMPTY(&uris) || strlen(AST_RWLIST_FIRST(&uris)->uri) <= len ) {
301 AST_RWLIST_INSERT_HEAD(&uris, urih, entry);
302 AST_RWLIST_UNLOCK(&uris);
303 return 0;
306 AST_RWLIST_TRAVERSE(&uris, uri, entry) {
307 if ( AST_RWLIST_NEXT(uri, entry)
308 && strlen(AST_RWLIST_NEXT(uri, entry)->uri) <= len ) {
309 AST_RWLIST_INSERT_AFTER(&uris, uri, urih, entry);
310 AST_RWLIST_UNLOCK(&uris);
311 return 0;
315 AST_RWLIST_INSERT_TAIL(&uris, urih, entry);
317 AST_RWLIST_UNLOCK(&uris);
319 return 0;
322 void ast_http_uri_unlink(struct ast_http_uri *urih)
324 AST_RWLIST_WRLOCK(&uris);
325 AST_RWLIST_REMOVE(&uris, urih, entry);
326 AST_RWLIST_UNLOCK(&uris);
329 /*! \note This assumes that the post_mappings list is locked */
330 static struct ast_http_post_mapping *find_post_mapping(const char *uri)
332 struct ast_http_post_mapping *post_map;
334 if (!ast_strlen_zero(prefix) && strncmp(prefix, uri, strlen(prefix))) {
335 ast_debug(1, "URI %s does not have prefix %s\n", uri, prefix);
336 return NULL;
339 uri += strlen(prefix);
340 if (*uri == '/')
341 uri++;
343 AST_RWLIST_TRAVERSE(&post_mappings, post_map, entry) {
344 if (!strcmp(uri, post_map->from))
345 return post_map;
348 return NULL;
351 static int get_filename(struct mm_mimepart *part, char *fn, size_t fn_len)
353 const char *filename;
355 filename = mm_content_getdispositionparambyname(part->type, "filename");
357 if (ast_strlen_zero(filename))
358 return -1;
360 ast_copy_string(fn, filename, fn_len);
362 return 0;
365 static void post_raw(struct mm_mimepart *part, const char *post_dir, const char *fn)
367 char filename[PATH_MAX];
368 FILE *f;
369 const char *body;
370 size_t body_len;
372 snprintf(filename, sizeof(filename), "%s/%s", post_dir, fn);
374 ast_debug(1, "Posting raw data to %s\n", filename);
376 if (!(f = fopen(filename, "w"))) {
377 ast_log(LOG_WARNING, "Unable to open %s for writing file from a POST!\n", filename);
378 return;
381 if (!(body = mm_mimepart_getbody(part, 0))) {
382 ast_debug(1, "Couldn't get the mimepart body\n");
383 fclose(f);
384 return;
386 body_len = mm_mimepart_getlength(part);
388 ast_debug(1, "Body length is %ld\n", (long int)body_len);
390 fwrite(body, 1, body_len, f);
392 fclose(f);
395 static struct ast_str *handle_post(struct ast_tcptls_server_instance *ser, char *uri,
396 int *status, char **title, int *contentlength, struct ast_variable *headers,
397 struct ast_variable *cookies)
399 char buf;
400 FILE *f;
401 size_t res;
402 struct ast_variable *var;
403 int content_len = 0;
404 MM_CTX *ctx;
405 int mm_res, i;
406 struct ast_http_post_mapping *post_map;
407 const char *post_dir;
408 unsigned long ident = 0;
410 for (var = cookies; var; var = var->next) {
411 if (strcasecmp(var->name, "mansession_id"))
412 continue;
414 if (sscanf(var->value, "%lx", &ident) != 1) {
415 *status = 400;
416 *title = ast_strdup("Bad Request");
417 return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
420 if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) {
421 *status = 401;
422 *title = ast_strdup("Unauthorized");
423 return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
426 break;
428 if (!var) {
429 *status = 401;
430 *title = ast_strdup("Unauthorized");
431 return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
434 if (!(f = tmpfile()))
435 return NULL;
437 for (var = headers; var; var = var->next) {
438 if (!strcasecmp(var->name, "Content-Length")) {
439 if ((sscanf(var->value, "%u", &content_len)) != 1) {
440 ast_log(LOG_ERROR, "Invalid Content-Length in POST request!\n");
441 fclose(f);
442 return NULL;
444 ast_debug(1, "Got a Content-Length of %d\n", content_len);
445 } else if (!strcasecmp(var->name, "Content-Type"))
446 fprintf(f, "Content-Type: %s\r\n\r\n", var->value);
449 while ((res = fread(&buf, 1, 1, ser->f))) {
450 fwrite(&buf, 1, 1, f);
451 content_len--;
452 if (!content_len)
453 break;
456 if (fseek(f, SEEK_SET, 0)) {
457 ast_debug(1, "Failed to seek temp file back to beginning.\n");
458 fclose(f);
459 return NULL;
462 AST_RWLIST_RDLOCK(&post_mappings);
463 if (!(post_map = find_post_mapping(uri))) {
464 ast_debug(1, "%s is not a valid URI for POST\n", uri);
465 AST_RWLIST_UNLOCK(&post_mappings);
466 fclose(f);
467 *status = 404;
468 *title = ast_strdup("Not Found");
469 return ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this server.");
471 post_dir = ast_strdupa(post_map->to);
472 post_map = NULL;
473 AST_RWLIST_UNLOCK(&post_mappings);
475 ast_debug(1, "Going to post files to dir %s\n", post_dir);
477 if (!(ctx = mm_context_new())) {
478 fclose(f);
479 return NULL;
482 mm_res = mm_parse_fileptr(ctx, f, MM_PARSE_LOOSE, 0);
483 fclose(f);
484 if (mm_res == -1) {
485 ast_log(LOG_ERROR, "Error parsing MIME data\n");
486 mm_context_free(ctx);
487 *status = 400;
488 *title = ast_strdup("Bad Request");
489 return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
492 mm_res = mm_context_countparts(ctx);
493 if (!mm_res) {
494 ast_log(LOG_ERROR, "Invalid MIME data, found no parts!\n");
495 mm_context_free(ctx);
496 *status = 400;
497 *title = ast_strdup("Bad Request");
498 return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
501 if (option_debug) {
502 if (mm_context_iscomposite(ctx))
503 ast_debug(1, "Found %d MIME parts\n", mm_res - 1);
504 else
505 ast_debug(1, "We have a flat (not multi-part) message\n");
508 for (i = 1; i < mm_res; i++) {
509 struct mm_mimepart *part;
510 char fn[PATH_MAX];
512 if (!(part = mm_context_getpart(ctx, i))) {
513 ast_debug(1, "Failed to get mime part num %d\n", i);
514 continue;
517 if (get_filename(part, fn, sizeof(fn))) {
518 ast_debug(1, "Failed to retrieve a filename for part num %d\n", i);
519 continue;
522 if (!part->type) {
523 ast_debug(1, "This part has no content struct?\n");
524 continue;
527 /* XXX This assumes the MIME part body is not encoded! */
528 post_raw(part, post_dir, fn);
531 mm_context_free(ctx);
533 *status = 200;
534 *title = ast_strdup("OK");
535 return ast_http_error(200, "OK", NULL, "File successfully uploaded.");
538 static struct ast_str *handle_uri(struct ast_tcptls_server_instance *ser, char *uri, int *status,
539 char **title, int *contentlength, struct ast_variable **cookies,
540 unsigned int *static_content)
542 char *c;
543 struct ast_str *out = NULL;
544 char *params = uri;
545 struct ast_http_uri *urih=NULL;
546 int l;
547 struct ast_variable *vars=NULL, *v, *prev = NULL;
548 struct http_uri_redirect *redirect;
550 strsep(&params, "?");
551 /* Extract arguments from the request and store them in variables. */
552 if (params) {
553 char *var, *val;
555 while ((val = strsep(&params, "&"))) {
556 var = strsep(&val, "=");
557 if (val)
558 ast_uri_decode(val);
559 else
560 val = "";
561 ast_uri_decode(var);
562 if ((v = ast_variable_new(var, val, ""))) {
563 if (vars)
564 prev->next = v;
565 else
566 vars = v;
567 prev = v;
572 * Append the cookies to the variables (the only reason to have them
573 * at the end is to avoid another pass of the cookies list to find
574 * the tail).
576 if (prev)
577 prev->next = *cookies;
578 else
579 vars = *cookies;
580 *cookies = NULL;
581 ast_uri_decode(uri);
583 AST_RWLIST_RDLOCK(&uri_redirects);
584 AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
585 if (!strcasecmp(uri, redirect->target)) {
586 char buf[512];
587 snprintf(buf, sizeof(buf), "Location: %s\r\n", redirect->dest);
588 out = ast_http_error(302, "Moved Temporarily", buf,
589 "There is no spoon...");
590 *status = 302;
591 *title = ast_strdup("Moved Temporarily");
592 break;
595 AST_RWLIST_UNLOCK(&uri_redirects);
596 if (redirect)
597 goto cleanup;
599 /* We want requests to start with the prefix and '/' */
600 l = strlen(prefix);
601 if (l && !strncasecmp(uri, prefix, l) && uri[l] == '/') {
602 uri += l + 1;
603 /* scan registered uris to see if we match one. */
604 AST_RWLIST_RDLOCK(&uris);
605 AST_RWLIST_TRAVERSE(&uris, urih, entry) {
606 l = strlen(urih->uri);
607 c = uri + l; /* candidate */
608 if (strncasecmp(urih->uri, uri, l) /* no match */
609 || (*c && *c != '/')) /* substring */
610 continue;
611 if (*c == '/')
612 c++;
613 if (!*c || urih->has_subtree) {
614 uri = c;
615 break;
618 if (!urih)
619 AST_RWLIST_UNLOCK(&uris);
621 if (urih) {
622 if (urih->static_content)
623 *static_content = 1;
624 out = urih->callback(ser, uri, vars, status, title, contentlength);
625 AST_RWLIST_UNLOCK(&uris);
626 } else {
627 out = ast_http_error(404, "Not Found", NULL,
628 "The requested URL was not found on this server.");
629 *status = 404;
630 *title = ast_strdup("Not Found");
633 cleanup:
634 ast_variables_destroy(vars);
635 return out;
638 #ifdef DO_SSL
639 #if defined(HAVE_FUNOPEN)
640 #define HOOK_T int
641 #define LEN_T int
642 #else
643 #define HOOK_T ssize_t
644 #define LEN_T size_t
645 #endif
647 * replacement read/write functions for SSL support.
648 * We use wrappers rather than SSL_read/SSL_write directly so
649 * we can put in some debugging.
651 /*static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
653 int i = SSL_read(cookie, buf, len-1);
654 #if 0
655 if (i >= 0)
656 buf[i] = '\0';
657 ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
658 #endif
659 return i;
662 static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
664 #if 0
665 char *s = alloca(len+1);
666 strncpy(s, buf, len);
667 s[len] = '\0';
668 ast_verbose("ssl write size %d <%s>\n", (int)len, s);
669 #endif
670 return SSL_write(cookie, buf, len);
673 static int ssl_close(void *cookie)
675 close(SSL_get_fd(cookie));
676 SSL_shutdown(cookie);
677 SSL_free(cookie);
678 return 0;
680 #endif /* DO_SSL */
682 static void *httpd_helper_thread(void *data)
684 char buf[4096];
685 char cookie[4096];
686 struct ast_tcptls_server_instance *ser = data;
687 struct ast_variable *var, *prev=NULL, *vars=NULL, *headers = NULL;
688 char *uri, *title=NULL;
689 int status = 200, contentlength = 0;
690 struct ast_str *out = NULL;
691 unsigned int static_content = 0;
693 if (!fgets(buf, sizeof(buf), ser->f))
694 goto done;
696 uri = ast_skip_nonblanks(buf); /* Skip method */
697 if (*uri)
698 *uri++ = '\0';
700 uri = ast_skip_blanks(uri); /* Skip white space */
702 if (*uri) { /* terminate at the first blank */
703 char *c = ast_skip_nonblanks(uri);
704 if (*c)
705 *c = '\0';
708 /* process "Cookie: " lines */
709 while (fgets(cookie, sizeof(cookie), ser->f)) {
710 char *vname, *vval;
711 int l;
713 /* Trim trailing characters */
714 ast_trim_blanks(cookie);
715 if (ast_strlen_zero(cookie))
716 break;
717 if (strncasecmp(cookie, "Cookie: ", 8)) {
718 char *name, *value;
720 value = ast_strdupa(cookie);
721 name = strsep(&value, ":");
722 if (!value)
723 continue;
724 value = ast_skip_blanks(value);
725 if (ast_strlen_zero(value))
726 continue;
727 var = ast_variable_new(name, value, "");
728 if (!var)
729 continue;
730 var->next = headers;
731 headers = var;
732 continue;
735 /* TODO - The cookie parsing code below seems to work
736 in IE6 and FireFox 1.5. However, it is not entirely
737 correct, and therefore may not work in all
738 circumstances.
739 For more details see RFC 2109 and RFC 2965 */
741 /* FireFox cookie strings look like:
742 Cookie: mansession_id="********"
743 InternetExplorer's look like:
744 Cookie: $Version="1"; mansession_id="********" */
746 /* If we got a FireFox cookie string, the name's right
747 after "Cookie: " */
748 vname = ast_skip_blanks(cookie + 8);
750 /* If we got an IE cookie string, we need to skip to
751 past the version to get to the name */
752 if (*vname == '$') {
753 strsep(&vname, ";");
754 if (!vname) /* no name ? */
755 continue;
756 vname = ast_skip_blanks(vname);
758 vval = strchr(vname, '=');
759 if (!vval)
760 continue;
761 /* Ditch the = and the quotes */
762 *vval++ = '\0';
763 if (*vval)
764 vval++;
765 if ( (l = strlen(vval)) )
766 vval[l - 1] = '\0'; /* trim trailing quote */
767 var = ast_variable_new(vname, vval, "");
768 if (var) {
769 if (prev)
770 prev->next = var;
771 else
772 vars = var;
773 prev = var;
777 if (!*uri)
778 out = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
779 else if (!strcasecmp(buf, "post"))
780 out = handle_post(ser, uri, &status, &title, &contentlength, headers, vars);
781 else if (strcasecmp(buf, "get"))
782 out = ast_http_error(501, "Not Implemented", NULL,
783 "Attempt to use unimplemented / unsupported method");
784 else /* try to serve it */
785 out = handle_uri(ser, uri, &status, &title, &contentlength, &vars, &static_content);
787 /* If they aren't mopped up already, clean up the cookies */
788 if (vars)
789 ast_variables_destroy(vars);
790 /* Clean up all the header information pulled as well */
791 if (headers)
792 ast_variables_destroy(headers);
794 if (out) {
795 struct timeval tv = ast_tvnow();
796 char timebuf[256];
797 struct ast_tm tm;
799 ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
800 fprintf(ser->f, "HTTP/1.1 %d %s\r\n"
801 "Server: Asterisk/%s\r\n"
802 "Date: %s\r\n"
803 "Connection: close\r\n"
804 "%s",
805 status, title ? title : "OK", ast_get_version(), timebuf,
806 static_content ? "" : "Cache-Control: no-cache, no-store\r\n");
807 /* We set the no-cache headers only for dynamic content.
808 * If you want to make sure the static file you requested is not from cache,
809 * append a random variable to your GET request. Ex: 'something.html?r=109987734'
811 if (!contentlength) { /* opaque body ? just dump it hoping it is properly formatted */
812 fprintf(ser->f, "%s", out->str);
813 } else {
814 char *tmp = strstr(out->str, "\r\n\r\n");
816 if (tmp) {
817 fprintf(ser->f, "Content-length: %d\r\n", contentlength);
818 /* first write the header, then the body */
819 fwrite(out->str, 1, (tmp + 4 - out->str), ser->f);
820 fwrite(tmp + 4, 1, contentlength, ser->f);
823 ast_free(out);
825 if (title)
826 ast_free(title);
828 done:
829 fclose(ser->f);
830 ast_free(ser);
831 return NULL;
835 * \brief Add a new URI redirect
836 * The entries in the redirect list are sorted by length, just like the list
837 * of URI handlers.
839 static void add_redirect(const char *value)
841 char *target, *dest;
842 struct http_uri_redirect *redirect, *cur;
843 unsigned int target_len;
844 unsigned int total_len;
846 dest = ast_strdupa(value);
847 dest = ast_skip_blanks(dest);
848 target = strsep(&dest, " ");
849 target = ast_skip_blanks(target);
850 target = strsep(&target, " "); /* trim trailing whitespace */
852 if (!dest) {
853 ast_log(LOG_WARNING, "Invalid redirect '%s'\n", value);
854 return;
857 target_len = strlen(target) + 1;
858 total_len = sizeof(*redirect) + target_len + strlen(dest) + 1;
860 if (!(redirect = ast_calloc(1, total_len)))
861 return;
863 redirect->dest = redirect->target + target_len;
864 strcpy(redirect->target, target);
865 strcpy(redirect->dest, dest);
867 AST_RWLIST_WRLOCK(&uri_redirects);
869 target_len--; /* So we can compare directly with strlen() */
870 if ( AST_RWLIST_EMPTY(&uri_redirects)
871 || strlen(AST_RWLIST_FIRST(&uri_redirects)->target) <= target_len ) {
872 AST_RWLIST_INSERT_HEAD(&uri_redirects, redirect, entry);
873 AST_RWLIST_UNLOCK(&uri_redirects);
874 return;
877 AST_RWLIST_TRAVERSE(&uri_redirects, cur, entry) {
878 if ( AST_RWLIST_NEXT(cur, entry)
879 && strlen(AST_RWLIST_NEXT(cur, entry)->target) <= target_len ) {
880 AST_RWLIST_INSERT_AFTER(&uri_redirects, cur, redirect, entry);
881 AST_RWLIST_UNLOCK(&uri_redirects);
882 return;
886 AST_RWLIST_INSERT_TAIL(&uri_redirects, redirect, entry);
888 AST_RWLIST_UNLOCK(&uri_redirects);
891 static void destroy_post_mapping(struct ast_http_post_mapping *post_map)
893 if (post_map->from)
894 ast_free(post_map->from);
895 if (post_map->to)
896 ast_free(post_map->to);
897 ast_free(post_map);
900 static void destroy_post_mappings(void)
902 struct ast_http_post_mapping *post_map;
904 AST_RWLIST_WRLOCK(&post_mappings);
905 while ((post_map = AST_RWLIST_REMOVE_HEAD(&post_mappings, entry)))
906 destroy_post_mapping(post_map);
907 AST_RWLIST_UNLOCK(&post_mappings);
910 static void add_post_mapping(const char *from, const char *to)
912 struct ast_http_post_mapping *post_map;
914 if (!(post_map = ast_calloc(1, sizeof(*post_map))))
915 return;
917 if (!(post_map->from = ast_strdup(from))) {
918 destroy_post_mapping(post_map);
919 return;
922 if (!(post_map->to = ast_strdup(to))) {
923 destroy_post_mapping(post_map);
924 return;
927 AST_RWLIST_WRLOCK(&post_mappings);
928 AST_RWLIST_INSERT_TAIL(&post_mappings, post_map, entry);
929 AST_RWLIST_UNLOCK(&post_mappings);
932 static int __ast_http_load(int reload)
934 struct ast_config *cfg;
935 struct ast_variable *v;
936 int enabled=0;
937 int newenablestatic=0;
938 struct hostent *hp;
939 struct ast_hostent ahp;
940 char newprefix[MAX_PREFIX];
941 int have_sslbindaddr = 0;
942 struct http_uri_redirect *redirect;
943 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
945 if ((cfg = ast_config_load("http.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED)
946 return 0;
948 /* default values */
949 memset(&http_desc.sin, 0, sizeof(http_desc.sin));
950 http_desc.sin.sin_port = htons(8088);
952 memset(&https_desc.sin, 0, sizeof(https_desc.sin));
953 https_desc.sin.sin_port = htons(8089);
955 strcpy(newprefix, DEFAULT_PREFIX);
957 http_tls_cfg.enabled = 0;
958 if (http_tls_cfg.certfile)
959 ast_free(http_tls_cfg.certfile);
960 http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
961 if (http_tls_cfg.cipher)
962 ast_free(http_tls_cfg.cipher);
963 http_tls_cfg.cipher = ast_strdup("");
965 AST_RWLIST_WRLOCK(&uri_redirects);
966 while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry)))
967 ast_free(redirect);
968 AST_RWLIST_UNLOCK(&uri_redirects);
970 destroy_post_mappings();
972 if (cfg) {
973 v = ast_variable_browse(cfg, "general");
974 for (; v; v = v->next) {
975 if (!strcasecmp(v->name, "enabled"))
976 enabled = ast_true(v->value);
977 else if (!strcasecmp(v->name, "sslenable"))
978 http_tls_cfg.enabled = ast_true(v->value);
979 else if (!strcasecmp(v->name, "sslbindport"))
980 https_desc.sin.sin_port = htons(atoi(v->value));
981 else if (!strcasecmp(v->name, "sslcert")) {
982 ast_free(http_tls_cfg.certfile);
983 http_tls_cfg.certfile = ast_strdup(v->value);
984 } else if (!strcasecmp(v->name, "sslcipher")) {
985 ast_free(http_tls_cfg.cipher);
986 http_tls_cfg.cipher = ast_strdup(v->value);
988 else if (!strcasecmp(v->name, "enablestatic"))
989 newenablestatic = ast_true(v->value);
990 else if (!strcasecmp(v->name, "bindport"))
991 http_desc.sin.sin_port = htons(atoi(v->value));
992 else if (!strcasecmp(v->name, "sslbindaddr")) {
993 if ((hp = ast_gethostbyname(v->value, &ahp))) {
994 memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
995 have_sslbindaddr = 1;
996 } else {
997 ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
999 } else if (!strcasecmp(v->name, "bindaddr")) {
1000 if ((hp = ast_gethostbyname(v->value, &ahp))) {
1001 memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
1002 } else {
1003 ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
1005 } else if (!strcasecmp(v->name, "prefix")) {
1006 if (!ast_strlen_zero(v->value)) {
1007 newprefix[0] = '/';
1008 ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
1009 } else {
1010 newprefix[0] = '\0';
1012 } else if (!strcasecmp(v->name, "redirect")) {
1013 add_redirect(v->value);
1014 } else {
1015 ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
1019 for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next)
1020 add_post_mapping(v->name, v->value);
1022 ast_config_destroy(cfg);
1024 if (!have_sslbindaddr)
1025 https_desc.sin.sin_addr = http_desc.sin.sin_addr;
1026 if (enabled)
1027 http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
1028 if (strcmp(prefix, newprefix))
1029 ast_copy_string(prefix, newprefix, sizeof(prefix));
1030 enablestatic = newenablestatic;
1031 ast_tcptls_server_start(&http_desc);
1032 if (ast_ssl_setup(https_desc.tls_cfg))
1033 ast_tcptls_server_start(&https_desc);
1035 return 0;
1038 static char *handle_show_http(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1040 struct ast_http_uri *urih;
1041 struct http_uri_redirect *redirect;
1042 struct ast_http_post_mapping *post_map;
1043 switch (cmd) {
1044 case CLI_INIT:
1045 e->command = "http show status";
1046 e->usage =
1047 "Usage: http show status\n"
1048 " Lists status of internal HTTP engine\n";
1049 return NULL;
1050 case CLI_GENERATE:
1051 return NULL;
1054 if (a->argc != 3)
1055 return CLI_SHOWUSAGE;
1056 ast_cli(a->fd, "HTTP Server Status:\n");
1057 ast_cli(a->fd, "Prefix: %s\n", prefix);
1058 if (!http_desc.oldsin.sin_family)
1059 ast_cli(a->fd, "Server Disabled\n\n");
1060 else {
1061 ast_cli(a->fd, "Server Enabled and Bound to %s:%d\n\n",
1062 ast_inet_ntoa(http_desc.oldsin.sin_addr),
1063 ntohs(http_desc.oldsin.sin_port));
1064 if (http_tls_cfg.enabled)
1065 ast_cli(a->fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
1066 ast_inet_ntoa(https_desc.oldsin.sin_addr),
1067 ntohs(https_desc.oldsin.sin_port));
1070 ast_cli(a->fd, "Enabled URI's:\n");
1071 AST_RWLIST_RDLOCK(&uris);
1072 if (AST_RWLIST_EMPTY(&uris)) {
1073 ast_cli(a->fd, "None.\n");
1074 } else {
1075 AST_RWLIST_TRAVERSE(&uris, urih, entry)
1076 ast_cli(a->fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
1078 AST_RWLIST_UNLOCK(&uris);
1080 ast_cli(a->fd, "\nEnabled Redirects:\n");
1081 AST_RWLIST_RDLOCK(&uri_redirects);
1082 AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry)
1083 ast_cli(a->fd, " %s => %s\n", redirect->target, redirect->dest);
1084 if (AST_RWLIST_EMPTY(&uri_redirects))
1085 ast_cli(a->fd, " None.\n");
1086 AST_RWLIST_UNLOCK(&uri_redirects);
1089 ast_cli(a->fd, "\nPOST mappings:\n");
1090 AST_RWLIST_RDLOCK(&post_mappings);
1091 AST_LIST_TRAVERSE(&post_mappings, post_map, entry)
1092 ast_cli(a->fd, "%s/%s => %s\n", prefix, post_map->from, post_map->to);
1093 ast_cli(a->fd, "%s\n", AST_LIST_EMPTY(&post_mappings) ? "None.\n" : "");
1094 AST_RWLIST_UNLOCK(&post_mappings);
1096 return CLI_SUCCESS;
1099 int ast_http_reload(void)
1101 return __ast_http_load(1);
1104 static struct ast_cli_entry cli_http[] = {
1105 AST_CLI_DEFINE(handle_show_http, "Display HTTP server status"),
1108 int ast_http_init(void)
1110 mm_library_init();
1111 mm_codec_registerdefaultcodecs();
1113 ast_http_uri_link(&statusuri);
1114 ast_http_uri_link(&staticuri);
1115 ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
1117 return __ast_http_load(0);