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 static struct ast_http_uri
*uris
;
69 static int httpfd
= -1;
70 static pthread_t master
= AST_PTHREADT_NULL
;
71 static char prefix
[MAX_PREFIX
];
72 static int prefix_len
= 0;
73 static struct sockaddr_in oldsin
;
74 static int enablestatic
=0;
76 /*! \brief Limit the kinds of files we're willing to serve up */
81 { "png", "image/png" },
82 { "jpg", "image/jpeg" },
83 { "js", "application/x-javascript" },
84 { "wav", "audio/x-wav" },
85 { "mp3", "audio/mpeg" },
88 static char *ftype2mtype(const char *ftype
, char *wkspace
, int wkspacelen
)
92 for (x
=0;x
<sizeof(mimetypes
) / sizeof(mimetypes
[0]); x
++) {
93 if (!strcasecmp(ftype
, mimetypes
[x
].ext
))
94 return mimetypes
[x
].mtype
;
97 snprintf(wkspace
, wkspacelen
, "text/%s", ftype
? ftype
: "plain");
101 static char *static_callback(struct sockaddr_in
*req
, const char *uri
, struct ast_variable
*vars
, int *status
, char **title
, int *contentlength
)
113 /* Yuck. I'm not really sold on this, but if you don't deliver static content it makes your configuration
114 substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
115 if (!enablestatic
|| ast_strlen_zero(uri
))
117 /* Disallow any funny filenames at all */
118 if ((uri
[0] < 33) || strchr("./|~@#$%^&*() \t", uri
[0]))
120 if (strstr(uri
, "/.."))
123 if ((ftype
= strrchr(uri
, '.')))
125 mtype
=ftype2mtype(ftype
, wkspace
, sizeof(wkspace
));
127 /* Cap maximum length */
128 len
= strlen(uri
) + strlen(ast_config_AST_DATA_DIR
) + strlen("/static-http/") + 5;
133 sprintf(path
, "%s/static-http/%s", ast_config_AST_DATA_DIR
, uri
);
136 if (S_ISDIR(st
.st_mode
))
138 fd
= open(path
, O_RDONLY
);
142 len
= st
.st_size
+ strlen(mtype
) + 40;
147 sprintf(c
, "Content-type: %s\r\n\r\n", mtype
);
149 *contentlength
= read(fd
, c
, st
.st_size
);
150 if (*contentlength
< 0) {
160 *title
= strdup("Not Found");
161 return ast_http_error(404, "Not Found", NULL
, "Nothing to see here. Move along.");
165 *title
= strdup("Access Denied");
166 return ast_http_error(403, "Access Denied", NULL
, "Sorry, I cannot let you do that, Dave.");
170 static char *httpstatus_callback(struct sockaddr_in
*req
, const char *uri
, struct ast_variable
*vars
, int *status
, char **title
, int *contentlength
)
173 size_t reslen
= sizeof(result
);
175 struct ast_variable
*v
;
176 char iabuf
[INET_ADDRSTRLEN
];
178 ast_build_string(&c
, &reslen
,
180 "<title>Asterisk HTTP Status</title>\r\n"
181 "<body bgcolor=\"#ffffff\">\r\n"
182 "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
183 "<h2> Asterisk™ HTTP Status</h2></td></tr>\r\n");
185 ast_build_string(&c
, &reslen
, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix
);
186 ast_build_string(&c
, &reslen
, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
187 ast_inet_ntoa(iabuf
, sizeof(iabuf
), oldsin
.sin_addr
));
188 ast_build_string(&c
, &reslen
, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
189 ntohs(oldsin
.sin_port
));
190 ast_build_string(&c
, &reslen
, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
193 if (strncasecmp(v
->name
, "cookie_", 7))
194 ast_build_string(&c
, &reslen
, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v
->name
, v
->value
);
197 ast_build_string(&c
, &reslen
, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
200 if (!strncasecmp(v
->name
, "cookie_", 7))
201 ast_build_string(&c
, &reslen
, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v
->name
, v
->value
);
204 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");
205 return strdup(result
);
208 static struct ast_http_uri statusuri
= {
209 .callback
= httpstatus_callback
,
210 .description
= "Asterisk HTTP General Status",
215 static struct ast_http_uri staticuri
= {
216 .callback
= static_callback
,
217 .description
= "Asterisk HTTP Static Delivery",
222 char *ast_http_error(int status
, const char *title
, const char *extra_header
, const char *text
)
226 "Content-type: text/html\r\n"
229 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
231 "<title>%d %s</title>\r\n"
236 "<address>Asterisk Server</address>\r\n"
237 "</body></html>\r\n",
238 (extra_header
? extra_header
: ""), status
, title
, title
, text
);
242 int ast_http_uri_link(struct ast_http_uri
*urih
)
244 struct ast_http_uri
*prev
=uris
;
245 if (!uris
|| strlen(uris
->uri
) <= strlen(urih
->uri
)) {
249 while (prev
->next
&& (strlen(prev
->next
->uri
) > strlen(urih
->uri
)))
252 urih
->next
= prev
->next
;
258 void ast_http_uri_unlink(struct ast_http_uri
*urih
)
260 struct ast_http_uri
*prev
= uris
;
267 if (prev
->next
== urih
) {
268 prev
->next
= urih
->next
;
275 static char *handle_uri(struct sockaddr_in
*sin
, char *uri
, int *status
, char **title
, int *contentlength
, struct ast_variable
**cookies
)
282 struct ast_http_uri
*urih
=NULL
;
284 struct ast_variable
*vars
=NULL
, *v
, *prev
= NULL
;
287 params
= strchr(uri
, '?');
291 while ((var
= strsep(¶ms
, "&"))) {
292 val
= strchr(var
, '=');
300 if ((v
= ast_variable_new(var
, val
))) {
310 prev
->next
= *cookies
;
315 if (!strncasecmp(uri
, prefix
, prefix_len
)) {
317 if (!*uri
|| (*uri
== '/')) {
322 len
= strlen(urih
->uri
);
323 if (!strncasecmp(urih
->uri
, uri
, len
)) {
324 if (!uri
[len
] || uri
[len
] == '/') {
328 if (!*turi
|| urih
->has_subtree
) {
339 c
= urih
->callback(sin
, uri
, vars
, status
, title
, contentlength
);
340 ast_variables_destroy(vars
);
341 } else if (ast_strlen_zero(uri
) && ast_strlen_zero(prefix
)) {
342 /* Special case: If no prefix, and no URI, send to /static/index.html */
343 c
= ast_http_error(302, "Moved Temporarily", "Location: /static/index.html\r\n", "This is not the page you are looking for...");
345 *title
= strdup("Moved Temporarily");
347 c
= ast_http_error(404, "Not Found", NULL
, "The requested URL was not found on this serer.");
349 *title
= strdup("Not Found");
354 static void *ast_httpd_helper_thread(void *data
)
359 struct ast_http_server_instance
*ser
= data
;
360 struct ast_variable
*var
, *prev
=NULL
, *vars
=NULL
;
361 char *uri
, *c
, *title
=NULL
;
363 int status
= 200, contentlength
= 0;
366 if (fgets(buf
, sizeof(buf
), ser
->f
)) {
369 while(*uri
&& (*uri
> 32))
376 /* Skip white space */
377 while (*uri
&& (*uri
< 33))
382 while (*c
&& (*c
> 32))
389 while (fgets(cookie
, sizeof(cookie
), ser
->f
)) {
390 /* Trim trailing characters */
391 while(!ast_strlen_zero(cookie
) && (cookie
[strlen(cookie
) - 1] < 33)) {
392 cookie
[strlen(cookie
) - 1] = '\0';
394 if (ast_strlen_zero(cookie
))
396 if (!strncasecmp(cookie
, "Cookie: ", 8)) {
398 vval
= strchr(vname
, '=');
400 /* Ditch the = and the quotes */
406 vval
[strlen(vval
) - 1] = '\0';
407 var
= ast_variable_new(vname
, vval
);
420 if (!strcasecmp(buf
, "get"))
421 c
= handle_uri(&ser
->requestor
, uri
, &status
, &title
, &contentlength
, &vars
);
423 c
= ast_http_error(501, "Not Implemented", NULL
, "Attempt to use unimplemented / unsupported method");\
425 c
= ast_http_error(400, "Bad Request", NULL
, "Invalid Request");
427 /* If they aren't mopped up already, clean up the cookies */
429 ast_variables_destroy(vars
);
432 c
= ast_http_error(500, "Internal Error", NULL
, "Internal Server Error");
435 strftime(timebuf
, sizeof(timebuf
), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t
));
436 ast_cli(ser
->fd
, "HTTP/1.1 %d %s\r\n", status
, title
? title
: "OK");
437 ast_cli(ser
->fd
, "Server: Asterisk\r\n");
438 ast_cli(ser
->fd
, "Date: %s\r\n", timebuf
);
439 ast_cli(ser
->fd
, "Connection: close\r\n");
442 tmp
= strstr(c
, "\r\n\r\n");
444 ast_cli(ser
->fd
, "Content-length: %d\r\n", contentlength
);
445 write(ser
->fd
, c
, (tmp
+ 4 - c
));
446 write(ser
->fd
, tmp
+ 4, contentlength
);
449 ast_cli(ser
->fd
, "%s", c
);
460 static void *http_root(void *data
)
463 struct sockaddr_in sin
;
465 struct ast_http_server_instance
*ser
;
470 ast_wait_for_input(httpfd
, -1);
471 sinlen
= sizeof(sin
);
472 fd
= accept(httpfd
, (struct sockaddr
*)&sin
, &sinlen
);
474 if ((errno
!= EAGAIN
) && (errno
!= EINTR
))
475 ast_log(LOG_WARNING
, "Accept failed: %s\n", strerror(errno
));
478 ser
= ast_calloc(1, sizeof(*ser
));
481 memcpy(&ser
->requestor
, &sin
, sizeof(ser
->requestor
));
482 if ((ser
->f
= fdopen(ser
->fd
, "w+"))) {
483 pthread_attr_init(&attr
);
484 pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
486 if (ast_pthread_create(&launched
, &attr
, ast_httpd_helper_thread
, ser
)) {
487 ast_log(LOG_WARNING
, "Unable to launch helper thread: %s\n", strerror(errno
));
492 ast_log(LOG_WARNING
, "fdopen failed!\n");
501 char *ast_http_setcookie(const char *var
, const char *val
, int expires
, char *buf
, size_t buflen
)
505 ast_build_string(&c
, &buflen
, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var
, val
);
507 ast_build_string(&c
, &buflen
, "; Max-Age=%d", expires
);
508 ast_build_string(&c
, &buflen
, "\r\n");
513 static void http_server_start(struct sockaddr_in
*sin
)
515 char iabuf
[INET_ADDRSTRLEN
];
519 /* Do nothing if nothing has changed */
520 if (!memcmp(&oldsin
, sin
, sizeof(oldsin
))) {
521 ast_log(LOG_DEBUG
, "Nothing changed in http\n");
525 memcpy(&oldsin
, sin
, sizeof(oldsin
));
527 /* Shutdown a running server if there is one */
528 if (master
!= AST_PTHREADT_NULL
) {
529 pthread_cancel(master
);
530 pthread_kill(master
, SIGURG
);
531 pthread_join(master
, NULL
);
537 /* If there's no new server, stop here */
538 if (!sin
->sin_family
)
542 httpfd
= socket(AF_INET
, SOCK_STREAM
, 0);
544 ast_log(LOG_WARNING
, "Unable to allocate socket: %s\n", strerror(errno
));
548 setsockopt(httpfd
, SOL_SOCKET
, SO_REUSEADDR
, &x
, sizeof(x
));
549 if (bind(httpfd
, (struct sockaddr
*)sin
, sizeof(*sin
))) {
550 ast_log(LOG_NOTICE
, "Unable to bind http server to %s:%d: %s\n",
551 ast_inet_ntoa(iabuf
, sizeof(iabuf
), sin
->sin_addr
), ntohs(sin
->sin_port
),
557 if (listen(httpfd
, 10)) {
558 ast_log(LOG_NOTICE
, "Unable to listen!\n");
563 flags
= fcntl(httpfd
, F_GETFL
);
564 fcntl(httpfd
, F_SETFL
, flags
| O_NONBLOCK
);
565 if (ast_pthread_create(&master
, NULL
, http_root
, NULL
)) {
566 ast_log(LOG_NOTICE
, "Unable to launch http server on %s:%d: %s\n",
567 ast_inet_ntoa(iabuf
, sizeof(iabuf
), sin
->sin_addr
), ntohs(sin
->sin_port
),
574 static int __ast_http_load(int reload
)
576 struct ast_config
*cfg
;
577 struct ast_variable
*v
;
579 int newenablestatic
=0;
580 struct sockaddr_in sin
;
582 struct ast_hostent ahp
;
583 char newprefix
[MAX_PREFIX
];
585 memset(&sin
, 0, sizeof(sin
));
587 strcpy(newprefix
, DEFAULT_PREFIX
);
588 cfg
= ast_config_load("http.conf");
590 v
= ast_variable_browse(cfg
, "general");
592 if (!strcasecmp(v
->name
, "enabled"))
593 enabled
= ast_true(v
->value
);
594 else if (!strcasecmp(v
->name
, "enablestatic"))
595 newenablestatic
= ast_true(v
->value
);
596 else if (!strcasecmp(v
->name
, "bindport"))
597 sin
.sin_port
= ntohs(atoi(v
->value
));
598 else if (!strcasecmp(v
->name
, "bindaddr")) {
599 if ((hp
= ast_gethostbyname(v
->value
, &ahp
))) {
600 memcpy(&sin
.sin_addr
, hp
->h_addr
, sizeof(sin
.sin_addr
));
602 ast_log(LOG_WARNING
, "Invalid bind address '%s'\n", v
->value
);
604 } else if (!strcasecmp(v
->name
, "prefix")) {
605 if (!ast_strlen_zero(v
->value
)) {
607 ast_copy_string(newprefix
+ 1, v
->value
, sizeof(newprefix
) - 1);
615 ast_config_destroy(cfg
);
618 sin
.sin_family
= AF_INET
;
619 if (strcmp(prefix
, newprefix
)) {
620 ast_copy_string(prefix
, newprefix
, sizeof(prefix
));
621 prefix_len
= strlen(prefix
);
623 enablestatic
= newenablestatic
;
624 http_server_start(&sin
);
628 static int handle_show_http(int fd
, int argc
, char *argv
[])
630 char iabuf
[INET_ADDRSTRLEN
];
631 struct ast_http_uri
*urih
;
633 return RESULT_SHOWUSAGE
;
634 ast_cli(fd
, "HTTP Server Status:\n");
635 ast_cli(fd
, "Prefix: %s\n", prefix
);
636 if (oldsin
.sin_family
)
637 ast_cli(fd
, "Server Enabled and Bound to %s:%d\n\n",
638 ast_inet_ntoa(iabuf
, sizeof(iabuf
), oldsin
.sin_addr
),
639 ntohs(oldsin
.sin_port
));
641 ast_cli(fd
, "Server Disabled\n\n");
642 ast_cli(fd
, "Enabled URI's:\n");
645 ast_cli(fd
, "%s/%s%s => %s\n", prefix
, urih
->uri
, (urih
->has_subtree
? "/..." : "" ), urih
->description
);
649 ast_cli(fd
, "None.\n");
650 return RESULT_SUCCESS
;
653 int ast_http_reload(void)
655 return __ast_http_load(1);
658 static char show_http_help
[] =
659 "Usage: http show status\n"
660 " Shows status of internal HTTP engine\n";
662 static struct ast_cli_entry http_cli
[] = {
663 { { "http", "show", "status", NULL
}, handle_show_http
,
664 "Display HTTP server status", show_http_help
},
667 int ast_http_init(void)
669 ast_http_uri_link(&statusuri
);
670 ast_http_uri_link(&staticuri
);
671 ast_cli_register_multiple(http_cli
, sizeof(http_cli
) / sizeof(http_cli
[0]));
672 return __ast_http_load(0);