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
));
543 ast_log(LOG_WARNING
, "fdopen failed!\n");
551 char *ast_http_setcookie(const char *var
, const char *val
, int expires
, char *buf
, size_t buflen
)
555 ast_build_string(&c
, &buflen
, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var
, val
);
557 ast_build_string(&c
, &buflen
, "; Max-Age=%d", expires
);
558 ast_build_string(&c
, &buflen
, "\r\n");
563 static void http_server_start(struct sockaddr_in
*sin
)
568 /* Do nothing if nothing has changed */
569 if (!memcmp(&oldsin
, sin
, sizeof(oldsin
))) {
570 ast_log(LOG_DEBUG
, "Nothing changed in http\n");
574 memcpy(&oldsin
, sin
, sizeof(oldsin
));
576 /* Shutdown a running server if there is one */
577 if (master
!= AST_PTHREADT_NULL
) {
578 pthread_cancel(master
);
579 pthread_kill(master
, SIGURG
);
580 pthread_join(master
, NULL
);
586 /* If there's no new server, stop here */
587 if (!sin
->sin_family
)
591 httpfd
= socket(AF_INET
, SOCK_STREAM
, 0);
593 ast_log(LOG_WARNING
, "Unable to allocate socket: %s\n", strerror(errno
));
597 setsockopt(httpfd
, SOL_SOCKET
, SO_REUSEADDR
, &x
, sizeof(x
));
598 if (bind(httpfd
, (struct sockaddr
*)sin
, sizeof(*sin
))) {
599 ast_log(LOG_NOTICE
, "Unable to bind http server to %s:%d: %s\n",
600 ast_inet_ntoa(sin
->sin_addr
), ntohs(sin
->sin_port
),
606 if (listen(httpfd
, 10)) {
607 ast_log(LOG_NOTICE
, "Unable to listen!\n");
612 flags
= fcntl(httpfd
, F_GETFL
);
613 fcntl(httpfd
, F_SETFL
, flags
| O_NONBLOCK
);
614 if (ast_pthread_create_background(&master
, NULL
, http_root
, NULL
)) {
615 ast_log(LOG_NOTICE
, "Unable to launch http server on %s:%d: %s\n",
616 ast_inet_ntoa(sin
->sin_addr
), ntohs(sin
->sin_port
),
623 static int __ast_http_load(int reload
)
625 struct ast_config
*cfg
;
626 struct ast_variable
*v
;
628 int newenablestatic
=0;
629 struct sockaddr_in sin
;
631 struct ast_hostent ahp
;
632 char newprefix
[MAX_PREFIX
];
634 memset(&sin
, 0, sizeof(sin
));
636 strcpy(newprefix
, DEFAULT_PREFIX
);
637 cfg
= ast_config_load("http.conf");
639 v
= ast_variable_browse(cfg
, "general");
641 if (!strcasecmp(v
->name
, "enabled"))
642 enabled
= ast_true(v
->value
);
643 else if (!strcasecmp(v
->name
, "enablestatic"))
644 newenablestatic
= ast_true(v
->value
);
645 else if (!strcasecmp(v
->name
, "bindport"))
646 sin
.sin_port
= ntohs(atoi(v
->value
));
647 else if (!strcasecmp(v
->name
, "bindaddr")) {
648 if ((hp
= ast_gethostbyname(v
->value
, &ahp
))) {
649 memcpy(&sin
.sin_addr
, hp
->h_addr
, sizeof(sin
.sin_addr
));
651 ast_log(LOG_WARNING
, "Invalid bind address '%s'\n", v
->value
);
653 } else if (!strcasecmp(v
->name
, "prefix")) {
654 if (!ast_strlen_zero(v
->value
)) {
656 ast_copy_string(newprefix
+ 1, v
->value
, sizeof(newprefix
) - 1);
664 ast_config_destroy(cfg
);
667 sin
.sin_family
= AF_INET
;
668 if (strcmp(prefix
, newprefix
)) {
669 ast_copy_string(prefix
, newprefix
, sizeof(prefix
));
670 prefix_len
= strlen(prefix
);
672 enablestatic
= newenablestatic
;
673 http_server_start(&sin
);
677 static int handle_show_http(int fd
, int argc
, char *argv
[])
679 struct ast_http_uri
*urih
;
681 return RESULT_SHOWUSAGE
;
682 ast_cli(fd
, "HTTP Server Status:\n");
683 ast_cli(fd
, "Prefix: %s\n", prefix
);
684 if (oldsin
.sin_family
)
685 ast_cli(fd
, "Server Enabled and Bound to %s:%d\n\n",
686 ast_inet_ntoa(oldsin
.sin_addr
),
687 ntohs(oldsin
.sin_port
));
689 ast_cli(fd
, "Server Disabled\n\n");
690 ast_cli(fd
, "Enabled URI's:\n");
691 ast_rwlock_rdlock(&uris_lock
);
694 ast_cli(fd
, "%s/%s%s => %s\n", prefix
, urih
->uri
, (urih
->has_subtree
? "/..." : "" ), urih
->description
);
698 ast_cli(fd
, "None.\n");
699 ast_rwlock_unlock(&uris_lock
);
700 return RESULT_SUCCESS
;
703 int ast_http_reload(void)
705 return __ast_http_load(1);
708 static char show_http_help
[] =
709 "Usage: http show status\n"
710 " Lists status of internal HTTP engine\n";
712 static struct ast_cli_entry cli_http
[] = {
713 { { "http", "show", "status", NULL
},
714 handle_show_http
, "Display HTTP server status",
718 int ast_http_init(void)
720 ast_http_uri_link(&statusuri
);
721 ast_http_uri_link(&staticuri
);
722 ast_cli_register_multiple(cli_http
, sizeof(cli_http
) / sizeof(struct ast_cli_entry
));
723 return __ast_http_load(0);