another dependency
[asterisk-bristuff.git] / main / http.c
blob80841a2235a0be006811f1394cda9c2902d756e8
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 pthread_attr_destroy(&attr);
543 } else {
544 ast_log(LOG_WARNING, "fdopen failed!\n");
545 close(ser->fd);
546 free(ser);
549 return NULL;
552 char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, size_t buflen)
554 char *c;
555 c = buf;
556 ast_build_string(&c, &buflen, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var, val);
557 if (expires)
558 ast_build_string(&c, &buflen, "; Max-Age=%d", expires);
559 ast_build_string(&c, &buflen, "\r\n");
560 return buf;
564 static void http_server_start(struct sockaddr_in *sin)
566 int flags;
567 int x = 1;
569 /* Do nothing if nothing has changed */
570 if (!memcmp(&oldsin, sin, sizeof(oldsin))) {
571 ast_log(LOG_DEBUG, "Nothing changed in http\n");
572 return;
575 memcpy(&oldsin, sin, sizeof(oldsin));
577 /* Shutdown a running server if there is one */
578 if (master != AST_PTHREADT_NULL) {
579 pthread_cancel(master);
580 pthread_kill(master, SIGURG);
581 pthread_join(master, NULL);
584 if (httpfd != -1)
585 close(httpfd);
587 /* If there's no new server, stop here */
588 if (!sin->sin_family)
589 return;
592 httpfd = socket(AF_INET, SOCK_STREAM, 0);
593 if (httpfd < 0) {
594 ast_log(LOG_WARNING, "Unable to allocate socket: %s\n", strerror(errno));
595 return;
598 setsockopt(httpfd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
599 if (bind(httpfd, (struct sockaddr *)sin, sizeof(*sin))) {
600 ast_log(LOG_NOTICE, "Unable to bind http server to %s:%d: %s\n",
601 ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
602 strerror(errno));
603 close(httpfd);
604 httpfd = -1;
605 return;
607 if (listen(httpfd, 10)) {
608 ast_log(LOG_NOTICE, "Unable to listen!\n");
609 close(httpfd);
610 httpfd = -1;
611 return;
613 flags = fcntl(httpfd, F_GETFL);
614 fcntl(httpfd, F_SETFL, flags | O_NONBLOCK);
615 if (ast_pthread_create_background(&master, NULL, http_root, NULL)) {
616 ast_log(LOG_NOTICE, "Unable to launch http server on %s:%d: %s\n",
617 ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
618 strerror(errno));
619 close(httpfd);
620 httpfd = -1;
624 static int __ast_http_load(int reload)
626 struct ast_config *cfg;
627 struct ast_variable *v;
628 int enabled=0;
629 int newenablestatic=0;
630 struct sockaddr_in sin;
631 struct hostent *hp;
632 struct ast_hostent ahp;
633 char newprefix[MAX_PREFIX];
635 memset(&sin, 0, sizeof(sin));
636 sin.sin_port = htons(8088);
637 strcpy(newprefix, DEFAULT_PREFIX);
638 cfg = ast_config_load("http.conf");
639 if (cfg) {
640 v = ast_variable_browse(cfg, "general");
641 while(v) {
642 if (!strcasecmp(v->name, "enabled"))
643 enabled = ast_true(v->value);
644 else if (!strcasecmp(v->name, "enablestatic"))
645 newenablestatic = ast_true(v->value);
646 else if (!strcasecmp(v->name, "bindport"))
647 sin.sin_port = ntohs(atoi(v->value));
648 else if (!strcasecmp(v->name, "bindaddr")) {
649 if ((hp = ast_gethostbyname(v->value, &ahp))) {
650 memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
651 } else {
652 ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
654 } else if (!strcasecmp(v->name, "prefix")) {
655 if (!ast_strlen_zero(v->value)) {
656 newprefix[0] = '/';
657 ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
658 } else {
659 newprefix[0] = '\0';
663 v = v->next;
665 ast_config_destroy(cfg);
667 if (enabled)
668 sin.sin_family = AF_INET;
669 if (strcmp(prefix, newprefix)) {
670 ast_copy_string(prefix, newprefix, sizeof(prefix));
671 prefix_len = strlen(prefix);
673 enablestatic = newenablestatic;
674 http_server_start(&sin);
675 return 0;
678 static int handle_show_http(int fd, int argc, char *argv[])
680 struct ast_http_uri *urih;
681 if (argc != 3)
682 return RESULT_SHOWUSAGE;
683 ast_cli(fd, "HTTP Server Status:\n");
684 ast_cli(fd, "Prefix: %s\n", prefix);
685 if (oldsin.sin_family)
686 ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
687 ast_inet_ntoa(oldsin.sin_addr),
688 ntohs(oldsin.sin_port));
689 else
690 ast_cli(fd, "Server Disabled\n\n");
691 ast_cli(fd, "Enabled URI's:\n");
692 ast_rwlock_rdlock(&uris_lock);
693 urih = uris;
694 while(urih){
695 ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
696 urih = urih->next;
698 if (!uris)
699 ast_cli(fd, "None.\n");
700 ast_rwlock_unlock(&uris_lock);
701 return RESULT_SUCCESS;
704 int ast_http_reload(void)
706 return __ast_http_load(1);
709 static char show_http_help[] =
710 "Usage: http show status\n"
711 " Lists status of internal HTTP engine\n";
713 static struct ast_cli_entry cli_http[] = {
714 { { "http", "show", "status", NULL },
715 handle_show_http, "Display HTTP server status",
716 show_http_help },
719 int ast_http_init(void)
721 ast_http_uri_link(&statusuri);
722 ast_http_uri_link(&staticuri);
723 ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
724 return __ast_http_load(0);