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