Let's also include aclocal.m4
[asterisk-bristuff.git] / main / http.c
blobd969e719f69915facee1391cb23e17185656c432
1 /*
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.
19 /*!
20 * \file
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
31 #include "asterisk.h"
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
35 #include <sys/types.h>
36 #include <stdio.h>
37 #include <unistd.h>
38 #include <stdlib.h>
39 #include <time.h>
40 #include <string.h>
41 #include <netinet/in.h>
42 #include <sys/time.h>
43 #include <sys/socket.h>
44 #include <sys/stat.h>
45 #include <sys/signal.h>
46 #include <arpa/inet.h>
47 #include <errno.h>
48 #include <fcntl.h>
49 #include <pthread.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"
60 #define MAX_PREFIX 80
61 #define DEFAULT_PREFIX "/asterisk"
63 struct ast_http_server_instance {
64 FILE *f;
65 int fd;
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 */
81 static struct {
82 const char *ext;
83 const char *mtype;
84 } mimetypes[] = {
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)
97 int x;
98 if (ftype) {
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");
105 return wkspace;
108 static char *static_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
110 char result[4096];
111 char *c=result;
112 char *path;
113 char *ftype;
114 const char *mtype;
115 char wkspace[80];
116 struct stat st;
117 int len;
118 int fd;
119 void *blob;
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))
124 goto out403;
125 /* Disallow any funny filenames at all */
126 if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0]))
127 goto out403;
128 if (strstr(uri, "/.."))
129 goto out403;
131 if ((ftype = strrchr(uri, '.')))
132 ftype++;
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;
137 if (len > 1024)
138 goto out403;
140 path = alloca(len);
141 sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
142 if (stat(path, &st))
143 goto out404;
144 if (S_ISDIR(st.st_mode))
145 goto out404;
146 fd = open(path, O_RDONLY);
147 if (fd < 0)
148 goto out403;
150 len = st.st_size + strlen(mtype) + 40;
152 blob = malloc(len);
153 if (blob) {
154 c = blob;
155 sprintf(c, "Content-type: %s\r\n\r\n", mtype);
156 c += strlen(c);
157 *contentlength = read(fd, c, st.st_size);
158 if (*contentlength < 0) {
159 close(fd);
160 free(blob);
161 goto out403;
164 close(fd);
165 return blob;
167 out404:
168 *status = 404;
169 *title = strdup("Not Found");
170 return ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this server.");
172 out403:
173 *status = 403;
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)
181 char result[4096];
182 size_t reslen = sizeof(result);
183 char *c=result;
184 struct ast_variable *v;
186 ast_build_string(&c, &reslen,
187 "\r\n"
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>&nbsp;&nbsp;Asterisk&trade; 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");
199 v = vars;
200 while(v) {
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);
203 v = v->next;
205 ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
206 v = vars;
207 while(v) {
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);
210 v = v->next;
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",
219 .uri = "httpstatus",
220 .has_subtree = 0,
223 static struct ast_http_uri staticuri = {
224 .callback = static_callback,
225 .description = "Asterisk HTTP Static Delivery",
226 .uri = "static",
227 .has_subtree = 1,
228 .static_content = 1,
231 char *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
233 char *c = NULL;
234 asprintf(&c,
235 "Content-type: text/html\r\n"
236 "%s"
237 "\r\n"
238 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
239 "<html><head>\r\n"
240 "<title>%d %s</title>\r\n"
241 "</head><body>\r\n"
242 "<h1>%s</h1>\r\n"
243 "<p>%s</p>\r\n"
244 "<hr />\r\n"
245 "<address>Asterisk Server</address>\r\n"
246 "</body></html>\r\n",
247 (extra_header ? extra_header : ""), status, title, title, text);
248 return c;
251 int ast_http_uri_link(struct ast_http_uri *urih)
253 struct ast_http_uri *prev;
255 ast_rwlock_wrlock(&uris_lock);
256 prev = uris;
257 if (!uris || strlen(uris->uri) <= strlen(urih->uri)) {
258 urih->next = uris;
259 uris = urih;
260 } else {
261 while (prev->next && (strlen(prev->next->uri) > strlen(urih->uri)))
262 prev = prev->next;
263 /* Insert it here */
264 urih->next = prev->next;
265 prev->next = urih;
267 ast_rwlock_unlock(&uris_lock);
269 return 0;
272 void ast_http_uri_unlink(struct ast_http_uri *urih)
274 struct ast_http_uri *prev;
276 ast_rwlock_wrlock(&uris_lock);
277 if (!uris) {
278 ast_rwlock_unlock(&uris_lock);
279 return;
281 prev = uris;
282 if (uris == urih) {
283 uris = uris->next;
285 while(prev->next) {
286 if (prev->next == urih) {
287 prev->next = urih->next;
288 break;
290 prev = prev->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)
299 char *c;
300 char *turi;
301 char *params;
302 char *var;
303 char *val;
304 struct ast_http_uri *urih=NULL;
305 int len;
306 struct ast_variable *vars=NULL, *v, *prev = NULL;
309 params = strchr(uri, '?');
310 if (params) {
311 *params = '\0';
312 params++;
313 while ((var = strsep(&params, "&"))) {
314 val = strchr(var, '=');
315 if (val) {
316 *val = '\0';
317 val++;
318 ast_uri_decode(val);
319 } else
320 val = "";
321 ast_uri_decode(var);
322 if ((v = ast_variable_new(var, val))) {
323 if (vars)
324 prev->next = v;
325 else
326 vars = v;
327 prev = v;
331 if (prev)
332 prev->next = *cookies;
333 else
334 vars = *cookies;
335 *cookies = NULL;
336 ast_uri_decode(uri);
337 if (!strncasecmp(uri, prefix, prefix_len)) {
338 uri += prefix_len;
339 if (!*uri || (*uri == '/')) {
340 if (*uri == '/')
341 uri++;
342 ast_rwlock_rdlock(&uris_lock);
343 urih = uris;
344 while(urih) {
345 len = strlen(urih->uri);
346 if (!strncasecmp(urih->uri, uri, len)) {
347 if (!uri[len] || uri[len] == '/') {
348 turi = uri + len;
349 if (*turi == '/')
350 turi++;
351 if (!*turi || urih->has_subtree) {
352 uri = turi;
353 break;
357 urih = urih->next;
359 if (!urih)
360 ast_rwlock_unlock(&uris_lock);
363 if (urih) {
364 if (urih->static_content)
365 *static_content = 1;
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.");
371 *status = 302;
372 *title = strdup("Moved Temporarily");
373 } else {
374 c = ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this server.");
375 *status = 404;
376 *title = strdup("Not Found");
378 ast_variables_destroy(vars);
379 return c;
382 static struct ast_variable *parse_cookies(char *cookies)
384 char *cur;
385 struct ast_variable *vars = NULL, *var;
387 /* Skip Cookie: */
388 cookies += 8;
390 while ((cur = strsep(&cookies, ";"))) {
391 char *name, *val;
393 name = val = cur;
394 strsep(&val, "=");
396 if (ast_strlen_zero(name) || ast_strlen_zero(val)) {
397 continue;
400 name = ast_strip(name);
401 val = ast_strip_quoted(val, "\"", "\"");
403 if (ast_strlen_zero(name) || ast_strlen_zero(val)) {
404 continue;
407 if (option_debug) {
408 ast_log(LOG_DEBUG, "mmm ... cookie! Name: '%s' Value: '%s'\n", name, val);
411 var = ast_variable_new(name, val);
412 var->next = vars;
413 vars = var;
416 return vars;
419 static void *ast_httpd_helper_thread(void *data)
421 char buf[4096];
422 char cookie[4096];
423 char timebuf[256];
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;
428 time_t t;
429 unsigned int static_content = 0;
431 if (fgets(buf, sizeof(buf), ser->f)) {
432 /* Skip method */
433 uri = buf;
434 while(*uri && (*uri > 32))
435 uri++;
436 if (*uri) {
437 *uri = '\0';
438 uri++;
441 /* Skip white space */
442 while (*uri && (*uri < 33))
443 uri++;
445 if (*uri) {
446 c = uri;
447 while (*c && (*c > 32))
448 c++;
449 if (*c) {
450 *c = '\0';
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))
460 break;
461 if (!strncasecmp(cookie, "Cookie: ", 8)) {
462 vars = parse_cookies(cookie);
466 if (*uri) {
467 if (!strcasecmp(buf, "get"))
468 c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars, &static_content);
469 else
470 c = ast_http_error(501, "Not Implemented", NULL, "Attempt to use unimplemented / unsupported method");\
471 } else
472 c = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
474 /* If they aren't mopped up already, clean up the cookies */
475 if (vars)
476 ast_variables_destroy(vars);
478 if (!c)
479 c = ast_http_error(500, "Internal Error", NULL, "Internal Server Error");
480 if (c) {
481 time(&t);
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");
487 if (!static_content)
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'
494 if (contentlength) {
495 char *tmp;
496 tmp = strstr(c, "\r\n\r\n");
497 if (tmp) {
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);
502 } else
503 ast_cli(ser->fd, "%s", c);
504 free(c);
506 if (title)
507 free(title);
509 fclose(ser->f);
510 free(ser);
511 return NULL;
514 static void *http_root(void *data)
516 int fd;
517 struct sockaddr_in sin;
518 socklen_t sinlen;
519 struct ast_http_server_instance *ser;
520 pthread_t launched;
521 pthread_attr_t attr;
523 for (;;) {
524 int flags;
526 ast_wait_for_input(httpfd, -1);
527 sinlen = sizeof(sin);
528 fd = accept(httpfd, (struct sockaddr *)&sin, &sinlen);
529 if (fd < 0) {
530 if ((errno != EAGAIN) && (errno != EINTR))
531 ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
532 continue;
534 ser = ast_calloc(1, sizeof(*ser));
535 if (!ser) {
536 ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno));
537 close(fd);
538 continue;
540 flags = fcntl(fd, F_GETFL);
541 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
542 ser->fd = fd;
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));
550 fclose(ser->f);
551 free(ser);
553 pthread_attr_destroy(&attr);
554 } else {
555 ast_log(LOG_WARNING, "fdopen failed!\n");
556 close(ser->fd);
557 free(ser);
560 return NULL;
563 char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, size_t buflen)
565 char *c;
566 c = buf;
567 ast_build_string(&c, &buflen, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var, val);
568 if (expires)
569 ast_build_string(&c, &buflen, "; Max-Age=%d", expires);
570 ast_build_string(&c, &buflen, "\r\n");
571 return buf;
575 static void http_server_start(struct sockaddr_in *sin)
577 int flags;
578 int x = 1;
580 /* Do nothing if nothing has changed */
581 if (!memcmp(&oldsin, sin, sizeof(oldsin))) {
582 ast_log(LOG_DEBUG, "Nothing changed in http\n");
583 return;
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);
595 if (httpfd != -1)
596 close(httpfd);
598 /* If there's no new server, stop here */
599 if (!sin->sin_family)
600 return;
603 httpfd = socket(AF_INET, SOCK_STREAM, 0);
604 if (httpfd < 0) {
605 ast_log(LOG_WARNING, "Unable to allocate socket: %s\n", strerror(errno));
606 return;
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),
613 strerror(errno));
614 close(httpfd);
615 httpfd = -1;
616 return;
618 if (listen(httpfd, 10)) {
619 ast_log(LOG_NOTICE, "Unable to listen!\n");
620 close(httpfd);
621 httpfd = -1;
622 return;
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),
629 strerror(errno));
630 close(httpfd);
631 httpfd = -1;
635 static int __ast_http_load(int reload)
637 struct ast_config *cfg;
638 struct ast_variable *v;
639 int enabled=0;
640 int newenablestatic=0;
641 struct sockaddr_in sin;
642 struct hostent *hp;
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");
652 if (cfg) {
653 v = ast_variable_browse(cfg, "general");
654 while(v) {
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));
664 } else {
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)) {
669 newprefix[0] = '/';
670 ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
671 } else {
672 newprefix[0] = '\0';
676 v = v->next;
678 ast_config_destroy(cfg);
680 if (enabled)
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);
691 return 0;
694 static int handle_show_http(int fd, int argc, char *argv[])
696 struct ast_http_uri *urih;
698 if (argc != 3)
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));
707 else
708 ast_cli(fd, "Server Disabled\n\n");
709 ast_cli(fd, "Enabled URI's:\n");
710 ast_rwlock_rdlock(&uris_lock);
711 urih = uris;
712 while(urih){
713 ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
714 urih = urih->next;
716 if (!uris)
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",
735 show_http_help },
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);