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