Use the constant that I really meant to use here ...
[asterisk-bristuff.git] / main / http.c
blob662afb03ed74ca929c006ef0b3f355df27de3d7f
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, "Nothing to see here. Move along.");
172 out403:
173 *status = 403;
174 *title = strdup("Access Denied");
175 return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
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", "This is not the page you are looking for...");
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 void *ast_httpd_helper_thread(void *data)
384 char buf[4096];
385 char cookie[4096];
386 char timebuf[256];
387 struct ast_http_server_instance *ser = data;
388 struct ast_variable *var, *prev=NULL, *vars=NULL;
389 char *uri, *c, *title=NULL;
390 char *vname, *vval;
391 int status = 200, contentlength = 0;
392 time_t t;
393 unsigned int static_content = 0;
395 if (fgets(buf, sizeof(buf), ser->f)) {
396 /* Skip method */
397 uri = buf;
398 while(*uri && (*uri > 32))
399 uri++;
400 if (*uri) {
401 *uri = '\0';
402 uri++;
405 /* Skip white space */
406 while (*uri && (*uri < 33))
407 uri++;
409 if (*uri) {
410 c = uri;
411 while (*c && (*c > 32))
412 c++;
413 if (*c) {
414 *c = '\0';
418 while (fgets(cookie, sizeof(cookie), ser->f)) {
419 /* Trim trailing characters */
420 while(!ast_strlen_zero(cookie) && (cookie[strlen(cookie) - 1] < 33)) {
421 cookie[strlen(cookie) - 1] = '\0';
423 if (ast_strlen_zero(cookie))
424 break;
425 if (!strncasecmp(cookie, "Cookie: ", 8)) {
427 /* TODO - The cookie parsing code below seems to work
428 in IE6 and FireFox 1.5. However, it is not entirely
429 correct, and therefore may not work in all
430 circumstances.
431 For more details see RFC 2109 and RFC 2965 */
433 /* FireFox cookie strings look like:
434 Cookie: mansession_id="********"
435 InternetExplorer's look like:
436 Cookie: $Version="1"; mansession_id="********" */
438 /* If we got a FireFox cookie string, the name's right
439 after "Cookie: " */
440 vname = cookie + 8;
442 /* If we got an IE cookie string, we need to skip to
443 past the version to get to the name */
444 if (*vname == '$') {
445 vname = strchr(vname, ';');
446 if (vname) {
447 vname++;
448 if (*vname == ' ')
449 vname++;
453 if (vname) {
454 vval = strchr(vname, '=');
455 if (vval) {
456 /* Ditch the = and the quotes */
457 *vval++ = '\0';
458 if (*vval)
459 vval++;
460 if (strlen(vval))
461 vval[strlen(vval) - 1] = '\0';
462 var = ast_variable_new(vname, vval);
463 if (var) {
464 if (prev)
465 prev->next = var;
466 else
467 vars = var;
468 prev = var;
475 if (*uri) {
476 if (!strcasecmp(buf, "get"))
477 c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars, &static_content);
478 else
479 c = ast_http_error(501, "Not Implemented", NULL, "Attempt to use unimplemented / unsupported method");\
480 } else
481 c = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
483 /* If they aren't mopped up already, clean up the cookies */
484 if (vars)
485 ast_variables_destroy(vars);
487 if (!c)
488 c = ast_http_error(500, "Internal Error", NULL, "Internal Server Error");
489 if (c) {
490 time(&t);
491 strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
492 ast_cli(ser->fd, "HTTP/1.1 %d %s\r\n", status, title ? title : "OK");
493 ast_cli(ser->fd, "Server: Asterisk/%s\r\n", ASTERISK_VERSION);
494 ast_cli(ser->fd, "Date: %s\r\n", timebuf);
495 ast_cli(ser->fd, "Connection: close\r\n");
496 if (!static_content)
497 ast_cli(ser->fd, "Cache-Control: no-cache, no-store\r\n");
498 if (contentlength) {
499 char *tmp;
500 tmp = strstr(c, "\r\n\r\n");
501 if (tmp) {
502 ast_cli(ser->fd, "Content-length: %d\r\n", contentlength);
503 write(ser->fd, c, (tmp + 4 - c));
504 write(ser->fd, tmp + 4, contentlength);
506 } else
507 ast_cli(ser->fd, "%s", c);
508 free(c);
510 if (title)
511 free(title);
513 fclose(ser->f);
514 free(ser);
515 return NULL;
518 static void *http_root(void *data)
520 int fd;
521 struct sockaddr_in sin;
522 socklen_t sinlen;
523 struct ast_http_server_instance *ser;
524 pthread_t launched;
525 pthread_attr_t attr;
527 for (;;) {
528 int flags;
530 ast_wait_for_input(httpfd, -1);
531 sinlen = sizeof(sin);
532 fd = accept(httpfd, (struct sockaddr *)&sin, &sinlen);
533 if (fd < 0) {
534 if ((errno != EAGAIN) && (errno != EINTR))
535 ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
536 continue;
538 ser = ast_calloc(1, sizeof(*ser));
539 if (!ser) {
540 ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno));
541 close(fd);
542 continue;
544 flags = fcntl(fd, F_GETFL);
545 fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
546 ser->fd = fd;
547 memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
548 if ((ser->f = fdopen(ser->fd, "w+"))) {
549 pthread_attr_init(&attr);
550 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
552 if (ast_pthread_create_background(&launched, &attr, ast_httpd_helper_thread, ser)) {
553 ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
554 fclose(ser->f);
555 free(ser);
557 pthread_attr_destroy(&attr);
558 } else {
559 ast_log(LOG_WARNING, "fdopen failed!\n");
560 close(ser->fd);
561 free(ser);
564 return NULL;
567 char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, size_t buflen)
569 char *c;
570 c = buf;
571 ast_build_string(&c, &buflen, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var, val);
572 if (expires)
573 ast_build_string(&c, &buflen, "; Max-Age=%d", expires);
574 ast_build_string(&c, &buflen, "\r\n");
575 return buf;
579 static void http_server_start(struct sockaddr_in *sin)
581 int flags;
582 int x = 1;
584 /* Do nothing if nothing has changed */
585 if (!memcmp(&oldsin, sin, sizeof(oldsin))) {
586 ast_log(LOG_DEBUG, "Nothing changed in http\n");
587 return;
590 memcpy(&oldsin, sin, sizeof(oldsin));
592 /* Shutdown a running server if there is one */
593 if (master != AST_PTHREADT_NULL) {
594 pthread_cancel(master);
595 pthread_kill(master, SIGURG);
596 pthread_join(master, NULL);
599 if (httpfd != -1)
600 close(httpfd);
602 /* If there's no new server, stop here */
603 if (!sin->sin_family)
604 return;
607 httpfd = socket(AF_INET, SOCK_STREAM, 0);
608 if (httpfd < 0) {
609 ast_log(LOG_WARNING, "Unable to allocate socket: %s\n", strerror(errno));
610 return;
613 setsockopt(httpfd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
614 if (bind(httpfd, (struct sockaddr *)sin, sizeof(*sin))) {
615 ast_log(LOG_NOTICE, "Unable to bind http server to %s:%d: %s\n",
616 ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
617 strerror(errno));
618 close(httpfd);
619 httpfd = -1;
620 return;
622 if (listen(httpfd, 10)) {
623 ast_log(LOG_NOTICE, "Unable to listen!\n");
624 close(httpfd);
625 httpfd = -1;
626 return;
628 flags = fcntl(httpfd, F_GETFL);
629 fcntl(httpfd, F_SETFL, flags | O_NONBLOCK);
630 if (ast_pthread_create_background(&master, NULL, http_root, NULL)) {
631 ast_log(LOG_NOTICE, "Unable to launch http server on %s:%d: %s\n",
632 ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
633 strerror(errno));
634 close(httpfd);
635 httpfd = -1;
639 static int __ast_http_load(int reload)
641 struct ast_config *cfg;
642 struct ast_variable *v;
643 int enabled=0;
644 int newenablestatic=0;
645 struct sockaddr_in sin;
646 struct hostent *hp;
647 struct ast_hostent ahp;
648 char newprefix[MAX_PREFIX];
650 memset(&sin, 0, sizeof(sin));
651 sin.sin_port = htons(8088);
653 strcpy(newprefix, DEFAULT_PREFIX);
655 cfg = ast_config_load("http.conf");
656 if (cfg) {
657 v = ast_variable_browse(cfg, "general");
658 while(v) {
659 if (!strcasecmp(v->name, "enabled"))
660 enabled = ast_true(v->value);
661 else if (!strcasecmp(v->name, "enablestatic"))
662 newenablestatic = ast_true(v->value);
663 else if (!strcasecmp(v->name, "bindport"))
664 sin.sin_port = ntohs(atoi(v->value));
665 else if (!strcasecmp(v->name, "bindaddr")) {
666 if ((hp = ast_gethostbyname(v->value, &ahp))) {
667 memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
668 } else {
669 ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
671 } else if (!strcasecmp(v->name, "prefix")) {
672 if (!ast_strlen_zero(v->value)) {
673 newprefix[0] = '/';
674 ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
675 } else {
676 newprefix[0] = '\0';
680 v = v->next;
682 ast_config_destroy(cfg);
684 if (enabled)
685 sin.sin_family = AF_INET;
686 if (strcmp(prefix, newprefix)) {
687 ast_copy_string(prefix, newprefix, sizeof(prefix));
688 prefix_len = strlen(prefix);
690 enablestatic = newenablestatic;
692 http_server_start(&sin);
695 return 0;
698 static int handle_show_http(int fd, int argc, char *argv[])
700 struct ast_http_uri *urih;
702 if (argc != 3)
703 return RESULT_SHOWUSAGE;
705 ast_cli(fd, "HTTP Server Status:\n");
706 ast_cli(fd, "Prefix: %s\n", prefix);
707 if (oldsin.sin_family)
708 ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
709 ast_inet_ntoa(oldsin.sin_addr),
710 ntohs(oldsin.sin_port));
711 else
712 ast_cli(fd, "Server Disabled\n\n");
713 ast_cli(fd, "Enabled URI's:\n");
714 ast_rwlock_rdlock(&uris_lock);
715 urih = uris;
716 while(urih){
717 ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
718 urih = urih->next;
720 if (!uris)
721 ast_cli(fd, "None.\n");
722 ast_rwlock_unlock(&uris_lock);
724 return RESULT_SUCCESS;
727 int ast_http_reload(void)
729 return __ast_http_load(1);
732 static char show_http_help[] =
733 "Usage: http show status\n"
734 " Lists status of internal HTTP engine\n";
736 static struct ast_cli_entry cli_http[] = {
737 { { "http", "show", "status", NULL },
738 handle_show_http, "Display HTTP server status",
739 show_http_help },
742 int ast_http_init(void)
744 ast_http_uri_link(&statusuri);
745 ast_http_uri_link(&staticuri);
746 ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
748 return __ast_http_load(0);