8a3792bdd44efc17018f81aa8674c61b2ed37d45
[asterisk/asterisk.git] / main / http.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2006, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
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.
13  *
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.
17  */
18
19 /*!
20  * \file 
21  * \brief http server for AMI access
22  *
23  * \author Mark Spencer <markster@digium.com>
24  *
25  * This program implements a tiny http server
26  * and was inspired by micro-httpd by Jef Poskanzer 
27  * 
28  * \ref AstHTTP - AMI over the http protocol
29  */
30
31 #include "asterisk.h"
32
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34
35 #include <time.h>
36 #include <sys/time.h>
37 #include <sys/stat.h>
38 #include <sys/signal.h>
39 #include <fcntl.h>
40
41 #include "asterisk/paths.h"     /* use ast_config_AST_DATA_DIR */
42 #include "asterisk/network.h"
43 #include "asterisk/cli.h"
44 #include "asterisk/tcptls.h"
45 #include "asterisk/http.h"
46 #include "asterisk/utils.h"
47 #include "asterisk/strings.h"
48 #include "asterisk/config.h"
49 #include "asterisk/stringfields.h"
50 #include "asterisk/ast_version.h"
51 #include "asterisk/manager.h"
52 #include "asterisk/_private.h"
53
54 #define MAX_PREFIX 80
55
56 /* See http.h for more information about the SSL implementation */
57 #if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE))
58 #define DO_SSL  /* comment in/out if you want to support ssl */
59 #endif
60
61 static struct ast_tls_config http_tls_cfg;
62
63 static void *httpd_helper_thread(void *arg);
64
65 /*!
66  * we have up to two accepting threads, one for http, one for https
67  */
68 static struct server_args http_desc = {
69         .accept_fd = -1,
70         .master = AST_PTHREADT_NULL,
71         .tls_cfg = NULL,
72         .poll_timeout = -1,
73         .name = "http server",
74         .accept_fn = ast_tcptls_server_root,
75         .worker_fn = httpd_helper_thread,
76 };
77
78 static struct server_args https_desc = {
79         .accept_fd = -1,
80         .master = AST_PTHREADT_NULL,
81         .tls_cfg = &http_tls_cfg,
82         .poll_timeout = -1,
83         .name = "https server",
84         .accept_fn = ast_tcptls_server_root,
85         .worker_fn = httpd_helper_thread,
86 };
87
88 static AST_RWLIST_HEAD_STATIC(uris, ast_http_uri);      /*!< list of supported handlers */
89
90 /* all valid URIs must be prepended by the string in prefix. */
91 static char prefix[MAX_PREFIX];
92 static int enablestatic;
93
94 /*! \brief Limit the kinds of files we're willing to serve up */
95 static struct {
96         const char *ext;
97         const char *mtype;
98 } mimetypes[] = {
99         { "png", "image/png" },
100         { "jpg", "image/jpeg" },
101         { "js", "application/x-javascript" },
102         { "wav", "audio/x-wav" },
103         { "mp3", "audio/mpeg" },
104         { "svg", "image/svg+xml" },
105         { "svgz", "image/svg+xml" },
106         { "gif", "image/gif" },
107 };
108
109 struct http_uri_redirect {
110         AST_LIST_ENTRY(http_uri_redirect) entry;
111         char *dest;
112         char target[0];
113 };
114
115 static AST_RWLIST_HEAD_STATIC(uri_redirects, http_uri_redirect);
116
117 static const char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
118 {
119         int x;
120
121         if (ftype) {
122                 for (x = 0; x < ARRAY_LEN(mimetypes); x++) {
123                         if (!strcasecmp(ftype, mimetypes[x].ext)) {
124                                 return mimetypes[x].mtype;
125                         }
126                 }
127         }
128
129         snprintf(wkspace, wkspacelen, "text/%s", S_OR(ftype, "plain"));
130
131         return wkspace;
132 }
133
134 static struct ast_str *static_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *vars, struct ast_variable *headers, int *status, char **title, int *contentlength)
135 {
136         char *path;
137         char *ftype;
138         const char *mtype;
139         char wkspace[80];
140         struct stat st;
141         int len;
142         int fd;
143         struct timeval tv = ast_tvnow();
144         char buf[256];
145         struct ast_tm tm;
146
147         /* Yuck.  I'm not really sold on this, but if you don't deliver static content it makes your configuration 
148            substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
149         if (!enablestatic || ast_strlen_zero(uri)) {
150                 goto out403;
151         }
152
153         /* Disallow any funny filenames at all */
154         if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0])) {
155                 goto out403;
156         }
157
158         if (strstr(uri, "/..")) {
159                 goto out403;
160         }
161                 
162         if ((ftype = strrchr(uri, '.'))) {
163                 ftype++;
164         }
165
166         mtype = ftype2mtype(ftype, wkspace, sizeof(wkspace));
167         
168         /* Cap maximum length */
169         if ((len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5) > 1024) {
170                 goto out403;
171         }
172                 
173         path = alloca(len);
174         sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
175         if (stat(path, &st)) {
176                 goto out404;
177         }
178
179         if (S_ISDIR(st.st_mode)) {
180                 goto out404;
181         }
182
183         if ((fd = open(path, O_RDONLY)) < 0) {
184                 goto out403;
185         }
186
187         ast_strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
188         fprintf(ser->f, "HTTP/1.1 200 OK\r\n"
189                 "Server: Asterisk/%s\r\n"
190                 "Date: %s\r\n"
191                 "Connection: close\r\n"
192                 "Cache-Control: no-cache, no-store\r\n"
193                 "Content-Length: %d\r\n"
194                 "Content-type: %s\r\n\r\n",
195                 ast_get_version(), buf, (int) st.st_size, mtype);
196
197         while ((len = read(fd, buf, sizeof(buf))) > 0) {
198                 fwrite(buf, 1, len, ser->f);
199         }
200
201         close(fd);
202
203         return NULL;
204
205 out404:
206         return ast_http_error((*status = 404),
207                               (*title = ast_strdup("Not Found")),
208                                NULL, "Nothing to see here.  Move along.");
209
210 out403:
211         return ast_http_error((*status = 403),
212                               (*title = ast_strdup("Access Denied")),
213                               NULL, "Sorry, I cannot let you do that, Dave.");
214 }
215
216
217 static struct ast_str *httpstatus_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *vars, struct ast_variable *headers, int *status, char **title, int *contentlength)
218 {
219         struct ast_str *out = ast_str_create(512);
220         struct ast_variable *v;
221
222         if (out == NULL) {
223                 return out;
224         }
225
226         ast_str_append(&out, 0,
227                        "\r\n"
228                        "<title>Asterisk HTTP Status</title>\r\n"
229                        "<body bgcolor=\"#ffffff\">\r\n"
230                        "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
231                        "<h2>&nbsp;&nbsp;Asterisk&trade; HTTP Status</h2></td></tr>\r\n");
232         ast_str_append(&out, 0, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
233         ast_str_append(&out, 0, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
234                        ast_inet_ntoa(http_desc.oldsin.sin_addr));
235         ast_str_append(&out, 0, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
236                        ntohs(http_desc.oldsin.sin_port));
237
238         if (http_tls_cfg.enabled) {
239                 ast_str_append(&out, 0, "<tr><td><i>SSL Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
240                                ntohs(https_desc.oldsin.sin_port));
241         }
242
243         ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
244
245         for (v = vars; v; v = v->next) {
246                 if (strncasecmp(v->name, "cookie_", 7)) {
247                         ast_str_append(&out, 0, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
248                 }
249         }
250
251         ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
252
253         for (v = vars; v; v = v->next) {
254                 if (!strncasecmp(v->name, "cookie_", 7)) {
255                         ast_str_append(&out, 0, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
256                 }
257         }
258
259         ast_str_append(&out, 0, "</table><center><font size=\"-1\"><i>Asterisk and Digium are registered trademarks of Digium, Inc.</i></font></center></body>\r\n");
260         return out;
261 }
262
263 static struct ast_http_uri statusuri = {
264         .callback = httpstatus_callback,
265         .description = "Asterisk HTTP General Status",
266         .uri = "httpstatus",
267         .supports_get = 1,
268         .data = NULL,
269         .key = __FILE__,
270 };
271         
272 static struct ast_http_uri staticuri = {
273         .callback = static_callback,
274         .description = "Asterisk HTTP Static Delivery",
275         .uri = "static",
276         .has_subtree = 1,
277         .static_content = 1,
278         .supports_get = 1,
279         .data = NULL,
280         .key= __FILE__,
281 };
282         
283 struct ast_str *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
284 {
285         struct ast_str *out = ast_str_create(512);
286
287         if (out == NULL) {
288                 return out;
289         }
290
291         ast_str_set(&out, 0,
292                     "Content-type: text/html\r\n"
293                     "%s"
294                     "\r\n"
295                     "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
296                     "<html><head>\r\n"
297                     "<title>%d %s</title>\r\n"
298                     "</head><body>\r\n"
299                     "<h1>%s</h1>\r\n"
300                     "<p>%s</p>\r\n"
301                     "<hr />\r\n"
302                     "<address>Asterisk Server</address>\r\n"
303                     "</body></html>\r\n",
304                     (extra_header ? extra_header : ""), status, title, title, text);
305
306         return out;
307 }
308
309 /*! \brief 
310  * Link the new uri into the list. 
311  *
312  * They are sorted by length of
313  * the string, not alphabetically. Duplicate entries are not replaced,
314  * but the insertion order (using <= and not just <) makes sure that
315  * more recent insertions hide older ones.
316  * On a lookup, we just scan the list and stop at the first matching entry.
317  */
318 int ast_http_uri_link(struct ast_http_uri *urih)
319 {
320         struct ast_http_uri *uri;
321         int len = strlen(urih->uri);
322
323         if (!(urih->supports_get || urih->supports_post)) {
324                 ast_log(LOG_WARNING, "URI handler does not provide either GET or POST method: %s (%s)\n", urih->uri, urih->description);
325                 return -1;
326         }
327
328         AST_RWLIST_WRLOCK(&uris);
329
330         if (AST_RWLIST_EMPTY(&uris) || strlen(AST_RWLIST_FIRST(&uris)->uri) <= len) {
331                 AST_RWLIST_INSERT_HEAD(&uris, urih, entry);
332                 AST_RWLIST_UNLOCK(&uris);
333
334                 return 0;
335         }
336
337         AST_RWLIST_TRAVERSE(&uris, uri, entry) {
338                 if (AST_RWLIST_NEXT(uri, entry) &&
339                     strlen(AST_RWLIST_NEXT(uri, entry)->uri) <= len) {
340                         AST_RWLIST_INSERT_AFTER(&uris, uri, urih, entry);
341                         AST_RWLIST_UNLOCK(&uris); 
342
343                         return 0;
344                 }
345         }
346
347         AST_RWLIST_INSERT_TAIL(&uris, urih, entry);
348
349         AST_RWLIST_UNLOCK(&uris);
350         
351         return 0;
352 }       
353
354 void ast_http_uri_unlink(struct ast_http_uri *urih)
355 {
356         AST_RWLIST_WRLOCK(&uris);
357         AST_RWLIST_REMOVE(&uris, urih, entry);
358         AST_RWLIST_UNLOCK(&uris);
359 }
360
361 void ast_http_uri_unlink_all_with_key(const char *key)
362 {
363         struct ast_http_uri *urih;
364         AST_RWLIST_WRLOCK(&uris);
365         AST_RWLIST_TRAVERSE_SAFE_BEGIN(&uris, urih, entry) {
366                 if (!strcmp(urih->key, key)) {
367                         AST_RWLIST_REMOVE_CURRENT(entry);
368                 }
369         }
370         AST_RWLIST_TRAVERSE_SAFE_END
371 }
372
373 static struct ast_str *handle_uri(struct ast_tcptls_session_instance *ser, char *uri, enum ast_http_method method,
374                                   int *status, char **title, int *contentlength, struct ast_variable **cookies, struct ast_variable *headers, 
375                                   unsigned int *static_content)
376 {
377         char *c;
378         struct ast_str *out = NULL;
379         char *params = uri;
380         struct ast_http_uri *urih = NULL;
381         int l;
382         struct ast_variable *vars = NULL, *v, *prev = NULL;
383         struct http_uri_redirect *redirect;
384         int saw_method = 0;
385
386         /* preserve previous behavior of only support URI parameters on GET requests */
387         if (method == AST_HTTP_GET) {
388                 strsep(&params, "?");
389                 
390                 /* Extract arguments from the request and store them in variables. */
391                 if (params) {
392                         char *var, *val;
393                         
394                         while ((val = strsep(&params, "&"))) {
395                                 var = strsep(&val, "=");
396                                 if (val) {
397                                         ast_uri_decode(val);
398                                 } else {
399                                         val = "";
400                                 }
401                                 ast_uri_decode(var);
402                                 if ((v = ast_variable_new(var, val, ""))) {
403                                         if (vars) {
404                                                 prev->next = v;
405                                         } else {
406                                                 vars = v;
407                                         }
408                                         prev = v;
409                                 }
410                         }
411                 }
412         }
413
414         /*
415          * Append the cookies to the variables (the only reason to have them
416          * at the end is to avoid another pass of the cookies list to find
417          * the tail).
418          */
419         if (prev) {
420                 prev->next = *cookies;
421         } else {
422                 vars = *cookies;
423         }
424         *cookies = NULL;
425
426         ast_uri_decode(uri);
427
428         AST_RWLIST_RDLOCK(&uri_redirects);
429         AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
430                 if (!strcasecmp(uri, redirect->target)) {
431                         char buf[512];
432
433                         snprintf(buf, sizeof(buf), "Location: %s\r\n", redirect->dest);
434                         out = ast_http_error((*status = 302),
435                                              (*title = ast_strdup("Moved Temporarily")),
436                                              buf, "There is no spoon...");
437
438                         break;
439                 }
440         }
441         AST_RWLIST_UNLOCK(&uri_redirects);
442
443         if (redirect) {
444                 goto cleanup;
445         }
446
447         /* We want requests to start with the (optional) prefix and '/' */
448         l = strlen(prefix);
449         if (!strncasecmp(uri, prefix, l) && uri[l] == '/') {
450                 uri += l + 1;
451                 /* scan registered uris to see if we match one. */
452                 AST_RWLIST_RDLOCK(&uris);
453                 AST_RWLIST_TRAVERSE(&uris, urih, entry) {
454                         if (!saw_method) {
455                                 switch (method) {
456                                 case AST_HTTP_GET:
457                                         if (urih->supports_get) {
458                                                 saw_method = 1;
459                                         }
460                                         break;
461                                 case AST_HTTP_POST:
462                                         if (urih->supports_post) {
463                                                 saw_method = 1;
464                                         }
465                                         break;
466                                 }
467                         }
468
469                         l = strlen(urih->uri);
470                         c = uri + l;    /* candidate */
471
472                         if (strncasecmp(urih->uri, uri, l) || /* no match */
473                             (*c && *c != '/')) { /* substring */
474                                 continue;
475                         }
476
477                         if (*c == '/') {
478                                 c++;
479                         }
480
481                         if (!*c || urih->has_subtree) {
482                                 if (((method == AST_HTTP_GET) && urih->supports_get) ||
483                                     ((method == AST_HTTP_POST) && urih->supports_post)) {
484                                         uri = c;
485
486                                         break;
487                                 }
488                         }
489                 }
490
491                 if (!urih) {
492                         AST_RWLIST_UNLOCK(&uris);
493                 }
494         }
495
496         if (urih) {
497                 *static_content = urih->static_content;
498                 out = urih->callback(ser, urih, uri, method, vars, headers, status, title, contentlength);
499                 AST_RWLIST_UNLOCK(&uris);
500         } else if (saw_method) {
501                 out = ast_http_error((*status = 404),
502                                      (*title = ast_strdup("Not Found")), NULL,
503                                      "The requested URL was not found on this server.");
504         } else {
505                 out = ast_http_error((*status = 501),
506                                      (*title = ast_strdup("Not Implemented")), NULL,
507                                      "Attempt to use unimplemented / unsupported method");
508         }
509
510 cleanup:
511         ast_variables_destroy(vars);
512
513         return out;
514 }
515
516 #ifdef DO_SSL
517 #if defined(HAVE_FUNOPEN)
518 #define HOOK_T int
519 #define LEN_T int
520 #else
521 #define HOOK_T ssize_t
522 #define LEN_T size_t
523 #endif
524
525 /*!
526  * replacement read/write functions for SSL support.
527  * We use wrappers rather than SSL_read/SSL_write directly so
528  * we can put in some debugging.
529  */
530 /*static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
531 {
532         int i = SSL_read(cookie, buf, len-1);
533 #if 0
534         if (i >= 0)
535                 buf[i] = '\0';
536         ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
537 #endif
538         return i;
539 }
540
541 static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
542 {
543 #if 0
544         char *s = alloca(len+1);
545         strncpy(s, buf, len);
546         s[len] = '\0';
547         ast_verbose("ssl write size %d <%s>\n", (int)len, s);
548 #endif
549         return SSL_write(cookie, buf, len);
550 }
551
552 static int ssl_close(void *cookie)
553 {
554         close(SSL_get_fd(cookie));
555         SSL_shutdown(cookie);
556         SSL_free(cookie);
557         return 0;
558 }*/
559 #endif  /* DO_SSL */
560
561 static void *httpd_helper_thread(void *data)
562 {
563         char buf[4096];
564         char cookie[4096];
565         struct ast_tcptls_session_instance *ser = data;
566         struct ast_variable *var, *prev=NULL, *vars=NULL, *headers = NULL;
567         char *uri, *title=NULL;
568         int status = 200, contentlength = 0;
569         struct ast_str *out = NULL;
570         unsigned int static_content = 0;
571
572         if (!fgets(buf, sizeof(buf), ser->f)) {
573                 goto done;
574         }
575
576         uri = ast_skip_nonblanks(buf);  /* Skip method */
577         if (*uri) {
578                 *uri++ = '\0';
579         }
580
581         uri = ast_skip_blanks(uri);     /* Skip white space */
582
583         if (*uri) {                     /* terminate at the first blank */
584                 char *c = ast_skip_nonblanks(uri);
585
586                 if (*c) {
587                         *c = '\0';
588                 }
589         }
590
591         /* process "Cookie: " lines */
592         while (fgets(cookie, sizeof(cookie), ser->f)) {
593                 char *vname, *vval;
594                 int l;
595
596                 /* Trim trailing characters */
597                 ast_trim_blanks(cookie);
598                 if (ast_strlen_zero(cookie)) {
599                         break;
600                 }
601                 if (strncasecmp(cookie, "Cookie: ", 8)) {
602                         char *name, *value;
603
604                         value = ast_strdupa(cookie);
605                         name = strsep(&value, ":");
606                         if (!value) {
607                                 continue;
608                         }
609                         value = ast_skip_blanks(value);
610                         if (ast_strlen_zero(value)) {
611                                 continue;
612                         }
613                         var = ast_variable_new(name, value, "");
614                         if (!var) { 
615                                 continue;
616                         }
617                         var->next = headers;
618                         headers = var;
619
620                         continue;
621                 }
622
623                 /* TODO - The cookie parsing code below seems to work   
624                    in IE6 and FireFox 1.5.  However, it is not entirely 
625                    correct, and therefore may not work in all           
626                    circumstances.                                       
627                       For more details see RFC 2109 and RFC 2965        */
628         
629                 /* FireFox cookie strings look like:                    
630                      Cookie: mansession_id="********"                   
631                    InternetExplorer's look like:                        
632                      Cookie: $Version="1"; mansession_id="********"     */
633                 
634                 /* If we got a FireFox cookie string, the name's right  
635                     after "Cookie: "                                    */
636                 vname = ast_skip_blanks(cookie + 8);
637                         
638                 /* If we got an IE cookie string, we need to skip to    
639                     past the version to get to the name                 */
640                 if (*vname == '$') {
641                         strsep(&vname, ";");
642                         if (!vname) {   /* no name ? */
643                                 continue;
644                         }
645                         vname = ast_skip_blanks(vname);
646                 }
647                 if (!(vval = strchr(vname, '='))) {
648                         continue;
649                 }
650                 /* Ditch the = and the quotes */
651                 *vval++ = '\0';
652                 if (*vval) {
653                         vval++;
654                 }
655                 if ((l = strlen(vval))) {
656                         vval[l - 1] = '\0';     /* trim trailing quote */
657                 }
658                 if ((var = ast_variable_new(vname, vval, ""))) {
659                         if (prev)
660                                 prev->next = var;
661                         else
662                                 vars = var;
663                         prev = var;
664                 }
665         }
666
667         if (!*uri) {
668                 out = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
669         } else if (strcasecmp(buf, "post") && strcasecmp(buf, "get")) {
670                 out = ast_http_error(501, "Not Implemented", NULL,
671                                      "Attempt to use unimplemented / unsupported method");
672         } else {        /* try to serve it */
673                 out = handle_uri(ser, uri, (!strcasecmp(buf, "get")) ? AST_HTTP_GET : AST_HTTP_POST,
674                                  &status, &title, &contentlength, &vars, headers, &static_content);
675         }
676
677         /* If they aren't mopped up already, clean up the cookies */
678         if (vars) {
679                 ast_variables_destroy(vars);
680         }
681         /* Clean up all the header information pulled as well */
682         if (headers) {
683                 ast_variables_destroy(headers);
684         }
685
686         if (out) {
687                 struct timeval tv = ast_tvnow();
688                 char timebuf[256];
689                 struct ast_tm tm;
690
691                 ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
692                 fprintf(ser->f,
693                         "HTTP/1.1 %d %s\r\n"
694                         "Server: Asterisk/%s\r\n"
695                         "Date: %s\r\n"
696                         "Connection: close\r\n"
697                         "%s",
698                         status, title ? title : "OK", ast_get_version(), timebuf,
699                         static_content ? "" : "Cache-Control: no-cache, no-store\r\n");
700                         /* We set the no-cache headers only for dynamic content.
701                         * If you want to make sure the static file you requested is not from cache,
702                         * append a random variable to your GET request.  Ex: 'something.html?r=109987734'
703                         */
704                 if (!contentlength) {   /* opaque body ? just dump it hoping it is properly formatted */
705                         fprintf(ser->f, "%s", out->str);
706                 } else {
707                         char *tmp = strstr(out->str, "\r\n\r\n");
708
709                         if (tmp) {
710                                 fprintf(ser->f, "Content-length: %d\r\n", contentlength);
711                                 /* first write the header, then the body */
712                                 fwrite(out->str, 1, (tmp + 4 - out->str), ser->f);
713                                 fwrite(tmp + 4, 1, contentlength, ser->f);
714                         }
715                 }
716                 ast_free(out);
717         }
718
719         if (title) {
720                 ast_free(title);
721         }
722
723 done:
724         fclose(ser->f);
725         ser = ast_tcptls_session_instance_destroy(ser);
726
727         return NULL;
728 }
729
730 /*!
731  * \brief Add a new URI redirect
732  * The entries in the redirect list are sorted by length, just like the list
733  * of URI handlers.
734  */
735 static void add_redirect(const char *value)
736 {
737         char *target, *dest;
738         struct http_uri_redirect *redirect, *cur;
739         unsigned int target_len;
740         unsigned int total_len;
741
742         dest = ast_strdupa(value);
743         dest = ast_skip_blanks(dest);
744         target = strsep(&dest, " ");
745         target = ast_skip_blanks(target);
746         target = strsep(&target, " "); /* trim trailing whitespace */
747
748         if (!dest) {
749                 ast_log(LOG_WARNING, "Invalid redirect '%s'\n", value);
750                 return;
751         }
752
753         target_len = strlen(target) + 1;
754         total_len = sizeof(*redirect) + target_len + strlen(dest) + 1;
755
756         if (!(redirect = ast_calloc(1, total_len))) {
757                 return;
758         }
759
760         redirect->dest = redirect->target + target_len;
761         strcpy(redirect->target, target);
762         strcpy(redirect->dest, dest);
763
764         AST_RWLIST_WRLOCK(&uri_redirects);
765
766         target_len--; /* So we can compare directly with strlen() */
767         if (AST_RWLIST_EMPTY(&uri_redirects) 
768             || strlen(AST_RWLIST_FIRST(&uri_redirects)->target) <= target_len) {
769                 AST_RWLIST_INSERT_HEAD(&uri_redirects, redirect, entry);
770                 AST_RWLIST_UNLOCK(&uri_redirects);
771
772                 return;
773         }
774
775         AST_RWLIST_TRAVERSE(&uri_redirects, cur, entry) {
776                 if (AST_RWLIST_NEXT(cur, entry) 
777                     && strlen(AST_RWLIST_NEXT(cur, entry)->target) <= target_len) {
778                         AST_RWLIST_INSERT_AFTER(&uri_redirects, cur, redirect, entry);
779                         AST_RWLIST_UNLOCK(&uri_redirects); 
780
781                         return;
782                 }
783         }
784
785         AST_RWLIST_INSERT_TAIL(&uri_redirects, redirect, entry);
786
787         AST_RWLIST_UNLOCK(&uri_redirects);
788 }
789
790 static int __ast_http_load(int reload)
791 {
792         struct ast_config *cfg;
793         struct ast_variable *v;
794         int enabled=0;
795         int newenablestatic=0;
796         struct hostent *hp;
797         struct ast_hostent ahp;
798         char newprefix[MAX_PREFIX] = "";
799         int have_sslbindaddr = 0;
800         struct http_uri_redirect *redirect;
801         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
802
803         if ((cfg = ast_config_load2("http.conf", "http", config_flags)) == CONFIG_STATUS_FILEUNCHANGED) {
804                 return 0;
805         }
806
807         /* default values */
808         memset(&http_desc.sin, 0, sizeof(http_desc.sin));
809         http_desc.sin.sin_port = htons(8088);
810
811         memset(&https_desc.sin, 0, sizeof(https_desc.sin));
812         https_desc.sin.sin_port = htons(8089);
813
814         http_tls_cfg.enabled = 0;
815         if (http_tls_cfg.certfile) {
816                 ast_free(http_tls_cfg.certfile);
817         }
818         http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
819         if (http_tls_cfg.cipher) {
820                 ast_free(http_tls_cfg.cipher);
821         }
822         http_tls_cfg.cipher = ast_strdup("");
823
824         AST_RWLIST_WRLOCK(&uri_redirects);
825         while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry))) {
826                 ast_free(redirect);
827         }
828         AST_RWLIST_UNLOCK(&uri_redirects);
829
830         if (cfg) {
831                 v = ast_variable_browse(cfg, "general");
832                 for (; v; v = v->next) {
833                         if (!strcasecmp(v->name, "enabled")) {
834                                 enabled = ast_true(v->value);
835                         } else if (!strcasecmp(v->name, "sslenable")) {
836                                 http_tls_cfg.enabled = ast_true(v->value);
837                         } else if (!strcasecmp(v->name, "sslbindport")) {
838                                 https_desc.sin.sin_port = htons(atoi(v->value));
839                         } else if (!strcasecmp(v->name, "sslcert")) {
840                                 ast_free(http_tls_cfg.certfile);
841                                 http_tls_cfg.certfile = ast_strdup(v->value);
842                         } else if (!strcasecmp(v->name, "sslcipher")) {
843                                 ast_free(http_tls_cfg.cipher);
844                                 http_tls_cfg.cipher = ast_strdup(v->value);
845                         } else if (!strcasecmp(v->name, "enablestatic")) {
846                                 newenablestatic = ast_true(v->value);
847                         } else if (!strcasecmp(v->name, "bindport")) {
848                                 http_desc.sin.sin_port = htons(atoi(v->value));
849                         } else if (!strcasecmp(v->name, "sslbindaddr")) {
850                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
851                                         memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
852                                         have_sslbindaddr = 1;
853                                 } else {
854                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
855                                 }
856                         } else if (!strcasecmp(v->name, "bindaddr")) {
857                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
858                                         memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
859                                 } else {
860                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
861                                 }
862                         } else if (!strcasecmp(v->name, "prefix")) {
863                                 if (!ast_strlen_zero(v->value)) {
864                                         newprefix[0] = '/';
865                                         ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
866                                 } else {
867                                         newprefix[0] = '\0';
868                                 }
869                         } else if (!strcasecmp(v->name, "redirect")) {
870                                 add_redirect(v->value);
871                         } else {
872                                 ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
873                         }
874                 }
875
876                 ast_config_destroy(cfg);
877         }
878
879         if (!have_sslbindaddr) {
880                 https_desc.sin.sin_addr = http_desc.sin.sin_addr;
881         }
882         if (enabled) {
883                 http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
884         }
885         if (strcmp(prefix, newprefix)) {
886                 ast_copy_string(prefix, newprefix, sizeof(prefix));
887         }
888         enablestatic = newenablestatic;
889         ast_tcptls_server_start(&http_desc);
890         if (ast_ssl_setup(https_desc.tls_cfg)) {
891                 ast_tcptls_server_start(&https_desc);
892         }
893
894         return 0;
895 }
896
897 static char *handle_show_http(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
898 {
899         struct ast_http_uri *urih;
900         struct http_uri_redirect *redirect;
901
902         switch (cmd) {
903         case CLI_INIT:
904                 e->command = "http show status";
905                 e->usage = 
906                         "Usage: http show status\n"
907                         "       Lists status of internal HTTP engine\n";
908                 return NULL;
909         case CLI_GENERATE:
910                 return NULL;
911         }
912         
913         if (a->argc != 3) {
914                 return CLI_SHOWUSAGE;
915         }
916         ast_cli(a->fd, "HTTP Server Status:\n");
917         ast_cli(a->fd, "Prefix: %s\n", prefix);
918         if (!http_desc.oldsin.sin_family) {
919                 ast_cli(a->fd, "Server Disabled\n\n");
920         } else {
921                 ast_cli(a->fd, "Server Enabled and Bound to %s:%d\n\n",
922                         ast_inet_ntoa(http_desc.oldsin.sin_addr),
923                         ntohs(http_desc.oldsin.sin_port));
924                 if (http_tls_cfg.enabled) {
925                         ast_cli(a->fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
926                                 ast_inet_ntoa(https_desc.oldsin.sin_addr),
927                                 ntohs(https_desc.oldsin.sin_port));
928                 }
929         }
930
931         ast_cli(a->fd, "Enabled URI's:\n");
932         AST_RWLIST_RDLOCK(&uris);
933         if (AST_RWLIST_EMPTY(&uris)) {
934                 ast_cli(a->fd, "None.\n");
935         } else {
936                 AST_RWLIST_TRAVERSE(&uris, urih, entry) {
937                         ast_cli(a->fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : ""), urih->description);
938                 }
939         }
940         AST_RWLIST_UNLOCK(&uris);
941
942         ast_cli(a->fd, "\nEnabled Redirects:\n");
943         AST_RWLIST_RDLOCK(&uri_redirects);
944         AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
945                 ast_cli(a->fd, "  %s => %s\n", redirect->target, redirect->dest);
946         }
947         if (AST_RWLIST_EMPTY(&uri_redirects)) {
948                 ast_cli(a->fd, "  None.\n");
949         }
950         AST_RWLIST_UNLOCK(&uri_redirects);
951
952         return CLI_SUCCESS;
953 }
954
955 int ast_http_reload(void)
956 {
957         return __ast_http_load(1);
958 }
959
960 static struct ast_cli_entry cli_http[] = {
961         AST_CLI_DEFINE(handle_show_http, "Display HTTP server status"),
962 };
963
964 int ast_http_init(void)
965 {
966         ast_http_uri_link(&statusuri);
967         ast_http_uri_link(&staticuri);
968         ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
969
970         return __ast_http_load(0);
971 }