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