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