add ExtenSpy variant of ChanSpy
[asterisk-bristuff.git] / http.c
blob9e24c5e60d431131be7e6eab294fcf5c6c48fd99
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 static struct ast_http_uri *uris;
69 static int httpfd = -1;
70 static pthread_t master = AST_PTHREADT_NULL;
71 static char prefix[MAX_PREFIX];
72 static int prefix_len = 0;
73 static struct sockaddr_in oldsin;
74 static int enablestatic=0;
76 /*! \brief Limit the kinds of files we're willing to serve up */
77 static struct {
78 char *ext;
79 char *mtype;
80 } mimetypes[] = {
81 { "png", "image/png" },
82 { "jpg", "image/jpeg" },
83 { "js", "application/x-javascript" },
84 { "wav", "audio/x-wav" },
85 { "mp3", "audio/mpeg" },
88 static char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
90 int x;
91 if (ftype) {
92 for (x=0;x<sizeof(mimetypes) / sizeof(mimetypes[0]); x++) {
93 if (!strcasecmp(ftype, mimetypes[x].ext))
94 return mimetypes[x].mtype;
97 snprintf(wkspace, wkspacelen, "text/%s", ftype ? ftype : "plain");
98 return wkspace;
101 static char *static_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
103 char result[4096];
104 char *c=result;
105 char *path;
106 char *ftype, *mtype;
107 char wkspace[80];
108 struct stat st;
109 int len;
110 int fd;
111 void *blob;
113 /* Yuck. I'm not really sold on this, but if you don't deliver static content it makes your configuration
114 substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
115 if (!enablestatic || ast_strlen_zero(uri))
116 goto out403;
117 /* Disallow any funny filenames at all */
118 if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0]))
119 goto out403;
120 if (strstr(uri, "/.."))
121 goto out403;
123 if ((ftype = strrchr(uri, '.')))
124 ftype++;
125 mtype=ftype2mtype(ftype, wkspace, sizeof(wkspace));
127 /* Cap maximum length */
128 len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5;
129 if (len > 1024)
130 goto out403;
132 path = alloca(len);
133 sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
134 if (stat(path, &st))
135 goto out404;
136 if (S_ISDIR(st.st_mode))
137 goto out404;
138 fd = open(path, O_RDONLY);
139 if (fd < 0)
140 goto out403;
142 len = st.st_size + strlen(mtype) + 40;
144 blob = malloc(len);
145 if (blob) {
146 c = blob;
147 sprintf(c, "Content-type: %s\r\n\r\n", mtype);
148 c += strlen(c);
149 *contentlength = read(fd, c, st.st_size);
150 if (*contentlength < 0) {
151 close(fd);
152 free(blob);
153 goto out403;
156 close(fd);
157 return blob;
159 out404:
160 *status = 404;
161 *title = strdup("Not Found");
162 return ast_http_error(404, "Not Found", NULL, "Nothing to see here. Move along.");
164 out403:
165 *status = 403;
166 *title = strdup("Access Denied");
167 return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
171 static char *httpstatus_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
173 char result[4096];
174 size_t reslen = sizeof(result);
175 char *c=result;
176 struct ast_variable *v;
178 ast_build_string(&c, &reslen,
179 "\r\n"
180 "<title>Asterisk HTTP Status</title>\r\n"
181 "<body bgcolor=\"#ffffff\">\r\n"
182 "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
183 "<h2>&nbsp;&nbsp;Asterisk&trade; HTTP Status</h2></td></tr>\r\n");
185 ast_build_string(&c, &reslen, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
186 ast_build_string(&c, &reslen, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
187 ast_inet_ntoa(oldsin.sin_addr));
188 ast_build_string(&c, &reslen, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
189 ntohs(oldsin.sin_port));
190 ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
191 v = vars;
192 while(v) {
193 if (strncasecmp(v->name, "cookie_", 7))
194 ast_build_string(&c, &reslen, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
195 v = v->next;
197 ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
198 v = vars;
199 while(v) {
200 if (!strncasecmp(v->name, "cookie_", 7))
201 ast_build_string(&c, &reslen, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
202 v = v->next;
204 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");
205 return strdup(result);
208 static struct ast_http_uri statusuri = {
209 .callback = httpstatus_callback,
210 .description = "Asterisk HTTP General Status",
211 .uri = "httpstatus",
212 .has_subtree = 0,
215 static struct ast_http_uri staticuri = {
216 .callback = static_callback,
217 .description = "Asterisk HTTP Static Delivery",
218 .uri = "static",
219 .has_subtree = 1,
222 char *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
224 char *c = NULL;
225 asprintf(&c,
226 "Content-type: text/html\r\n"
227 "%s"
228 "\r\n"
229 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
230 "<html><head>\r\n"
231 "<title>%d %s</title>\r\n"
232 "</head><body>\r\n"
233 "<h1>%s</h1>\r\n"
234 "<p>%s</p>\r\n"
235 "<hr />\r\n"
236 "<address>Asterisk Server</address>\r\n"
237 "</body></html>\r\n",
238 (extra_header ? extra_header : ""), status, title, title, text);
239 return c;
242 int ast_http_uri_link(struct ast_http_uri *urih)
244 struct ast_http_uri *prev=uris;
245 if (!uris || strlen(uris->uri) <= strlen(urih->uri)) {
246 urih->next = uris;
247 uris = urih;
248 } else {
249 while (prev->next && (strlen(prev->next->uri) > strlen(urih->uri)))
250 prev = prev->next;
251 /* Insert it here */
252 urih->next = prev->next;
253 prev->next = urih;
255 return 0;
258 void ast_http_uri_unlink(struct ast_http_uri *urih)
260 struct ast_http_uri *prev = uris;
261 if (!uris)
262 return;
263 if (uris == urih) {
264 uris = uris->next;
266 while(prev->next) {
267 if (prev->next == urih) {
268 prev->next = urih->next;
269 break;
271 prev = prev->next;
275 static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **title, int *contentlength, struct ast_variable **cookies)
277 char *c;
278 char *turi;
279 char *params;
280 char *var;
281 char *val;
282 struct ast_http_uri *urih=NULL;
283 int len;
284 struct ast_variable *vars=NULL, *v, *prev = NULL;
287 params = strchr(uri, '?');
288 if (params) {
289 *params = '\0';
290 params++;
291 while ((var = strsep(&params, "&"))) {
292 val = strchr(var, '=');
293 if (val) {
294 *val = '\0';
295 val++;
296 ast_uri_decode(val);
297 } else
298 val = "";
299 ast_uri_decode(var);
300 if ((v = ast_variable_new(var, val))) {
301 if (vars)
302 prev->next = v;
303 else
304 vars = v;
305 prev = v;
309 if (prev)
310 prev->next = *cookies;
311 else
312 vars = *cookies;
313 *cookies = NULL;
314 ast_uri_decode(uri);
315 if (!strncasecmp(uri, prefix, prefix_len)) {
316 uri += prefix_len;
317 if (!*uri || (*uri == '/')) {
318 if (*uri == '/')
319 uri++;
320 urih = uris;
321 while(urih) {
322 len = strlen(urih->uri);
323 if (!strncasecmp(urih->uri, uri, len)) {
324 if (!uri[len] || uri[len] == '/') {
325 turi = uri + len;
326 if (*turi == '/')
327 turi++;
328 if (!*turi || urih->has_subtree) {
329 uri = turi;
330 break;
334 urih = urih->next;
338 if (urih) {
339 c = urih->callback(sin, uri, vars, status, title, contentlength);
340 ast_variables_destroy(vars);
341 } else if (ast_strlen_zero(uri) && ast_strlen_zero(prefix)) {
342 /* Special case: If no prefix, and no URI, send to /static/index.html */
343 c = ast_http_error(302, "Moved Temporarily", "Location: /static/index.html\r\n", "This is not the page you are looking for...");
344 *status = 302;
345 *title = strdup("Moved Temporarily");
346 } else {
347 c = ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this serer.");
348 *status = 404;
349 *title = strdup("Not Found");
351 return c;
354 static void *ast_httpd_helper_thread(void *data)
356 char buf[4096];
357 char cookie[4096];
358 char timebuf[256];
359 struct ast_http_server_instance *ser = data;
360 struct ast_variable *var, *prev=NULL, *vars=NULL;
361 char *uri, *c, *title=NULL;
362 char *vname, *vval;
363 int status = 200, contentlength = 0;
364 time_t t;
366 if (fgets(buf, sizeof(buf), ser->f)) {
367 /* Skip method */
368 uri = buf;
369 while(*uri && (*uri > 32))
370 uri++;
371 if (*uri) {
372 *uri = '\0';
373 uri++;
376 /* Skip white space */
377 while (*uri && (*uri < 33))
378 uri++;
380 if (*uri) {
381 c = uri;
382 while (*c && (*c > 32))
383 c++;
384 if (*c) {
385 *c = '\0';
389 while (fgets(cookie, sizeof(cookie), ser->f)) {
390 /* Trim trailing characters */
391 while(!ast_strlen_zero(cookie) && (cookie[strlen(cookie) - 1] < 33)) {
392 cookie[strlen(cookie) - 1] = '\0';
394 if (ast_strlen_zero(cookie))
395 break;
396 if (!strncasecmp(cookie, "Cookie: ", 8)) {
397 vname = cookie + 8;
398 vval = strchr(vname, '=');
399 if (vval) {
400 /* Ditch the = and the quotes */
401 *vval = '\0';
402 vval++;
403 if (*vval)
404 vval++;
405 if (strlen(vval))
406 vval[strlen(vval) - 1] = '\0';
407 var = ast_variable_new(vname, vval);
408 if (var) {
409 if (prev)
410 prev->next = var;
411 else
412 vars = var;
413 prev = var;
419 if (*uri) {
420 if (!strcasecmp(buf, "get"))
421 c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars);
422 else
423 c = ast_http_error(501, "Not Implemented", NULL, "Attempt to use unimplemented / unsupported method");\
424 } else
425 c = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
427 /* If they aren't mopped up already, clean up the cookies */
428 if (vars)
429 ast_variables_destroy(vars);
431 if (!c)
432 c = ast_http_error(500, "Internal Error", NULL, "Internal Server Error");
433 if (c) {
434 time(&t);
435 strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
436 ast_cli(ser->fd, "HTTP/1.1 %d %s\r\n", status, title ? title : "OK");
437 ast_cli(ser->fd, "Server: Asterisk\r\n");
438 ast_cli(ser->fd, "Date: %s\r\n", timebuf);
439 ast_cli(ser->fd, "Connection: close\r\n");
440 if (contentlength) {
441 char *tmp;
442 tmp = strstr(c, "\r\n\r\n");
443 if (tmp) {
444 ast_cli(ser->fd, "Content-length: %d\r\n", contentlength);
445 write(ser->fd, c, (tmp + 4 - c));
446 write(ser->fd, tmp + 4, contentlength);
448 } else
449 ast_cli(ser->fd, "%s", c);
450 free(c);
452 if (title)
453 free(title);
455 fclose(ser->f);
456 free(ser);
457 return NULL;
460 static void *http_root(void *data)
462 int fd;
463 struct sockaddr_in sin;
464 socklen_t sinlen;
465 struct ast_http_server_instance *ser;
466 pthread_t launched;
467 pthread_attr_t attr;
469 for (;;) {
470 ast_wait_for_input(httpfd, -1);
471 sinlen = sizeof(sin);
472 fd = accept(httpfd, (struct sockaddr *)&sin, &sinlen);
473 if (fd < 0) {
474 if ((errno != EAGAIN) && (errno != EINTR))
475 ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
476 continue;
478 ser = ast_calloc(1, sizeof(*ser));
479 if (ser) {
480 ser->fd = fd;
481 memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
482 if ((ser->f = fdopen(ser->fd, "w+"))) {
483 pthread_attr_init(&attr);
484 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
486 if (ast_pthread_create(&launched, &attr, ast_httpd_helper_thread, ser)) {
487 ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
488 fclose(ser->f);
489 free(ser);
491 } else {
492 ast_log(LOG_WARNING, "fdopen failed!\n");
493 close(ser->fd);
494 free(ser);
498 return NULL;
501 char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, size_t buflen)
503 char *c;
504 c = buf;
505 ast_build_string(&c, &buflen, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var, val);
506 if (expires)
507 ast_build_string(&c, &buflen, "; Max-Age=%d", expires);
508 ast_build_string(&c, &buflen, "\r\n");
509 return buf;
513 static void http_server_start(struct sockaddr_in *sin)
515 int flags;
516 int x = 1;
518 /* Do nothing if nothing has changed */
519 if (!memcmp(&oldsin, sin, sizeof(oldsin))) {
520 ast_log(LOG_DEBUG, "Nothing changed in http\n");
521 return;
524 memcpy(&oldsin, sin, sizeof(oldsin));
526 /* Shutdown a running server if there is one */
527 if (master != AST_PTHREADT_NULL) {
528 pthread_cancel(master);
529 pthread_kill(master, SIGURG);
530 pthread_join(master, NULL);
533 if (httpfd != -1)
534 close(httpfd);
536 /* If there's no new server, stop here */
537 if (!sin->sin_family)
538 return;
541 httpfd = socket(AF_INET, SOCK_STREAM, 0);
542 if (httpfd < 0) {
543 ast_log(LOG_WARNING, "Unable to allocate socket: %s\n", strerror(errno));
544 return;
547 setsockopt(httpfd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
548 if (bind(httpfd, (struct sockaddr *)sin, sizeof(*sin))) {
549 ast_log(LOG_NOTICE, "Unable to bind http server to %s:%d: %s\n",
550 ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
551 strerror(errno));
552 close(httpfd);
553 httpfd = -1;
554 return;
556 if (listen(httpfd, 10)) {
557 ast_log(LOG_NOTICE, "Unable to listen!\n");
558 close(httpfd);
559 httpfd = -1;
560 return;
562 flags = fcntl(httpfd, F_GETFL);
563 fcntl(httpfd, F_SETFL, flags | O_NONBLOCK);
564 if (ast_pthread_create(&master, NULL, http_root, NULL)) {
565 ast_log(LOG_NOTICE, "Unable to launch http server on %s:%d: %s\n",
566 ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
567 strerror(errno));
568 close(httpfd);
569 httpfd = -1;
573 static int __ast_http_load(int reload)
575 struct ast_config *cfg;
576 struct ast_variable *v;
577 int enabled=0;
578 int newenablestatic=0;
579 struct sockaddr_in sin;
580 struct hostent *hp;
581 struct ast_hostent ahp;
582 char newprefix[MAX_PREFIX];
584 memset(&sin, 0, sizeof(sin));
585 sin.sin_port = 8088;
586 strcpy(newprefix, DEFAULT_PREFIX);
587 cfg = ast_config_load("http.conf");
588 if (cfg) {
589 v = ast_variable_browse(cfg, "general");
590 while(v) {
591 if (!strcasecmp(v->name, "enabled"))
592 enabled = ast_true(v->value);
593 else if (!strcasecmp(v->name, "enablestatic"))
594 newenablestatic = ast_true(v->value);
595 else if (!strcasecmp(v->name, "bindport"))
596 sin.sin_port = ntohs(atoi(v->value));
597 else if (!strcasecmp(v->name, "bindaddr")) {
598 if ((hp = ast_gethostbyname(v->value, &ahp))) {
599 memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
600 } else {
601 ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
603 } else if (!strcasecmp(v->name, "prefix")) {
604 if (!ast_strlen_zero(v->value)) {
605 newprefix[0] = '/';
606 ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
607 } else {
608 newprefix[0] = '\0';
612 v = v->next;
614 ast_config_destroy(cfg);
616 if (enabled)
617 sin.sin_family = AF_INET;
618 if (strcmp(prefix, newprefix)) {
619 ast_copy_string(prefix, newprefix, sizeof(prefix));
620 prefix_len = strlen(prefix);
622 enablestatic = newenablestatic;
623 http_server_start(&sin);
624 return 0;
627 static int handle_show_http(int fd, int argc, char *argv[])
629 struct ast_http_uri *urih;
630 if (argc != 3)
631 return RESULT_SHOWUSAGE;
632 ast_cli(fd, "HTTP Server Status:\n");
633 ast_cli(fd, "Prefix: %s\n", prefix);
634 if (oldsin.sin_family)
635 ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
636 ast_inet_ntoa(oldsin.sin_addr),
637 ntohs(oldsin.sin_port));
638 else
639 ast_cli(fd, "Server Disabled\n\n");
640 ast_cli(fd, "Enabled URI's:\n");
641 urih = uris;
642 while(urih){
643 ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
644 urih = urih->next;
646 if (!uris)
647 ast_cli(fd, "None.\n");
648 return RESULT_SUCCESS;
651 int ast_http_reload(void)
653 return __ast_http_load(1);
656 static char show_http_help[] =
657 "Usage: http show status\n"
658 " Shows status of internal HTTP engine\n";
660 static struct ast_cli_entry http_cli[] = {
661 { { "http", "show", "status", NULL }, handle_show_http,
662 "Display HTTP server status", show_http_help },
665 int ast_http_init(void)
667 ast_http_uri_link(&statusuri);
668 ast_http_uri_link(&staticuri);
669 ast_cli_register_multiple(http_cli, sizeof(http_cli) / sizeof(http_cli[0]));
670 return __ast_http_load(0);