Merged revisions 114600 via svnmerge from
[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 struct ast_variable *parse_cookies(char *cookies)
562 {
563         char *cur;
564         struct ast_variable *vars = NULL, *var;
565
566         /* Skip Cookie: */
567         cookies += 8;
568
569         while ((cur = strsep(&cookies, ";"))) {
570                 char *name, *val;
571                 
572                 name = val = cur;
573                 strsep(&val, "=");
574
575                 if (ast_strlen_zero(name) || ast_strlen_zero(val)) {
576                         continue;
577                 }
578
579                 name = ast_strip(name);
580                 val = ast_strip_quoted(val, "\"", "\"");
581
582                 if (ast_strlen_zero(name) || ast_strlen_zero(val)) {
583                         continue;
584                 }
585
586                 if (option_debug) {
587                         ast_log(LOG_DEBUG, "mmm ... cookie!  Name: '%s'  Value: '%s'\n", name, val);
588                 }
589
590                 var = ast_variable_new(name, val, __FILE__);
591                 var->next = vars;
592                 vars = var;
593         }
594
595         return vars;
596 }
597
598 static void *httpd_helper_thread(void *data)
599 {
600         char buf[4096];
601         char cookie[4096];
602         struct ast_tcptls_session_instance *ser = data;
603         struct ast_variable *vars=NULL, *headers = NULL;
604         char *uri, *title=NULL;
605         int status = 200, contentlength = 0;
606         struct ast_str *out = NULL;
607         unsigned int static_content = 0;
608
609         if (!fgets(buf, sizeof(buf), ser->f)) {
610                 goto done;
611         }
612
613         uri = ast_skip_nonblanks(buf);  /* Skip method */
614         if (*uri) {
615                 *uri++ = '\0';
616         }
617
618         uri = ast_skip_blanks(uri);     /* Skip white space */
619
620         if (*uri) {                     /* terminate at the first blank */
621                 char *c = ast_skip_nonblanks(uri);
622
623                 if (*c) {
624                         *c = '\0';
625                 }
626         }
627
628         /* process "Cookie: " lines */
629         while (fgets(cookie, sizeof(cookie), ser->f)) {
630                 /* Trim trailing characters */
631                 ast_trim_blanks(cookie);
632                 if (ast_strlen_zero(cookie)) {
633                         break;
634                 }
635                 if (strncasecmp(cookie, "Cookie: ", 8)) {
636                         char *name, *value;
637                         struct ast_variable *var;
638
639                         value = ast_strdupa(cookie);
640                         name = strsep(&value, ":");
641                         if (!value) {
642                                 continue;
643                         }
644                         value = ast_skip_blanks(value);
645                         if (ast_strlen_zero(value)) {
646                                 continue;
647                         }
648                         var = ast_variable_new(name, value, "");
649                         if (!var) { 
650                                 continue;
651                         }
652                         var->next = headers;
653                         headers = var;
654
655                         continue;
656                 }
657
658                 if (vars) {
659                         ast_variables_destroy(vars);
660                 }
661                 vars = parse_cookies(cookie);
662         }
663
664         if (!*uri) {
665                 out = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
666         } else if (strcasecmp(buf, "post") && strcasecmp(buf, "get")) {
667                 out = ast_http_error(501, "Not Implemented", NULL,
668                                      "Attempt to use unimplemented / unsupported method");
669         } else {        /* try to serve it */
670                 out = handle_uri(ser, uri, (!strcasecmp(buf, "get")) ? AST_HTTP_GET : AST_HTTP_POST,
671                                  &status, &title, &contentlength, &vars, headers, &static_content);
672         }
673
674         /* If they aren't mopped up already, clean up the cookies */
675         if (vars) {
676                 ast_variables_destroy(vars);
677         }
678         /* Clean up all the header information pulled as well */
679         if (headers) {
680                 ast_variables_destroy(headers);
681         }
682
683         if (out) {
684                 struct timeval tv = ast_tvnow();
685                 char timebuf[256];
686                 struct ast_tm tm;
687
688                 ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
689                 fprintf(ser->f,
690                         "HTTP/1.1 %d %s\r\n"
691                         "Server: Asterisk/%s\r\n"
692                         "Date: %s\r\n"
693                         "Connection: close\r\n"
694                         "%s",
695                         status, title ? title : "OK", ast_get_version(), timebuf,
696                         static_content ? "" : "Cache-Control: no-cache, no-store\r\n");
697                         /* We set the no-cache headers only for dynamic content.
698                         * If you want to make sure the static file you requested is not from cache,
699                         * append a random variable to your GET request.  Ex: 'something.html?r=109987734'
700                         */
701                 if (!contentlength) {   /* opaque body ? just dump it hoping it is properly formatted */
702                         fprintf(ser->f, "%s", out->str);
703                 } else {
704                         char *tmp = strstr(out->str, "\r\n\r\n");
705
706                         if (tmp) {
707                                 fprintf(ser->f, "Content-length: %d\r\n", contentlength);
708                                 /* first write the header, then the body */
709                                 fwrite(out->str, 1, (tmp + 4 - out->str), ser->f);
710                                 fwrite(tmp + 4, 1, contentlength, ser->f);
711                         }
712                 }
713                 ast_free(out);
714         }
715
716         if (title) {
717                 ast_free(title);
718         }
719
720 done:
721         fclose(ser->f);
722         ser = ast_tcptls_session_instance_destroy(ser);
723
724         return NULL;
725 }
726
727 /*!
728  * \brief Add a new URI redirect
729  * The entries in the redirect list are sorted by length, just like the list
730  * of URI handlers.
731  */
732 static void add_redirect(const char *value)
733 {
734         char *target, *dest;
735         struct http_uri_redirect *redirect, *cur;
736         unsigned int target_len;
737         unsigned int total_len;
738
739         dest = ast_strdupa(value);
740         dest = ast_skip_blanks(dest);
741         target = strsep(&dest, " ");
742         target = ast_skip_blanks(target);
743         target = strsep(&target, " "); /* trim trailing whitespace */
744
745         if (!dest) {
746                 ast_log(LOG_WARNING, "Invalid redirect '%s'\n", value);
747                 return;
748         }
749
750         target_len = strlen(target) + 1;
751         total_len = sizeof(*redirect) + target_len + strlen(dest) + 1;
752
753         if (!(redirect = ast_calloc(1, total_len))) {
754                 return;
755         }
756
757         redirect->dest = redirect->target + target_len;
758         strcpy(redirect->target, target);
759         strcpy(redirect->dest, dest);
760
761         AST_RWLIST_WRLOCK(&uri_redirects);
762
763         target_len--; /* So we can compare directly with strlen() */
764         if (AST_RWLIST_EMPTY(&uri_redirects) 
765             || strlen(AST_RWLIST_FIRST(&uri_redirects)->target) <= target_len) {
766                 AST_RWLIST_INSERT_HEAD(&uri_redirects, redirect, entry);
767                 AST_RWLIST_UNLOCK(&uri_redirects);
768
769                 return;
770         }
771
772         AST_RWLIST_TRAVERSE(&uri_redirects, cur, entry) {
773                 if (AST_RWLIST_NEXT(cur, entry) 
774                     && strlen(AST_RWLIST_NEXT(cur, entry)->target) <= target_len) {
775                         AST_RWLIST_INSERT_AFTER(&uri_redirects, cur, redirect, entry);
776                         AST_RWLIST_UNLOCK(&uri_redirects); 
777
778                         return;
779                 }
780         }
781
782         AST_RWLIST_INSERT_TAIL(&uri_redirects, redirect, entry);
783
784         AST_RWLIST_UNLOCK(&uri_redirects);
785 }
786
787 static int __ast_http_load(int reload)
788 {
789         struct ast_config *cfg;
790         struct ast_variable *v;
791         int enabled=0;
792         int newenablestatic=0;
793         struct hostent *hp;
794         struct ast_hostent ahp;
795         char newprefix[MAX_PREFIX] = "";
796         int have_sslbindaddr = 0;
797         struct http_uri_redirect *redirect;
798         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
799
800         if ((cfg = ast_config_load2("http.conf", "http", config_flags)) == CONFIG_STATUS_FILEUNCHANGED) {
801                 return 0;
802         }
803
804         /* default values */
805         memset(&http_desc.sin, 0, sizeof(http_desc.sin));
806         http_desc.sin.sin_port = htons(8088);
807
808         memset(&https_desc.sin, 0, sizeof(https_desc.sin));
809         https_desc.sin.sin_port = htons(8089);
810
811         http_tls_cfg.enabled = 0;
812         if (http_tls_cfg.certfile) {
813                 ast_free(http_tls_cfg.certfile);
814         }
815         http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
816         if (http_tls_cfg.cipher) {
817                 ast_free(http_tls_cfg.cipher);
818         }
819         http_tls_cfg.cipher = ast_strdup("");
820
821         AST_RWLIST_WRLOCK(&uri_redirects);
822         while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry))) {
823                 ast_free(redirect);
824         }
825         AST_RWLIST_UNLOCK(&uri_redirects);
826
827         if (cfg) {
828                 v = ast_variable_browse(cfg, "general");
829                 for (; v; v = v->next) {
830                         if (!strcasecmp(v->name, "enabled")) {
831                                 enabled = ast_true(v->value);
832                         } else if (!strcasecmp(v->name, "sslenable")) {
833                                 http_tls_cfg.enabled = ast_true(v->value);
834                         } else if (!strcasecmp(v->name, "sslbindport")) {
835                                 https_desc.sin.sin_port = htons(atoi(v->value));
836                         } else if (!strcasecmp(v->name, "sslcert")) {
837                                 ast_free(http_tls_cfg.certfile);
838                                 http_tls_cfg.certfile = ast_strdup(v->value);
839                         } else if (!strcasecmp(v->name, "sslcipher")) {
840                                 ast_free(http_tls_cfg.cipher);
841                                 http_tls_cfg.cipher = ast_strdup(v->value);
842                         } else if (!strcasecmp(v->name, "enablestatic")) {
843                                 newenablestatic = ast_true(v->value);
844                         } else if (!strcasecmp(v->name, "bindport")) {
845                                 http_desc.sin.sin_port = htons(atoi(v->value));
846                         } else if (!strcasecmp(v->name, "sslbindaddr")) {
847                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
848                                         memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
849                                         have_sslbindaddr = 1;
850                                 } else {
851                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
852                                 }
853                         } else if (!strcasecmp(v->name, "bindaddr")) {
854                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
855                                         memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
856                                 } else {
857                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
858                                 }
859                         } else if (!strcasecmp(v->name, "prefix")) {
860                                 if (!ast_strlen_zero(v->value)) {
861                                         newprefix[0] = '/';
862                                         ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
863                                 } else {
864                                         newprefix[0] = '\0';
865                                 }
866                         } else if (!strcasecmp(v->name, "redirect")) {
867                                 add_redirect(v->value);
868                         } else {
869                                 ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
870                         }
871                 }
872
873                 ast_config_destroy(cfg);
874         }
875
876         if (!have_sslbindaddr) {
877                 https_desc.sin.sin_addr = http_desc.sin.sin_addr;
878         }
879         if (enabled) {
880                 http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
881         }
882         if (strcmp(prefix, newprefix)) {
883                 ast_copy_string(prefix, newprefix, sizeof(prefix));
884         }
885         enablestatic = newenablestatic;
886         ast_tcptls_server_start(&http_desc);
887         if (ast_ssl_setup(https_desc.tls_cfg)) {
888                 ast_tcptls_server_start(&https_desc);
889         }
890
891         return 0;
892 }
893
894 static char *handle_show_http(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
895 {
896         struct ast_http_uri *urih;
897         struct http_uri_redirect *redirect;
898
899         switch (cmd) {
900         case CLI_INIT:
901                 e->command = "http show status";
902                 e->usage = 
903                         "Usage: http show status\n"
904                         "       Lists status of internal HTTP engine\n";
905                 return NULL;
906         case CLI_GENERATE:
907                 return NULL;
908         }
909         
910         if (a->argc != 3) {
911                 return CLI_SHOWUSAGE;
912         }
913         ast_cli(a->fd, "HTTP Server Status:\n");
914         ast_cli(a->fd, "Prefix: %s\n", prefix);
915         if (!http_desc.oldsin.sin_family) {
916                 ast_cli(a->fd, "Server Disabled\n\n");
917         } else {
918                 ast_cli(a->fd, "Server Enabled and Bound to %s:%d\n\n",
919                         ast_inet_ntoa(http_desc.oldsin.sin_addr),
920                         ntohs(http_desc.oldsin.sin_port));
921                 if (http_tls_cfg.enabled) {
922                         ast_cli(a->fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
923                                 ast_inet_ntoa(https_desc.oldsin.sin_addr),
924                                 ntohs(https_desc.oldsin.sin_port));
925                 }
926         }
927
928         ast_cli(a->fd, "Enabled URI's:\n");
929         AST_RWLIST_RDLOCK(&uris);
930         if (AST_RWLIST_EMPTY(&uris)) {
931                 ast_cli(a->fd, "None.\n");
932         } else {
933                 AST_RWLIST_TRAVERSE(&uris, urih, entry) {
934                         ast_cli(a->fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : ""), urih->description);
935                 }
936         }
937         AST_RWLIST_UNLOCK(&uris);
938
939         ast_cli(a->fd, "\nEnabled Redirects:\n");
940         AST_RWLIST_RDLOCK(&uri_redirects);
941         AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
942                 ast_cli(a->fd, "  %s => %s\n", redirect->target, redirect->dest);
943         }
944         if (AST_RWLIST_EMPTY(&uri_redirects)) {
945                 ast_cli(a->fd, "  None.\n");
946         }
947         AST_RWLIST_UNLOCK(&uri_redirects);
948
949         return CLI_SUCCESS;
950 }
951
952 int ast_http_reload(void)
953 {
954         return __ast_http_load(1);
955 }
956
957 static struct ast_cli_entry cli_http[] = {
958         AST_CLI_DEFINE(handle_show_http, "Display HTTP server status"),
959 };
960
961 int ast_http_init(void)
962 {
963         ast_http_uri_link(&statusuri);
964         ast_http_uri_link(&staticuri);
965         ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
966
967         return __ast_http_load(0);
968 }