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
, "The requested URL was not found on this server.");
174 *title
= strdup("Access Denied");
175 return ast_http_error(403, "Access Denied", NULL
, "You do not have permission to access the requested URL.");
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", "Redirecting to /static/index.html.");
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 struct ast_variable
*parse_cookies(char *cookies
)
385 struct ast_variable
*vars
= NULL
, *var
;
390 while ((cur
= strsep(&cookies
, ";"))) {
396 if (ast_strlen_zero(name
) || ast_strlen_zero(val
)) {
400 name
= ast_strip(name
);
401 val
= ast_strip_quoted(val
, "\"", "\"");
403 if (ast_strlen_zero(name
) || ast_strlen_zero(val
)) {
408 ast_log(LOG_DEBUG
, "mmm ... cookie! Name: '%s' Value: '%s'\n", name
, val
);
411 var
= ast_variable_new(name
, val
);
419 static void *ast_httpd_helper_thread(void *data
)
424 struct ast_http_server_instance
*ser
= data
;
425 struct ast_variable
*vars
= NULL
;
426 char *uri
, *c
, *title
=NULL
;
427 int status
= 200, contentlength
= 0;
429 unsigned int static_content
= 0;
431 if (fgets(buf
, sizeof(buf
), ser
->f
)) {
434 while(*uri
&& (*uri
> 32))
441 /* Skip white space */
442 while (*uri
&& (*uri
< 33))
447 while (*c
&& (*c
> 32))
454 while (fgets(cookie
, sizeof(cookie
), ser
->f
)) {
455 /* Trim trailing characters */
456 while(!ast_strlen_zero(cookie
) && (cookie
[strlen(cookie
) - 1] < 33)) {
457 cookie
[strlen(cookie
) - 1] = '\0';
459 if (ast_strlen_zero(cookie
))
461 if (!strncasecmp(cookie
, "Cookie: ", 8)) {
462 vars
= parse_cookies(cookie
);
467 if (!strcasecmp(buf
, "get"))
468 c
= handle_uri(&ser
->requestor
, uri
, &status
, &title
, &contentlength
, &vars
, &static_content
);
470 c
= ast_http_error(501, "Not Implemented", NULL
, "Attempt to use unimplemented / unsupported method");\
472 c
= ast_http_error(400, "Bad Request", NULL
, "Invalid Request");
474 /* If they aren't mopped up already, clean up the cookies */
476 ast_variables_destroy(vars
);
479 c
= ast_http_error(500, "Internal Error", NULL
, "Internal Server Error");
482 strftime(timebuf
, sizeof(timebuf
), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t
));
483 ast_cli(ser
->fd
, "HTTP/1.1 %d %s\r\n", status
, title
? title
: "OK");
484 ast_cli(ser
->fd
, "Server: Asterisk/%s\r\n", ASTERISK_VERSION
);
485 ast_cli(ser
->fd
, "Date: %s\r\n", timebuf
);
486 ast_cli(ser
->fd
, "Connection: close\r\n");
488 ast_cli(ser
->fd
, "Cache-Control: no-cache, no-store\r\n");
489 /* We set the no-cache headers only for dynamic content.
490 * If you want to make sure the static file you requested is not from cache,
491 * append a random variable to your GET request. Ex: 'something.html?r=109987734'
496 tmp
= strstr(c
, "\r\n\r\n");
498 ast_cli(ser
->fd
, "Content-length: %d\r\n", contentlength
);
499 write(ser
->fd
, c
, (tmp
+ 4 - c
));
500 write(ser
->fd
, tmp
+ 4, contentlength
);
503 ast_cli(ser
->fd
, "%s", c
);
514 static void *http_root(void *data
)
517 struct sockaddr_in sin
;
519 struct ast_http_server_instance
*ser
;
526 ast_wait_for_input(httpfd
, -1);
527 sinlen
= sizeof(sin
);
528 fd
= accept(httpfd
, (struct sockaddr
*)&sin
, &sinlen
);
530 if ((errno
!= EAGAIN
) && (errno
!= EINTR
))
531 ast_log(LOG_WARNING
, "Accept failed: %s\n", strerror(errno
));
534 ser
= ast_calloc(1, sizeof(*ser
));
536 ast_log(LOG_WARNING
, "No memory for new session: %s\n", strerror(errno
));
540 flags
= fcntl(fd
, F_GETFL
);
541 fcntl(fd
, F_SETFL
, flags
& ~O_NONBLOCK
);
543 memcpy(&ser
->requestor
, &sin
, sizeof(ser
->requestor
));
544 if ((ser
->f
= fdopen(ser
->fd
, "w+"))) {
545 pthread_attr_init(&attr
);
546 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
548 if (ast_pthread_create_background(&launched
, &attr
, ast_httpd_helper_thread
, ser
)) {
549 ast_log(LOG_WARNING
, "Unable to launch helper thread: %s\n", strerror(errno
));
553 pthread_attr_destroy(&attr
);
555 ast_log(LOG_WARNING
, "fdopen failed!\n");
563 char *ast_http_setcookie(const char *var
, const char *val
, int expires
, char *buf
, size_t buflen
)
567 ast_build_string(&c
, &buflen
, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var
, val
);
569 ast_build_string(&c
, &buflen
, "; Max-Age=%d", expires
);
570 ast_build_string(&c
, &buflen
, "\r\n");
575 static void http_server_start(struct sockaddr_in
*sin
)
580 /* Do nothing if nothing has changed */
581 if (!memcmp(&oldsin
, sin
, sizeof(oldsin
))) {
582 ast_log(LOG_DEBUG
, "Nothing changed in http\n");
586 memcpy(&oldsin
, sin
, sizeof(oldsin
));
588 /* Shutdown a running server if there is one */
589 if (master
!= AST_PTHREADT_NULL
) {
590 pthread_cancel(master
);
591 pthread_kill(master
, SIGURG
);
592 pthread_join(master
, NULL
);
598 /* If there's no new server, stop here */
599 if (!sin
->sin_family
)
603 httpfd
= socket(AF_INET
, SOCK_STREAM
, 0);
605 ast_log(LOG_WARNING
, "Unable to allocate socket: %s\n", strerror(errno
));
609 setsockopt(httpfd
, SOL_SOCKET
, SO_REUSEADDR
, &x
, sizeof(x
));
610 if (bind(httpfd
, (struct sockaddr
*)sin
, sizeof(*sin
))) {
611 ast_log(LOG_NOTICE
, "Unable to bind http server to %s:%d: %s\n",
612 ast_inet_ntoa(sin
->sin_addr
), ntohs(sin
->sin_port
),
618 if (listen(httpfd
, 10)) {
619 ast_log(LOG_NOTICE
, "Unable to listen!\n");
624 flags
= fcntl(httpfd
, F_GETFL
);
625 fcntl(httpfd
, F_SETFL
, flags
| O_NONBLOCK
);
626 if (ast_pthread_create_background(&master
, NULL
, http_root
, NULL
)) {
627 ast_log(LOG_NOTICE
, "Unable to launch http server on %s:%d: %s\n",
628 ast_inet_ntoa(sin
->sin_addr
), ntohs(sin
->sin_port
),
635 static int __ast_http_load(int reload
)
637 struct ast_config
*cfg
;
638 struct ast_variable
*v
;
640 int newenablestatic
=0;
641 struct sockaddr_in sin
;
643 struct ast_hostent ahp
;
644 char newprefix
[MAX_PREFIX
];
646 memset(&sin
, 0, sizeof(sin
));
647 sin
.sin_port
= htons(8088);
649 strcpy(newprefix
, DEFAULT_PREFIX
);
651 cfg
= ast_config_load("http.conf");
653 v
= ast_variable_browse(cfg
, "general");
655 if (!strcasecmp(v
->name
, "enabled"))
656 enabled
= ast_true(v
->value
);
657 else if (!strcasecmp(v
->name
, "enablestatic"))
658 newenablestatic
= ast_true(v
->value
);
659 else if (!strcasecmp(v
->name
, "bindport"))
660 sin
.sin_port
= ntohs(atoi(v
->value
));
661 else if (!strcasecmp(v
->name
, "bindaddr")) {
662 if ((hp
= ast_gethostbyname(v
->value
, &ahp
))) {
663 memcpy(&sin
.sin_addr
, hp
->h_addr
, sizeof(sin
.sin_addr
));
665 ast_log(LOG_WARNING
, "Invalid bind address '%s'\n", v
->value
);
667 } else if (!strcasecmp(v
->name
, "prefix")) {
668 if (!ast_strlen_zero(v
->value
)) {
670 ast_copy_string(newprefix
+ 1, v
->value
, sizeof(newprefix
) - 1);
678 ast_config_destroy(cfg
);
681 sin
.sin_family
= AF_INET
;
682 if (strcmp(prefix
, newprefix
)) {
683 ast_copy_string(prefix
, newprefix
, sizeof(prefix
));
684 prefix_len
= strlen(prefix
);
686 enablestatic
= newenablestatic
;
688 http_server_start(&sin
);
694 static int handle_show_http(int fd
, int argc
, char *argv
[])
696 struct ast_http_uri
*urih
;
699 return RESULT_SHOWUSAGE
;
701 ast_cli(fd
, "HTTP Server Status:\n");
702 ast_cli(fd
, "Prefix: %s\n", prefix
);
703 if (oldsin
.sin_family
)
704 ast_cli(fd
, "Server Enabled and Bound to %s:%d\n\n",
705 ast_inet_ntoa(oldsin
.sin_addr
),
706 ntohs(oldsin
.sin_port
));
708 ast_cli(fd
, "Server Disabled\n\n");
709 ast_cli(fd
, "Enabled URI's:\n");
710 ast_rwlock_rdlock(&uris_lock
);
713 ast_cli(fd
, "%s/%s%s => %s\n", prefix
, urih
->uri
, (urih
->has_subtree
? "/..." : "" ), urih
->description
);
717 ast_cli(fd
, "None.\n");
718 ast_rwlock_unlock(&uris_lock
);
720 return RESULT_SUCCESS
;
723 int ast_http_reload(void)
725 return __ast_http_load(1);
728 static char show_http_help
[] =
729 "Usage: http show status\n"
730 " Lists status of internal HTTP engine\n";
732 static struct ast_cli_entry cli_http
[] = {
733 { { "http", "show", "status", NULL
},
734 handle_show_http
, "Display HTTP server status",
738 int ast_http_init(void)
740 ast_http_uri_link(&statusuri
);
741 ast_http_uri_link(&staticuri
);
742 ast_cli_register_multiple(cli_http
, sizeof(cli_http
) / sizeof(struct ast_cli_entry
));
744 return __ast_http_load(0);