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 <sys/types.h>
41 #include <netinet/in.h>
43 #include <sys/socket.h>
45 #include <sys/signal.h>
46 #include <arpa/inet.h>
51 #include "asterisk/cli.h"
52 #include "asterisk/http.h"
53 #include "asterisk/utils.h"
54 #include "asterisk/strings.h"
55 #include "asterisk/options.h"
56 #include "asterisk/config.h"
57 #include "asterisk/version.h"
58 #include "asterisk/manager.h"
61 #define DEFAULT_PREFIX "/asterisk"
63 struct ast_http_server_instance
{
66 struct sockaddr_in requestor
;
67 ast_http_callback callback
;
70 AST_RWLOCK_DEFINE_STATIC(uris_lock
);
71 static struct ast_http_uri
*uris
;
73 static int httpfd
= -1;
74 static pthread_t master
= AST_PTHREADT_NULL
;
75 static char prefix
[MAX_PREFIX
];
76 static int prefix_len
;
77 static struct sockaddr_in oldsin
;
78 static int enablestatic
;
80 /*! \brief Limit the kinds of files we're willing to serve up */
85 { "png", "image/png" },
86 { "jpg", "image/jpeg" },
87 { "js", "application/x-javascript" },
88 { "wav", "audio/x-wav" },
89 { "mp3", "audio/mpeg" },
90 { "svg", "image/svg+xml" },
91 { "svgz", "image/svg+xml" },
92 { "gif", "image/gif" },
95 static const char *ftype2mtype(const char *ftype
, char *wkspace
, int wkspacelen
)
99 for (x
=0;x
<sizeof(mimetypes
) / sizeof(mimetypes
[0]); x
++) {
100 if (!strcasecmp(ftype
, mimetypes
[x
].ext
))
101 return mimetypes
[x
].mtype
;
104 snprintf(wkspace
, wkspacelen
, "text/%s", ftype
? ftype
: "plain");
108 static char *static_callback(struct sockaddr_in
*req
, const char *uri
, struct ast_variable
*vars
, int *status
, char **title
, int *contentlength
)
121 /* Yuck. I'm not really sold on this, but if you don't deliver static content it makes your configuration
122 substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
123 if (!enablestatic
|| ast_strlen_zero(uri
))
125 /* Disallow any funny filenames at all */
126 if ((uri
[0] < 33) || strchr("./|~@#$%^&*() \t", uri
[0]))
128 if (strstr(uri
, "/.."))
131 if ((ftype
= strrchr(uri
, '.')))
133 mtype
= ftype2mtype(ftype
, wkspace
, sizeof(wkspace
));
135 /* Cap maximum length */
136 len
= strlen(uri
) + strlen(ast_config_AST_DATA_DIR
) + strlen("/static-http/") + 5;
141 sprintf(path
, "%s/static-http/%s", ast_config_AST_DATA_DIR
, uri
);
144 if (S_ISDIR(st
.st_mode
))
146 fd
= open(path
, O_RDONLY
);
150 len
= st
.st_size
+ strlen(mtype
) + 40;
155 sprintf(c
, "Content-type: %s\r\n\r\n", mtype
);
157 *contentlength
= read(fd
, c
, st
.st_size
);
158 if (*contentlength
< 0) {
169 *title
= strdup("Not Found");
170 return ast_http_error(404, "Not Found", NULL
, "Nothing to see here. Move along.");
174 *title
= strdup("Access Denied");
175 return ast_http_error(403, "Access Denied", NULL
, "Sorry, I cannot let you do that, Dave.");
179 static char *httpstatus_callback(struct sockaddr_in
*req
, const char *uri
, struct ast_variable
*vars
, int *status
, char **title
, int *contentlength
)
182 size_t reslen
= sizeof(result
);
184 struct ast_variable
*v
;
186 ast_build_string(&c
, &reslen
,
188 "<title>Asterisk HTTP Status</title>\r\n"
189 "<body bgcolor=\"#ffffff\">\r\n"
190 "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
191 "<h2> Asterisk™ HTTP Status</h2></td></tr>\r\n");
193 ast_build_string(&c
, &reslen
, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix
);
194 ast_build_string(&c
, &reslen
, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
195 ast_inet_ntoa(oldsin
.sin_addr
));
196 ast_build_string(&c
, &reslen
, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
197 ntohs(oldsin
.sin_port
));
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>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v
->name
, v
->value
);
205 ast_build_string(&c
, &reslen
, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
208 if (!strncasecmp(v
->name
, "cookie_", 7))
209 ast_build_string(&c
, &reslen
, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v
->name
, v
->value
);
212 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");
213 return strdup(result
);
216 static struct ast_http_uri statusuri
= {
217 .callback
= httpstatus_callback
,
218 .description
= "Asterisk HTTP General Status",
223 static struct ast_http_uri staticuri
= {
224 .callback
= static_callback
,
225 .description
= "Asterisk HTTP Static Delivery",
231 char *ast_http_error(int status
, const char *title
, const char *extra_header
, const char *text
)
235 "Content-type: text/html\r\n"
238 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
240 "<title>%d %s</title>\r\n"
245 "<address>Asterisk Server</address>\r\n"
246 "</body></html>\r\n",
247 (extra_header
? extra_header
: ""), status
, title
, title
, text
);
251 int ast_http_uri_link(struct ast_http_uri
*urih
)
253 struct ast_http_uri
*prev
;
255 ast_rwlock_wrlock(&uris_lock
);
257 if (!uris
|| strlen(uris
->uri
) <= strlen(urih
->uri
)) {
261 while (prev
->next
&& (strlen(prev
->next
->uri
) > strlen(urih
->uri
)))
264 urih
->next
= prev
->next
;
267 ast_rwlock_unlock(&uris_lock
);
272 void ast_http_uri_unlink(struct ast_http_uri
*urih
)
274 struct ast_http_uri
*prev
;
276 ast_rwlock_wrlock(&uris_lock
);
278 ast_rwlock_unlock(&uris_lock
);
286 if (prev
->next
== urih
) {
287 prev
->next
= urih
->next
;
292 ast_rwlock_unlock(&uris_lock
);
295 static char *handle_uri(struct sockaddr_in
*sin
, char *uri
, int *status
,
296 char **title
, int *contentlength
, struct ast_variable
**cookies
,
297 unsigned int *static_content
)
304 struct ast_http_uri
*urih
=NULL
;
306 struct ast_variable
*vars
=NULL
, *v
, *prev
= NULL
;
309 params
= strchr(uri
, '?');
313 while ((var
= strsep(¶ms
, "&"))) {
314 val
= strchr(var
, '=');
322 if ((v
= ast_variable_new(var
, val
))) {
332 prev
->next
= *cookies
;
337 if (!strncasecmp(uri
, prefix
, prefix_len
)) {
339 if (!*uri
|| (*uri
== '/')) {
342 ast_rwlock_rdlock(&uris_lock
);
345 len
= strlen(urih
->uri
);
346 if (!strncasecmp(urih
->uri
, uri
, len
)) {
347 if (!uri
[len
] || uri
[len
] == '/') {
351 if (!*turi
|| urih
->has_subtree
) {
360 ast_rwlock_unlock(&uris_lock
);
364 if (urih
->static_content
)
366 c
= urih
->callback(sin
, uri
, vars
, status
, title
, contentlength
);
367 ast_rwlock_unlock(&uris_lock
);
368 } else if (ast_strlen_zero(uri
) && ast_strlen_zero(prefix
)) {
369 /* Special case: If no prefix, and no URI, send to /static/index.html */
370 c
= ast_http_error(302, "Moved Temporarily", "Location: /static/index.html\r\n", "This is not the page you are looking for...");
372 *title
= strdup("Moved Temporarily");
374 c
= ast_http_error(404, "Not Found", NULL
, "The requested URL was not found on this server.");
376 *title
= strdup("Not Found");
378 ast_variables_destroy(vars
);
382 static void *ast_httpd_helper_thread(void *data
)
387 struct ast_http_server_instance
*ser
= data
;
388 struct ast_variable
*var
, *prev
=NULL
, *vars
=NULL
;
389 char *uri
, *c
, *title
=NULL
;
391 int status
= 200, contentlength
= 0;
393 unsigned int static_content
= 0;
395 if (fgets(buf
, sizeof(buf
), ser
->f
)) {
398 while(*uri
&& (*uri
> 32))
405 /* Skip white space */
406 while (*uri
&& (*uri
< 33))
411 while (*c
&& (*c
> 32))
418 while (fgets(cookie
, sizeof(cookie
), ser
->f
)) {
419 /* Trim trailing characters */
420 while(!ast_strlen_zero(cookie
) && (cookie
[strlen(cookie
) - 1] < 33)) {
421 cookie
[strlen(cookie
) - 1] = '\0';
423 if (ast_strlen_zero(cookie
))
425 if (!strncasecmp(cookie
, "Cookie: ", 8)) {
427 /* TODO - The cookie parsing code below seems to work
428 in IE6 and FireFox 1.5. However, it is not entirely
429 correct, and therefore may not work in all
431 For more details see RFC 2109 and RFC 2965 */
433 /* FireFox cookie strings look like:
434 Cookie: mansession_id="********"
435 InternetExplorer's look like:
436 Cookie: $Version="1"; mansession_id="********" */
438 /* If we got a FireFox cookie string, the name's right
442 /* If we got an IE cookie string, we need to skip to
443 past the version to get to the name */
445 vname
= strchr(vname
, ';');
454 vval
= strchr(vname
, '=');
456 /* Ditch the = and the quotes */
461 vval
[strlen(vval
) - 1] = '\0';
462 var
= ast_variable_new(vname
, vval
);
476 if (!strcasecmp(buf
, "get"))
477 c
= handle_uri(&ser
->requestor
, uri
, &status
, &title
, &contentlength
, &vars
, &static_content
);
479 c
= ast_http_error(501, "Not Implemented", NULL
, "Attempt to use unimplemented / unsupported method");\
481 c
= ast_http_error(400, "Bad Request", NULL
, "Invalid Request");
483 /* If they aren't mopped up already, clean up the cookies */
485 ast_variables_destroy(vars
);
488 c
= ast_http_error(500, "Internal Error", NULL
, "Internal Server Error");
491 strftime(timebuf
, sizeof(timebuf
), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t
));
492 ast_cli(ser
->fd
, "HTTP/1.1 %d %s\r\n", status
, title
? title
: "OK");
493 ast_cli(ser
->fd
, "Server: Asterisk/%s\r\n", ASTERISK_VERSION
);
494 ast_cli(ser
->fd
, "Date: %s\r\n", timebuf
);
495 ast_cli(ser
->fd
, "Connection: close\r\n");
497 ast_cli(ser
->fd
, "Cache-Control: no-cache, no-store\r\n");
498 /* We set the no-cache headers only for dynamic content.
499 * If you want to make sure the static file you requested is not from cache,
500 * append a random variable to your GET request. Ex: 'something.html?r=109987734'
505 tmp
= strstr(c
, "\r\n\r\n");
507 ast_cli(ser
->fd
, "Content-length: %d\r\n", contentlength
);
508 write(ser
->fd
, c
, (tmp
+ 4 - c
));
509 write(ser
->fd
, tmp
+ 4, contentlength
);
512 ast_cli(ser
->fd
, "%s", c
);
523 static void *http_root(void *data
)
526 struct sockaddr_in sin
;
528 struct ast_http_server_instance
*ser
;
535 ast_wait_for_input(httpfd
, -1);
536 sinlen
= sizeof(sin
);
537 fd
= accept(httpfd
, (struct sockaddr
*)&sin
, &sinlen
);
539 if ((errno
!= EAGAIN
) && (errno
!= EINTR
))
540 ast_log(LOG_WARNING
, "Accept failed: %s\n", strerror(errno
));
543 ser
= ast_calloc(1, sizeof(*ser
));
545 ast_log(LOG_WARNING
, "No memory for new session: %s\n", strerror(errno
));
549 flags
= fcntl(fd
, F_GETFL
);
550 fcntl(fd
, F_SETFL
, flags
& ~O_NONBLOCK
);
552 memcpy(&ser
->requestor
, &sin
, sizeof(ser
->requestor
));
553 if ((ser
->f
= fdopen(ser
->fd
, "w+"))) {
554 pthread_attr_init(&attr
);
555 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
557 if (ast_pthread_create_background(&launched
, &attr
, ast_httpd_helper_thread
, ser
)) {
558 ast_log(LOG_WARNING
, "Unable to launch helper thread: %s\n", strerror(errno
));
562 pthread_attr_destroy(&attr
);
564 ast_log(LOG_WARNING
, "fdopen failed!\n");
572 char *ast_http_setcookie(const char *var
, const char *val
, int expires
, char *buf
, size_t buflen
)
576 ast_build_string(&c
, &buflen
, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var
, val
);
578 ast_build_string(&c
, &buflen
, "; Max-Age=%d", expires
);
579 ast_build_string(&c
, &buflen
, "\r\n");
584 static void http_server_start(struct sockaddr_in
*sin
)
589 /* Do nothing if nothing has changed */
590 if (!memcmp(&oldsin
, sin
, sizeof(oldsin
))) {
591 ast_log(LOG_DEBUG
, "Nothing changed in http\n");
595 memcpy(&oldsin
, sin
, sizeof(oldsin
));
597 /* Shutdown a running server if there is one */
598 if (master
!= AST_PTHREADT_NULL
) {
599 pthread_cancel(master
);
600 pthread_kill(master
, SIGURG
);
601 pthread_join(master
, NULL
);
607 /* If there's no new server, stop here */
608 if (!sin
->sin_family
)
612 httpfd
= socket(AF_INET
, SOCK_STREAM
, 0);
614 ast_log(LOG_WARNING
, "Unable to allocate socket: %s\n", strerror(errno
));
618 setsockopt(httpfd
, SOL_SOCKET
, SO_REUSEADDR
, &x
, sizeof(x
));
619 if (bind(httpfd
, (struct sockaddr
*)sin
, sizeof(*sin
))) {
620 ast_log(LOG_NOTICE
, "Unable to bind http server to %s:%d: %s\n",
621 ast_inet_ntoa(sin
->sin_addr
), ntohs(sin
->sin_port
),
627 if (listen(httpfd
, 10)) {
628 ast_log(LOG_NOTICE
, "Unable to listen!\n");
633 flags
= fcntl(httpfd
, F_GETFL
);
634 fcntl(httpfd
, F_SETFL
, flags
| O_NONBLOCK
);
635 if (ast_pthread_create_background(&master
, NULL
, http_root
, NULL
)) {
636 ast_log(LOG_NOTICE
, "Unable to launch http server on %s:%d: %s\n",
637 ast_inet_ntoa(sin
->sin_addr
), ntohs(sin
->sin_port
),
644 static int __ast_http_load(int reload
)
646 struct ast_config
*cfg
;
647 struct ast_variable
*v
;
649 int newenablestatic
=0;
650 struct sockaddr_in sin
;
652 struct ast_hostent ahp
;
653 char newprefix
[MAX_PREFIX
];
655 memset(&sin
, 0, sizeof(sin
));
656 sin
.sin_port
= htons(8088);
658 strcpy(newprefix
, DEFAULT_PREFIX
);
660 cfg
= ast_config_load("http.conf");
662 v
= ast_variable_browse(cfg
, "general");
664 if (!strcasecmp(v
->name
, "enabled"))
665 enabled
= ast_true(v
->value
);
666 else if (!strcasecmp(v
->name
, "enablestatic"))
667 newenablestatic
= ast_true(v
->value
);
668 else if (!strcasecmp(v
->name
, "bindport"))
669 sin
.sin_port
= ntohs(atoi(v
->value
));
670 else if (!strcasecmp(v
->name
, "bindaddr")) {
671 if ((hp
= ast_gethostbyname(v
->value
, &ahp
))) {
672 memcpy(&sin
.sin_addr
, hp
->h_addr
, sizeof(sin
.sin_addr
));
674 ast_log(LOG_WARNING
, "Invalid bind address '%s'\n", v
->value
);
676 } else if (!strcasecmp(v
->name
, "prefix")) {
677 if (!ast_strlen_zero(v
->value
)) {
679 ast_copy_string(newprefix
+ 1, v
->value
, sizeof(newprefix
) - 1);
687 ast_config_destroy(cfg
);
690 sin
.sin_family
= AF_INET
;
691 if (strcmp(prefix
, newprefix
)) {
692 ast_copy_string(prefix
, newprefix
, sizeof(prefix
));
693 prefix_len
= strlen(prefix
);
695 enablestatic
= newenablestatic
;
697 http_server_start(&sin
);
703 static int handle_show_http(int fd
, int argc
, char *argv
[])
705 struct ast_http_uri
*urih
;
708 return RESULT_SHOWUSAGE
;
710 ast_cli(fd
, "HTTP Server Status:\n");
711 ast_cli(fd
, "Prefix: %s\n", prefix
);
712 if (oldsin
.sin_family
)
713 ast_cli(fd
, "Server Enabled and Bound to %s:%d\n\n",
714 ast_inet_ntoa(oldsin
.sin_addr
),
715 ntohs(oldsin
.sin_port
));
717 ast_cli(fd
, "Server Disabled\n\n");
718 ast_cli(fd
, "Enabled URI's:\n");
719 ast_rwlock_rdlock(&uris_lock
);
722 ast_cli(fd
, "%s/%s%s => %s\n", prefix
, urih
->uri
, (urih
->has_subtree
? "/..." : "" ), urih
->description
);
726 ast_cli(fd
, "None.\n");
727 ast_rwlock_unlock(&uris_lock
);
729 return RESULT_SUCCESS
;
732 int ast_http_reload(void)
734 return __ast_http_load(1);
737 static char show_http_help
[] =
738 "Usage: http show status\n"
739 " Lists status of internal HTTP engine\n";
741 static struct ast_cli_entry cli_http
[] = {
742 { { "http", "show", "status", NULL
},
743 handle_show_http
, "Display HTTP server status",
747 int ast_http_init(void)
749 ast_http_uri_link(&statusuri
);
750 ast_http_uri_link(&staticuri
);
751 ast_cli_register_multiple(cli_http
, sizeof(cli_http
) / sizeof(struct ast_cli_entry
));
753 return __ast_http_load(0);