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"
60 struct ast_http_server_instance
{
63 struct sockaddr_in requestor
;
64 ast_http_callback callback
;
67 AST_RWLOCK_DEFINE_STATIC(uris_lock
);
68 static struct ast_http_uri
*uris
;
70 static int httpfd
= -1;
71 static pthread_t master
= AST_PTHREADT_NULL
;
72 static char prefix
[MAX_PREFIX
];
73 static int prefix_len
;
74 static struct sockaddr_in oldsin
;
75 static int enablestatic
;
77 /*! \brief Limit the kinds of files we're willing to serve up */
82 { "png", "image/png" },
83 { "jpg", "image/jpeg" },
84 { "js", "application/x-javascript" },
85 { "wav", "audio/x-wav" },
86 { "mp3", "audio/mpeg" },
89 static char *ftype2mtype(const char *ftype
, char *wkspace
, int wkspacelen
)
93 for (x
=0;x
<sizeof(mimetypes
) / sizeof(mimetypes
[0]); x
++) {
94 if (!strcasecmp(ftype
, mimetypes
[x
].ext
))
95 return mimetypes
[x
].mtype
;
98 snprintf(wkspace
, wkspacelen
, "text/%s", ftype
? ftype
: "plain");
102 static char *static_callback(struct sockaddr_in
*req
, const char *uri
, struct ast_variable
*vars
, int *status
, char **title
, int *contentlength
)
114 /* Yuck. I'm not really sold on this, but if you don't deliver static content it makes your configuration
115 substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
116 if (!enablestatic
|| ast_strlen_zero(uri
))
118 /* Disallow any funny filenames at all */
119 if ((uri
[0] < 33) || strchr("./|~@#$%^&*() \t", uri
[0]))
121 if (strstr(uri
, "/.."))
124 if ((ftype
= strrchr(uri
, '.')))
126 mtype
=ftype2mtype(ftype
, wkspace
, sizeof(wkspace
));
128 /* Cap maximum length */
129 len
= strlen(uri
) + strlen(ast_config_AST_DATA_DIR
) + strlen("/static-http/") + 5;
134 sprintf(path
, "%s/static-http/%s", ast_config_AST_DATA_DIR
, uri
);
137 if (S_ISDIR(st
.st_mode
))
139 fd
= open(path
, O_RDONLY
);
143 len
= st
.st_size
+ strlen(mtype
) + 40;
148 sprintf(c
, "Content-type: %s\r\n\r\n", mtype
);
150 *contentlength
= read(fd
, c
, st
.st_size
);
151 if (*contentlength
< 0) {
162 *title
= strdup("Not Found");
163 return ast_http_error(404, "Not Found", NULL
, "Nothing to see here. Move along.");
167 *title
= strdup("Access Denied");
168 return ast_http_error(403, "Access Denied", NULL
, "Sorry, I cannot let you do that, Dave.");
172 static char *httpstatus_callback(struct sockaddr_in
*req
, const char *uri
, struct ast_variable
*vars
, int *status
, char **title
, int *contentlength
)
175 size_t reslen
= sizeof(result
);
177 struct ast_variable
*v
;
179 ast_build_string(&c
, &reslen
,
181 "<title>Asterisk HTTP Status</title>\r\n"
182 "<body bgcolor=\"#ffffff\">\r\n"
183 "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
184 "<h2> Asterisk™ HTTP Status</h2></td></tr>\r\n");
186 ast_build_string(&c
, &reslen
, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix
);
187 ast_build_string(&c
, &reslen
, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
188 ast_inet_ntoa(oldsin
.sin_addr
));
189 ast_build_string(&c
, &reslen
, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
190 ntohs(oldsin
.sin_port
));
191 ast_build_string(&c
, &reslen
, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
194 if (strncasecmp(v
->name
, "cookie_", 7))
195 ast_build_string(&c
, &reslen
, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v
->name
, v
->value
);
198 ast_build_string(&c
, &reslen
, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
201 if (!strncasecmp(v
->name
, "cookie_", 7))
202 ast_build_string(&c
, &reslen
, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v
->name
, v
->value
);
205 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");
206 return strdup(result
);
209 static struct ast_http_uri statusuri
= {
210 .callback
= httpstatus_callback
,
211 .description
= "Asterisk HTTP General Status",
216 static struct ast_http_uri staticuri
= {
217 .callback
= static_callback
,
218 .description
= "Asterisk HTTP Static Delivery",
223 char *ast_http_error(int status
, const char *title
, const char *extra_header
, const char *text
)
227 "Content-type: text/html\r\n"
230 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
232 "<title>%d %s</title>\r\n"
237 "<address>Asterisk Server</address>\r\n"
238 "</body></html>\r\n",
239 (extra_header
? extra_header
: ""), status
, title
, title
, text
);
243 int ast_http_uri_link(struct ast_http_uri
*urih
)
245 struct ast_http_uri
*prev
;
247 ast_rwlock_wrlock(&uris_lock
);
249 if (!uris
|| strlen(uris
->uri
) <= strlen(urih
->uri
)) {
253 while (prev
->next
&& (strlen(prev
->next
->uri
) > strlen(urih
->uri
)))
256 urih
->next
= prev
->next
;
259 ast_rwlock_unlock(&uris_lock
);
264 void ast_http_uri_unlink(struct ast_http_uri
*urih
)
266 struct ast_http_uri
*prev
;
268 ast_rwlock_wrlock(&uris_lock
);
270 ast_rwlock_unlock(&uris_lock
);
278 if (prev
->next
== urih
) {
279 prev
->next
= urih
->next
;
284 ast_rwlock_unlock(&uris_lock
);
287 static char *handle_uri(struct sockaddr_in
*sin
, char *uri
, int *status
, char **title
, int *contentlength
, struct ast_variable
**cookies
)
294 struct ast_http_uri
*urih
=NULL
;
296 struct ast_variable
*vars
=NULL
, *v
, *prev
= NULL
;
299 params
= strchr(uri
, '?');
303 while ((var
= strsep(¶ms
, "&"))) {
304 val
= strchr(var
, '=');
312 if ((v
= ast_variable_new(var
, val
))) {
322 prev
->next
= *cookies
;
327 if (!strncasecmp(uri
, prefix
, prefix_len
)) {
329 if (!*uri
|| (*uri
== '/')) {
332 ast_rwlock_rdlock(&uris_lock
);
335 len
= strlen(urih
->uri
);
336 if (!strncasecmp(urih
->uri
, uri
, len
)) {
337 if (!uri
[len
] || uri
[len
] == '/') {
341 if (!*turi
|| urih
->has_subtree
) {
350 ast_rwlock_unlock(&uris_lock
);
354 c
= urih
->callback(sin
, uri
, vars
, status
, title
, contentlength
);
355 ast_rwlock_unlock(&uris_lock
);
356 } else if (ast_strlen_zero(uri
) && ast_strlen_zero(prefix
)) {
357 /* Special case: If no prefix, and no URI, send to /static/index.html */
358 c
= ast_http_error(302, "Moved Temporarily", "Location: /static/index.html\r\n", "This is not the page you are looking for...");
360 *title
= strdup("Moved Temporarily");
362 c
= ast_http_error(404, "Not Found", NULL
, "The requested URL was not found on this server.");
364 *title
= strdup("Not Found");
366 ast_variables_destroy(vars
);
370 static void *ast_httpd_helper_thread(void *data
)
375 struct ast_http_server_instance
*ser
= data
;
376 struct ast_variable
*var
, *prev
=NULL
, *vars
=NULL
;
377 char *uri
, *c
, *title
=NULL
;
379 int status
= 200, contentlength
= 0;
382 if (fgets(buf
, sizeof(buf
), ser
->f
)) {
385 while(*uri
&& (*uri
> 32))
392 /* Skip white space */
393 while (*uri
&& (*uri
< 33))
398 while (*c
&& (*c
> 32))
405 while (fgets(cookie
, sizeof(cookie
), ser
->f
)) {
406 /* Trim trailing characters */
407 while(!ast_strlen_zero(cookie
) && (cookie
[strlen(cookie
) - 1] < 33)) {
408 cookie
[strlen(cookie
) - 1] = '\0';
410 if (ast_strlen_zero(cookie
))
412 if (!strncasecmp(cookie
, "Cookie: ", 8)) {
414 /* TODO - The cookie parsing code below seems to work
415 in IE6 and FireFox 1.5. However, it is not entirely
416 correct, and therefore may not work in all
418 For more details see RFC 2109 and RFC 2965 */
420 /* FireFox cookie strings look like:
421 Cookie: mansession_id="********"
422 InternetExplorer's look like:
423 Cookie: $Version="1"; mansession_id="********" */
425 /* If we got a FireFox cookie string, the name's right
429 /* If we got an IE cookie string, we need to skip to
430 past the version to get to the name */
432 vname
= strchr(vname
, ';');
441 vval
= strchr(vname
, '=');
443 /* Ditch the = and the quotes */
448 vval
[strlen(vval
) - 1] = '\0';
449 var
= ast_variable_new(vname
, vval
);
463 if (!strcasecmp(buf
, "get"))
464 c
= handle_uri(&ser
->requestor
, uri
, &status
, &title
, &contentlength
, &vars
);
466 c
= ast_http_error(501, "Not Implemented", NULL
, "Attempt to use unimplemented / unsupported method");\
468 c
= ast_http_error(400, "Bad Request", NULL
, "Invalid Request");
470 /* If they aren't mopped up already, clean up the cookies */
472 ast_variables_destroy(vars
);
475 c
= ast_http_error(500, "Internal Error", NULL
, "Internal Server Error");
478 strftime(timebuf
, sizeof(timebuf
), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t
));
479 ast_cli(ser
->fd
, "HTTP/1.1 %d %s\r\n", status
, title
? title
: "OK");
480 ast_cli(ser
->fd
, "Server: Asterisk\r\n");
481 ast_cli(ser
->fd
, "Date: %s\r\n", timebuf
);
482 ast_cli(ser
->fd
, "Connection: close\r\n");
485 tmp
= strstr(c
, "\r\n\r\n");
487 ast_cli(ser
->fd
, "Content-length: %d\r\n", contentlength
);
488 write(ser
->fd
, c
, (tmp
+ 4 - c
));
489 write(ser
->fd
, tmp
+ 4, contentlength
);
492 ast_cli(ser
->fd
, "%s", c
);
503 static void *http_root(void *data
)
506 struct sockaddr_in sin
;
508 struct ast_http_server_instance
*ser
;
515 ast_wait_for_input(httpfd
, -1);
516 sinlen
= sizeof(sin
);
517 fd
= accept(httpfd
, (struct sockaddr
*)&sin
, &sinlen
);
519 if ((errno
!= EAGAIN
) && (errno
!= EINTR
))
520 ast_log(LOG_WARNING
, "Accept failed: %s\n", strerror(errno
));
523 ser
= ast_calloc(1, sizeof(*ser
));
525 ast_log(LOG_WARNING
, "No memory for new session: %s\n", strerror(errno
));
529 flags
= fcntl(fd
, F_GETFL
);
530 fcntl(fd
, F_SETFL
, flags
& ~O_NONBLOCK
);
532 memcpy(&ser
->requestor
, &sin
, sizeof(ser
->requestor
));
533 if ((ser
->f
= fdopen(ser
->fd
, "w+"))) {
534 pthread_attr_init(&attr
);
535 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
537 if (ast_pthread_create_background(&launched
, &attr
, ast_httpd_helper_thread
, ser
)) {
538 ast_log(LOG_WARNING
, "Unable to launch helper thread: %s\n", strerror(errno
));
542 pthread_attr_destroy(&attr
);
544 ast_log(LOG_WARNING
, "fdopen failed!\n");
552 char *ast_http_setcookie(const char *var
, const char *val
, int expires
, char *buf
, size_t buflen
)
556 ast_build_string(&c
, &buflen
, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var
, val
);
558 ast_build_string(&c
, &buflen
, "; Max-Age=%d", expires
);
559 ast_build_string(&c
, &buflen
, "\r\n");
564 static void http_server_start(struct sockaddr_in
*sin
)
569 /* Do nothing if nothing has changed */
570 if (!memcmp(&oldsin
, sin
, sizeof(oldsin
))) {
571 ast_log(LOG_DEBUG
, "Nothing changed in http\n");
575 memcpy(&oldsin
, sin
, sizeof(oldsin
));
577 /* Shutdown a running server if there is one */
578 if (master
!= AST_PTHREADT_NULL
) {
579 pthread_cancel(master
);
580 pthread_kill(master
, SIGURG
);
581 pthread_join(master
, NULL
);
587 /* If there's no new server, stop here */
588 if (!sin
->sin_family
)
592 httpfd
= socket(AF_INET
, SOCK_STREAM
, 0);
594 ast_log(LOG_WARNING
, "Unable to allocate socket: %s\n", strerror(errno
));
598 setsockopt(httpfd
, SOL_SOCKET
, SO_REUSEADDR
, &x
, sizeof(x
));
599 if (bind(httpfd
, (struct sockaddr
*)sin
, sizeof(*sin
))) {
600 ast_log(LOG_NOTICE
, "Unable to bind http server to %s:%d: %s\n",
601 ast_inet_ntoa(sin
->sin_addr
), ntohs(sin
->sin_port
),
607 if (listen(httpfd
, 10)) {
608 ast_log(LOG_NOTICE
, "Unable to listen!\n");
613 flags
= fcntl(httpfd
, F_GETFL
);
614 fcntl(httpfd
, F_SETFL
, flags
| O_NONBLOCK
);
615 if (ast_pthread_create_background(&master
, NULL
, http_root
, NULL
)) {
616 ast_log(LOG_NOTICE
, "Unable to launch http server on %s:%d: %s\n",
617 ast_inet_ntoa(sin
->sin_addr
), ntohs(sin
->sin_port
),
624 static int __ast_http_load(int reload
)
626 struct ast_config
*cfg
;
627 struct ast_variable
*v
;
629 int newenablestatic
=0;
630 struct sockaddr_in sin
;
632 struct ast_hostent ahp
;
633 char newprefix
[MAX_PREFIX
];
635 memset(&sin
, 0, sizeof(sin
));
636 sin
.sin_port
= htons(8088);
637 strcpy(newprefix
, DEFAULT_PREFIX
);
638 cfg
= ast_config_load("http.conf");
640 v
= ast_variable_browse(cfg
, "general");
642 if (!strcasecmp(v
->name
, "enabled"))
643 enabled
= ast_true(v
->value
);
644 else if (!strcasecmp(v
->name
, "enablestatic"))
645 newenablestatic
= ast_true(v
->value
);
646 else if (!strcasecmp(v
->name
, "bindport"))
647 sin
.sin_port
= ntohs(atoi(v
->value
));
648 else if (!strcasecmp(v
->name
, "bindaddr")) {
649 if ((hp
= ast_gethostbyname(v
->value
, &ahp
))) {
650 memcpy(&sin
.sin_addr
, hp
->h_addr
, sizeof(sin
.sin_addr
));
652 ast_log(LOG_WARNING
, "Invalid bind address '%s'\n", v
->value
);
654 } else if (!strcasecmp(v
->name
, "prefix")) {
655 if (!ast_strlen_zero(v
->value
)) {
657 ast_copy_string(newprefix
+ 1, v
->value
, sizeof(newprefix
) - 1);
665 ast_config_destroy(cfg
);
668 sin
.sin_family
= AF_INET
;
669 if (strcmp(prefix
, newprefix
)) {
670 ast_copy_string(prefix
, newprefix
, sizeof(prefix
));
671 prefix_len
= strlen(prefix
);
673 enablestatic
= newenablestatic
;
674 http_server_start(&sin
);
678 static int handle_show_http(int fd
, int argc
, char *argv
[])
680 struct ast_http_uri
*urih
;
682 return RESULT_SHOWUSAGE
;
683 ast_cli(fd
, "HTTP Server Status:\n");
684 ast_cli(fd
, "Prefix: %s\n", prefix
);
685 if (oldsin
.sin_family
)
686 ast_cli(fd
, "Server Enabled and Bound to %s:%d\n\n",
687 ast_inet_ntoa(oldsin
.sin_addr
),
688 ntohs(oldsin
.sin_port
));
690 ast_cli(fd
, "Server Disabled\n\n");
691 ast_cli(fd
, "Enabled URI's:\n");
692 ast_rwlock_rdlock(&uris_lock
);
695 ast_cli(fd
, "%s/%s%s => %s\n", prefix
, urih
->uri
, (urih
->has_subtree
? "/..." : "" ), urih
->description
);
699 ast_cli(fd
, "None.\n");
700 ast_rwlock_unlock(&uris_lock
);
701 return RESULT_SUCCESS
;
704 int ast_http_reload(void)
706 return __ast_http_load(1);
709 static char show_http_help
[] =
710 "Usage: http show status\n"
711 " Lists status of internal HTTP engine\n";
713 static struct ast_cli_entry cli_http
[] = {
714 { { "http", "show", "status", NULL
},
715 handle_show_http
, "Display HTTP server status",
719 int ast_http_init(void)
721 ast_http_uri_link(&statusuri
);
722 ast_http_uri_link(&staticuri
);
723 ast_cli_register_multiple(cli_http
, sizeof(cli_http
) / sizeof(struct ast_cli_entry
));
724 return __ast_http_load(0);