Fix a trunk compilation warning.
[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                         char *tmp = ast_strdupa(v->value);
815                         if (cookies) {
816                                 ast_variables_destroy(cookies);
817                         }
818
819                         cookies = parse_cookies(tmp);
820                 }
821         }
822         return cookies;
823 }
824
825
826 static void *httpd_helper_thread(void *data)
827 {
828         char buf[4096];
829         char header_line[4096];
830         struct ast_tcptls_session_instance *ser = data;
831         struct ast_variable *headers = NULL;
832         struct ast_variable *tail = headers;
833         char *uri, *method;
834         enum ast_http_method http_method = AST_HTTP_UNKNOWN;
835
836         if (!fgets(buf, sizeof(buf), ser->f)) {
837                 goto done;
838         }
839
840         /* Get method */
841         method = ast_skip_blanks(buf);
842         uri = ast_skip_nonblanks(method);
843         if (*uri) {
844                 *uri++ = '\0';
845         }
846
847         if (!strcasecmp(method,"GET")) {
848                 http_method = AST_HTTP_GET;
849         } else if (!strcasecmp(method,"POST")) {
850                 http_method = AST_HTTP_POST;
851         } else if (!strcasecmp(method,"HEAD")) {
852                 http_method = AST_HTTP_HEAD;
853         } else if (!strcasecmp(method,"PUT")) {
854                 http_method = AST_HTTP_PUT;
855         }
856
857         uri = ast_skip_blanks(uri);     /* Skip white space */
858
859         if (*uri) {                     /* terminate at the first blank */
860                 char *c = ast_skip_nonblanks(uri);
861
862                 if (*c) {
863                         *c = '\0';
864                 }
865         }
866
867         /* process "Request Headers" lines */
868         while (fgets(header_line, sizeof(header_line), ser->f)) {
869                 char *name, *value;
870
871                 /* Trim trailing characters */
872                 ast_trim_blanks(header_line);
873                 if (ast_strlen_zero(header_line)) {
874                         break;
875                 }
876
877                 value = header_line;
878                 name = strsep(&value, ":");
879                 if (!value) {
880                         continue;
881                 }
882
883                 value = ast_skip_blanks(value);
884                 if (ast_strlen_zero(value) || ast_strlen_zero(name)) {
885                         continue;
886                 }
887
888                 ast_trim_blanks(name);
889
890                 if (!headers) {
891                         headers = ast_variable_new(name, value, __FILE__);
892                         tail = headers;
893                 } else {
894                         tail->next = ast_variable_new(name, value, __FILE__);
895                         tail = tail->next;
896                 }
897         }
898
899         if (!*uri) {
900                 ast_http_error(ser, 400, "Bad Request", "Invalid Request");
901                 return NULL;
902         }
903
904         handle_uri(ser, uri, http_method, headers);
905
906         /* Clean up all the header information pulled as well */
907         if (headers) {
908                 ast_variables_destroy(headers);
909         }
910
911 done:
912         if (ser->f) {
913                 fclose(ser->f);
914         }
915         ao2_ref(ser, -1);
916         ser = NULL;
917         return NULL;
918 }
919
920 /*!
921  * \brief Add a new URI redirect
922  * The entries in the redirect list are sorted by length, just like the list
923  * of URI handlers.
924  */
925 static void add_redirect(const char *value)
926 {
927         char *target, *dest;
928         struct http_uri_redirect *redirect, *cur;
929         unsigned int target_len;
930         unsigned int total_len;
931
932         dest = ast_strdupa(value);
933         dest = ast_skip_blanks(dest);
934         target = strsep(&dest, " ");
935         target = ast_skip_blanks(target);
936         target = strsep(&target, " "); /* trim trailing whitespace */
937
938         if (!dest) {
939                 ast_log(LOG_WARNING, "Invalid redirect '%s'\n", value);
940                 return;
941         }
942
943         target_len = strlen(target) + 1;
944         total_len = sizeof(*redirect) + target_len + strlen(dest) + 1;
945
946         if (!(redirect = ast_calloc(1, total_len))) {
947                 return;
948         }
949         redirect->dest = redirect->target + target_len;
950         strcpy(redirect->target, target);
951         strcpy(redirect->dest, dest);
952
953         AST_RWLIST_WRLOCK(&uri_redirects);
954
955         target_len--; /* So we can compare directly with strlen() */
956         if (AST_RWLIST_EMPTY(&uri_redirects)
957                 || strlen(AST_RWLIST_FIRST(&uri_redirects)->target) <= target_len ) {
958                 AST_RWLIST_INSERT_HEAD(&uri_redirects, redirect, entry);
959                 AST_RWLIST_UNLOCK(&uri_redirects);
960
961                 return;
962         }
963
964         AST_RWLIST_TRAVERSE(&uri_redirects, cur, entry) {
965                 if (AST_RWLIST_NEXT(cur, entry)
966                         && strlen(AST_RWLIST_NEXT(cur, entry)->target) <= target_len ) {
967                         AST_RWLIST_INSERT_AFTER(&uri_redirects, cur, redirect, entry);
968                         AST_RWLIST_UNLOCK(&uri_redirects);
969                         return;
970                 }
971         }
972
973         AST_RWLIST_INSERT_TAIL(&uri_redirects, redirect, entry);
974
975         AST_RWLIST_UNLOCK(&uri_redirects);
976 }
977
978 static int __ast_http_load(int reload)
979 {
980         struct ast_config *cfg;
981         struct ast_variable *v;
982         int enabled=0;
983         int newenablestatic=0;
984         struct hostent *hp;
985         struct ast_hostent ahp;
986         char newprefix[MAX_PREFIX] = "";
987         struct http_uri_redirect *redirect;
988         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
989
990         cfg = ast_config_load2("http.conf", "http", config_flags);
991         if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
992                 return 0;
993         }
994
995         /* default values */
996         memset(&http_desc.local_address, 0, sizeof(http_desc.local_address));
997         http_desc.local_address.sin_port = htons(8088);
998
999         memset(&https_desc.local_address, 0, sizeof(https_desc.local_address));
1000         https_desc.local_address.sin_port = htons(8089);
1001
1002         http_tls_cfg.enabled = 0;
1003         if (http_tls_cfg.certfile) {
1004                 ast_free(http_tls_cfg.certfile);
1005         }
1006         http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
1007
1008         if (http_tls_cfg.pvtfile) {
1009                 ast_free(http_tls_cfg.pvtfile);
1010         }
1011         http_tls_cfg.pvtfile = ast_strdup("");
1012
1013         if (http_tls_cfg.cipher) {
1014                 ast_free(http_tls_cfg.cipher);
1015         }
1016         http_tls_cfg.cipher = ast_strdup("");
1017
1018         AST_RWLIST_WRLOCK(&uri_redirects);
1019         while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry))) {
1020                 ast_free(redirect);
1021         }
1022         AST_RWLIST_UNLOCK(&uri_redirects);
1023
1024         if (cfg) {
1025                 v = ast_variable_browse(cfg, "general");
1026                 for (; v; v = v->next) {
1027
1028                         /* handle tls conf */
1029                         if (!ast_tls_read_conf(&http_tls_cfg, &https_desc, v->name, v->value)) {
1030                                 continue;
1031                         }
1032
1033                         if (!strcasecmp(v->name, "enabled")) {
1034                                 enabled = ast_true(v->value);
1035                         } else if (!strcasecmp(v->name, "enablestatic")) {
1036                                 newenablestatic = ast_true(v->value);
1037                         } else if (!strcasecmp(v->name, "bindport")) {
1038                                 http_desc.local_address.sin_port = htons(atoi(v->value));
1039                         } else if (!strcasecmp(v->name, "bindaddr")) {
1040                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
1041                                         memcpy(&http_desc.local_address.sin_addr, hp->h_addr, sizeof(http_desc.local_address.sin_addr));
1042                                 } else {
1043                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
1044                                 }
1045                         } else if (!strcasecmp(v->name, "prefix")) {
1046                                 if (!ast_strlen_zero(v->value)) {
1047                                         newprefix[0] = '/';
1048                                         ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
1049                                 } else {
1050                                         newprefix[0] = '\0';
1051                                 }
1052                         } else if (!strcasecmp(v->name, "redirect")) {
1053                                 add_redirect(v->value);
1054                         } else {
1055                                 ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
1056                         }
1057                 }
1058
1059                 ast_config_destroy(cfg);
1060         }
1061         /* if the https addres has not been set, default is the same as non secure http */
1062         if (!https_desc.local_address.sin_addr.s_addr) {
1063                 https_desc.local_address.sin_addr = http_desc.local_address.sin_addr;
1064         }
1065         if (enabled) {
1066                 http_desc.local_address.sin_family = https_desc.local_address.sin_family = AF_INET;
1067         }
1068         if (strcmp(prefix, newprefix)) {
1069                 ast_copy_string(prefix, newprefix, sizeof(prefix));
1070         }
1071         enablestatic = newenablestatic;
1072         ast_tcptls_server_start(&http_desc);
1073         if (ast_ssl_setup(https_desc.tls_cfg)) {
1074                 ast_tcptls_server_start(&https_desc);
1075         }
1076
1077         return 0;
1078 }
1079
1080 static char *handle_show_http(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1081 {
1082         struct ast_http_uri *urih;
1083         struct http_uri_redirect *redirect;
1084
1085         switch (cmd) {
1086         case CLI_INIT:
1087                 e->command = "http show status";
1088                 e->usage =
1089                         "Usage: http show status\n"
1090                         "       Lists status of internal HTTP engine\n";
1091                 return NULL;
1092         case CLI_GENERATE:
1093                 return NULL;
1094         }
1095
1096         if (a->argc != 3) {
1097                 return CLI_SHOWUSAGE;
1098         }
1099         ast_cli(a->fd, "HTTP Server Status:\n");
1100         ast_cli(a->fd, "Prefix: %s\n", prefix);
1101         if (!http_desc.old_address.sin_family) {
1102                 ast_cli(a->fd, "Server Disabled\n\n");
1103         } else {
1104                 ast_cli(a->fd, "Server Enabled and Bound to %s:%d\n\n",
1105                         ast_inet_ntoa(http_desc.old_address.sin_addr),
1106                         ntohs(http_desc.old_address.sin_port));
1107                 if (http_tls_cfg.enabled) {
1108                         ast_cli(a->fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
1109                                 ast_inet_ntoa(https_desc.old_address.sin_addr),
1110                                 ntohs(https_desc.old_address.sin_port));
1111                 }
1112         }
1113
1114         ast_cli(a->fd, "Enabled URI's:\n");
1115         AST_RWLIST_RDLOCK(&uris);
1116         if (AST_RWLIST_EMPTY(&uris)) {
1117                 ast_cli(a->fd, "None.\n");
1118         } else {
1119                 AST_RWLIST_TRAVERSE(&uris, urih, entry)
1120                         ast_cli(a->fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
1121         }
1122         AST_RWLIST_UNLOCK(&uris);
1123
1124         ast_cli(a->fd, "\nEnabled Redirects:\n");
1125         AST_RWLIST_RDLOCK(&uri_redirects);
1126         AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry)
1127                 ast_cli(a->fd, "  %s => %s\n", redirect->target, redirect->dest);
1128         if (AST_RWLIST_EMPTY(&uri_redirects)) {
1129                 ast_cli(a->fd, "  None.\n");
1130         }
1131         AST_RWLIST_UNLOCK(&uri_redirects);
1132
1133         return CLI_SUCCESS;
1134 }
1135
1136 int ast_http_reload(void)
1137 {
1138         return __ast_http_load(1);
1139 }
1140
1141 static struct ast_cli_entry cli_http[] = {
1142         AST_CLI_DEFINE(handle_show_http, "Display HTTP server status"),
1143 };
1144
1145 int ast_http_init(void)
1146 {
1147         ast_http_uri_link(&statusuri);
1148         ast_http_uri_link(&staticuri);
1149         ast_cli_register_multiple(cli_http, ARRAY_LEN(cli_http));
1150
1151         return __ast_http_load(0);
1152 }