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>
24 * This program implements a tiny http server supporting the "get" method
25 * only and was inspired by micro-httpd by Jef Poskanzer
27 * \ref AstHTTP - AMI over the http protocol
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34 #include <sys/types.h>
40 #include <netinet/in.h>
42 #include <sys/socket.h>
44 #include <sys/signal.h>
45 #include <arpa/inet.h>
50 #include "asterisk/cli.h"
51 #include "asterisk/http.h"
52 #include "asterisk/utils.h"
53 #include "asterisk/strings.h"
54 #include "asterisk/options.h"
55 #include "asterisk/config.h"
58 #define DEFAULT_PREFIX "/asterisk"
61 * In order to have SSL support, we need the openssl libraries.
62 * Still we can decide whether or not to use them by commenting
63 * in or out the DO_SSL macro.
64 * We declare most of ssl support variables unconditionally,
65 * because their number is small and this simplifies the code.
67 #if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE))
68 #define DO_SSL /* comment in/out if you want to support ssl */
72 #include <openssl/ssl.h>
73 #include <openssl/err.h>
78 #define AST_CERTFILE "asterisk.pem"
80 static char *certfile;
84 * describes a server instance
86 struct server_instance {
87 FILE *f; /* fopen/funopen result */
88 int fd; /* the socket returned by accept() */
89 int is_ssl; /* is this an ssl session ? */
91 SSL *ssl; /* ssl state */
93 struct sockaddr_in requestor;
94 ast_http_callback callback;
98 * arguments for the accepting thread
101 struct sockaddr_in sin;
102 struct sockaddr_in oldsin;
103 int is_ssl; /* is this an SSL accept ? */
109 * we have up to two accepting threads, one for http, one for https
111 static struct server_args http_desc = {
113 .master = AST_PTHREADT_NULL,
117 static struct server_args https_desc = {
119 .master = AST_PTHREADT_NULL,
123 static struct ast_http_uri *uris; /*!< list of supported handlers */
125 /* all valid URIs must be prepended by the string in prefix. */
126 static char prefix[MAX_PREFIX];
127 static int enablestatic=0;
129 /*! \brief Limit the kinds of files we're willing to serve up */
134 { "png", "image/png" },
135 { "jpg", "image/jpeg" },
136 { "js", "application/x-javascript" },
137 { "wav", "audio/x-wav" },
138 { "mp3", "audio/mpeg" },
141 static char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
145 for (x=0;x<sizeof(mimetypes) / sizeof(mimetypes[0]); x++) {
146 if (!strcasecmp(ftype, mimetypes[x].ext))
147 return mimetypes[x].mtype;
150 snprintf(wkspace, wkspacelen, "text/%s", ftype ? ftype : "plain");
154 /* like ast_uri_decode, but replace '+' with ' ' */
155 static char *uri_decode(char *buf)
159 for (c = buf; *c; c++) {
165 static char *static_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
177 /* Yuck. I'm not really sold on this, but if you don't deliver static content it makes your configuration
178 substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
179 if (!enablestatic || ast_strlen_zero(uri))
181 /* Disallow any funny filenames at all */
182 if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0]))
184 if (strstr(uri, "/.."))
187 if ((ftype = strrchr(uri, '.')))
189 mtype=ftype2mtype(ftype, wkspace, sizeof(wkspace));
191 /* Cap maximum length */
192 len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5;
197 sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
200 if (S_ISDIR(st.st_mode))
202 fd = open(path, O_RDONLY);
206 len = st.st_size + strlen(mtype) + 40;
211 sprintf(c, "Content-type: %s\r\n\r\n", mtype);
213 *contentlength = read(fd, c, st.st_size);
214 if (*contentlength < 0) {
225 *title = strdup("Not Found");
226 return ast_http_error(404, "Not Found", NULL, "Nothing to see here. Move along.");
230 *title = strdup("Access Denied");
231 return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
235 static char *httpstatus_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
238 size_t reslen = sizeof(result);
240 struct ast_variable *v;
242 ast_build_string(&c, &reslen,
244 "<title>Asterisk HTTP Status</title>\r\n"
245 "<body bgcolor=\"#ffffff\">\r\n"
246 "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
247 "<h2> Asterisk™ HTTP Status</h2></td></tr>\r\n");
249 ast_build_string(&c, &reslen, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
250 ast_build_string(&c, &reslen, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
251 ast_inet_ntoa(http_desc.oldsin.sin_addr));
252 ast_build_string(&c, &reslen, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
253 ntohs(http_desc.oldsin.sin_port));
255 ast_build_string(&c, &reslen, "<tr><td><i>SSL Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
256 ntohs(https_desc.oldsin.sin_port));
257 ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
260 if (strncasecmp(v->name, "cookie_", 7))
261 ast_build_string(&c, &reslen, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
264 ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
267 if (!strncasecmp(v->name, "cookie_", 7))
268 ast_build_string(&c, &reslen, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
271 ast_build_string(&c, &reslen, "</table><center><font size=\"-1\"><i>Asterisk and Digium are registered trademarks of Digium, Inc.</i></font></center></body>\r\n");
272 return strdup(result);
275 static struct ast_http_uri statusuri = {
276 .callback = httpstatus_callback,
277 .description = "Asterisk HTTP General Status",
282 static struct ast_http_uri staticuri = {
283 .callback = static_callback,
284 .description = "Asterisk HTTP Static Delivery",
289 char *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
293 "Content-type: text/html\r\n"
296 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
298 "<title>%d %s</title>\r\n"
303 "<address>Asterisk Server</address>\r\n"
304 "</body></html>\r\n",
305 (extra_header ? extra_header : ""), status, title, title, text);
310 * Link the new uri into the list. They are sorted by length of
311 * the string, not alphabetically. Duplicate entries are not replaced,
312 * but the insertion order (using <= and not just <) makes sure that
313 * more recent insertions hide older ones.
314 * On a lookup, we just scan the list and stop at the first matching entry.
316 int ast_http_uri_link(struct ast_http_uri *urih)
318 struct ast_http_uri *prev=uris;
319 int len = strlen(urih->uri);
321 if (!uris || strlen(uris->uri) <= len ) {
325 while (prev->next && strlen(prev->next->uri) > len)
328 urih->next = prev->next;
334 void ast_http_uri_unlink(struct ast_http_uri *urih)
336 struct ast_http_uri *prev = uris;
343 if (prev->next == urih) {
344 prev->next = urih->next;
351 static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **title, int *contentlength, struct ast_variable **cookies)
355 struct ast_http_uri *urih=NULL;
357 struct ast_variable *vars=NULL, *v, *prev = NULL;
359 strsep(¶ms, "?");
360 /* Extract arguments from the request and store them in variables. */
364 while ((val = strsep(¶ms, "&"))) {
365 var = strsep(&val, "=");
371 if ((v = ast_variable_new(var, val))) {
381 * Append the cookies to the variables (the only reason to have them
382 * at the end is to avoid another pass of the cookies list to find
386 prev->next = *cookies;
392 /* We want requests to start with the prefix and '/' */
394 if (l && !strncasecmp(uri, prefix, l) && uri[l] == '/') {
396 /* scan registered uris to see if we match one. */
397 for (urih = uris; urih; urih = urih->next) {
398 l = strlen(urih->uri);
399 c = uri + l; /* candidate */
400 if (strncasecmp(urih->uri, uri, l) /* no match */
401 || (*c && *c != '/')) /* substring */
405 if (!*c || urih->has_subtree) {
412 c = urih->callback(sin, uri, vars, status, title, contentlength);
413 } else if (ast_strlen_zero(uri) && ast_strlen_zero(prefix)) {
414 /* Special case: no prefix, no URI, send to /static/index.html */
415 c = ast_http_error(302, "Moved Temporarily",
416 "Location: /static/index.html\r\n",
417 "This is not the page you are looking for...");
419 *title = strdup("Moved Temporarily");
421 c = ast_http_error(404, "Not Found", NULL,
422 "The requested URL was not found on this server.");
424 *title = strdup("Not Found");
426 ast_variables_destroy(vars);
431 #if defined(HAVE_FUNOPEN)
435 #define HOOK_T ssize_t
439 * replacement read/write functions for SSL support.
440 * We use wrappers rather than SSL_read/SSL_write directly so
441 * we can put in some debugging.
443 static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
445 int i = SSL_read(cookie, buf, len-1);
449 ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
454 static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
457 char *s = alloca(len+1);
458 strncpy(s, buf, len);
460 ast_verbose("ssl write size %d <%s>\n", (int)len, s);
462 return SSL_write(cookie, buf, len);
465 static int ssl_close(void *cookie)
467 close(SSL_get_fd(cookie));
468 SSL_shutdown(cookie);
474 static void *ast_httpd_helper_thread(void *data)
478 struct server_instance *ser = data;
479 struct ast_variable *var, *prev=NULL, *vars=NULL;
480 char *uri, *c, *title=NULL;
481 int status = 200, contentlength = 0;
484 * open a FILE * as appropriate.
487 ser->f = fdopen(ser->fd, "w+");
489 else if ( (ser->ssl = SSL_new(ssl_ctx)) ) {
490 SSL_set_fd(ser->ssl, ser->fd);
491 if (SSL_accept(ser->ssl) == 0)
492 ast_verbose(" error setting up ssl connection");
494 #if defined(HAVE_FUNOPEN) /* the BSD interface */
495 ser->f = funopen(ser->ssl, ssl_read, ssl_write, NULL, ssl_close);
497 #elif defined(HAVE_FOPENCOOKIE) /* the glibc/linux interface */
498 static const cookie_io_functions_t cookie_funcs = {
499 ssl_read, ssl_write, NULL, ssl_close
501 ser->f = fopencookie(ser->ssl, "w+", cookie_funcs);
503 /* could add other methods here */
506 if (!ser->f) /* no success opening descriptor stacking */
513 ast_log(LOG_WARNING, "FILE * open failed!\n");
517 if (!fgets(buf, sizeof(buf), ser->f))
520 uri = ast_skip_nonblanks(buf); /* Skip method */
524 uri = ast_skip_blanks(uri); /* Skip white space */
526 if (*uri) { /* terminate at the first blank */
527 c = ast_skip_nonblanks(uri);
532 /* process "Cookie: " lines */
533 while (fgets(cookie, sizeof(cookie), ser->f)) {
537 /* Trim trailing characters */
538 ast_trim_blanks(cookie);
539 if (ast_strlen_zero(cookie))
541 if (strncasecmp(cookie, "Cookie: ", 8))
544 /* TODO - The cookie parsing code below seems to work
545 in IE6 and FireFox 1.5. However, it is not entirely
546 correct, and therefore may not work in all
548 For more details see RFC 2109 and RFC 2965 */
550 /* FireFox cookie strings look like:
551 Cookie: mansession_id="********"
552 InternetExplorer's look like:
553 Cookie: $Version="1"; mansession_id="********" */
555 /* If we got a FireFox cookie string, the name's right
557 vname = ast_skip_blanks(cookie + 8);
559 /* If we got an IE cookie string, we need to skip to
560 past the version to get to the name */
563 if (!vname) /* no name ? */
565 vname = ast_skip_blanks(vname);
567 vval = strchr(vname, '=');
570 /* Ditch the = and the quotes */
574 if ( (l = strlen(vval)) )
575 vval[l - 1] = '\0'; /* trim trailing quote */
576 var = ast_variable_new(vname, vval);
587 c = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
588 else if (strcasecmp(buf, "get"))
589 c = ast_http_error(501, "Not Implemented", NULL,
590 "Attempt to use unimplemented / unsupported method");
591 else /* try to serve it */
592 c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars);
594 /* If they aren't mopped up already, clean up the cookies */
596 ast_variables_destroy(vars);
599 c = ast_http_error(500, "Internal Error", NULL, "Internal Server Error");
601 time_t t = time(NULL);
604 strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
605 fprintf(ser->f, "HTTP/1.1 %d %s\r\n"
606 "Server: Asterisk\r\n"
608 "Connection: close\r\n",
609 status, title ? title : "OK", timebuf);
610 if (!contentlength) { /* opaque body ? just dump it hoping it is properly formatted */
611 fprintf(ser->f, "%s", c);
613 char *tmp = strstr(c, "\r\n\r\n");
616 fprintf(ser->f, "Content-length: %d\r\n", contentlength);
617 /* first write the header, then the body */
618 fwrite(c, 1, (tmp + 4 - c), ser->f);
619 fwrite(tmp + 4, 1, contentlength, ser->f);
634 static void *http_root(void *data)
636 struct server_args *desc = data;
638 struct sockaddr_in sin;
640 struct server_instance *ser;
647 ast_wait_for_input(desc->accept_fd, -1);
648 sinlen = sizeof(sin);
649 fd = accept(desc->accept_fd, (struct sockaddr *)&sin, &sinlen);
651 if ((errno != EAGAIN) && (errno != EINTR))
652 ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
655 ser = ast_calloc(1, sizeof(*ser));
657 ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno));
661 flags = fcntl(fd, F_GETFL);
662 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
664 ser->is_ssl = desc->is_ssl;
665 memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
667 pthread_attr_init(&attr);
668 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
670 if (ast_pthread_create_background(&launched, &attr, ast_httpd_helper_thread, ser)) {
671 ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
679 char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, size_t buflen)
683 ast_build_string(&c, &buflen, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var, val);
685 ast_build_string(&c, &buflen, "; Max-Age=%d", expires);
686 ast_build_string(&c, &buflen, "\r\n");
690 static int ssl_setup(void)
698 SSL_load_error_strings();
699 SSLeay_add_ssl_algorithms();
700 ssl_ctx = SSL_CTX_new( SSLv23_server_method() );
701 if (!ast_strlen_zero(certfile)) {
702 if (SSL_CTX_use_certificate_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) == 0 ||
703 SSL_CTX_use_PrivateKey_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) == 0 ||
704 SSL_CTX_check_private_key(ssl_ctx) == 0 ) {
705 ast_verbose("ssl cert error <%s>", certfile);
711 if (!ast_strlen_zero(cipher)) {
712 if (SSL_CTX_set_cipher_list(ssl_ctx, cipher) == 0 ) {
713 ast_verbose("ssl cipher error <%s>", cipher);
719 ast_verbose("ssl cert ok");
724 static void http_server_start(struct server_args *desc)
729 /* Do nothing if nothing has changed */
730 if (!memcmp(&desc->oldsin, &desc->sin, sizeof(desc->oldsin))) {
732 ast_log(LOG_DEBUG, "Nothing changed in http\n");
736 desc->oldsin = desc->sin;
738 /* Shutdown a running server if there is one */
739 if (desc->master != AST_PTHREADT_NULL) {
740 pthread_cancel(desc->master);
741 pthread_kill(desc->master, SIGURG);
742 pthread_join(desc->master, NULL);
745 if (desc->accept_fd != -1)
746 close(desc->accept_fd);
748 /* If there's no new server, stop here */
749 if (desc->sin.sin_family == 0)
753 desc->accept_fd = socket(AF_INET, SOCK_STREAM, 0);
754 if (desc->accept_fd < 0) {
755 ast_log(LOG_WARNING, "Unable to allocate socket: %s\n", strerror(errno));
759 setsockopt(desc->accept_fd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
760 if (bind(desc->accept_fd, (struct sockaddr *)&desc->sin, sizeof(desc->sin))) {
761 ast_log(LOG_NOTICE, "Unable to bind http server to %s:%d: %s\n",
762 ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
766 if (listen(desc->accept_fd, 10)) {
767 ast_log(LOG_NOTICE, "Unable to listen!\n");
768 close(desc->accept_fd);
769 desc->accept_fd = -1;
772 flags = fcntl(desc->accept_fd, F_GETFL);
773 fcntl(desc->accept_fd, F_SETFL, flags | O_NONBLOCK);
774 if (ast_pthread_create_background(&desc->master, NULL, http_root, desc)) {
775 ast_log(LOG_NOTICE, "Unable to launch http server on %s:%d: %s\n",
776 ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
783 close(desc->accept_fd);
784 desc->accept_fd = -1;
787 static int __ast_http_load(int reload)
789 struct ast_config *cfg;
790 struct ast_variable *v;
792 int newenablestatic=0;
794 struct ast_hostent ahp;
795 char newprefix[MAX_PREFIX];
797 memset(&http_desc.sin, 0, sizeof(http_desc.sin));
798 http_desc.sin.sin_port = htons(8088);
799 memset(&https_desc.sin, 0, sizeof(https_desc.sin));
800 https_desc.sin.sin_port = htons(8089);
801 strcpy(newprefix, DEFAULT_PREFIX);
802 cfg = ast_config_load("http.conf");
807 certfile = ast_strdup(AST_CERTFILE);
810 cipher = ast_strdup("");
813 v = ast_variable_browse(cfg, "general");
815 if (!strcasecmp(v->name, "enabled"))
816 enabled = ast_true(v->value);
817 else if (!strcasecmp(v->name, "sslenable"))
818 do_ssl = ast_true(v->value);
819 else if (!strcasecmp(v->name, "sslbindport"))
820 https_desc.sin.sin_port = htons(atoi(v->value));
821 else if (!strcasecmp(v->name, "sslcert")) {
823 certfile = ast_strdup(v->value);
824 } else if (!strcasecmp(v->name, "sslcipher")) {
826 cipher = ast_strdup(v->value);
828 else if (!strcasecmp(v->name, "enablestatic"))
829 newenablestatic = ast_true(v->value);
830 else if (!strcasecmp(v->name, "bindport"))
831 http_desc.sin.sin_port = htons(atoi(v->value));
832 else if (!strcasecmp(v->name, "bindaddr")) {
833 if ((hp = ast_gethostbyname(v->value, &ahp))) {
834 memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
835 memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
837 ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
839 } else if (!strcasecmp(v->name, "prefix")) {
840 if (!ast_strlen_zero(v->value)) {
842 ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
850 ast_config_destroy(cfg);
853 http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
854 if (strcmp(prefix, newprefix))
855 ast_copy_string(prefix, newprefix, sizeof(prefix));
856 enablestatic = newenablestatic;
857 http_server_start(&http_desc);
859 http_server_start(&https_desc);
863 static int handle_show_http(int fd, int argc, char *argv[])
865 struct ast_http_uri *urih;
867 return RESULT_SHOWUSAGE;
868 ast_cli(fd, "HTTP Server Status:\n");
869 ast_cli(fd, "Prefix: %s\n", prefix);
870 if (!http_desc.oldsin.sin_family)
871 ast_cli(fd, "Server Disabled\n\n");
873 ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
874 ast_inet_ntoa(http_desc.oldsin.sin_addr),
875 ntohs(http_desc.oldsin.sin_port));
877 ast_cli(fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
878 ast_inet_ntoa(https_desc.oldsin.sin_addr),
879 ntohs(https_desc.oldsin.sin_port));
881 ast_cli(fd, "Enabled URI's:\n");
884 ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
888 ast_cli(fd, "None.\n");
889 return RESULT_SUCCESS;
892 int ast_http_reload(void)
894 return __ast_http_load(1);
897 static char show_http_help[] =
898 "Usage: http show status\n"
899 " Lists status of internal HTTP engine\n";
901 static struct ast_cli_entry cli_http[] = {
902 { { "http", "show", "status", NULL },
903 handle_show_http, "Display HTTP server status",
907 int ast_http_init(void)
909 ast_http_uri_link(&statusuri);
910 ast_http_uri_link(&staticuri);
911 ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
912 return __ast_http_load(0);