put this value into the correct property
[asterisk-bristuff.git] / main / http.c
blob67341e8a75f6932150759d45878009e87b97ab63
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>
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
30 #include "asterisk.h"
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34 #include <sys/types.h>
35 #include <stdio.h>
36 #include <unistd.h>
37 #include <stdlib.h>
38 #include <time.h>
39 #include <string.h>
40 #include <netinet/in.h>
41 #include <sys/time.h>
42 #include <sys/socket.h>
43 #include <sys/stat.h>
44 #include <sys/signal.h>
45 #include <arpa/inet.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <pthread.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"
57 #define MAX_PREFIX 80
58 #define DEFAULT_PREFIX "/asterisk"
60 struct ast_http_server_instance {
61 FILE *f;
62 int fd;
63 struct sockaddr_in requestor;
64 ast_http_callback callback;
67 AST_RWLOCK_DEFINE_STATIC(uris_lock);
68 static struct ast_http_uri *uris;
70 static int httpfd = -1;
71 static pthread_t master = AST_PTHREADT_NULL;
72 static char prefix[MAX_PREFIX];
73 static int prefix_len;
74 static struct sockaddr_in oldsin;
75 static int enablestatic;
77 /*! \brief Limit the kinds of files we're willing to serve up */
78 static struct {
79 char *ext;
80 char *mtype;
81 } mimetypes[] = {
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)
91 int x;
92 if (ftype) {
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");
99 return wkspace;
102 static char *static_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
104 char result[4096];
105 char *c=result;
106 char *path;
107 char *ftype, *mtype;
108 char wkspace[80];
109 struct stat st;
110 int len;
111 int fd;
112 void *blob;
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))
117 goto out403;
118 /* Disallow any funny filenames at all */
119 if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0]))
120 goto out403;
121 if (strstr(uri, "/.."))
122 goto out403;
124 if ((ftype = strrchr(uri, '.')))
125 ftype++;
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;
130 if (len > 1024)
131 goto out403;
133 path = alloca(len);
134 sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
135 if (stat(path, &st))
136 goto out404;
137 if (S_ISDIR(st.st_mode))
138 goto out404;
139 fd = open(path, O_RDONLY);
140 if (fd < 0)
141 goto out403;
143 len = st.st_size + strlen(mtype) + 40;
145 blob = malloc(len);
146 if (blob) {
147 c = blob;
148 sprintf(c, "Content-type: %s\r\n\r\n", mtype);
149 c += strlen(c);
150 *contentlength = read(fd, c, st.st_size);
151 if (*contentlength < 0) {
152 close(fd);
153 free(blob);
154 goto out403;
157 close(fd);
158 return blob;
160 out404:
161 *status = 404;
162 *title = strdup("Not Found");
163 return ast_http_error(404, "Not Found", NULL, "Nothing to see here. Move along.");
165 out403:
166 *status = 403;
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)
174 char result[4096];
175 size_t reslen = sizeof(result);
176 char *c=result;
177 struct ast_variable *v;
179 ast_build_string(&c, &reslen,
180 "\r\n"
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>&nbsp;&nbsp;Asterisk&trade; 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");
192 v = vars;
193 while(v) {
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);
196 v = v->next;
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>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
203 v = v->next;
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",
212 .uri = "httpstatus",
213 .has_subtree = 0,
216 static struct ast_http_uri staticuri = {
217 .callback = static_callback,
218 .description = "Asterisk HTTP Static Delivery",
219 .uri = "static",
220 .has_subtree = 1,
223 char *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
225 char *c = NULL;
226 asprintf(&c,
227 "Content-type: text/html\r\n"
228 "%s"
229 "\r\n"
230 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
231 "<html><head>\r\n"
232 "<title>%d %s</title>\r\n"
233 "</head><body>\r\n"
234 "<h1>%s</h1>\r\n"
235 "<p>%s</p>\r\n"
236 "<hr />\r\n"
237 "<address>Asterisk Server</address>\r\n"
238 "</body></html>\r\n",
239 (extra_header ? extra_header : ""), status, title, title, text);
240 return c;
243 int ast_http_uri_link(struct ast_http_uri *urih)
245 struct ast_http_uri *prev;
247 ast_rwlock_wrlock(&uris_lock);
248 prev = uris;
249 if (!uris || strlen(uris->uri) <= strlen(urih->uri)) {
250 urih->next = uris;
251 uris = urih;
252 } else {
253 while (prev->next && (strlen(prev->next->uri) > strlen(urih->uri)))
254 prev = prev->next;
255 /* Insert it here */
256 urih->next = prev->next;
257 prev->next = urih;
259 ast_rwlock_unlock(&uris_lock);
261 return 0;
264 void ast_http_uri_unlink(struct ast_http_uri *urih)
266 struct ast_http_uri *prev;
268 ast_rwlock_wrlock(&uris_lock);
269 if (!uris) {
270 ast_rwlock_unlock(&uris_lock);
271 return;
273 prev = uris;
274 if (uris == urih) {
275 uris = uris->next;
277 while(prev->next) {
278 if (prev->next == urih) {
279 prev->next = urih->next;
280 break;
282 prev = prev->next;
284 ast_rwlock_unlock(&uris_lock);
287 static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **title, int *contentlength, struct ast_variable **cookies)
289 char *c;
290 char *turi;
291 char *params;
292 char *var;
293 char *val;
294 struct ast_http_uri *urih=NULL;
295 int len;
296 struct ast_variable *vars=NULL, *v, *prev = NULL;
299 params = strchr(uri, '?');
300 if (params) {
301 *params = '\0';
302 params++;
303 while ((var = strsep(&params, "&"))) {
304 val = strchr(var, '=');
305 if (val) {
306 *val = '\0';
307 val++;
308 ast_uri_decode(val);
309 } else
310 val = "";
311 ast_uri_decode(var);
312 if ((v = ast_variable_new(var, val))) {
313 if (vars)
314 prev->next = v;
315 else
316 vars = v;
317 prev = v;
321 if (prev)
322 prev->next = *cookies;
323 else
324 vars = *cookies;
325 *cookies = NULL;
326 ast_uri_decode(uri);
327 if (!strncasecmp(uri, prefix, prefix_len)) {
328 uri += prefix_len;
329 if (!*uri || (*uri == '/')) {
330 if (*uri == '/')
331 uri++;
332 ast_rwlock_rdlock(&uris_lock);
333 urih = uris;
334 while(urih) {
335 len = strlen(urih->uri);
336 if (!strncasecmp(urih->uri, uri, len)) {
337 if (!uri[len] || uri[len] == '/') {
338 turi = uri + len;
339 if (*turi == '/')
340 turi++;
341 if (!*turi || urih->has_subtree) {
342 uri = turi;
343 break;
347 urih = urih->next;
349 if (!urih)
350 ast_rwlock_unlock(&uris_lock);
353 if (urih) {
354 c = urih->callback(sin, uri, vars, status, title, contentlength);
355 ast_rwlock_unlock(&uris_lock);
356 } else if (ast_strlen_zero(uri) && ast_strlen_zero(prefix)) {
357 /* Special case: If no prefix, and no URI, send to /static/index.html */
358 c = ast_http_error(302, "Moved Temporarily", "Location: /static/index.html\r\n", "This is not the page you are looking for...");
359 *status = 302;
360 *title = strdup("Moved Temporarily");
361 } else {
362 c = ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this server.");
363 *status = 404;
364 *title = strdup("Not Found");
366 ast_variables_destroy(vars);
367 return c;
370 static void *ast_httpd_helper_thread(void *data)
372 char buf[4096];
373 char cookie[4096];
374 char timebuf[256];
375 struct ast_http_server_instance *ser = data;
376 struct ast_variable *var, *prev=NULL, *vars=NULL;
377 char *uri, *c, *title=NULL;
378 char *vname, *vval;
379 int status = 200, contentlength = 0;
380 time_t t;
382 if (fgets(buf, sizeof(buf), ser->f)) {
383 /* Skip method */
384 uri = buf;
385 while(*uri && (*uri > 32))
386 uri++;
387 if (*uri) {
388 *uri = '\0';
389 uri++;
392 /* Skip white space */
393 while (*uri && (*uri < 33))
394 uri++;
396 if (*uri) {
397 c = uri;
398 while (*c && (*c > 32))
399 c++;
400 if (*c) {
401 *c = '\0';
405 while (fgets(cookie, sizeof(cookie), ser->f)) {
406 /* Trim trailing characters */
407 while(!ast_strlen_zero(cookie) && (cookie[strlen(cookie) - 1] < 33)) {
408 cookie[strlen(cookie) - 1] = '\0';
410 if (ast_strlen_zero(cookie))
411 break;
412 if (!strncasecmp(cookie, "Cookie: ", 8)) {
414 /* TODO - The cookie parsing code below seems to work
415 in IE6 and FireFox 1.5. However, it is not entirely
416 correct, and therefore may not work in all
417 circumstances.
418 For more details see RFC 2109 and RFC 2965 */
420 /* FireFox cookie strings look like:
421 Cookie: mansession_id="********"
422 InternetExplorer's look like:
423 Cookie: $Version="1"; mansession_id="********" */
425 /* If we got a FireFox cookie string, the name's right
426 after "Cookie: " */
427 vname = cookie + 8;
429 /* If we got an IE cookie string, we need to skip to
430 past the version to get to the name */
431 if (*vname == '$') {
432 vname = strchr(vname, ';');
433 if (vname) {
434 vname++;
435 if (*vname == ' ')
436 vname++;
440 if (vname) {
441 vval = strchr(vname, '=');
442 if (vval) {
443 /* Ditch the = and the quotes */
444 *vval++ = '\0';
445 if (*vval)
446 vval++;
447 if (strlen(vval))
448 vval[strlen(vval) - 1] = '\0';
449 var = ast_variable_new(vname, vval);
450 if (var) {
451 if (prev)
452 prev->next = var;
453 else
454 vars = var;
455 prev = var;
462 if (*uri) {
463 if (!strcasecmp(buf, "get"))
464 c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars);
465 else
466 c = ast_http_error(501, "Not Implemented", NULL, "Attempt to use unimplemented / unsupported method");\
467 } else
468 c = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
470 /* If they aren't mopped up already, clean up the cookies */
471 if (vars)
472 ast_variables_destroy(vars);
474 if (!c)
475 c = ast_http_error(500, "Internal Error", NULL, "Internal Server Error");
476 if (c) {
477 time(&t);
478 strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
479 ast_cli(ser->fd, "HTTP/1.1 %d %s\r\n", status, title ? title : "OK");
480 ast_cli(ser->fd, "Server: Asterisk\r\n");
481 ast_cli(ser->fd, "Date: %s\r\n", timebuf);
482 ast_cli(ser->fd, "Connection: close\r\n");
483 if (contentlength) {
484 char *tmp;
485 tmp = strstr(c, "\r\n\r\n");
486 if (tmp) {
487 ast_cli(ser->fd, "Content-length: %d\r\n", contentlength);
488 write(ser->fd, c, (tmp + 4 - c));
489 write(ser->fd, tmp + 4, contentlength);
491 } else
492 ast_cli(ser->fd, "%s", c);
493 free(c);
495 if (title)
496 free(title);
498 fclose(ser->f);
499 free(ser);
500 return NULL;
503 static void *http_root(void *data)
505 int fd;
506 struct sockaddr_in sin;
507 socklen_t sinlen;
508 struct ast_http_server_instance *ser;
509 pthread_t launched;
510 pthread_attr_t attr;
512 for (;;) {
513 int flags;
515 ast_wait_for_input(httpfd, -1);
516 sinlen = sizeof(sin);
517 fd = accept(httpfd, (struct sockaddr *)&sin, &sinlen);
518 if (fd < 0) {
519 if ((errno != EAGAIN) && (errno != EINTR))
520 ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
521 continue;
523 ser = ast_calloc(1, sizeof(*ser));
524 if (!ser) {
525 ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno));
526 close(fd);
527 continue;
529 flags = fcntl(fd, F_GETFL);
530 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
531 ser->fd = fd;
532 memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
533 if ((ser->f = fdopen(ser->fd, "w+"))) {
534 pthread_attr_init(&attr);
535 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
537 if (ast_pthread_create_background(&launched, &attr, ast_httpd_helper_thread, ser)) {
538 ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
539 fclose(ser->f);
540 free(ser);
542 } else {
543 ast_log(LOG_WARNING, "fdopen failed!\n");
544 close(ser->fd);
545 free(ser);
548 return NULL;
551 char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, size_t buflen)
553 char *c;
554 c = buf;
555 ast_build_string(&c, &buflen, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var, val);
556 if (expires)
557 ast_build_string(&c, &buflen, "; Max-Age=%d", expires);
558 ast_build_string(&c, &buflen, "\r\n");
559 return buf;
563 static void http_server_start(struct sockaddr_in *sin)
565 int flags;
566 int x = 1;
568 /* Do nothing if nothing has changed */
569 if (!memcmp(&oldsin, sin, sizeof(oldsin))) {
570 ast_log(LOG_DEBUG, "Nothing changed in http\n");
571 return;
574 memcpy(&oldsin, sin, sizeof(oldsin));
576 /* Shutdown a running server if there is one */
577 if (master != AST_PTHREADT_NULL) {
578 pthread_cancel(master);
579 pthread_kill(master, SIGURG);
580 pthread_join(master, NULL);
583 if (httpfd != -1)
584 close(httpfd);
586 /* If there's no new server, stop here */
587 if (!sin->sin_family)
588 return;
591 httpfd = socket(AF_INET, SOCK_STREAM, 0);
592 if (httpfd < 0) {
593 ast_log(LOG_WARNING, "Unable to allocate socket: %s\n", strerror(errno));
594 return;
597 setsockopt(httpfd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
598 if (bind(httpfd, (struct sockaddr *)sin, sizeof(*sin))) {
599 ast_log(LOG_NOTICE, "Unable to bind http server to %s:%d: %s\n",
600 ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
601 strerror(errno));
602 close(httpfd);
603 httpfd = -1;
604 return;
606 if (listen(httpfd, 10)) {
607 ast_log(LOG_NOTICE, "Unable to listen!\n");
608 close(httpfd);
609 httpfd = -1;
610 return;
612 flags = fcntl(httpfd, F_GETFL);
613 fcntl(httpfd, F_SETFL, flags | O_NONBLOCK);
614 if (ast_pthread_create_background(&master, NULL, http_root, NULL)) {
615 ast_log(LOG_NOTICE, "Unable to launch http server on %s:%d: %s\n",
616 ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
617 strerror(errno));
618 close(httpfd);
619 httpfd = -1;
623 static int __ast_http_load(int reload)
625 struct ast_config *cfg;
626 struct ast_variable *v;
627 int enabled=0;
628 int newenablestatic=0;
629 struct sockaddr_in sin;
630 struct hostent *hp;
631 struct ast_hostent ahp;
632 char newprefix[MAX_PREFIX];
634 memset(&sin, 0, sizeof(sin));
635 sin.sin_port = 8088;
636 strcpy(newprefix, DEFAULT_PREFIX);
637 cfg = ast_config_load("http.conf");
638 if (cfg) {
639 v = ast_variable_browse(cfg, "general");
640 while(v) {
641 if (!strcasecmp(v->name, "enabled"))
642 enabled = ast_true(v->value);
643 else if (!strcasecmp(v->name, "enablestatic"))
644 newenablestatic = ast_true(v->value);
645 else if (!strcasecmp(v->name, "bindport"))
646 sin.sin_port = ntohs(atoi(v->value));
647 else if (!strcasecmp(v->name, "bindaddr")) {
648 if ((hp = ast_gethostbyname(v->value, &ahp))) {
649 memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
650 } else {
651 ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
653 } else if (!strcasecmp(v->name, "prefix")) {
654 if (!ast_strlen_zero(v->value)) {
655 newprefix[0] = '/';
656 ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
657 } else {
658 newprefix[0] = '\0';
662 v = v->next;
664 ast_config_destroy(cfg);
666 if (enabled)
667 sin.sin_family = AF_INET;
668 if (strcmp(prefix, newprefix)) {
669 ast_copy_string(prefix, newprefix, sizeof(prefix));
670 prefix_len = strlen(prefix);
672 enablestatic = newenablestatic;
673 http_server_start(&sin);
674 return 0;
677 static int handle_show_http(int fd, int argc, char *argv[])
679 struct ast_http_uri *urih;
680 if (argc != 3)
681 return RESULT_SHOWUSAGE;
682 ast_cli(fd, "HTTP Server Status:\n");
683 ast_cli(fd, "Prefix: %s\n", prefix);
684 if (oldsin.sin_family)
685 ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
686 ast_inet_ntoa(oldsin.sin_addr),
687 ntohs(oldsin.sin_port));
688 else
689 ast_cli(fd, "Server Disabled\n\n");
690 ast_cli(fd, "Enabled URI's:\n");
691 ast_rwlock_rdlock(&uris_lock);
692 urih = uris;
693 while(urih){
694 ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
695 urih = urih->next;
697 if (!uris)
698 ast_cli(fd, "None.\n");
699 ast_rwlock_unlock(&uris_lock);
700 return RESULT_SUCCESS;
703 int ast_http_reload(void)
705 return __ast_http_load(1);
708 static char show_http_help[] =
709 "Usage: http show status\n"
710 " Lists status of internal HTTP engine\n";
712 static struct ast_cli_entry cli_http[] = {
713 { { "http", "show", "status", NULL },
714 handle_show_http, "Display HTTP server status",
715 show_http_help },
718 int ast_http_init(void)
720 ast_http_uri_link(&statusuri);
721 ast_http_uri_link(&staticuri);
722 ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
723 return __ast_http_load(0);