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;
72 /* all valid URIs must be prepended by the string in prefix. */
73 static char prefix[MAX_PREFIX];
74 static struct sockaddr_in oldsin;
75 static int enablestatic=0;
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);
244 * Link the new uri into the list. They are sorted by length of
245 * the string, not alphabetically. Duplicate entries are not replaced,
246 * but the insertion order (using <= and not just <) makes sure that
247 * more recent insertions hide older ones.
248 * On a lookup, we just scan the list and stop at the first matching entry.
250 int ast_http_uri_link(struct ast_http_uri *urih)
252 struct ast_http_uri *prev=uris;
253 int len = strlen(urih->uri);
255 if (!uris || strlen(uris->uri) <= len ) {
259 while (prev->next && strlen(prev->next->uri) > len)
262 urih->next = prev->next;
268 void ast_http_uri_unlink(struct ast_http_uri *urih)
270 struct ast_http_uri *prev = uris;
277 if (prev->next == urih) {
278 prev->next = urih->next;
285 static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **title, int *contentlength, struct ast_variable **cookies)
289 struct ast_http_uri *urih=NULL;
291 struct ast_variable *vars=NULL, *v, *prev = NULL;
293 strsep(¶ms, "?");
294 /* Extract arguments from the request and store them in variables. */
298 while ((val = strsep(¶ms, "&"))) {
299 var = strsep(&val, "=");
305 if ((v = ast_variable_new(var, val))) {
315 * Append the cookies to the variables (the only reason to have them
316 * at the end is to avoid another pass of the cookies list to find
320 prev->next = *cookies;
326 /* We want requests to start with the prefix and '/' */
328 if (l && !strncasecmp(uri, prefix, l) && uri[l] == '/') {
330 /* scan registered uris to see if we match one. */
331 for (urih = uris; urih; urih = urih->next) {
332 l = strlen(urih->uri);
333 c = uri + l; /* candidate */
334 if (strncasecmp(urih->uri, uri, l) /* no match */
335 || (*c && *c != '/')) /* substring */
339 if (!*c || urih->has_subtree) {
346 c = urih->callback(sin, uri, vars, status, title, contentlength);
347 } else if (ast_strlen_zero(uri) && ast_strlen_zero(prefix)) {
348 /* Special case: no prefix, no URI, send to /static/index.html */
349 c = ast_http_error(302, "Moved Temporarily",
350 "Location: /static/index.html\r\n",
351 "This is not the page you are looking for...");
353 *title = strdup("Moved Temporarily");
355 c = ast_http_error(404, "Not Found", NULL,
356 "The requested URL was not found on this server.");
358 *title = strdup("Not Found");
360 ast_variables_destroy(vars);
364 static void *ast_httpd_helper_thread(void *data)
368 struct ast_http_server_instance *ser = data;
369 struct ast_variable *var, *prev=NULL, *vars=NULL;
370 char *uri, *c, *title=NULL;
371 int status = 200, contentlength = 0;
373 if (!fgets(buf, sizeof(buf), ser->f))
376 uri = ast_skip_nonblanks(buf); /* Skip method */
380 uri = ast_skip_blanks(uri); /* Skip white space */
382 if (*uri) { /* terminate at the first blank */
383 c = ast_skip_nonblanks(uri);
388 /* process "Cookie: " lines */
389 while (fgets(cookie, sizeof(cookie), ser->f)) {
393 /* Trim trailing characters */
394 ast_trim_blanks(cookie);
395 if (ast_strlen_zero(cookie))
397 if (strncasecmp(cookie, "Cookie: ", 8))
400 /* TODO - The cookie parsing code below seems to work
401 in IE6 and FireFox 1.5. However, it is not entirely
402 correct, and therefore may not work in all
404 For more details see RFC 2109 and RFC 2965 */
406 /* FireFox cookie strings look like:
407 Cookie: mansession_id="********"
408 InternetExplorer's look like:
409 Cookie: $Version="1"; mansession_id="********" */
411 /* If we got a FireFox cookie string, the name's right
413 vname = ast_skip_blanks(cookie + 8);
415 /* If we got an IE cookie string, we need to skip to
416 past the version to get to the name */
419 if (!vname) /* no name ? */
421 vname = ast_skip_blanks(vname);
423 vval = strchr(vname, '=');
426 /* Ditch the = and the quotes */
430 if ( (l = strlen(vval)) )
431 vval[l - 1] = '\0'; /* trim trailing quote */
432 var = ast_variable_new(vname, vval);
443 c = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
444 else if (strcasecmp(buf, "get"))
445 c = ast_http_error(501, "Not Implemented", NULL,
446 "Attempt to use unimplemented / unsupported method");
447 else /* try to serve it */
448 c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars);
450 /* If they aren't mopped up already, clean up the cookies */
452 ast_variables_destroy(vars);
455 c = ast_http_error(500, "Internal Error", NULL, "Internal Server Error");
457 time_t t = time(NULL);
460 strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
461 ast_cli(ser->fd, "HTTP/1.1 %d %s\r\n", status, title ? title : "OK");
462 ast_cli(ser->fd, "Server: Asterisk\r\n");
463 ast_cli(ser->fd, "Date: %s\r\n", timebuf);
464 ast_cli(ser->fd, "Connection: close\r\n");
466 char *tmp = strstr(c, "\r\n\r\n");
469 ast_cli(ser->fd, "Content-length: %d\r\n", contentlength);
470 /* first write the header, then the body */
471 write(ser->fd, c, (tmp + 4 - c));
472 write(ser->fd, tmp + 4, contentlength);
475 ast_cli(ser->fd, "%s", c);
487 static void *http_root(void *data)
490 struct sockaddr_in sin;
492 struct ast_http_server_instance *ser;
499 ast_wait_for_input(httpfd, -1);
500 sinlen = sizeof(sin);
501 fd = accept(httpfd, (struct sockaddr *)&sin, &sinlen);
503 if ((errno != EAGAIN) && (errno != EINTR))
504 ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
507 ser = ast_calloc(1, sizeof(*ser));
509 ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno));
513 flags = fcntl(fd, F_GETFL);
514 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
516 memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
517 if ((ser->f = fdopen(ser->fd, "w+"))) {
518 pthread_attr_init(&attr);
519 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
521 if (ast_pthread_create_background(&launched, &attr, ast_httpd_helper_thread, ser)) {
522 ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
527 ast_log(LOG_WARNING, "fdopen failed!\n");
535 char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, size_t buflen)
539 ast_build_string(&c, &buflen, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var, val);
541 ast_build_string(&c, &buflen, "; Max-Age=%d", expires);
542 ast_build_string(&c, &buflen, "\r\n");
547 static void http_server_start(struct sockaddr_in *sin)
552 /* Do nothing if nothing has changed */
553 if (!memcmp(&oldsin, sin, sizeof(oldsin))) {
555 ast_log(LOG_DEBUG, "Nothing changed in http\n");
559 memcpy(&oldsin, sin, sizeof(oldsin));
561 /* Shutdown a running server if there is one */
562 if (master != AST_PTHREADT_NULL) {
563 pthread_cancel(master);
564 pthread_kill(master, SIGURG);
565 pthread_join(master, NULL);
571 /* If there's no new server, stop here */
572 if (!sin->sin_family)
576 httpfd = socket(AF_INET, SOCK_STREAM, 0);
578 ast_log(LOG_WARNING, "Unable to allocate socket: %s\n", strerror(errno));
582 setsockopt(httpfd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
583 if (bind(httpfd, (struct sockaddr *)sin, sizeof(*sin))) {
584 ast_log(LOG_NOTICE, "Unable to bind http server to %s:%d: %s\n",
585 ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
591 if (listen(httpfd, 10)) {
592 ast_log(LOG_NOTICE, "Unable to listen!\n");
597 flags = fcntl(httpfd, F_GETFL);
598 fcntl(httpfd, F_SETFL, flags | O_NONBLOCK);
599 if (ast_pthread_create_background(&master, NULL, http_root, NULL)) {
600 ast_log(LOG_NOTICE, "Unable to launch http server on %s:%d: %s\n",
601 ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
608 static int __ast_http_load(int reload)
610 struct ast_config *cfg;
611 struct ast_variable *v;
613 int newenablestatic=0;
614 struct sockaddr_in sin;
616 struct ast_hostent ahp;
617 char newprefix[MAX_PREFIX];
619 memset(&sin, 0, sizeof(sin));
621 strcpy(newprefix, DEFAULT_PREFIX);
622 cfg = ast_config_load("http.conf");
624 v = ast_variable_browse(cfg, "general");
626 if (!strcasecmp(v->name, "enabled"))
627 enabled = ast_true(v->value);
628 else if (!strcasecmp(v->name, "enablestatic"))
629 newenablestatic = ast_true(v->value);
630 else if (!strcasecmp(v->name, "bindport"))
631 sin.sin_port = ntohs(atoi(v->value));
632 else if (!strcasecmp(v->name, "bindaddr")) {
633 if ((hp = ast_gethostbyname(v->value, &ahp))) {
634 memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
636 ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
638 } else if (!strcasecmp(v->name, "prefix")) {
639 if (!ast_strlen_zero(v->value)) {
641 ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
649 ast_config_destroy(cfg);
652 sin.sin_family = AF_INET;
653 if (strcmp(prefix, newprefix))
654 ast_copy_string(prefix, newprefix, sizeof(prefix));
655 enablestatic = newenablestatic;
656 http_server_start(&sin);
660 static int handle_show_http(int fd, int argc, char *argv[])
662 struct ast_http_uri *urih;
664 return RESULT_SHOWUSAGE;
665 ast_cli(fd, "HTTP Server Status:\n");
666 ast_cli(fd, "Prefix: %s\n", prefix);
667 if (oldsin.sin_family)
668 ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
669 ast_inet_ntoa(oldsin.sin_addr),
670 ntohs(oldsin.sin_port));
672 ast_cli(fd, "Server Disabled\n\n");
673 ast_cli(fd, "Enabled URI's:\n");
676 ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
680 ast_cli(fd, "None.\n");
681 return RESULT_SUCCESS;
684 int ast_http_reload(void)
686 return __ast_http_load(1);
689 static char show_http_help[] =
690 "Usage: http list status\n"
691 " Lists status of internal HTTP engine\n";
693 static struct ast_cli_entry cli_http[] = {
694 { { "http", "list", "status", NULL },
695 handle_show_http, "Display HTTP server status",
699 int ast_http_init(void)
701 ast_http_uri_link(&statusuri);
702 ast_http_uri_link(&staticuri);
703 ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
704 return __ast_http_load(0);