Yeah, simplify that logic a bit...
[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 #ifdef ENABLE_UPLOADS
42 #include <gmime/gmime.h>
43 #endif /* ENABLE_UPLOADS */
44
45 #include "asterisk/paths.h"     /* use ast_config_AST_DATA_DIR */
46 #include "asterisk/network.h"
47 #include "asterisk/cli.h"
48 #include "asterisk/tcptls.h"
49 #include "asterisk/http.h"
50 #include "asterisk/utils.h"
51 #include "asterisk/strings.h"
52 #include "asterisk/config.h"
53 #include "asterisk/stringfields.h"
54 #include "asterisk/ast_version.h"
55 #include "asterisk/manager.h"
56 #include "asterisk/_private.h"
57
58 #define MAX_PREFIX 80
59
60 /* See http.h for more information about the SSL implementation */
61 #if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE))
62 #define DO_SSL  /* comment in/out if you want to support ssl */
63 #endif
64
65 static struct ast_tls_config http_tls_cfg;
66
67 static void *httpd_helper_thread(void *arg);
68
69 /*!
70  * we have up to two accepting threads, one for http, one for https
71  */
72 static struct server_args http_desc = {
73         .accept_fd = -1,
74         .master = AST_PTHREADT_NULL,
75         .tls_cfg = NULL,
76         .poll_timeout = -1,
77         .name = "http server",
78         .accept_fn = ast_tcptls_server_root,
79         .worker_fn = httpd_helper_thread,
80 };
81
82 static struct server_args https_desc = {
83         .accept_fd = -1,
84         .master = AST_PTHREADT_NULL,
85         .tls_cfg = &http_tls_cfg,
86         .poll_timeout = -1,
87         .name = "https server",
88         .accept_fn = ast_tcptls_server_root,
89         .worker_fn = httpd_helper_thread,
90 };
91
92 static AST_RWLIST_HEAD_STATIC(uris, ast_http_uri);      /*!< list of supported handlers */
93
94 #ifdef ENABLE_UPLOADS
95 struct ast_http_post_mapping {
96         AST_RWLIST_ENTRY(ast_http_post_mapping) entry;
97         char *from;
98         char *to;
99 };
100
101 static AST_RWLIST_HEAD_STATIC(post_mappings, ast_http_post_mapping);
102
103 struct mime_cbinfo {
104         int count;
105         const char *post_dir;
106 };
107 #endif /* ENABLE_UPLOADS */
108
109 /* all valid URIs must be prepended by the string in prefix. */
110 static char prefix[MAX_PREFIX];
111 static int enablestatic;
112
113 /*! \brief Limit the kinds of files we're willing to serve up */
114 static struct {
115         const char *ext;
116         const char *mtype;
117 } mimetypes[] = {
118         { "png", "image/png" },
119         { "jpg", "image/jpeg" },
120         { "js", "application/x-javascript" },
121         { "wav", "audio/x-wav" },
122         { "mp3", "audio/mpeg" },
123         { "svg", "image/svg+xml" },
124         { "svgz", "image/svg+xml" },
125         { "gif", "image/gif" },
126 };
127
128 struct http_uri_redirect {
129         AST_LIST_ENTRY(http_uri_redirect) entry;
130         char *dest;
131         char target[0];
132 };
133
134 static AST_RWLIST_HEAD_STATIC(uri_redirects, http_uri_redirect);
135
136 static const char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
137 {
138         int x;
139
140         if (ftype) {
141                 for (x = 0; x < ARRAY_LEN(mimetypes); x++) {
142                         if (!strcasecmp(ftype, mimetypes[x].ext)) {
143                                 return mimetypes[x].mtype;
144                         }
145                 }
146         }
147
148         snprintf(wkspace, wkspacelen, "text/%s", S_OR(ftype, "plain"));
149
150         return wkspace;
151 }
152
153 static struct ast_str *static_callback(struct ast_tcptls_session_instance *ser, const char *uri, enum ast_http_method method,
154                                        struct ast_variable *vars, int *status, char **title, int *contentlength)
155 {
156         char *path;
157         char *ftype;
158         const char *mtype;
159         char wkspace[80];
160         struct stat st;
161         int len;
162         int fd;
163         struct timeval tv = ast_tvnow();
164         char buf[256];
165         struct ast_tm tm;
166
167         /* Yuck.  I'm not really sold on this, but if you don't deliver static content it makes your configuration 
168            substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
169         if (!enablestatic || ast_strlen_zero(uri)) {
170                 goto out403;
171         }
172
173         /* Disallow any funny filenames at all */
174         if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0])) {
175                 goto out403;
176         }
177
178         if (strstr(uri, "/..")) {
179                 goto out403;
180         }
181                 
182         if ((ftype = strrchr(uri, '.'))) {
183                 ftype++;
184         }
185
186         mtype = ftype2mtype(ftype, wkspace, sizeof(wkspace));
187         
188         /* Cap maximum length */
189         if ((len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5) > 1024) {
190                 goto out403;
191         }
192                 
193         path = alloca(len);
194         sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_DIR, uri);
195         if (stat(path, &st)) {
196                 goto out404;
197         }
198
199         if (S_ISDIR(st.st_mode)) {
200                 goto out404;
201         }
202
203         if ((fd = open(path, O_RDONLY)) < 0) {
204                 goto out403;
205         }
206
207         ast_strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
208         fprintf(ser->f, "HTTP/1.1 200 OK\r\n"
209                 "Server: Asterisk/%s\r\n"
210                 "Date: %s\r\n"
211                 "Connection: close\r\n"
212                 "Cache-Control: no-cache, no-store\r\n"
213                 "Content-Length: %d\r\n"
214                 "Content-type: %s\r\n\r\n",
215                 ast_get_version(), buf, (int) st.st_size, mtype);
216
217         while ((len = read(fd, buf, sizeof(buf))) > 0) {
218                 fwrite(buf, 1, len, ser->f);
219         }
220
221         close(fd);
222
223         return NULL;
224
225 out404:
226         return ast_http_error((*status = 404),
227                               (*title = ast_strdup("Not Found")),
228                                NULL, "Nothing to see here.  Move along.");
229
230 out403:
231         return ast_http_error((*status = 403),
232                               (*title = ast_strdup("Access Denied")),
233                               NULL, "Sorry, I cannot let you do that, Dave.");
234 }
235
236
237 static struct ast_str *httpstatus_callback(struct ast_tcptls_session_instance *ser, const char *uri, enum ast_http_method method,
238                                            struct ast_variable *vars, int *status, char **title, int *contentlength)
239 {
240         struct ast_str *out = ast_str_create(512);
241         struct ast_variable *v;
242
243         if (out == NULL) {
244                 return out;
245         }
246
247         ast_str_append(&out, 0,
248                        "\r\n"
249                        "<title>Asterisk HTTP Status</title>\r\n"
250                        "<body bgcolor=\"#ffffff\">\r\n"
251                        "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
252                        "<h2>&nbsp;&nbsp;Asterisk&trade; HTTP Status</h2></td></tr>\r\n");
253         ast_str_append(&out, 0, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
254         ast_str_append(&out, 0, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
255                        ast_inet_ntoa(http_desc.oldsin.sin_addr));
256         ast_str_append(&out, 0, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
257                        ntohs(http_desc.oldsin.sin_port));
258
259         if (http_tls_cfg.enabled) {
260                 ast_str_append(&out, 0, "<tr><td><i>SSL Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
261                                ntohs(https_desc.oldsin.sin_port));
262         }
263
264         ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
265
266         for (v = vars; v; v = v->next) {
267                 if (strncasecmp(v->name, "cookie_", 7)) {
268                         ast_str_append(&out, 0, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
269                 }
270         }
271
272         ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
273
274         for (v = vars; v; v = v->next) {
275                 if (!strncasecmp(v->name, "cookie_", 7)) {
276                         ast_str_append(&out, 0, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
277                 }
278         }
279
280         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");
281         return out;
282 }
283
284 static struct ast_http_uri statusuri = {
285         .callback = httpstatus_callback,
286         .description = "Asterisk HTTP General Status",
287         .uri = "httpstatus",
288         .supports_get = 1,
289 };
290         
291 static struct ast_http_uri staticuri = {
292         .callback = static_callback,
293         .description = "Asterisk HTTP Static Delivery",
294         .uri = "static",
295         .has_subtree = 1,
296         .static_content = 1,
297         .supports_get = 1,
298 };
299         
300 struct ast_str *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
301 {
302         struct ast_str *out = ast_str_create(512);
303
304         if (out == NULL) {
305                 return out;
306         }
307
308         ast_str_set(&out, 0,
309                     "Content-type: text/html\r\n"
310                     "%s"
311                     "\r\n"
312                     "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
313                     "<html><head>\r\n"
314                     "<title>%d %s</title>\r\n"
315                     "</head><body>\r\n"
316                     "<h1>%s</h1>\r\n"
317                     "<p>%s</p>\r\n"
318                     "<hr />\r\n"
319                     "<address>Asterisk Server</address>\r\n"
320                     "</body></html>\r\n",
321                     (extra_header ? extra_header : ""), status, title, title, text);
322
323         return out;
324 }
325
326 /*! \brief 
327  * Link the new uri into the list. 
328  *
329  * They are sorted by length of
330  * the string, not alphabetically. Duplicate entries are not replaced,
331  * but the insertion order (using <= and not just <) makes sure that
332  * more recent insertions hide older ones.
333  * On a lookup, we just scan the list and stop at the first matching entry.
334  */
335 int ast_http_uri_link(struct ast_http_uri *urih)
336 {
337         struct ast_http_uri *uri;
338         int len = strlen(urih->uri);
339
340         if (!(urih->supports_get || urih->supports_post)) {
341                 ast_log(LOG_WARNING, "URI handler does not provide either GET or POST method: %s (%s)\n", urih->uri, urih->description);
342                 return -1;
343         }
344
345         AST_RWLIST_WRLOCK(&uris);
346
347         if (AST_RWLIST_EMPTY(&uris) || strlen(AST_RWLIST_FIRST(&uris)->uri) <= len) {
348                 AST_RWLIST_INSERT_HEAD(&uris, urih, entry);
349                 AST_RWLIST_UNLOCK(&uris);
350
351                 return 0;
352         }
353
354         AST_RWLIST_TRAVERSE(&uris, uri, entry) {
355                 if (AST_RWLIST_NEXT(uri, entry) &&
356                     strlen(AST_RWLIST_NEXT(uri, entry)->uri) <= len) {
357                         AST_RWLIST_INSERT_AFTER(&uris, uri, urih, entry);
358                         AST_RWLIST_UNLOCK(&uris); 
359
360                         return 0;
361                 }
362         }
363
364         AST_RWLIST_INSERT_TAIL(&uris, urih, entry);
365
366         AST_RWLIST_UNLOCK(&uris);
367         
368         return 0;
369 }       
370
371 void ast_http_uri_unlink(struct ast_http_uri *urih)
372 {
373         AST_RWLIST_WRLOCK(&uris);
374         AST_RWLIST_REMOVE(&uris, urih, entry);
375         AST_RWLIST_UNLOCK(&uris);
376 }
377
378 #ifdef ENABLE_UPLOADS
379 /*! \note This assumes that the post_mappings list is locked */
380 static struct ast_http_post_mapping *find_post_mapping(const char *uri)
381 {
382         struct ast_http_post_mapping *post_map;
383
384         if (!ast_strlen_zero(prefix) && strncmp(prefix, uri, strlen(prefix))) {
385                 ast_debug(1, "URI %s does not have prefix %s\n", uri, prefix);
386
387                 return NULL;
388         }
389
390         uri += strlen(prefix);
391         if (*uri == '/') {
392                 uri++;
393         }
394         
395         AST_RWLIST_TRAVERSE(&post_mappings, post_map, entry) {
396                 if (!strcmp(uri, post_map->from)) {
397                         return post_map;
398                 }
399         }
400
401         return NULL;
402 }
403
404 static void post_raw(GMimePart *part, const char *post_dir, const char *fn)
405 {
406         char filename[PATH_MAX];
407         GMimeDataWrapper *content;
408         GMimeStream *stream;
409         int fd;
410
411         snprintf(filename, sizeof(filename), "%s/%s", post_dir, fn);
412
413         ast_debug(1, "Posting raw data to %s\n", filename);
414
415         if ((fd = open(filename, O_CREAT | O_WRONLY, 0666)) == -1) {
416                 ast_log(LOG_WARNING, "Unable to open %s for writing file from a POST!\n", filename);
417
418                 return;
419         }
420
421         stream = g_mime_stream_fs_new(fd);
422
423         content = g_mime_part_get_content_object(part);
424         g_mime_data_wrapper_write_to_stream(content, stream);
425         g_mime_stream_flush(stream);
426
427         g_object_unref(content);
428         g_object_unref(stream);
429 }
430
431 static GMimeMessage *parse_message(FILE *f)
432 {
433         GMimeMessage *message;
434         GMimeParser *parser;
435         GMimeStream *stream;
436
437         stream = g_mime_stream_file_new(f);
438
439         parser = g_mime_parser_new_with_stream(stream);
440         g_mime_parser_set_respect_content_length(parser, 1);
441         
442         g_object_unref(stream);
443
444         message = g_mime_parser_construct_message(parser);
445
446         g_object_unref(parser);
447
448         return message;
449 }
450
451 static void process_message_callback(GMimeObject *part, gpointer user_data)
452 {
453         struct mime_cbinfo *cbinfo = user_data;
454
455         cbinfo->count++;
456
457         /* We strip off the headers before we get here, so should only see GMIME_IS_PART */
458         if (GMIME_IS_MESSAGE_PART(part)) {
459                 ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MESSAGE_PART\n");
460                 return;
461         } else if (GMIME_IS_MESSAGE_PARTIAL(part)) {
462                 ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MESSAGE_PARTIAL\n");
463                 return;
464         } else if (GMIME_IS_MULTIPART(part)) {
465                 GList *l;
466                 
467                 ast_log(LOG_WARNING, "Got unexpected GMIME_IS_MULTIPART, trying to process subparts\n");
468                 l = GMIME_MULTIPART(part)->subparts;
469                 while (l) {
470                         process_message_callback(l->data, cbinfo);
471                         l = l->next;
472                 }
473         } else if (GMIME_IS_PART(part)) {
474                 const char *filename;
475
476                 if (ast_strlen_zero(filename = g_mime_part_get_filename(GMIME_PART(part)))) {
477                         ast_debug(1, "Skipping part with no filename\n");
478                         return;
479                 }
480
481                 post_raw(GMIME_PART(part), cbinfo->post_dir, filename);
482         } else {
483                 ast_log(LOG_ERROR, "Encountered unknown MIME part. This should never happen!\n");
484         }
485 }
486
487 static int process_message(GMimeMessage *message, const char *post_dir)
488 {
489         struct mime_cbinfo cbinfo = {
490                 .count = 0,
491                 .post_dir = post_dir,
492         };
493
494         g_mime_message_foreach_part(message, process_message_callback, &cbinfo);
495
496         return cbinfo.count;
497 }
498
499 static struct ast_str *handle_post(struct ast_tcptls_session_instance *ser, char *uri, 
500                                    int *status, char **title, int *contentlength, struct ast_variable *headers,
501                                    struct ast_variable *cookies)
502 {
503         char buf[4096];
504         FILE *f;
505         size_t res;
506         struct ast_variable *var;
507         int content_len = 0;
508         struct ast_http_post_mapping *post_map;
509         const char *post_dir;
510         unsigned long ident = 0;
511         GMimeMessage *message;
512         int message_count = 0;
513
514         for (var = cookies; var; var = var->next) {
515                 if (strcasecmp(var->name, "mansession_id")) {
516                         continue;
517                 }
518
519                 if (sscanf(var->value, "%lx", &ident) != 1) {
520                         return ast_http_error((*status = 400),
521                                               (*title = ast_strdup("Bad Request")),
522                                               NULL, "The was an error parsing the request.");
523                 }
524
525                 if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) {
526                         return ast_http_error((*status = 401),
527                                               (*title = ast_strdup("Unauthorized")),
528                                               NULL, "You are not authorized to make this request.");
529                 }
530
531                 break;
532         }
533         if (!var) {
534                 return ast_http_error((*status = 401),
535                                       (*title = ast_strdup("Unauthorized")),
536                                       NULL, "You are not authorized to make this request.");
537         }
538
539         if (!(f = tmpfile())) {
540                 return NULL;
541         }
542
543         for (var = headers; var; var = var->next) {
544                 if (!strcasecmp(var->name, "Content-Length")) {
545                         if ((sscanf(var->value, "%u", &content_len)) != 1) {
546                                 ast_log(LOG_ERROR, "Invalid Content-Length in POST request!\n");
547                                 fclose(f);
548
549                                 return NULL;
550                         }
551                         ast_debug(1, "Got a Content-Length of %d\n", content_len);
552                 } else if (!strcasecmp(var->name, "Content-Type")) {
553                         fprintf(f, "Content-Type: %s\r\n\r\n", var->value);
554                 }
555         }
556
557         for (res = sizeof(buf); content_len; content_len -= res) {
558                 if (content_len < res) {
559                         res = content_len;
560                 }
561                 fread(buf, 1, res, ser->f);
562                 fwrite(buf, 1, res, f);
563         }
564
565         if (fseek(f, SEEK_SET, 0)) {
566                 ast_debug(1, "Failed to seek temp file back to beginning.\n");
567                 fclose(f);
568
569                 return NULL;
570         }
571
572         AST_RWLIST_RDLOCK(&post_mappings);
573         if (!(post_map = find_post_mapping(uri))) {
574                 ast_debug(1, "%s is not a valid URI for POST\n", uri);
575                 AST_RWLIST_UNLOCK(&post_mappings);
576
577                 return ast_http_error((*status = 404),
578                                       (*title = ast_strdup("Not Found")),
579                                       NULL, "The requested URL was not found on this server.");
580         }
581
582         post_dir = ast_strdupa(post_map->to);
583         post_map = NULL;
584         AST_RWLIST_UNLOCK(&post_mappings);
585
586         ast_debug(1, "Going to post files to dir %s\n", post_dir);
587
588         message = parse_message(f); /* Takes ownership and will close f */
589
590         if (!message) {
591                 ast_log(LOG_ERROR, "Error parsing MIME data\n");
592
593                 return ast_http_error((*status = 400),
594                                       (*title = ast_strdup("Bad Request")),
595                                       NULL, "The was an error parsing the request.");
596         }
597
598         if (!(message_count = process_message(message, post_dir))) {
599                 ast_log(LOG_ERROR, "Invalid MIME data, found no parts!\n");
600
601                 return ast_http_error((*status = 400),
602                                       (*title = ast_strdup("Bad Request")),
603                                       NULL, "The was an error parsing the request.");
604         }
605
606         return ast_http_error((*status = 200),
607                               (*title = ast_strdup("OK")),
608                               NULL, "File successfully uploaded.");
609 }
610 #endif /* ENABLE_UPLOADS */
611
612 static struct ast_str *handle_uri(struct ast_tcptls_session_instance *ser, char *uri, enum ast_http_method method,
613                                   int *status, char **title, int *contentlength, struct ast_variable **cookies, 
614                                   unsigned int *static_content)
615 {
616         char *c;
617         struct ast_str *out = NULL;
618         char *params = uri;
619         struct ast_http_uri *urih = NULL;
620         int l;
621         struct ast_variable *vars = NULL, *v, *prev = NULL;
622         struct http_uri_redirect *redirect;
623         int saw_method = 0;
624
625         /* preserve previous behavior of only support URI parameters on GET requests */
626         if (method == AST_HTTP_GET) {
627                 strsep(&params, "?");
628                 
629                 /* Extract arguments from the request and store them in variables. */
630                 if (params) {
631                         char *var, *val;
632                         
633                         while ((val = strsep(&params, "&"))) {
634                                 var = strsep(&val, "=");
635                                 if (val) {
636                                         ast_uri_decode(val);
637                                 } else {
638                                         val = "";
639                                 }
640                                 ast_uri_decode(var);
641                                 if ((v = ast_variable_new(var, val, ""))) {
642                                         if (vars) {
643                                                 prev->next = v;
644                                         } else {
645                                                 vars = v;
646                                         }
647                                         prev = v;
648                                 }
649                         }
650                 }
651         }
652
653         /*
654          * Append the cookies to the variables (the only reason to have them
655          * at the end is to avoid another pass of the cookies list to find
656          * the tail).
657          */
658         if (prev) {
659                 prev->next = *cookies;
660         } else {
661                 vars = *cookies;
662         }
663         *cookies = NULL;
664
665         ast_uri_decode(uri);
666
667         AST_RWLIST_RDLOCK(&uri_redirects);
668         AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
669                 if (!strcasecmp(uri, redirect->target)) {
670                         char buf[512];
671
672                         snprintf(buf, sizeof(buf), "Location: %s\r\n", redirect->dest);
673                         out = ast_http_error((*status = 302),
674                                              (*title = ast_strdup("Moved Temporarily")),
675                                              buf, "There is no spoon...");
676
677                         break;
678                 }
679         }
680         AST_RWLIST_UNLOCK(&uri_redirects);
681
682         if (redirect) {
683                 goto cleanup;
684         }
685
686         /* We want requests to start with the (optional) prefix and '/' */
687         l = strlen(prefix);
688         if (!strncasecmp(uri, prefix, l) && uri[l] == '/') {
689                 uri += l + 1;
690                 /* scan registered uris to see if we match one. */
691                 AST_RWLIST_RDLOCK(&uris);
692                 AST_RWLIST_TRAVERSE(&uris, urih, entry) {
693                         if (!saw_method) {
694                                 switch (method) {
695                                 case AST_HTTP_GET:
696                                         if (urih->supports_get) {
697                                                 saw_method = 1;
698                                         }
699                                         break;
700                                 case AST_HTTP_POST:
701                                         if (urih->supports_post) {
702                                                 saw_method = 1;
703                                         }
704                                         break;
705                                 }
706                         }
707
708                         l = strlen(urih->uri);
709                         c = uri + l;    /* candidate */
710
711                         if (strncasecmp(urih->uri, uri, l) || /* no match */
712                             (*c && *c != '/')) { /* substring */
713                                 continue;
714                         }
715
716                         if (*c == '/') {
717                                 c++;
718                         }
719
720                         if (!*c || urih->has_subtree) {
721                                 if (((method == AST_HTTP_GET) && urih->supports_get) ||
722                                     ((method == AST_HTTP_POST) && urih->supports_post)) {
723                                         uri = c;
724
725                                         break;
726                                 }
727                         }
728                 }
729
730                 if (!urih) {
731                         AST_RWLIST_UNLOCK(&uris);
732                 }
733         }
734
735         if (urih) {
736                 *static_content = urih->static_content;
737                 out = urih->callback(ser, uri, method, vars, status, title, contentlength);
738                 AST_RWLIST_UNLOCK(&uris);
739         } else if (saw_method) {
740                 out = ast_http_error((*status = 404),
741                                      (*title = ast_strdup("Not Found")), NULL,
742                                      "The requested URL was not found on this server.");
743         } else {
744                 out = ast_http_error((*status = 501),
745                                      (*title = ast_strdup("Not Implemented")), NULL,
746                                      "Attempt to use unimplemented / unsupported method");
747         }
748
749 cleanup:
750         ast_variables_destroy(vars);
751
752         return out;
753 }
754
755 #ifdef DO_SSL
756 #if defined(HAVE_FUNOPEN)
757 #define HOOK_T int
758 #define LEN_T int
759 #else
760 #define HOOK_T ssize_t
761 #define LEN_T size_t
762 #endif
763
764 /*!
765  * replacement read/write functions for SSL support.
766  * We use wrappers rather than SSL_read/SSL_write directly so
767  * we can put in some debugging.
768  */
769 /*static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
770 {
771         int i = SSL_read(cookie, buf, len-1);
772 #if 0
773         if (i >= 0)
774                 buf[i] = '\0';
775         ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
776 #endif
777         return i;
778 }
779
780 static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
781 {
782 #if 0
783         char *s = alloca(len+1);
784         strncpy(s, buf, len);
785         s[len] = '\0';
786         ast_verbose("ssl write size %d <%s>\n", (int)len, s);
787 #endif
788         return SSL_write(cookie, buf, len);
789 }
790
791 static int ssl_close(void *cookie)
792 {
793         close(SSL_get_fd(cookie));
794         SSL_shutdown(cookie);
795         SSL_free(cookie);
796         return 0;
797 }*/
798 #endif  /* DO_SSL */
799
800 static void *httpd_helper_thread(void *data)
801 {
802         char buf[4096];
803         char cookie[4096];
804         struct ast_tcptls_session_instance *ser = data;
805         struct ast_variable *var, *prev=NULL, *vars=NULL, *headers = NULL;
806         char *uri, *title=NULL;
807         int status = 200, contentlength = 0;
808         struct ast_str *out = NULL;
809         unsigned int static_content = 0;
810
811         if (!fgets(buf, sizeof(buf), ser->f)) {
812                 goto done;
813         }
814
815         uri = ast_skip_nonblanks(buf);  /* Skip method */
816         if (*uri) {
817                 *uri++ = '\0';
818         }
819
820         uri = ast_skip_blanks(uri);     /* Skip white space */
821
822         if (*uri) {                     /* terminate at the first blank */
823                 char *c = ast_skip_nonblanks(uri);
824
825                 if (*c) {
826                         *c = '\0';
827                 }
828         }
829
830         /* process "Cookie: " lines */
831         while (fgets(cookie, sizeof(cookie), ser->f)) {
832                 char *vname, *vval;
833                 int l;
834
835                 /* Trim trailing characters */
836                 ast_trim_blanks(cookie);
837                 if (ast_strlen_zero(cookie)) {
838                         break;
839                 }
840                 if (strncasecmp(cookie, "Cookie: ", 8)) {
841                         char *name, *value;
842
843                         value = ast_strdupa(cookie);
844                         name = strsep(&value, ":");
845                         if (!value) {
846                                 continue;
847                         }
848                         value = ast_skip_blanks(value);
849                         if (ast_strlen_zero(value)) {
850                                 continue;
851                         }
852                         var = ast_variable_new(name, value, "");
853                         if (!var) { 
854                                 continue;
855                         }
856                         var->next = headers;
857                         headers = var;
858
859                         continue;
860                 }
861
862                 /* TODO - The cookie parsing code below seems to work   
863                    in IE6 and FireFox 1.5.  However, it is not entirely 
864                    correct, and therefore may not work in all           
865                    circumstances.                                       
866                       For more details see RFC 2109 and RFC 2965        */
867         
868                 /* FireFox cookie strings look like:                    
869                      Cookie: mansession_id="********"                   
870                    InternetExplorer's look like:                        
871                      Cookie: $Version="1"; mansession_id="********"     */
872                 
873                 /* If we got a FireFox cookie string, the name's right  
874                     after "Cookie: "                                    */
875                 vname = ast_skip_blanks(cookie + 8);
876                         
877                 /* If we got an IE cookie string, we need to skip to    
878                     past the version to get to the name                 */
879                 if (*vname == '$') {
880                         strsep(&vname, ";");
881                         if (!vname) {   /* no name ? */
882                                 continue;
883                         }
884                         vname = ast_skip_blanks(vname);
885                 }
886                 if (!(vval = strchr(vname, '='))) {
887                         continue;
888                 }
889                 /* Ditch the = and the quotes */
890                 *vval++ = '\0';
891                 if (*vval) {
892                         vval++;
893                 }
894                 if ((l = strlen(vval))) {
895                         vval[l - 1] = '\0';     /* trim trailing quote */
896                 }
897                 if ((var = ast_variable_new(vname, vval, ""))) {
898                         if (prev)
899                                 prev->next = var;
900                         else
901                                 vars = var;
902                         prev = var;
903                 }
904         }
905
906         if (!*uri) {
907                 out = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
908         } else if (strcasecmp(buf, "post") && strcasecmp(buf, "get")) {
909                 out = ast_http_error(501, "Not Implemented", NULL,
910                                      "Attempt to use unimplemented / unsupported method");
911         } else {        /* try to serve it */
912                 out = handle_uri(ser, uri, (!strcasecmp(buf, "get")) ? AST_HTTP_GET : AST_HTTP_POST,
913                                  &status, &title, &contentlength, &vars, &static_content);
914         }
915
916         /* If they aren't mopped up already, clean up the cookies */
917         if (vars) {
918                 ast_variables_destroy(vars);
919         }
920         /* Clean up all the header information pulled as well */
921         if (headers) {
922                 ast_variables_destroy(headers);
923         }
924
925         if (out) {
926                 struct timeval tv = ast_tvnow();
927                 char timebuf[256];
928                 struct ast_tm tm;
929
930                 ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
931                 fprintf(ser->f,
932                         "HTTP/1.1 %d %s\r\n"
933                         "Server: Asterisk/%s\r\n"
934                         "Date: %s\r\n"
935                         "Connection: close\r\n"
936                         "%s",
937                         status, title ? title : "OK", ast_get_version(), timebuf,
938                         static_content ? "" : "Cache-Control: no-cache, no-store\r\n");
939                         /* We set the no-cache headers only for dynamic content.
940                         * If you want to make sure the static file you requested is not from cache,
941                         * append a random variable to your GET request.  Ex: 'something.html?r=109987734'
942                         */
943                 if (!contentlength) {   /* opaque body ? just dump it hoping it is properly formatted */
944                         fprintf(ser->f, "%s", out->str);
945                 } else {
946                         char *tmp = strstr(out->str, "\r\n\r\n");
947
948                         if (tmp) {
949                                 fprintf(ser->f, "Content-length: %d\r\n", contentlength);
950                                 /* first write the header, then the body */
951                                 fwrite(out->str, 1, (tmp + 4 - out->str), ser->f);
952                                 fwrite(tmp + 4, 1, contentlength, ser->f);
953                         }
954                 }
955                 ast_free(out);
956         }
957
958         if (title) {
959                 ast_free(title);
960         }
961
962 done:
963         fclose(ser->f);
964         ser = ast_tcptls_session_instance_destroy(ser);
965
966         return NULL;
967 }
968
969 /*!
970  * \brief Add a new URI redirect
971  * The entries in the redirect list are sorted by length, just like the list
972  * of URI handlers.
973  */
974 static void add_redirect(const char *value)
975 {
976         char *target, *dest;
977         struct http_uri_redirect *redirect, *cur;
978         unsigned int target_len;
979         unsigned int total_len;
980
981         dest = ast_strdupa(value);
982         dest = ast_skip_blanks(dest);
983         target = strsep(&dest, " ");
984         target = ast_skip_blanks(target);
985         target = strsep(&target, " "); /* trim trailing whitespace */
986
987         if (!dest) {
988                 ast_log(LOG_WARNING, "Invalid redirect '%s'\n", value);
989                 return;
990         }
991
992         target_len = strlen(target) + 1;
993         total_len = sizeof(*redirect) + target_len + strlen(dest) + 1;
994
995         if (!(redirect = ast_calloc(1, total_len))) {
996                 return;
997         }
998
999         redirect->dest = redirect->target + target_len;
1000         strcpy(redirect->target, target);
1001         strcpy(redirect->dest, dest);
1002
1003         AST_RWLIST_WRLOCK(&uri_redirects);
1004
1005         target_len--; /* So we can compare directly with strlen() */
1006         if (AST_RWLIST_EMPTY(&uri_redirects) 
1007             || strlen(AST_RWLIST_FIRST(&uri_redirects)->target) <= target_len) {
1008                 AST_RWLIST_INSERT_HEAD(&uri_redirects, redirect, entry);
1009                 AST_RWLIST_UNLOCK(&uri_redirects);
1010
1011                 return;
1012         }
1013
1014         AST_RWLIST_TRAVERSE(&uri_redirects, cur, entry) {
1015                 if (AST_RWLIST_NEXT(cur, entry) 
1016                     && strlen(AST_RWLIST_NEXT(cur, entry)->target) <= target_len) {
1017                         AST_RWLIST_INSERT_AFTER(&uri_redirects, cur, redirect, entry);
1018                         AST_RWLIST_UNLOCK(&uri_redirects); 
1019
1020                         return;
1021                 }
1022         }
1023
1024         AST_RWLIST_INSERT_TAIL(&uri_redirects, redirect, entry);
1025
1026         AST_RWLIST_UNLOCK(&uri_redirects);
1027 }
1028
1029 #ifdef ENABLE_UPLOADS
1030 static void destroy_post_mapping(struct ast_http_post_mapping *post_map)
1031 {
1032         if (post_map->from) {
1033                 ast_free(post_map->from);
1034         }
1035         if (post_map->to) {
1036                 ast_free(post_map->to);
1037         }
1038         ast_free(post_map);
1039 }
1040
1041 static void destroy_post_mappings(void)
1042 {
1043         struct ast_http_post_mapping *post_map;
1044
1045         AST_RWLIST_WRLOCK(&post_mappings);
1046         while ((post_map = AST_RWLIST_REMOVE_HEAD(&post_mappings, entry))) {
1047                 destroy_post_mapping(post_map);
1048         }
1049         AST_RWLIST_UNLOCK(&post_mappings);
1050 }
1051
1052 static void add_post_mapping(const char *from, const char *to)
1053 {
1054         struct ast_http_post_mapping *post_map;
1055
1056         if (!(post_map = ast_calloc(1, sizeof(*post_map)))) {
1057                 return;
1058         }
1059
1060         if (!(post_map->from = ast_strdup(from))) {
1061                 destroy_post_mapping(post_map);
1062
1063                 return;
1064         }
1065
1066         if (!(post_map->to = ast_strdup(to))) {
1067                 destroy_post_mapping(post_map);
1068
1069                 return;
1070         }
1071
1072         AST_RWLIST_WRLOCK(&post_mappings);
1073         AST_RWLIST_INSERT_TAIL(&post_mappings, post_map, entry);
1074         AST_RWLIST_UNLOCK(&post_mappings);
1075 }
1076 #endif /* ENABLE_UPLOADS */
1077
1078 static int __ast_http_load(int reload)
1079 {
1080         struct ast_config *cfg;
1081         struct ast_variable *v;
1082         int enabled=0;
1083         int newenablestatic=0;
1084         struct hostent *hp;
1085         struct ast_hostent ahp;
1086         char newprefix[MAX_PREFIX] = "";
1087         int have_sslbindaddr = 0;
1088         struct http_uri_redirect *redirect;
1089         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
1090
1091         if ((cfg = ast_config_load2("http.conf", "http", config_flags)) == CONFIG_STATUS_FILEUNCHANGED) {
1092                 return 0;
1093         }
1094
1095         /* default values */
1096         memset(&http_desc.sin, 0, sizeof(http_desc.sin));
1097         http_desc.sin.sin_port = htons(8088);
1098
1099         memset(&https_desc.sin, 0, sizeof(https_desc.sin));
1100         https_desc.sin.sin_port = htons(8089);
1101
1102         http_tls_cfg.enabled = 0;
1103         if (http_tls_cfg.certfile) {
1104                 ast_free(http_tls_cfg.certfile);
1105         }
1106         http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
1107         if (http_tls_cfg.cipher) {
1108                 ast_free(http_tls_cfg.cipher);
1109         }
1110         http_tls_cfg.cipher = ast_strdup("");
1111
1112         AST_RWLIST_WRLOCK(&uri_redirects);
1113         while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry))) {
1114                 ast_free(redirect);
1115         }
1116         AST_RWLIST_UNLOCK(&uri_redirects);
1117
1118 #ifdef ENABLE_UPLOADS
1119         destroy_post_mappings();
1120 #endif /* ENABLE_UPLOADS */
1121
1122         if (cfg) {
1123                 v = ast_variable_browse(cfg, "general");
1124                 for (; v; v = v->next) {
1125                         if (!strcasecmp(v->name, "enabled")) {
1126                                 enabled = ast_true(v->value);
1127                         } else if (!strcasecmp(v->name, "sslenable")) {
1128                                 http_tls_cfg.enabled = ast_true(v->value);
1129                         } else if (!strcasecmp(v->name, "sslbindport")) {
1130                                 https_desc.sin.sin_port = htons(atoi(v->value));
1131                         } else if (!strcasecmp(v->name, "sslcert")) {
1132                                 ast_free(http_tls_cfg.certfile);
1133                                 http_tls_cfg.certfile = ast_strdup(v->value);
1134                         } else if (!strcasecmp(v->name, "sslcipher")) {
1135                                 ast_free(http_tls_cfg.cipher);
1136                                 http_tls_cfg.cipher = ast_strdup(v->value);
1137                         } else if (!strcasecmp(v->name, "enablestatic")) {
1138                                 newenablestatic = ast_true(v->value);
1139                         } else if (!strcasecmp(v->name, "bindport")) {
1140                                 http_desc.sin.sin_port = htons(atoi(v->value));
1141                         } else if (!strcasecmp(v->name, "sslbindaddr")) {
1142                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
1143                                         memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
1144                                         have_sslbindaddr = 1;
1145                                 } else {
1146                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
1147                                 }
1148                         } else if (!strcasecmp(v->name, "bindaddr")) {
1149                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
1150                                         memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
1151                                 } else {
1152                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
1153                                 }
1154                         } else if (!strcasecmp(v->name, "prefix")) {
1155                                 if (!ast_strlen_zero(v->value)) {
1156                                         newprefix[0] = '/';
1157                                         ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
1158                                 } else {
1159                                         newprefix[0] = '\0';
1160                                 }
1161                         } else if (!strcasecmp(v->name, "redirect")) {
1162                                 add_redirect(v->value);
1163                         } else {
1164                                 ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
1165                         }
1166                 }
1167
1168 #ifdef ENABLE_UPLOADS
1169                 for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next) {
1170                         add_post_mapping(v->name, v->value);
1171                 }
1172 #endif /* ENABLE_UPLOADS */
1173
1174                 ast_config_destroy(cfg);
1175         }
1176
1177         if (!have_sslbindaddr) {
1178                 https_desc.sin.sin_addr = http_desc.sin.sin_addr;
1179         }
1180         if (enabled) {
1181                 http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
1182         }
1183         if (strcmp(prefix, newprefix)) {
1184                 ast_copy_string(prefix, newprefix, sizeof(prefix));
1185         }
1186         enablestatic = newenablestatic;
1187         ast_tcptls_server_start(&http_desc);
1188         if (ast_ssl_setup(https_desc.tls_cfg)) {
1189                 ast_tcptls_server_start(&https_desc);
1190         }
1191
1192         return 0;
1193 }
1194
1195 static char *handle_show_http(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1196 {
1197         struct ast_http_uri *urih;
1198         struct http_uri_redirect *redirect;
1199
1200 #ifdef ENABLE_UPLOADS
1201         struct ast_http_post_mapping *post_map;
1202 #endif /* ENABLE_UPLOADS */
1203
1204         switch (cmd) {
1205         case CLI_INIT:
1206                 e->command = "http show status";
1207                 e->usage = 
1208                         "Usage: http show status\n"
1209                         "       Lists status of internal HTTP engine\n";
1210                 return NULL;
1211         case CLI_GENERATE:
1212                 return NULL;
1213         }
1214         
1215         if (a->argc != 3) {
1216                 return CLI_SHOWUSAGE;
1217         }
1218         ast_cli(a->fd, "HTTP Server Status:\n");
1219         ast_cli(a->fd, "Prefix: %s\n", prefix);
1220         if (!http_desc.oldsin.sin_family) {
1221                 ast_cli(a->fd, "Server Disabled\n\n");
1222         } else {
1223                 ast_cli(a->fd, "Server Enabled and Bound to %s:%d\n\n",
1224                         ast_inet_ntoa(http_desc.oldsin.sin_addr),
1225                         ntohs(http_desc.oldsin.sin_port));
1226                 if (http_tls_cfg.enabled) {
1227                         ast_cli(a->fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
1228                                 ast_inet_ntoa(https_desc.oldsin.sin_addr),
1229                                 ntohs(https_desc.oldsin.sin_port));
1230                 }
1231         }
1232
1233         ast_cli(a->fd, "Enabled URI's:\n");
1234         AST_RWLIST_RDLOCK(&uris);
1235         if (AST_RWLIST_EMPTY(&uris)) {
1236                 ast_cli(a->fd, "None.\n");
1237         } else {
1238                 AST_RWLIST_TRAVERSE(&uris, urih, entry) {
1239                         ast_cli(a->fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : ""), urih->description);
1240                 }
1241         }
1242         AST_RWLIST_UNLOCK(&uris);
1243
1244         ast_cli(a->fd, "\nEnabled Redirects:\n");
1245         AST_RWLIST_RDLOCK(&uri_redirects);
1246         AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
1247                 ast_cli(a->fd, "  %s => %s\n", redirect->target, redirect->dest);
1248         }
1249         if (AST_RWLIST_EMPTY(&uri_redirects)) {
1250                 ast_cli(a->fd, "  None.\n");
1251         }
1252         AST_RWLIST_UNLOCK(&uri_redirects);
1253
1254
1255 #ifdef ENABLE_UPLOADS
1256         ast_cli(a->fd, "\nPOST mappings:\n");
1257         AST_RWLIST_RDLOCK(&post_mappings);
1258         AST_LIST_TRAVERSE(&post_mappings, post_map, entry) {
1259                 ast_cli(a->fd, "%s/%s => %s\n", prefix, post_map->from, post_map->to);
1260         }
1261         ast_cli(a->fd, "%s\n", AST_LIST_EMPTY(&post_mappings) ? "None.\n" : "");
1262         AST_RWLIST_UNLOCK(&post_mappings);
1263 #endif /* ENABLE_UPLOADS */
1264
1265         return CLI_SUCCESS;
1266 }
1267
1268 int ast_http_reload(void)
1269 {
1270         return __ast_http_load(1);
1271 }
1272
1273 static struct ast_cli_entry cli_http[] = {
1274         AST_CLI_DEFINE(handle_show_http, "Display HTTP server status"),
1275 };
1276
1277 int ast_http_init(void)
1278 {
1279 #ifdef ENABLE_UPLOADS
1280         g_mime_init(0);
1281 #endif /* ENABLE_UPLOADS */
1282
1283         ast_http_uri_link(&statusuri);
1284         ast_http_uri_link(&staticuri);
1285         ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
1286
1287         return __ast_http_load(0);
1288 }