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");
500 tmp
= strstr(c
, "\r\n\r\n");
502 ast_cli(ser
->fd
, "Content-length: %d\r\n", contentlength
);
503 write(ser
->fd
, c
, (tmp
+ 4 - c
));
504 write(ser
->fd
, tmp
+ 4, contentlength
);
507 ast_cli(ser
->fd
, "%s", c
);
518 static void *http_root(void *data
)
521 struct sockaddr_in sin
;
523 struct ast_http_server_instance
*ser
;
530 ast_wait_for_input(httpfd
, -1);
531 sinlen
= sizeof(sin
);
532 fd
= accept(httpfd
, (struct sockaddr
*)&sin
, &sinlen
);
534 if ((errno
!= EAGAIN
) && (errno
!= EINTR
))
535 ast_log(LOG_WARNING
, "Accept failed: %s\n", strerror(errno
));
538 ser
= ast_calloc(1, sizeof(*ser
));
540 ast_log(LOG_WARNING
, "No memory for new session: %s\n", strerror(errno
));
544 flags
= fcntl(fd
, F_GETFL
);
545 fcntl(fd
, F_SETFL
, flags
& ~O_NONBLOCK
);
547 memcpy(&ser
->requestor
, &sin
, sizeof(ser
->requestor
));
548 if ((ser
->f
= fdopen(ser
->fd
, "w+"))) {
549 pthread_attr_init(&attr
);
550 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
552 if (ast_pthread_create_background(&launched
, &attr
, ast_httpd_helper_thread
, ser
)) {
553 ast_log(LOG_WARNING
, "Unable to launch helper thread: %s\n", strerror(errno
));
557 pthread_attr_destroy(&attr
);
559 ast_log(LOG_WARNING
, "fdopen failed!\n");
567 char *ast_http_setcookie(const char *var
, const char *val
, int expires
, char *buf
, size_t buflen
)
571 ast_build_string(&c
, &buflen
, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var
, val
);
573 ast_build_string(&c
, &buflen
, "; Max-Age=%d", expires
);
574 ast_build_string(&c
, &buflen
, "\r\n");
579 static void http_server_start(struct sockaddr_in
*sin
)
584 /* Do nothing if nothing has changed */
585 if (!memcmp(&oldsin
, sin
, sizeof(oldsin
))) {
586 ast_log(LOG_DEBUG
, "Nothing changed in http\n");
590 memcpy(&oldsin
, sin
, sizeof(oldsin
));
592 /* Shutdown a running server if there is one */
593 if (master
!= AST_PTHREADT_NULL
) {
594 pthread_cancel(master
);
595 pthread_kill(master
, SIGURG
);
596 pthread_join(master
, NULL
);
602 /* If there's no new server, stop here */
603 if (!sin
->sin_family
)
607 httpfd
= socket(AF_INET
, SOCK_STREAM
, 0);
609 ast_log(LOG_WARNING
, "Unable to allocate socket: %s\n", strerror(errno
));
613 setsockopt(httpfd
, SOL_SOCKET
, SO_REUSEADDR
, &x
, sizeof(x
));
614 if (bind(httpfd
, (struct sockaddr
*)sin
, sizeof(*sin
))) {
615 ast_log(LOG_NOTICE
, "Unable to bind http server to %s:%d: %s\n",
616 ast_inet_ntoa(sin
->sin_addr
), ntohs(sin
->sin_port
),
622 if (listen(httpfd
, 10)) {
623 ast_log(LOG_NOTICE
, "Unable to listen!\n");
628 flags
= fcntl(httpfd
, F_GETFL
);
629 fcntl(httpfd
, F_SETFL
, flags
| O_NONBLOCK
);
630 if (ast_pthread_create_background(&master
, NULL
, http_root
, NULL
)) {
631 ast_log(LOG_NOTICE
, "Unable to launch http server on %s:%d: %s\n",
632 ast_inet_ntoa(sin
->sin_addr
), ntohs(sin
->sin_port
),
639 static int __ast_http_load(int reload
)
641 struct ast_config
*cfg
;
642 struct ast_variable
*v
;
644 int newenablestatic
=0;
645 struct sockaddr_in sin
;
647 struct ast_hostent ahp
;
648 char newprefix
[MAX_PREFIX
];
650 memset(&sin
, 0, sizeof(sin
));
651 sin
.sin_port
= htons(8088);
653 strcpy(newprefix
, DEFAULT_PREFIX
);
655 cfg
= ast_config_load("http.conf");
657 v
= ast_variable_browse(cfg
, "general");
659 if (!strcasecmp(v
->name
, "enabled"))
660 enabled
= ast_true(v
->value
);
661 else if (!strcasecmp(v
->name
, "enablestatic"))
662 newenablestatic
= ast_true(v
->value
);
663 else if (!strcasecmp(v
->name
, "bindport"))
664 sin
.sin_port
= ntohs(atoi(v
->value
));
665 else if (!strcasecmp(v
->name
, "bindaddr")) {
666 if ((hp
= ast_gethostbyname(v
->value
, &ahp
))) {
667 memcpy(&sin
.sin_addr
, hp
->h_addr
, sizeof(sin
.sin_addr
));
669 ast_log(LOG_WARNING
, "Invalid bind address '%s'\n", v
->value
);
671 } else if (!strcasecmp(v
->name
, "prefix")) {
672 if (!ast_strlen_zero(v
->value
)) {
674 ast_copy_string(newprefix
+ 1, v
->value
, sizeof(newprefix
) - 1);
682 ast_config_destroy(cfg
);
685 sin
.sin_family
= AF_INET
;
686 if (strcmp(prefix
, newprefix
)) {
687 ast_copy_string(prefix
, newprefix
, sizeof(prefix
));
688 prefix_len
= strlen(prefix
);
690 enablestatic
= newenablestatic
;
692 http_server_start(&sin
);
698 static int handle_show_http(int fd
, int argc
, char *argv
[])
700 struct ast_http_uri
*urih
;
703 return RESULT_SHOWUSAGE
;
705 ast_cli(fd
, "HTTP Server Status:\n");
706 ast_cli(fd
, "Prefix: %s\n", prefix
);
707 if (oldsin
.sin_family
)
708 ast_cli(fd
, "Server Enabled and Bound to %s:%d\n\n",
709 ast_inet_ntoa(oldsin
.sin_addr
),
710 ntohs(oldsin
.sin_port
));
712 ast_cli(fd
, "Server Disabled\n\n");
713 ast_cli(fd
, "Enabled URI's:\n");
714 ast_rwlock_rdlock(&uris_lock
);
717 ast_cli(fd
, "%s/%s%s => %s\n", prefix
, urih
->uri
, (urih
->has_subtree
? "/..." : "" ), urih
->description
);
721 ast_cli(fd
, "None.\n");
722 ast_rwlock_unlock(&uris_lock
);
724 return RESULT_SUCCESS
;
727 int ast_http_reload(void)
729 return __ast_http_load(1);
732 static char show_http_help
[] =
733 "Usage: http show status\n"
734 " Lists status of internal HTTP engine\n";
736 static struct ast_cli_entry cli_http
[] = {
737 { { "http", "show", "status", NULL
},
738 handle_show_http
, "Display HTTP server status",
742 int ast_http_init(void)
744 ast_http_uri_link(&statusuri
);
745 ast_http_uri_link(&staticuri
);
746 ast_cli_register_multiple(cli_http
, sizeof(cli_http
) / sizeof(struct ast_cli_entry
));
748 return __ast_http_load(0);