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) {
161 *title = strdup("Not Found");
162 return ast_http_error(404, "Not Found", NULL, "Nothing to see here. Move along.");
166 *title = strdup("Access Denied");
167 return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
171 static char *httpstatus_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
174 size_t reslen = sizeof(result);
176 struct ast_variable *v;
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(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, '?');
290 while ((var = strsep(¶ms, "&"))) {
291 val = strchr(var, '=');
298 if ((v = ast_variable_new(var, val))) {
308 prev->next = *cookies;
313 if (!strncasecmp(uri, prefix, prefix_len)) {
315 if (!*uri || (*uri == '/')) {
320 len = strlen(urih->uri);
321 if (!strncasecmp(urih->uri, uri, len)) {
322 if (!uri[len] || uri[len] == '/') {
326 if (!*turi || urih->has_subtree) {
337 c = urih->callback(sin, uri, vars, status, title, contentlength);
338 ast_variables_destroy(vars);
339 } else if (ast_strlen_zero(uri) && ast_strlen_zero(prefix)) {
340 /* Special case: If no prefix, and no URI, send to /static/index.html */
341 c = ast_http_error(302, "Moved Temporarily", "Location: /static/index.html\r\n", "This is not the page you are looking for...");
343 *title = strdup("Moved Temporarily");
345 c = ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this server.");
347 *title = strdup("Not Found");
352 static void *ast_httpd_helper_thread(void *data)
357 struct ast_http_server_instance *ser = data;
358 struct ast_variable *var, *prev=NULL, *vars=NULL;
359 char *uri, *c, *title=NULL;
361 int status = 200, contentlength = 0;
364 if (fgets(buf, sizeof(buf), ser->f)) {
365 uri = ast_skip_nonblanks(buf); /* Skip method */
369 uri = ast_skip_blanks(uri); /* Skip white space */
371 if (*uri) { /* terminate at the first blank */
372 c = ast_skip_nonblanks(uri);
377 /* process "Cookie: " lines */
378 while (fgets(cookie, sizeof(cookie), ser->f)) {
379 /* Trim trailing characters */
380 ast_trim_blanks(cookie);
381 if (ast_strlen_zero(cookie))
383 if (strncasecmp(cookie, "Cookie: ", 8))
386 /* XXX fix indentation */
388 /* TODO - The cookie parsing code below seems to work
389 in IE6 and FireFox 1.5. However, it is not entirely
390 correct, and therefore may not work in all
392 For more details see RFC 2109 and RFC 2965 */
394 /* FireFox cookie strings look like:
395 Cookie: mansession_id="********"
396 InternetExplorer's look like:
397 Cookie: $Version="1"; mansession_id="********" */
399 /* If we got a FireFox cookie string, the name's right
403 /* If we got an IE cookie string, we need to skip to
404 past the version to get to the name */
406 vname = strchr(vname, ';');
415 vval = strchr(vname, '=');
417 /* Ditch the = and the quotes */
422 vval[strlen(vval) - 1] = '\0';
423 var = ast_variable_new(vname, vval);
436 if (!strcasecmp(buf, "get"))
437 c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars);
439 c = ast_http_error(501, "Not Implemented", NULL, "Attempt to use unimplemented / unsupported method");\
441 c = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
443 /* If they aren't mopped up already, clean up the cookies */
445 ast_variables_destroy(vars);
448 c = ast_http_error(500, "Internal Error", NULL, "Internal Server Error");
451 strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
452 ast_cli(ser->fd, "HTTP/1.1 %d %s\r\n", status, title ? title : "OK");
453 ast_cli(ser->fd, "Server: Asterisk\r\n");
454 ast_cli(ser->fd, "Date: %s\r\n", timebuf);
455 ast_cli(ser->fd, "Connection: close\r\n");
458 tmp = strstr(c, "\r\n\r\n");
460 ast_cli(ser->fd, "Content-length: %d\r\n", contentlength);
461 write(ser->fd, c, (tmp + 4 - c));
462 write(ser->fd, tmp + 4, contentlength);
465 ast_cli(ser->fd, "%s", c);
476 static void *http_root(void *data)
479 struct sockaddr_in sin;
481 struct ast_http_server_instance *ser;
488 ast_wait_for_input(httpfd, -1);
489 sinlen = sizeof(sin);
490 fd = accept(httpfd, (struct sockaddr *)&sin, &sinlen);
492 if ((errno != EAGAIN) && (errno != EINTR))
493 ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
496 ser = ast_calloc(1, sizeof(*ser));
498 ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno));
502 flags = fcntl(fd, F_GETFL);
503 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
505 memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
506 if ((ser->f = fdopen(ser->fd, "w+"))) {
507 pthread_attr_init(&attr);
508 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
510 if (ast_pthread_create_background(&launched, &attr, ast_httpd_helper_thread, ser)) {
511 ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
516 ast_log(LOG_WARNING, "fdopen failed!\n");
524 char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, size_t buflen)
528 ast_build_string(&c, &buflen, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var, val);
530 ast_build_string(&c, &buflen, "; Max-Age=%d", expires);
531 ast_build_string(&c, &buflen, "\r\n");
536 static void http_server_start(struct sockaddr_in *sin)
541 /* Do nothing if nothing has changed */
542 if (!memcmp(&oldsin, sin, sizeof(oldsin))) {
544 ast_log(LOG_DEBUG, "Nothing changed in http\n");
548 memcpy(&oldsin, sin, sizeof(oldsin));
550 /* Shutdown a running server if there is one */
551 if (master != AST_PTHREADT_NULL) {
552 pthread_cancel(master);
553 pthread_kill(master, SIGURG);
554 pthread_join(master, NULL);
560 /* If there's no new server, stop here */
561 if (!sin->sin_family)
565 httpfd = socket(AF_INET, SOCK_STREAM, 0);
567 ast_log(LOG_WARNING, "Unable to allocate socket: %s\n", strerror(errno));
571 setsockopt(httpfd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
572 if (bind(httpfd, (struct sockaddr *)sin, sizeof(*sin))) {
573 ast_log(LOG_NOTICE, "Unable to bind http server to %s:%d: %s\n",
574 ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
580 if (listen(httpfd, 10)) {
581 ast_log(LOG_NOTICE, "Unable to listen!\n");
586 flags = fcntl(httpfd, F_GETFL);
587 fcntl(httpfd, F_SETFL, flags | O_NONBLOCK);
588 if (ast_pthread_create_background(&master, NULL, http_root, NULL)) {
589 ast_log(LOG_NOTICE, "Unable to launch http server on %s:%d: %s\n",
590 ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
597 static int __ast_http_load(int reload)
599 struct ast_config *cfg;
600 struct ast_variable *v;
602 int newenablestatic=0;
603 struct sockaddr_in sin;
605 struct ast_hostent ahp;
606 char newprefix[MAX_PREFIX];
608 memset(&sin, 0, sizeof(sin));
610 strcpy(newprefix, DEFAULT_PREFIX);
611 cfg = ast_config_load("http.conf");
613 v = ast_variable_browse(cfg, "general");
615 if (!strcasecmp(v->name, "enabled"))
616 enabled = ast_true(v->value);
617 else if (!strcasecmp(v->name, "enablestatic"))
618 newenablestatic = ast_true(v->value);
619 else if (!strcasecmp(v->name, "bindport"))
620 sin.sin_port = ntohs(atoi(v->value));
621 else if (!strcasecmp(v->name, "bindaddr")) {
622 if ((hp = ast_gethostbyname(v->value, &ahp))) {
623 memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
625 ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
627 } else if (!strcasecmp(v->name, "prefix")) {
628 if (!ast_strlen_zero(v->value)) {
630 ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
638 ast_config_destroy(cfg);
641 sin.sin_family = AF_INET;
642 if (strcmp(prefix, newprefix)) {
643 ast_copy_string(prefix, newprefix, sizeof(prefix));
644 prefix_len = strlen(prefix);
646 enablestatic = newenablestatic;
647 http_server_start(&sin);
651 static int handle_show_http(int fd, int argc, char *argv[])
653 struct ast_http_uri *urih;
655 return RESULT_SHOWUSAGE;
656 ast_cli(fd, "HTTP Server Status:\n");
657 ast_cli(fd, "Prefix: %s\n", prefix);
658 if (oldsin.sin_family)
659 ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
660 ast_inet_ntoa(oldsin.sin_addr),
661 ntohs(oldsin.sin_port));
663 ast_cli(fd, "Server Disabled\n\n");
664 ast_cli(fd, "Enabled URI's:\n");
667 ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
671 ast_cli(fd, "None.\n");
672 return RESULT_SUCCESS;
675 int ast_http_reload(void)
677 return __ast_http_load(1);
680 static char show_http_help[] =
681 "Usage: http list status\n"
682 " Lists status of internal HTTP engine\n";
684 static struct ast_cli_entry cli_http[] = {
685 { { "http", "list", "status", NULL },
686 handle_show_http, "Display HTTP server status",
690 int ast_http_init(void)
692 ast_http_uri_link(&statusuri);
693 ast_http_uri_link(&staticuri);
694 ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
695 return __ast_http_load(0);