actually implement HTTP request dispatching based on both URI and method; reduce...
[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)) {
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 prefix and '/' */
687         if ((l = strlen(prefix)) && !strncasecmp(uri, prefix, l) && uri[l] == '/') {
688                 uri += l + 1;
689                 /* scan registered uris to see if we match one. */
690                 AST_RWLIST_RDLOCK(&uris);
691                 AST_RWLIST_TRAVERSE(&uris, urih, entry) {
692                         if (!saw_method) {
693                                 switch (method) {
694                                 case AST_HTTP_GET:
695                                         if (urih->supports_get) {
696                                                 saw_method = 1;
697                                         }
698                                         break;
699                                 case AST_HTTP_POST:
700                                         if (urih->supports_post) {
701                                                 saw_method = 1;
702                                         }
703                                         break;
704                                 }
705                         }
706
707                         l = strlen(urih->uri);
708                         c = uri + l;    /* candidate */
709
710                         if (strncasecmp(urih->uri, uri, l) || /* no match */
711                             (*c && *c != '/')) { /* substring */
712                                 continue;
713                         }
714
715                         if (*c == '/') {
716                                 c++;
717                         }
718
719                         if (!*c || urih->has_subtree) {
720                                 if (((method == AST_HTTP_GET) && urih->supports_get) ||
721                                     ((method == AST_HTTP_POST) && urih->supports_post)) {
722                                         uri = c;
723
724                                         break;
725                                 }
726                         }
727                 }
728
729                 if (!urih) {
730                         AST_RWLIST_UNLOCK(&uris);
731                 }
732         }
733
734         if (urih) {
735                 *static_content = urih->static_content;
736                 out = urih->callback(ser, uri, method, vars, status, title, contentlength);
737                 AST_RWLIST_UNLOCK(&uris);
738         } else if (saw_method) {
739                 out = ast_http_error((*status = 404),
740                                      (*title = ast_strdup("Not Found")), NULL,
741                                      "The requested URL was not found on this server.");
742         } else {
743                 out = ast_http_error((*status = 501),
744                                      (*title = ast_strdup("Not Implemented")), NULL,
745                                      "Attempt to use unimplemented / unsupported method");
746         }
747
748 cleanup:
749         ast_variables_destroy(vars);
750
751         return out;
752 }
753
754 #ifdef DO_SSL
755 #if defined(HAVE_FUNOPEN)
756 #define HOOK_T int
757 #define LEN_T int
758 #else
759 #define HOOK_T ssize_t
760 #define LEN_T size_t
761 #endif
762
763 /*!
764  * replacement read/write functions for SSL support.
765  * We use wrappers rather than SSL_read/SSL_write directly so
766  * we can put in some debugging.
767  */
768 /*static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
769 {
770         int i = SSL_read(cookie, buf, len-1);
771 #if 0
772         if (i >= 0)
773                 buf[i] = '\0';
774         ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
775 #endif
776         return i;
777 }
778
779 static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
780 {
781 #if 0
782         char *s = alloca(len+1);
783         strncpy(s, buf, len);
784         s[len] = '\0';
785         ast_verbose("ssl write size %d <%s>\n", (int)len, s);
786 #endif
787         return SSL_write(cookie, buf, len);
788 }
789
790 static int ssl_close(void *cookie)
791 {
792         close(SSL_get_fd(cookie));
793         SSL_shutdown(cookie);
794         SSL_free(cookie);
795         return 0;
796 }*/
797 #endif  /* DO_SSL */
798
799 static void *httpd_helper_thread(void *data)
800 {
801         char buf[4096];
802         char cookie[4096];
803         struct ast_tcptls_session_instance *ser = data;
804         struct ast_variable *var, *prev=NULL, *vars=NULL, *headers = NULL;
805         char *uri, *title=NULL;
806         int status = 200, contentlength = 0;
807         struct ast_str *out = NULL;
808         unsigned int static_content = 0;
809
810         if (!fgets(buf, sizeof(buf), ser->f)) {
811                 goto done;
812         }
813
814         uri = ast_skip_nonblanks(buf);  /* Skip method */
815         if (*uri) {
816                 *uri++ = '\0';
817         }
818
819         uri = ast_skip_blanks(uri);     /* Skip white space */
820
821         if (*uri) {                     /* terminate at the first blank */
822                 char *c = ast_skip_nonblanks(uri);
823
824                 if (*c) {
825                         *c = '\0';
826                 }
827         }
828
829         /* process "Cookie: " lines */
830         while (fgets(cookie, sizeof(cookie), ser->f)) {
831                 char *vname, *vval;
832                 int l;
833
834                 /* Trim trailing characters */
835                 ast_trim_blanks(cookie);
836                 if (ast_strlen_zero(cookie)) {
837                         break;
838                 }
839                 if (strncasecmp(cookie, "Cookie: ", 8)) {
840                         char *name, *value;
841
842                         value = ast_strdupa(cookie);
843                         name = strsep(&value, ":");
844                         if (!value) {
845                                 continue;
846                         }
847                         value = ast_skip_blanks(value);
848                         if (ast_strlen_zero(value)) {
849                                 continue;
850                         }
851                         var = ast_variable_new(name, value, "");
852                         if (!var) { 
853                                 continue;
854                         }
855                         var->next = headers;
856                         headers = var;
857
858                         continue;
859                 }
860
861                 /* TODO - The cookie parsing code below seems to work   
862                    in IE6 and FireFox 1.5.  However, it is not entirely 
863                    correct, and therefore may not work in all           
864                    circumstances.                                       
865                       For more details see RFC 2109 and RFC 2965        */
866         
867                 /* FireFox cookie strings look like:                    
868                      Cookie: mansession_id="********"                   
869                    InternetExplorer's look like:                        
870                      Cookie: $Version="1"; mansession_id="********"     */
871                 
872                 /* If we got a FireFox cookie string, the name's right  
873                     after "Cookie: "                                    */
874                 vname = ast_skip_blanks(cookie + 8);
875                         
876                 /* If we got an IE cookie string, we need to skip to    
877                     past the version to get to the name                 */
878                 if (*vname == '$') {
879                         strsep(&vname, ";");
880                         if (!vname) {   /* no name ? */
881                                 continue;
882                         }
883                         vname = ast_skip_blanks(vname);
884                 }
885                 if (!(vval = strchr(vname, '='))) {
886                         continue;
887                 }
888                 /* Ditch the = and the quotes */
889                 *vval++ = '\0';
890                 if (*vval) {
891                         vval++;
892                 }
893                 if ((l = strlen(vval))) {
894                         vval[l - 1] = '\0';     /* trim trailing quote */
895                 }
896                 if ((var = ast_variable_new(vname, vval, ""))) {
897                         if (prev)
898                                 prev->next = var;
899                         else
900                                 vars = var;
901                         prev = var;
902                 }
903         }
904
905         if (!*uri) {
906                 out = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
907         } else if (strcasecmp(buf, "post") && strcasecmp(buf, "get")) {
908                 out = ast_http_error(501, "Not Implemented", NULL,
909                                      "Attempt to use unimplemented / unsupported method");
910         } else {        /* try to serve it */
911                 out = handle_uri(ser, uri, (strcasecmp(buf, "get")) ? AST_HTTP_GET : AST_HTTP_POST,
912                                  &status, &title, &contentlength, &vars, &static_content);
913         }
914
915         /* If they aren't mopped up already, clean up the cookies */
916         if (vars) {
917                 ast_variables_destroy(vars);
918         }
919         /* Clean up all the header information pulled as well */
920         if (headers) {
921                 ast_variables_destroy(headers);
922         }
923
924         if (out) {
925                 struct timeval tv = ast_tvnow();
926                 char timebuf[256];
927                 struct ast_tm tm;
928
929                 ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
930                 fprintf(ser->f,
931                         "HTTP/1.1 %d %s\r\n"
932                         "Server: Asterisk/%s\r\n"
933                         "Date: %s\r\n"
934                         "Connection: close\r\n"
935                         "%s",
936                         status, title ? title : "OK", ast_get_version(), timebuf,
937                         static_content ? "" : "Cache-Control: no-cache, no-store\r\n");
938                         /* We set the no-cache headers only for dynamic content.
939                         * If you want to make sure the static file you requested is not from cache,
940                         * append a random variable to your GET request.  Ex: 'something.html?r=109987734'
941                         */
942                 if (!contentlength) {   /* opaque body ? just dump it hoping it is properly formatted */
943                         fprintf(ser->f, "%s", out->str);
944                 } else {
945                         char *tmp = strstr(out->str, "\r\n\r\n");
946
947                         if (tmp) {
948                                 fprintf(ser->f, "Content-length: %d\r\n", contentlength);
949                                 /* first write the header, then the body */
950                                 fwrite(out->str, 1, (tmp + 4 - out->str), ser->f);
951                                 fwrite(tmp + 4, 1, contentlength, ser->f);
952                         }
953                 }
954                 ast_free(out);
955         }
956
957         if (title) {
958                 ast_free(title);
959         }
960
961 done:
962         fclose(ser->f);
963         ser = ast_tcptls_session_instance_destroy(ser);
964
965         return NULL;
966 }
967
968 /*!
969  * \brief Add a new URI redirect
970  * The entries in the redirect list are sorted by length, just like the list
971  * of URI handlers.
972  */
973 static void add_redirect(const char *value)
974 {
975         char *target, *dest;
976         struct http_uri_redirect *redirect, *cur;
977         unsigned int target_len;
978         unsigned int total_len;
979
980         dest = ast_strdupa(value);
981         dest = ast_skip_blanks(dest);
982         target = strsep(&dest, " ");
983         target = ast_skip_blanks(target);
984         target = strsep(&target, " "); /* trim trailing whitespace */
985
986         if (!dest) {
987                 ast_log(LOG_WARNING, "Invalid redirect '%s'\n", value);
988                 return;
989         }
990
991         target_len = strlen(target) + 1;
992         total_len = sizeof(*redirect) + target_len + strlen(dest) + 1;
993
994         if (!(redirect = ast_calloc(1, total_len))) {
995                 return;
996         }
997
998         redirect->dest = redirect->target + target_len;
999         strcpy(redirect->target, target);
1000         strcpy(redirect->dest, dest);
1001
1002         AST_RWLIST_WRLOCK(&uri_redirects);
1003
1004         target_len--; /* So we can compare directly with strlen() */
1005         if (AST_RWLIST_EMPTY(&uri_redirects) 
1006             || strlen(AST_RWLIST_FIRST(&uri_redirects)->target) <= target_len) {
1007                 AST_RWLIST_INSERT_HEAD(&uri_redirects, redirect, entry);
1008                 AST_RWLIST_UNLOCK(&uri_redirects);
1009
1010                 return;
1011         }
1012
1013         AST_RWLIST_TRAVERSE(&uri_redirects, cur, entry) {
1014                 if (AST_RWLIST_NEXT(cur, entry) 
1015                     && strlen(AST_RWLIST_NEXT(cur, entry)->target) <= target_len) {
1016                         AST_RWLIST_INSERT_AFTER(&uri_redirects, cur, redirect, entry);
1017                         AST_RWLIST_UNLOCK(&uri_redirects); 
1018
1019                         return;
1020                 }
1021         }
1022
1023         AST_RWLIST_INSERT_TAIL(&uri_redirects, redirect, entry);
1024
1025         AST_RWLIST_UNLOCK(&uri_redirects);
1026 }
1027
1028 #ifdef ENABLE_UPLOADS
1029 static void destroy_post_mapping(struct ast_http_post_mapping *post_map)
1030 {
1031         if (post_map->from) {
1032                 ast_free(post_map->from);
1033         }
1034         if (post_map->to) {
1035                 ast_free(post_map->to);
1036         }
1037         ast_free(post_map);
1038 }
1039
1040 static void destroy_post_mappings(void)
1041 {
1042         struct ast_http_post_mapping *post_map;
1043
1044         AST_RWLIST_WRLOCK(&post_mappings);
1045         while ((post_map = AST_RWLIST_REMOVE_HEAD(&post_mappings, entry))) {
1046                 destroy_post_mapping(post_map);
1047         }
1048         AST_RWLIST_UNLOCK(&post_mappings);
1049 }
1050
1051 static void add_post_mapping(const char *from, const char *to)
1052 {
1053         struct ast_http_post_mapping *post_map;
1054
1055         if (!(post_map = ast_calloc(1, sizeof(*post_map)))) {
1056                 return;
1057         }
1058
1059         if (!(post_map->from = ast_strdup(from))) {
1060                 destroy_post_mapping(post_map);
1061
1062                 return;
1063         }
1064
1065         if (!(post_map->to = ast_strdup(to))) {
1066                 destroy_post_mapping(post_map);
1067
1068                 return;
1069         }
1070
1071         AST_RWLIST_WRLOCK(&post_mappings);
1072         AST_RWLIST_INSERT_TAIL(&post_mappings, post_map, entry);
1073         AST_RWLIST_UNLOCK(&post_mappings);
1074 }
1075 #endif /* ENABLE_UPLOADS */
1076
1077 static int __ast_http_load(int reload)
1078 {
1079         struct ast_config *cfg;
1080         struct ast_variable *v;
1081         int enabled=0;
1082         int newenablestatic=0;
1083         struct hostent *hp;
1084         struct ast_hostent ahp;
1085         char newprefix[MAX_PREFIX] = "";
1086         int have_sslbindaddr = 0;
1087         struct http_uri_redirect *redirect;
1088         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
1089
1090         if ((cfg = ast_config_load("http.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED) {
1091                 return 0;
1092         }
1093
1094         /* default values */
1095         memset(&http_desc.sin, 0, sizeof(http_desc.sin));
1096         http_desc.sin.sin_port = htons(8088);
1097
1098         memset(&https_desc.sin, 0, sizeof(https_desc.sin));
1099         https_desc.sin.sin_port = htons(8089);
1100
1101         http_tls_cfg.enabled = 0;
1102         if (http_tls_cfg.certfile) {
1103                 ast_free(http_tls_cfg.certfile);
1104         }
1105         http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
1106         if (http_tls_cfg.cipher) {
1107                 ast_free(http_tls_cfg.cipher);
1108         }
1109         http_tls_cfg.cipher = ast_strdup("");
1110
1111         AST_RWLIST_WRLOCK(&uri_redirects);
1112         while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry))) {
1113                 ast_free(redirect);
1114         }
1115         AST_RWLIST_UNLOCK(&uri_redirects);
1116
1117 #ifdef ENABLE_UPLOADS
1118         destroy_post_mappings();
1119 #endif /* ENABLE_UPLOADS */
1120
1121         if (cfg) {
1122                 v = ast_variable_browse(cfg, "general");
1123                 for (; v; v = v->next) {
1124                         if (!strcasecmp(v->name, "enabled")) {
1125                                 enabled = ast_true(v->value);
1126                         } else if (!strcasecmp(v->name, "sslenable")) {
1127                                 http_tls_cfg.enabled = ast_true(v->value);
1128                         } else if (!strcasecmp(v->name, "sslbindport")) {
1129                                 https_desc.sin.sin_port = htons(atoi(v->value));
1130                         } else if (!strcasecmp(v->name, "sslcert")) {
1131                                 ast_free(http_tls_cfg.certfile);
1132                                 http_tls_cfg.certfile = ast_strdup(v->value);
1133                         } else if (!strcasecmp(v->name, "sslcipher")) {
1134                                 ast_free(http_tls_cfg.cipher);
1135                                 http_tls_cfg.cipher = ast_strdup(v->value);
1136                         } else if (!strcasecmp(v->name, "enablestatic")) {
1137                                 newenablestatic = ast_true(v->value);
1138                         } else if (!strcasecmp(v->name, "bindport")) {
1139                                 http_desc.sin.sin_port = htons(atoi(v->value));
1140                         } else if (!strcasecmp(v->name, "sslbindaddr")) {
1141                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
1142                                         memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
1143                                         have_sslbindaddr = 1;
1144                                 } else {
1145                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
1146                                 }
1147                         } else if (!strcasecmp(v->name, "bindaddr")) {
1148                                 if ((hp = ast_gethostbyname(v->value, &ahp))) {
1149                                         memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
1150                                 } else {
1151                                         ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
1152                                 }
1153                         } else if (!strcasecmp(v->name, "prefix")) {
1154                                 if (!ast_strlen_zero(v->value)) {
1155                                         newprefix[0] = '/';
1156                                         ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
1157                                 } else {
1158                                         newprefix[0] = '\0';
1159                                 }
1160                         } else if (!strcasecmp(v->name, "redirect")) {
1161                                 add_redirect(v->value);
1162                         } else {
1163                                 ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
1164                         }
1165                 }
1166
1167 #ifdef ENABLE_UPLOADS
1168                 for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next) {
1169                         add_post_mapping(v->name, v->value);
1170                 }
1171 #endif /* ENABLE_UPLOADS */
1172
1173                 ast_config_destroy(cfg);
1174         }
1175
1176         if (!have_sslbindaddr) {
1177                 https_desc.sin.sin_addr = http_desc.sin.sin_addr;
1178         }
1179         if (enabled) {
1180                 http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
1181         }
1182         if (strcmp(prefix, newprefix)) {
1183                 ast_copy_string(prefix, newprefix, sizeof(prefix));
1184         }
1185         enablestatic = newenablestatic;
1186         ast_tcptls_server_start(&http_desc);
1187         if (ast_ssl_setup(https_desc.tls_cfg)) {
1188                 ast_tcptls_server_start(&https_desc);
1189         }
1190
1191         return 0;
1192 }
1193
1194 static char *handle_show_http(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1195 {
1196         struct ast_http_uri *urih;
1197         struct http_uri_redirect *redirect;
1198
1199 #ifdef ENABLE_UPLOADS
1200         struct ast_http_post_mapping *post_map;
1201 #endif /* ENABLE_UPLOADS */
1202
1203         switch (cmd) {
1204         case CLI_INIT:
1205                 e->command = "http show status";
1206                 e->usage = 
1207                         "Usage: http show status\n"
1208                         "       Lists status of internal HTTP engine\n";
1209                 return NULL;
1210         case CLI_GENERATE:
1211                 return NULL;
1212         }
1213         
1214         if (a->argc != 3) {
1215                 return CLI_SHOWUSAGE;
1216         }
1217         ast_cli(a->fd, "HTTP Server Status:\n");
1218         ast_cli(a->fd, "Prefix: %s\n", prefix);
1219         if (!http_desc.oldsin.sin_family) {
1220                 ast_cli(a->fd, "Server Disabled\n\n");
1221         } else {
1222                 ast_cli(a->fd, "Server Enabled and Bound to %s:%d\n\n",
1223                         ast_inet_ntoa(http_desc.oldsin.sin_addr),
1224                         ntohs(http_desc.oldsin.sin_port));
1225                 if (http_tls_cfg.enabled) {
1226                         ast_cli(a->fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
1227                                 ast_inet_ntoa(https_desc.oldsin.sin_addr),
1228                                 ntohs(https_desc.oldsin.sin_port));
1229                 }
1230         }
1231
1232         ast_cli(a->fd, "Enabled URI's:\n");
1233         AST_RWLIST_RDLOCK(&uris);
1234         if (AST_RWLIST_EMPTY(&uris)) {
1235                 ast_cli(a->fd, "None.\n");
1236         } else {
1237                 AST_RWLIST_TRAVERSE(&uris, urih, entry) {
1238                         ast_cli(a->fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : ""), urih->description);
1239                 }
1240         }
1241         AST_RWLIST_UNLOCK(&uris);
1242
1243         ast_cli(a->fd, "\nEnabled Redirects:\n");
1244         AST_RWLIST_RDLOCK(&uri_redirects);
1245         AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
1246                 ast_cli(a->fd, "  %s => %s\n", redirect->target, redirect->dest);
1247         }
1248         if (AST_RWLIST_EMPTY(&uri_redirects)) {
1249                 ast_cli(a->fd, "  None.\n");
1250         }
1251         AST_RWLIST_UNLOCK(&uri_redirects);
1252
1253
1254 #ifdef ENABLE_UPLOADS
1255         ast_cli(a->fd, "\nPOST mappings:\n");
1256         AST_RWLIST_RDLOCK(&post_mappings);
1257         AST_LIST_TRAVERSE(&post_mappings, post_map, entry) {
1258                 ast_cli(a->fd, "%s/%s => %s\n", prefix, post_map->from, post_map->to);
1259         }
1260         ast_cli(a->fd, "%s\n", AST_LIST_EMPTY(&post_mappings) ? "None.\n" : "");
1261         AST_RWLIST_UNLOCK(&post_mappings);
1262 #endif /* ENABLE_UPLOADS */
1263
1264         return CLI_SUCCESS;
1265 }
1266
1267 int ast_http_reload(void)
1268 {
1269         return __ast_http_load(1);
1270 }
1271
1272 static struct ast_cli_entry cli_http[] = {
1273         AST_CLI_DEFINE(handle_show_http, "Display HTTP server status"),
1274 };
1275
1276 int ast_http_init(void)
1277 {
1278 #ifdef ENABLE_UPLOADS
1279         g_mime_init(0);
1280 #endif /* ENABLE_UPLOADS */
1281
1282         ast_http_uri_link(&statusuri);
1283         ast_http_uri_link(&staticuri);
1284         ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
1285
1286         return __ast_http_load(0);
1287 }