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.
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
33 ASTERISK_FILE_VERSION(__FILE__
, "$Revision$")
35 #include "asterisk/paths.h" /* use ast_config_AST_DATA_DIR */
36 #include "asterisk/network.h"
40 #include <sys/signal.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"
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 */
63 static struct ast_tls_config http_tls_cfg
;
65 static void *httpd_helper_thread(void *arg
);
68 * we have up to two accepting threads, one for http, one for https
70 static struct server_args http_desc
= {
72 .master
= AST_PTHREADT_NULL
,
75 .name
= "http server",
76 .accept_fn
= ast_tcptls_server_root
,
77 .worker_fn
= httpd_helper_thread
,
80 static struct server_args https_desc
= {
82 .master
= AST_PTHREADT_NULL
,
83 .tls_cfg
= &http_tls_cfg
,
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
;
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 */
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
;
125 static AST_RWLIST_HEAD_STATIC(uri_redirects
, http_uri_redirect
);
127 static const char *ftype2mtype(const char *ftype
, char *wkspace
, int wkspacelen
)
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");
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
)
149 struct timeval tv
= ast_tvnow();
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
))
157 /* Disallow any funny filenames at all */
158 if ((uri
[0] < 33) || strchr("./|~@#$%^&*() \t", uri
[0]))
160 if (strstr(uri
, "/.."))
163 if ((ftype
= strrchr(uri
, '.')))
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;
173 sprintf(path
, "%s/static-http/%s", ast_config_AST_DATA_DIR
, uri
);
176 if (S_ISDIR(st
.st_mode
))
178 fd
= open(path
, O_RDONLY
);
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"
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
);
200 *title
= ast_strdup("Not Found");
201 return ast_http_error(404, "Not Found", NULL
, "Nothing to see here. Move along.");
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
;
218 ast_str_append(&out
, 0,
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> Asterisk™ 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");
247 static struct ast_http_uri statusuri
= {
248 .callback
= httpstatus_callback
,
249 .description
= "Asterisk HTTP General Status",
254 static struct ast_http_uri staticuri
= {
255 .callback
= static_callback
,
256 .description
= "Asterisk HTTP Static Delivery",
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);
268 "Content-type: text/html\r\n"
271 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
273 "<title>%d %s</title>\r\n"
278 "<address>Asterisk Server</address>\r\n"
279 "</body></html>\r\n",
280 (extra_header
? extra_header
: ""), status
, title
, title
, text
);
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
);
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
);
315 AST_RWLIST_INSERT_TAIL(&uris
, urih
, entry
);
317 AST_RWLIST_UNLOCK(&uris
);
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
);
339 uri
+= strlen(prefix
);
343 AST_RWLIST_TRAVERSE(&post_mappings
, post_map
, entry
) {
344 if (!strcmp(uri
, post_map
->from
))
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
))
360 ast_copy_string(fn
, filename
, fn_len
);
365 static void post_raw(struct mm_mimepart
*part
, const char *post_dir
, const char *fn
)
367 char filename
[PATH_MAX
];
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
);
381 if (!(body
= mm_mimepart_getbody(part
, 0))) {
382 ast_debug(1, "Couldn't get the mimepart body\n");
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
);
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
)
402 struct ast_variable
*var
;
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"))
414 if (sscanf(var
->value
, "%lx", &ident
) != 1) {
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
)) {
422 *title
= ast_strdup("Unauthorized");
423 return ast_http_error(401, "Unauthorized", NULL
, "You are not authorized to make this request.");
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()))
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");
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
);
456 if (fseek(f
, SEEK_SET
, 0)) {
457 ast_debug(1, "Failed to seek temp file back to beginning.\n");
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
);
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
);
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())) {
482 mm_res
= mm_parse_fileptr(ctx
, f
, MM_PARSE_LOOSE
, 0);
485 ast_log(LOG_ERROR
, "Error parsing MIME data\n");
486 mm_context_free(ctx
);
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
);
494 ast_log(LOG_ERROR
, "Invalid MIME data, found no parts!\n");
495 mm_context_free(ctx
);
497 *title
= ast_strdup("Bad Request");
498 return ast_http_error(400, "Bad Request", NULL
, "The was an error parsing the request.");
502 if (mm_context_iscomposite(ctx
))
503 ast_debug(1, "Found %d MIME parts\n", mm_res
- 1);
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
;
512 if (!(part
= mm_context_getpart(ctx
, i
))) {
513 ast_debug(1, "Failed to get mime part num %d\n", i
);
517 if (get_filename(part
, fn
, sizeof(fn
))) {
518 ast_debug(1, "Failed to retrieve a filename for part num %d\n", i
);
523 ast_debug(1, "This part has no content struct?\n");
527 /* XXX This assumes the MIME part body is not encoded! */
528 post_raw(part
, post_dir
, fn
);
531 mm_context_free(ctx
);
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
)
543 struct ast_str
*out
= NULL
;
545 struct ast_http_uri
*urih
=NULL
;
547 struct ast_variable
*vars
=NULL
, *v
, *prev
= NULL
;
548 struct http_uri_redirect
*redirect
;
550 strsep(¶ms
, "?");
551 /* Extract arguments from the request and store them in variables. */
555 while ((val
= strsep(¶ms
, "&"))) {
556 var
= strsep(&val
, "=");
562 if ((v
= ast_variable_new(var
, val
, ""))) {
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
577 prev
->next
= *cookies
;
583 AST_RWLIST_RDLOCK(&uri_redirects
);
584 AST_RWLIST_TRAVERSE(&uri_redirects
, redirect
, entry
) {
585 if (!strcasecmp(uri
, redirect
->target
)) {
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...");
591 *title
= ast_strdup("Moved Temporarily");
595 AST_RWLIST_UNLOCK(&uri_redirects
);
599 /* We want requests to start with the prefix and '/' */
601 if (l
&& !strncasecmp(uri
, prefix
, l
) && uri
[l
] == '/') {
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 */
613 if (!*c
|| urih
->has_subtree
) {
619 AST_RWLIST_UNLOCK(&uris
);
622 if (urih
->static_content
)
624 out
= urih
->callback(ser
, uri
, vars
, status
, title
, contentlength
);
625 AST_RWLIST_UNLOCK(&uris
);
627 out
= ast_http_error(404, "Not Found", NULL
,
628 "The requested URL was not found on this server.");
630 *title
= ast_strdup("Not Found");
634 ast_variables_destroy(vars
);
639 #if defined(HAVE_FUNOPEN)
643 #define HOOK_T ssize_t
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);
657 ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
662 static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
665 char *s = alloca(len+1);
666 strncpy(s, buf, len);
668 ast_verbose("ssl write size %d <%s>\n", (int)len, s);
670 return SSL_write(cookie, buf, len);
673 static int ssl_close(void *cookie)
675 close(SSL_get_fd(cookie));
676 SSL_shutdown(cookie);
682 static void *httpd_helper_thread(void *data
)
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
))
696 uri
= ast_skip_nonblanks(buf
); /* Skip method */
700 uri
= ast_skip_blanks(uri
); /* Skip white space */
702 if (*uri
) { /* terminate at the first blank */
703 char *c
= ast_skip_nonblanks(uri
);
708 /* process "Cookie: " lines */
709 while (fgets(cookie
, sizeof(cookie
), ser
->f
)) {
713 /* Trim trailing characters */
714 ast_trim_blanks(cookie
);
715 if (ast_strlen_zero(cookie
))
717 if (strncasecmp(cookie
, "Cookie: ", 8)) {
720 value
= ast_strdupa(cookie
);
721 name
= strsep(&value
, ":");
724 value
= ast_skip_blanks(value
);
725 if (ast_strlen_zero(value
))
727 var
= ast_variable_new(name
, value
, "");
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
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
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 */
754 if (!vname
) /* no name ? */
756 vname
= ast_skip_blanks(vname
);
758 vval
= strchr(vname
, '=');
761 /* Ditch the = and the quotes */
765 if ( (l
= strlen(vval
)) )
766 vval
[l
- 1] = '\0'; /* trim trailing quote */
767 var
= ast_variable_new(vname
, vval
, "");
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 */
789 ast_variables_destroy(vars
);
790 /* Clean up all the header information pulled as well */
792 ast_variables_destroy(headers
);
795 struct timeval tv
= ast_tvnow();
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"
803 "Connection: close\r\n"
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
);
814 char *tmp
= strstr(out
->str
, "\r\n\r\n");
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
);
835 * \brief Add a new URI redirect
836 * The entries in the redirect list are sorted by length, just like the list
839 static void add_redirect(const char *value
)
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 */
853 ast_log(LOG_WARNING
, "Invalid redirect '%s'\n", value
);
857 target_len
= strlen(target
) + 1;
858 total_len
= sizeof(*redirect
) + target_len
+ strlen(dest
) + 1;
860 if (!(redirect
= ast_calloc(1, total_len
)))
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
);
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
);
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
)
894 ast_free(post_map
->from
);
896 ast_free(post_map
->to
);
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
))))
917 if (!(post_map
->from
= ast_strdup(from
))) {
918 destroy_post_mapping(post_map
);
922 if (!(post_map
->to
= ast_strdup(to
))) {
923 destroy_post_mapping(post_map
);
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
;
937 int newenablestatic
=0;
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
)
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
)))
968 AST_RWLIST_UNLOCK(&uri_redirects
);
970 destroy_post_mappings();
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;
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
));
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
)) {
1008 ast_copy_string(newprefix
+ 1, v
->value
, sizeof(newprefix
) - 1);
1010 newprefix
[0] = '\0';
1012 } else if (!strcasecmp(v
->name
, "redirect")) {
1013 add_redirect(v
->value
);
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
;
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
);
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
;
1045 e
->command
= "http show status";
1047 "Usage: http show status\n"
1048 " Lists status of internal HTTP engine\n";
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");
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");
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
);
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)
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);