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