Merge in ast_strftime branch, which changes timestamps to be accurate to the microsec...
[asterisk/asterisk.git] / main / http.c
index 26a4438..b61858c 100644 (file)
@@ -21,8 +21,9 @@
  * \brief http server for AMI access
  *
  * \author Mark Spencer <markster@digium.com>
- * This program implements a tiny http server supporting the "get" method
- * only and was inspired by micro-httpd by Jef Poskanzer 
+ *
+ * This program implements a tiny http server
+ * and was inspired by micro-httpd by Jef Poskanzer 
  * 
  * \ref AstHTTP - AMI over the http protocol
  */
@@ -47,45 +48,91 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include <fcntl.h>
 #include <pthread.h>
 
+#include "minimime/mm.h"
+
 #include "asterisk/cli.h"
 #include "asterisk/http.h"
 #include "asterisk/utils.h"
 #include "asterisk/strings.h"
 #include "asterisk/options.h"
 #include "asterisk/config.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/version.h"
+#include "asterisk/manager.h"
 
 #define MAX_PREFIX 80
 #define DEFAULT_PREFIX "/asterisk"
 
-struct ast_http_server_instance {
-       FILE *f;
-       int fd;
-       struct sockaddr_in requestor;
-       ast_http_callback callback;
+/* See http.h for more information about the SSL implementation */
+#if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE))
+#define        DO_SSL  /* comment in/out if you want to support ssl */
+#endif
+
+static struct tls_config http_tls_cfg;
+
+static void *httpd_helper_thread(void *arg);
+
+/*!
+ * we have up to two accepting threads, one for http, one for https
+ */
+static struct server_args http_desc = {
+       .accept_fd = -1,
+       .master = AST_PTHREADT_NULL,
+       .tls_cfg = NULL,
+       .poll_timeout = -1,
+       .name = "http server",
+       .accept_fn = server_root,
+       .worker_fn = httpd_helper_thread,
 };
 
-static struct ast_http_uri *uris;
+static struct server_args https_desc = {
+       .accept_fd = -1,
+       .master = AST_PTHREADT_NULL,
+       .tls_cfg = &http_tls_cfg,
+       .poll_timeout = -1,
+       .name = "https server",
+       .accept_fn = server_root,
+       .worker_fn = httpd_helper_thread,
+};
+
+static AST_RWLIST_HEAD_STATIC(uris, ast_http_uri);     /*!< list of supported handlers */
+
+struct ast_http_post_mapping {
+       AST_RWLIST_ENTRY(ast_http_post_mapping) entry;
+       char *from;
+       char *to;
+};
 
-static int httpfd = -1;
-static pthread_t master = AST_PTHREADT_NULL;
+static AST_RWLIST_HEAD_STATIC(post_mappings, ast_http_post_mapping);
+
+/* all valid URIs must be prepended by the string in prefix. */
 static char prefix[MAX_PREFIX];
-static int prefix_len = 0;
-static struct sockaddr_in oldsin;
-static int enablestatic=0;
+static int enablestatic;
 
 /*! \brief Limit the kinds of files we're willing to serve up */
 static struct {
-       char *ext;
-       char *mtype;
+       const char *ext;
+       const char *mtype;
 } mimetypes[] = {
        { "png", "image/png" },
        { "jpg", "image/jpeg" },
        { "js", "application/x-javascript" },
        { "wav", "audio/x-wav" },
        { "mp3", "audio/mpeg" },
+       { "svg", "image/svg+xml" },
+       { "svgz", "image/svg+xml" },
+       { "gif", "image/gif" },
+};
+
+struct http_uri_redirect {
+       AST_LIST_ENTRY(http_uri_redirect) entry;
+       char *dest;
+       char target[0];
 };
 
-static char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
+static AST_RWLIST_HEAD_STATIC(uri_redirects, http_uri_redirect);
+
+static const char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
 {
        int x;
        if (ftype) {
@@ -98,17 +145,18 @@ static char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
        return wkspace;
 }
 
-static char *static_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
+static struct ast_str *static_callback(struct server_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
 {
-       char result[4096];
-       char *c=result;
        char *path;
-       char *ftype, *mtype;
+       char *ftype;
+       const char *mtype;
        char wkspace[80];
        struct stat st;
        int len;
        int fd;
-       void *blob;
+       struct timeval tv = ast_tvnow();
+       char buf[256];
+       struct ast_tm tm;
 
        /* Yuck.  I'm not really sold on this, but if you don't deliver static content it makes your configuration 
           substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
@@ -122,7 +170,7 @@ static char *static_callback(struct sockaddr_in *req, const char *uri, struct as
                
        if ((ftype = strrchr(uri, '.')))
                ftype++;
-       mtype=ftype2mtype(ftype, wkspace, sizeof(wkspace));
+       mtype = ftype2mtype(ftype, wkspace, sizeof(wkspace));
        
        /* Cap maximum length */
        len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5;
@@ -138,71 +186,70 @@ static char *static_callback(struct sockaddr_in *req, const char *uri, struct as
        fd = open(path, O_RDONLY);
        if (fd < 0)
                goto out403;
-       
-       len = st.st_size + strlen(mtype) + 40;
-       
-       blob = malloc(len);
-       if (blob) {
-               c = blob;
-               sprintf(c, "Content-type: %s\r\n\r\n", mtype);
-               c += strlen(c);
-               *contentlength = read(fd, c, st.st_size);
-               if (*contentlength < 0) {
-                       close(fd);
-                       free(blob);
-                       goto out403;
-               }
-       }
+
+       ast_strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
+       fprintf(ser->f, "HTTP/1.1 200 OK\r\n"
+               "Server: Asterisk/%s\r\n"
+               "Date: %s\r\n"
+               "Connection: close\r\n"
+               "Cache-Control: no-cache, no-store\r\n"
+               "Content-Length: %d\r\n"
+               "Content-type: %s\r\n\r\n",
+               ASTERISK_VERSION, buf, (int) st.st_size, mtype);
+
+       while ((len = read(fd, buf, sizeof(buf))) > 0)
+               fwrite(buf, 1, len, ser->f);
+
        close(fd);
-       return blob;
+       return NULL;
 
 out404:
        *status = 404;
-       *title = strdup("Not Found");
+       *title = ast_strdup("Not Found");
        return ast_http_error(404, "Not Found", NULL, "Nothing to see here.  Move along.");
 
 out403:
        *status = 403;
-       *title = strdup("Access Denied");
+       *title = ast_strdup("Access Denied");
        return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
 }
 
 
-static char *httpstatus_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
+static struct ast_str *httpstatus_callback(struct server_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
 {
-       char result[4096];
-       size_t reslen = sizeof(result);
-       char *c=result;
+       struct ast_str *out = ast_str_create(512);
        struct ast_variable *v;
 
-       ast_build_string(&c, &reslen,
+       if (out == NULL)
+               return out;
+
+       ast_str_append(&out, 0,
                "\r\n"
                "<title>Asterisk HTTP Status</title>\r\n"
                "<body bgcolor=\"#ffffff\">\r\n"
                "<table bgcolor=\"#f1f1f1\" align=\"center\"><tr><td bgcolor=\"#e0e0ff\" colspan=\"2\" width=\"500\">\r\n"
                "<h2>&nbsp;&nbsp;Asterisk&trade; HTTP Status</h2></td></tr>\r\n");
 
-       ast_build_string(&c, &reslen, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
-       ast_build_string(&c, &reslen, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
-                       ast_inet_ntoa(oldsin.sin_addr));
-       ast_build_string(&c, &reslen, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
-                       ntohs(oldsin.sin_port));
-       ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
-       v = vars;
-       while(v) {
+       ast_str_append(&out, 0, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
+       ast_str_append(&out, 0, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
+                       ast_inet_ntoa(http_desc.oldsin.sin_addr));
+       ast_str_append(&out, 0, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
+                       ntohs(http_desc.oldsin.sin_port));
+       if (http_tls_cfg.enabled)
+               ast_str_append(&out, 0, "<tr><td><i>SSL Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
+                       ntohs(https_desc.oldsin.sin_port));
+       ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
+       for (v = vars; v; v = v->next) {
                if (strncasecmp(v->name, "cookie_", 7))
-                       ast_build_string(&c, &reslen, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
-               v = v->next;
+                       ast_str_append(&out, 0, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
        }
-       ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
-       v = vars;
-       while(v) {
+       ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
+       for (v = vars; v; v = v->next) {
                if (!strncasecmp(v->name, "cookie_", 7))
-                       ast_build_string(&c, &reslen, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
-               v = v->next;
+                       ast_str_append(&out, 0, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
        }
-       ast_build_string(&c, &reslen, "</table><center><font size=\"-1\"><i>Asterisk and Digium are registered trademarks of Digium, Inc.</i></font></center></body>\r\n");
-       return strdup(result);
+       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");
+       return out;
 }
 
 static struct ast_http_uri statusuri = {
@@ -217,12 +264,15 @@ static struct ast_http_uri staticuri = {
        .description = "Asterisk HTTP Static Delivery",
        .uri = "static",
        .has_subtree = 1,
+       .static_content = 1,
 };
        
-char *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
+struct ast_str *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
 {
-       char *c = NULL;
-       asprintf(&c,
+       struct ast_str *out = ast_str_create(512);
+       if (out == NULL)
+               return out;
+       ast_str_set(&out, 0,
                "Content-type: text/html\r\n"
                "%s"
                "\r\n"
@@ -236,65 +286,285 @@ char *ast_http_error(int status, const char *title, const char *extra_header, co
                "<address>Asterisk Server</address>\r\n"
                "</body></html>\r\n",
                        (extra_header ? extra_header : ""), status, title, title, text);
-       return c;
+       return out;
 }
 
+/*! \brief 
+ * Link the new uri into the list. 
+ *
+ * They are sorted by length of
+ * the string, not alphabetically. Duplicate entries are not replaced,
+ * but the insertion order (using <= and not just <) makes sure that
+ * more recent insertions hide older ones.
+ * On a lookup, we just scan the list and stop at the first matching entry.
+ */
 int ast_http_uri_link(struct ast_http_uri *urih)
 {
-       struct ast_http_uri *prev=uris;
-       if (!uris || strlen(uris->uri) <= strlen(urih->uri)) {
-               urih->next = uris;
-               uris = urih;
-       } else {
-               while (prev->next && (strlen(prev->next->uri) > strlen(urih->uri)))
-                       prev = prev->next;
-               /* Insert it here */
-               urih->next = prev->next;
-               prev->next = urih;
+       struct ast_http_uri *uri;
+       int len = strlen(urih->uri);
+
+       AST_RWLIST_WRLOCK(&uris);
+
+       if ( AST_RWLIST_EMPTY(&uris) || strlen(AST_RWLIST_FIRST(&uris)->uri) <= len ) {
+               AST_RWLIST_INSERT_HEAD(&uris, urih, entry);
+               AST_RWLIST_UNLOCK(&uris);
+               return 0;
+       }
+
+       AST_RWLIST_TRAVERSE(&uris, uri, entry) {
+               if ( AST_RWLIST_NEXT(uri, entry) 
+                       && strlen(AST_RWLIST_NEXT(uri, entry)->uri) <= len ) {
+                       AST_RWLIST_INSERT_AFTER(&uris, uri, urih, entry);
+                       AST_RWLIST_UNLOCK(&uris); 
+                       return 0;
+               }
        }
+
+       AST_RWLIST_INSERT_TAIL(&uris, urih, entry);
+
+       AST_RWLIST_UNLOCK(&uris);
+       
        return 0;
 }      
 
 void ast_http_uri_unlink(struct ast_http_uri *urih)
 {
-       struct ast_http_uri *prev = uris;
-       if (!uris)
+       AST_RWLIST_WRLOCK(&uris);
+       AST_RWLIST_REMOVE(&uris, urih, entry);
+       AST_RWLIST_UNLOCK(&uris);
+}
+
+/*! \note This assumes that the post_mappings list is locked */
+static struct ast_http_post_mapping *find_post_mapping(const char *uri)
+{
+       struct ast_http_post_mapping *post_map;
+
+       if (!ast_strlen_zero(prefix) && strncmp(prefix, uri, strlen(prefix))) {
+               ast_debug(1, "URI %s does not have prefix %s\n", uri, prefix);
+               return NULL;
+       }
+
+       uri += strlen(prefix);
+       if (*uri == '/')
+               uri++;
+       
+       AST_RWLIST_TRAVERSE(&post_mappings, post_map, entry) {
+               if (!strcmp(uri, post_map->from))
+                       return post_map;
+       }
+
+       return NULL;
+}
+
+static int get_filename(struct mm_mimepart *part, char *fn, size_t fn_len)
+{
+       const char *filename;
+
+       filename = mm_content_getdispositionparambyname(part->type, "filename");
+
+       if (ast_strlen_zero(filename))
+               return -1;
+
+       ast_copy_string(fn, filename, fn_len);
+
+       return 0;
+}
+
+static void post_raw(struct mm_mimepart *part, const char *post_dir, const char *fn)
+{
+       char filename[PATH_MAX];
+       FILE *f;
+       const char *body;
+       size_t body_len;
+
+       snprintf(filename, sizeof(filename), "%s/%s", post_dir, fn);
+
+       ast_debug(1, "Posting raw data to %s\n", filename);
+
+       if (!(f = fopen(filename, "w"))) {
+               ast_log(LOG_WARNING, "Unable to open %s for writing file from a POST!\n", filename);
+               return;
+       }
+
+       if (!(body = mm_mimepart_getbody(part, 0))) {
+               ast_debug(1, "Couldn't get the mimepart body\n");
+               fclose(f);
                return;
-       if (uris == urih) {
-               uris = uris->next;
        }
-       while(prev->next) {
-               if (prev->next == urih) {
-                       prev->next = urih->next;
+       body_len = mm_mimepart_getlength(part);
+
+       ast_debug(1, "Body length is %ld\n", (long int)body_len);
+
+       fwrite(body, 1, body_len, f);
+
+       fclose(f);
+}
+
+static struct ast_str *handle_post(struct server_instance *ser, char *uri, 
+       int *status, char **title, int *contentlength, struct ast_variable *headers,
+       struct ast_variable *cookies)
+{
+       char buf;
+       FILE *f;
+       size_t res;
+       struct ast_variable *var;
+       int content_len = 0;
+       MM_CTX *ctx;
+       int mm_res, i;
+       struct ast_http_post_mapping *post_map;
+       const char *post_dir;
+       unsigned long ident = 0;
+
+       for (var = cookies; var; var = var->next) {
+               if (strcasecmp(var->name, "mansession_id"))
+                       continue;
+
+               if (sscanf(var->value, "%lx", &ident) != 1) {
+                       *status = 400;
+                       *title = ast_strdup("Bad Request");
+                       return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
+               }
+
+               if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) {
+                       *status = 401;
+                       *title = ast_strdup("Unauthorized");
+                       return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
+               }
+
+               break;
+       }
+       if (!var) {
+               *status = 401;
+               *title = ast_strdup("Unauthorized");
+               return ast_http_error(401, "Unauthorized", NULL, "You are not authorized to make this request.");
+       }
+
+       if (!(f = tmpfile()))
+               return NULL;
+
+       for (var = headers; var; var = var->next) {
+               if (!strcasecmp(var->name, "Content-Length")) {
+                       if ((sscanf(var->value, "%u", &content_len)) != 1) {
+                               ast_log(LOG_ERROR, "Invalid Content-Length in POST request!\n");
+                               fclose(f);
+                               return NULL;
+                       }
+                       ast_debug(1, "Got a Content-Length of %d\n", content_len);
+               } else if (!strcasecmp(var->name, "Content-Type"))
+                       fprintf(f, "Content-Type: %s\r\n\r\n", var->value);
+       }
+
+       while ((res = fread(&buf, 1, 1, ser->f))) {
+               fwrite(&buf, 1, 1, f);
+               content_len--;
+               if (!content_len)
                        break;
+       }
+
+       if (fseek(f, SEEK_SET, 0)) {
+               ast_debug(1, "Failed to seek temp file back to beginning.\n");
+               fclose(f);
+               return NULL;
+       }
+
+       AST_RWLIST_RDLOCK(&post_mappings);
+       if (!(post_map = find_post_mapping(uri))) {
+               ast_debug(1, "%s is not a valid URI for POST\n", uri);
+               AST_RWLIST_UNLOCK(&post_mappings);
+               fclose(f);
+               *status = 404;
+               *title = ast_strdup("Not Found");
+               return ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this server.");
+       }
+       post_dir = ast_strdupa(post_map->to);
+       post_map = NULL;
+       AST_RWLIST_UNLOCK(&post_mappings);
+
+       ast_debug(1, "Going to post files to dir %s\n", post_dir);
+
+       if (!(ctx = mm_context_new())) {
+               fclose(f);
+               return NULL;
+       }
+
+       mm_res = mm_parse_fileptr(ctx, f, MM_PARSE_LOOSE, 0);
+       fclose(f);
+       if (mm_res == -1) {
+               ast_log(LOG_ERROR, "Error parsing MIME data\n");
+               mm_context_free(ctx);
+               *status = 400;
+               *title = ast_strdup("Bad Request");
+               return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
+       }
+
+       mm_res = mm_context_countparts(ctx);
+       if (!mm_res) {
+               ast_log(LOG_ERROR, "Invalid MIME data, found no parts!\n");
+               mm_context_free(ctx);
+               *status = 400;
+               *title = ast_strdup("Bad Request");
+               return ast_http_error(400, "Bad Request", NULL, "The was an error parsing the request.");
+       }
+
+       if (option_debug) {
+               if (mm_context_iscomposite(ctx))
+                       ast_debug(1, "Found %d MIME parts\n", mm_res - 1);
+               else
+                       ast_debug(1, "We have a flat (not multi-part) message\n");
+       }
+
+       for (i = 1; i < mm_res; i++) {
+               struct mm_mimepart *part;
+               char fn[PATH_MAX];
+
+               if (!(part = mm_context_getpart(ctx, i))) {
+                       ast_debug(1, "Failed to get mime part num %d\n", i);
+                       continue;
+               }
+
+               if (get_filename(part, fn, sizeof(fn))) {
+                       ast_debug(1, "Failed to retrieve a filename for part num %d\n", i);
+                       continue;
+               }
+       
+               if (!part->type) {
+                       ast_debug(1, "This part has no content struct?\n");
+                       continue;
                }
-               prev = prev->next;
+
+               /* XXX This assumes the MIME part body is not encoded! */
+               post_raw(part, post_dir, fn);
        }
+
+       mm_context_free(ctx);
+
+       *status = 200;
+       *title = ast_strdup("OK");
+       return ast_http_error(200, "OK", NULL, "File successfully uploaded.");
 }
 
-static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **title, int *contentlength, struct ast_variable **cookies)
+static struct ast_str *handle_uri(struct server_instance *ser, char *uri, int *status, 
+       char **title, int *contentlength, struct ast_variable **cookies, 
+       unsigned int *static_content)
 {
        char *c;
-       char *turi;
-       char *params;
-       char *var;
-       char *val;
+       struct ast_str *out = NULL;
+       char *params = uri;
        struct ast_http_uri *urih=NULL;
-       int len;
+       int l;
        struct ast_variable *vars=NULL, *v, *prev = NULL;
-       
-       
-       params = strchr(uri, '?');
+       struct http_uri_redirect *redirect;
+
+       strsep(&params, "?");
+       /* Extract arguments from the request and store them in variables. */
        if (params) {
-               *params = '\0';
-               params++;
-               while ((var = strsep(&params, "&"))) {
-                       val = strchr(var, '=');
-                       if (val) {
-                               *val = '\0';
-                               val++;
+               char *var, *val;
+
+               while ((val = strsep(&params, "&"))) {
+                       var = strsep(&val, "=");
+                       if (val)
                                ast_uri_decode(val);
-                       } else 
+                       else 
                                val = "";
                        ast_uri_decode(var);
                        if ((v = ast_variable_new(var, val))) {
@@ -306,199 +576,327 @@ static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **
                        }
                }
        }
+       /*
+        * Append the cookies to the variables (the only reason to have them
+        * at the end is to avoid another pass of the cookies list to find
+        * the tail).
+        */
        if (prev)
                prev->next = *cookies;
        else
                vars = *cookies;
        *cookies = NULL;
        ast_uri_decode(uri);
-       if (!strncasecmp(uri, prefix, prefix_len)) {
-               uri += prefix_len;
-               if (!*uri || (*uri == '/')) {
-                       if (*uri == '/')
-                               uri++;
-                       urih = uris;
-                       while(urih) {
-                               len = strlen(urih->uri);
-                               if (!strncasecmp(urih->uri, uri, len)) {
-                                       if (!uri[len] || uri[len] == '/') {
-                                               turi = uri + len;
-                                               if (*turi == '/')
-                                                       turi++;
-                                               if (!*turi || urih->has_subtree) {
-                                                       uri = turi;
-                                                       break;
-                                               }
-                                       }
-                               }
-                               urih = urih->next;
+
+       AST_RWLIST_RDLOCK(&uri_redirects);
+       AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
+               if (!strcasecmp(uri, redirect->target)) {
+                       char buf[512];
+                       snprintf(buf, sizeof(buf), "Location: %s\r\n", redirect->dest);
+                       out = ast_http_error(302, "Moved Temporarily", buf,
+                               "There is no spoon...");
+                       *status = 302;
+                       *title = ast_strdup("Moved Temporarily");
+                       break;
+               }
+       }
+       AST_RWLIST_UNLOCK(&uri_redirects);
+       if (redirect)
+               goto cleanup;
+
+       /* We want requests to start with the prefix and '/' */
+       l = strlen(prefix);
+       if (l && !strncasecmp(uri, prefix, l) && uri[l] == '/') {
+               uri += l + 1;
+               /* scan registered uris to see if we match one. */
+               AST_RWLIST_RDLOCK(&uris);
+               AST_RWLIST_TRAVERSE(&uris, urih, entry) {
+                       l = strlen(urih->uri);
+                       c = uri + l;    /* candidate */
+                       if (strncasecmp(urih->uri, uri, l) /* no match */
+                           || (*c && *c != '/')) /* substring */
+                               continue;
+                       if (*c == '/')
+                               c++;
+                       if (!*c || urih->has_subtree) {
+                               uri = c;
+                               break;
                        }
                }
+               if (!urih)
+                       AST_RWLIST_UNLOCK(&uris);
        }
        if (urih) {
-               c = urih->callback(sin, uri, vars, status, title, contentlength);
-               ast_variables_destroy(vars);
-       } else if (ast_strlen_zero(uri) && ast_strlen_zero(prefix)) {
-               /* Special case: If no prefix, and no URI, send to /static/index.html */
-               c = ast_http_error(302, "Moved Temporarily", "Location: /static/index.html\r\n", "This is not the page you are looking for...");
-               *status = 302;
-               *title = strdup("Moved Temporarily");
+               if (urih->static_content)
+                       *static_content = 1;
+               out = urih->callback(ser, uri, vars, status, title, contentlength);
+               AST_RWLIST_UNLOCK(&uris);
        } else {
-               c = ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this server.");
+               out = ast_http_error(404, "Not Found", NULL,
+                       "The requested URL was not found on this server.");
                *status = 404;
-               *title = strdup("Not Found");
+               *title = ast_strdup("Not Found");
+       }
+
+cleanup:
+       ast_variables_destroy(vars);
+       return out;
+}
+
+#ifdef DO_SSL
+#if defined(HAVE_FUNOPEN)
+#define HOOK_T int
+#define LEN_T int
+#else
+#define HOOK_T ssize_t
+#define LEN_T size_t
+#endif
+/*!
+ * replacement read/write functions for SSL support.
+ * We use wrappers rather than SSL_read/SSL_write directly so
+ * we can put in some debugging.
+ */
+static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
+{
+       int i = SSL_read(cookie, buf, len-1);
+#if 0
+       if (i >= 0)
+               buf[i] = '\0';
+       ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
+#endif
+       return i;
+}
+
+static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
+{
+#if 0
+       char *s = alloca(len+1);
+       strncpy(s, buf, len);
+       s[len] = '\0';
+       ast_verbose("ssl write size %d <%s>\n", (int)len, s);
+#endif
+       return SSL_write(cookie, buf, len);
+}
+
+static int ssl_close(void *cookie)
+{
+       close(SSL_get_fd(cookie));
+       SSL_shutdown(cookie);
+       SSL_free(cookie);
+       return 0;
+}
+#endif /* DO_SSL */
+
+/*!
+ * creates a FILE * from the fd passed by the accept thread.
+ * This operation is potentially expensive (certificate verification),
+ * so we do it in the child thread context.
+ */
+static void *make_file_from_fd(void *data)
+{
+       struct server_instance *ser = data;
+
+       /*
+        * open a FILE * as appropriate.
+        */
+       if (!ser->parent->tls_cfg)
+               ser->f = fdopen(ser->fd, "w+");
+#ifdef DO_SSL
+       else if ( (ser->ssl = SSL_new(ser->parent->tls_cfg->ssl_ctx)) ) {
+               SSL_set_fd(ser->ssl, ser->fd);
+               if (SSL_accept(ser->ssl) == 0)
+                       ast_verbose(" error setting up ssl connection");
+               else {
+#if defined(HAVE_FUNOPEN)      /* the BSD interface */
+                       ser->f = funopen(ser->ssl, ssl_read, ssl_write, NULL, ssl_close);
+
+#elif defined(HAVE_FOPENCOOKIE)        /* the glibc/linux interface */
+                       static const cookie_io_functions_t cookie_funcs = {
+                               ssl_read, ssl_write, NULL, ssl_close
+                       };
+                       ser->f = fopencookie(ser->ssl, "w+", cookie_funcs);
+#else
+                       /* could add other methods here */
+#endif
+               }
+               if (!ser->f)    /* no success opening descriptor stacking */
+                       SSL_free(ser->ssl);
+       }
+#endif /* DO_SSL */
+
+       if (!ser->f) {
+               close(ser->fd);
+               ast_log(LOG_WARNING, "FILE * open failed!\n");
+               ast_free(ser);
+               return NULL;
        }
-       return c;
+       return ser->parent->worker_fn(ser);
 }
 
-static void *ast_httpd_helper_thread(void *data)
+static void *httpd_helper_thread(void *data)
 {
        char buf[4096];
        char cookie[4096];
-       char timebuf[256];
-       struct ast_http_server_instance *ser = data;
-       struct ast_variable *var, *prev=NULL, *vars=NULL;
-       char *uri, *c, *title=NULL;
-       char *vname, *vval;
+       struct server_instance *ser = data;
+       struct ast_variable *var, *prev=NULL, *vars=NULL, *headers = NULL;
+       char *uri, *title=NULL;
        int status = 200, contentlength = 0;
-       time_t t;
-
-       if (fgets(buf, sizeof(buf), ser->f)) {
-               /* Skip method */
-               uri = buf;
-               while(*uri && (*uri > 32))
-                       uri++;
-               if (*uri) {
-                       *uri = '\0';
-                       uri++;
-               }
+       struct ast_str *out = NULL;
+       unsigned int static_content = 0;
 
-               /* Skip white space */
-               while (*uri && (*uri < 33))
-                       uri++;
+       if (!fgets(buf, sizeof(buf), ser->f))
+               goto done;
 
-               if (*uri) {
-                       c = uri;
-                       while (*c && (*c > 32))
-                                c++;
-                       if (*c) {
-                               *c = '\0';
-                       }
-               }
+       uri = ast_skip_nonblanks(buf);  /* Skip method */
+       if (*uri)
+               *uri++ = '\0';
 
-               while (fgets(cookie, sizeof(cookie), ser->f)) {
-                       /* Trim trailing characters */
-                       while(!ast_strlen_zero(cookie) && (cookie[strlen(cookie) - 1] < 33)) {
-                               cookie[strlen(cookie) - 1] = '\0';
-                       }
-                       if (ast_strlen_zero(cookie))
-                               break;
-                       if (!strncasecmp(cookie, "Cookie: ", 8)) {
+       uri = ast_skip_blanks(uri);     /* Skip white space */
+
+       if (*uri) {                     /* terminate at the first blank */
+               char *c = ast_skip_nonblanks(uri);
+               if (*c)
+                       *c = '\0';
+       }
+
+       /* process "Cookie: " lines */
+       while (fgets(cookie, sizeof(cookie), ser->f)) {
+               char *vname, *vval;
+               int l;
+
+               /* Trim trailing characters */
+               ast_trim_blanks(cookie);
+               if (ast_strlen_zero(cookie))
+                       break;
+               if (strncasecmp(cookie, "Cookie: ", 8)) {
+                       char *name, *value;
 
-                               /* TODO - The cookie parsing code below seems to work   
-                                  in IE6 and FireFox 1.5.  However, it is not entirely 
-                                  correct, and therefore may not work in all           
-                                  circumstances.                                       
-                                     For more details see RFC 2109 and RFC 2965        */
+                       value = ast_strdupa(cookie);
+                       name = strsep(&value, ":");
+                       if (!value)
+                               continue;
+                       value = ast_skip_blanks(value);
+                       if (ast_strlen_zero(value))
+                               continue;
+                       var = ast_variable_new(name, value);
+                       if (!var)
+                               continue;
+                       var->next = headers;
+                       headers = var;
+                       continue;
+               }
+
+               /* TODO - The cookie parsing code below seems to work   
+                  in IE6 and FireFox 1.5.  However, it is not entirely 
+                  correct, and therefore may not work in all           
+                  circumstances.                                       
+                     For more details see RFC 2109 and RFC 2965        */
+       
+               /* FireFox cookie strings look like:                    
+                    Cookie: mansession_id="********"                   
+                  InternetExplorer's look like:                        
+                    Cookie: $Version="1"; mansession_id="********"     */
+               
+               /* If we got a FireFox cookie string, the name's right  
+                   after "Cookie: "                                    */
+               vname = ast_skip_blanks(cookie + 8);
                        
-                               /* FireFox cookie strings look like:                    
-                                    Cookie: mansession_id="********"                   
-                                  InternetExplorer's look like:                        
-                                    Cookie: $Version="1"; mansession_id="********"     */
-                               
-                               /* If we got a FireFox cookie string, the name's right  
-                                   after "Cookie: "                                    */
-                                vname = cookie + 8;
-                               
-                               /* If we got an IE cookie string, we need to skip to    
-                                   past the version to get to the name                 */
-                               if (*vname == '$') {
-                                       vname = strchr(vname, ';');
-                                       if (vname) { 
-                                               vname++;
-                                               if (*vname == ' ')
-                                                       vname++;
-                                       }
-                               }
-                               
-                               if (vname) {
-                                       vval = strchr(vname, '=');
-                                       if (vval) {
-                                               /* Ditch the = and the quotes */
-                                               *vval++ = '\0';
-                                               if (*vval)
-                                                       vval++;
-                                               if (strlen(vval))
-                                                       vval[strlen(vval) - 1] = '\0';
-                                               var = ast_variable_new(vname, vval);
-                                               if (var) {
-                                                       if (prev)
-                                                               prev->next = var;
-                                                       else
-                                                               vars = var;
-                                                       prev = var;
-                                               }
-                                       }
-                               }
-                       }
+               /* If we got an IE cookie string, we need to skip to    
+                   past the version to get to the name                 */
+               if (*vname == '$') {
+                       strsep(&vname, ";");
+                       if (!vname)     /* no name ? */
+                               continue;
+                       vname = ast_skip_blanks(vname);
                }
+               vval = strchr(vname, '=');
+               if (!vval)
+                       continue;
+               /* Ditch the = and the quotes */
+               *vval++ = '\0';
+               if (*vval)
+                       vval++;
+               if ( (l = strlen(vval)) )
+                       vval[l - 1] = '\0';     /* trim trailing quote */
+               var = ast_variable_new(vname, vval);
+               if (var) {
+                       if (prev)
+                               prev->next = var;
+                       else
+                               vars = var;
+                       prev = var;
+               }
+       }
 
-               if (*uri) {
-                       if (!strcasecmp(buf, "get")) 
-                               c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars);
-                       else 
-                               c = ast_http_error(501, "Not Implemented", NULL, "Attempt to use unimplemented / unsupported method");\
-               } else 
-                       c = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
-
-               /* If they aren't mopped up already, clean up the cookies */
-               if (vars)
-                       ast_variables_destroy(vars);
-
-               if (!c)
-                       c = ast_http_error(500, "Internal Error", NULL, "Internal Server Error");
-               if (c) {
-                       time(&t);
-                       strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
-                       ast_cli(ser->fd, "HTTP/1.1 %d %s\r\n", status, title ? title : "OK");
-                       ast_cli(ser->fd, "Server: Asterisk\r\n");
-                       ast_cli(ser->fd, "Date: %s\r\n", timebuf);
-                       ast_cli(ser->fd, "Connection: close\r\n");
-                       if (contentlength) {
-                               char *tmp;
-                               tmp = strstr(c, "\r\n\r\n");
-                               if (tmp) {
-                                       ast_cli(ser->fd, "Content-length: %d\r\n", contentlength);
-                                       write(ser->fd, c, (tmp + 4 - c));
-                                       write(ser->fd, tmp + 4, contentlength);
-                               }
-                       } else
-                               ast_cli(ser->fd, "%s", c);
-                       free(c);
+       if (!*uri)
+               out = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
+       else if (!strcasecmp(buf, "post")) 
+               out = handle_post(ser, uri, &status, &title, &contentlength, headers, vars);
+       else if (strcasecmp(buf, "get")) 
+               out = ast_http_error(501, "Not Implemented", NULL,
+                       "Attempt to use unimplemented / unsupported method");
+       else    /* try to serve it */
+               out = handle_uri(ser, uri, &status, &title, &contentlength, &vars, &static_content);
+
+       /* If they aren't mopped up already, clean up the cookies */
+       if (vars)
+               ast_variables_destroy(vars);
+
+       if (out) {
+               struct timeval tv = ast_tvnow();
+               char timebuf[256];
+               struct ast_tm tm;
+
+               ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
+               fprintf(ser->f, "HTTP/1.1 %d %s\r\n"
+                               "Server: Asterisk/%s\r\n"
+                               "Date: %s\r\n"
+                               "Connection: close\r\n"
+                               "%s",
+                       status, title ? title : "OK", ASTERISK_VERSION, timebuf,
+                       static_content ? "" : "Cache-Control: no-cache, no-store\r\n");
+               if (!contentlength) {   /* opaque body ? just dump it hoping it is properly formatted */
+                       fprintf(ser->f, "%s", out->str);
+               } else {
+                       char *tmp = strstr(out->str, "\r\n\r\n");
+
+                       if (tmp) {
+                               fprintf(ser->f, "Content-length: %d\r\n", contentlength);
+                               /* first write the header, then the body */
+                               fwrite(out->str, 1, (tmp + 4 - out->str), ser->f);
+                               fwrite(tmp + 4, 1, contentlength, ser->f);
+                       }
                }
-               if (title)
-                       free(title);
+               ast_free(out);
        }
+       if (title)
+               ast_free(title);
+
+done:
        fclose(ser->f);
-       free(ser);
+       ast_free(ser);
        return NULL;
 }
 
-static void *http_root(void *data)
+void *server_root(void *data)
 {
+       struct server_args *desc = data;
        int fd;
        struct sockaddr_in sin;
        socklen_t sinlen;
-       struct ast_http_server_instance *ser;
+       struct server_instance *ser;
        pthread_t launched;
-       pthread_attr_t attr;
        
        for (;;) {
-               int flags;
+               int i, flags;
 
-               ast_wait_for_input(httpfd, -1);
+               if (desc->periodic_fn)
+                       desc->periodic_fn(desc);
+               i = ast_wait_for_input(desc->accept_fd, desc->poll_timeout);
+               if (i <= 0)
+                       continue;
                sinlen = sizeof(sin);
-               fd = accept(httpfd, (struct sockaddr *)&sin, &sinlen);
+               fd = accept(desc->accept_fd, (struct sockaddr *)&sin, &sinlen);
                if (fd < 0) {
                        if ((errno != EAGAIN) && (errno != EINTR))
                                ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
@@ -513,96 +911,216 @@ static void *http_root(void *data)
                flags = fcntl(fd, F_GETFL);
                fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
                ser->fd = fd;
+               ser->parent = desc;
                memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
-               if ((ser->f = fdopen(ser->fd, "w+"))) {
-                       pthread_attr_init(&attr);
-                       pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
                        
-                       if (ast_pthread_create_background(&launched, &attr, ast_httpd_helper_thread, ser)) {
-                               ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
-                               fclose(ser->f);
-                               free(ser);
-                       }
-               } else {
-                       ast_log(LOG_WARNING, "fdopen failed!\n");
+               if (ast_pthread_create_detached_background(&launched, NULL, make_file_from_fd, ser)) {
+                       ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
                        close(ser->fd);
-                       free(ser);
+                       ast_free(ser);
                }
+
        }
        return NULL;
 }
 
-char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, size_t buflen)
+int ssl_setup(struct tls_config *cfg)
 {
-       char *c;
-       c = buf;
-       ast_build_string(&c, &buflen, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var, val);
-       if (expires)
-               ast_build_string(&c, &buflen, "; Max-Age=%d", expires);
-       ast_build_string(&c, &buflen, "\r\n");
-       return buf;
+#ifndef DO_SSL
+       cfg->enabled = 0;
+       return 0;
+#else
+       if (!cfg->enabled)
+               return 0;
+       SSL_load_error_strings();
+       SSLeay_add_ssl_algorithms();
+       cfg->ssl_ctx = SSL_CTX_new( SSLv23_server_method() );
+       if (!ast_strlen_zero(cfg->certfile)) {
+               if (SSL_CTX_use_certificate_file(cfg->ssl_ctx, cfg->certfile, SSL_FILETYPE_PEM) == 0 ||
+                   SSL_CTX_use_PrivateKey_file(cfg->ssl_ctx, cfg->certfile, SSL_FILETYPE_PEM) == 0 ||
+                   SSL_CTX_check_private_key(cfg->ssl_ctx) == 0 ) {
+                       ast_verbose("ssl cert error <%s>", cfg->certfile);
+                       sleep(2);
+                       cfg->enabled = 0;
+                       return 0;
+               }
+       }
+       if (!ast_strlen_zero(cfg->cipher)) {
+               if (SSL_CTX_set_cipher_list(cfg->ssl_ctx, cfg->cipher) == 0 ) {
+                       ast_verbose("ssl cipher error <%s>", cfg->cipher);
+                       sleep(2);
+                       cfg->enabled = 0;
+                       return 0;
+               }
+       }
+       ast_verbose("ssl cert ok");
+       return 1;
+#endif
 }
 
-
-static void http_server_start(struct sockaddr_in *sin)
+/*!
+ * This is a generic (re)start routine for a TCP server,
+ * which does the socket/bind/listen and starts a thread for handling
+ * accept().
+ */
+void server_start(struct server_args *desc)
 {
        int flags;
        int x = 1;
        
        /* Do nothing if nothing has changed */
-       if (!memcmp(&oldsin, sin, sizeof(oldsin))) {
-               if (option_debug)
-                       ast_log(LOG_DEBUG, "Nothing changed in http\n");
+       if (!memcmp(&desc->oldsin, &desc->sin, sizeof(desc->oldsin))) {
+               ast_debug(1, "Nothing changed in %s\n", desc->name);
                return;
        }
        
-       memcpy(&oldsin, sin, sizeof(oldsin));
+       desc->oldsin = desc->sin;
        
        /* Shutdown a running server if there is one */
-       if (master != AST_PTHREADT_NULL) {
-               pthread_cancel(master);
-               pthread_kill(master, SIGURG);
-               pthread_join(master, NULL);
+       if (desc->master != AST_PTHREADT_NULL) {
+               pthread_cancel(desc->master);
+               pthread_kill(desc->master, SIGURG);
+               pthread_join(desc->master, NULL);
        }
        
-       if (httpfd != -1)
-               close(httpfd);
+       if (desc->accept_fd != -1)
+               close(desc->accept_fd);
 
        /* If there's no new server, stop here */
-       if (!sin->sin_family)
+       if (desc->sin.sin_family == 0)
                return;
-       
-       
-       httpfd = socket(AF_INET, SOCK_STREAM, 0);
-       if (httpfd < 0) {
-               ast_log(LOG_WARNING, "Unable to allocate socket: %s\n", strerror(errno));
+
+       desc->accept_fd = socket(AF_INET, SOCK_STREAM, 0);
+       if (desc->accept_fd < 0) {
+               ast_log(LOG_WARNING, "Unable to allocate socket for %s: %s\n",
+                       desc->name, strerror(errno));
                return;
        }
        
-       setsockopt(httpfd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
-       if (bind(httpfd, (struct sockaddr *)sin, sizeof(*sin))) {
-               ast_log(LOG_NOTICE, "Unable to bind http server to %s:%d: %s\n",
-                       ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
+       setsockopt(desc->accept_fd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
+       if (bind(desc->accept_fd, (struct sockaddr *)&desc->sin, sizeof(desc->sin))) {
+               ast_log(LOG_NOTICE, "Unable to bind %s to %s:%d: %s\n",
+                       desc->name,
+                       ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
+                       strerror(errno));
+               goto error;
+       }
+       if (listen(desc->accept_fd, 10)) {
+               ast_log(LOG_NOTICE, "Unable to listen for %s!\n", desc->name);
+               goto error;
+       }
+       flags = fcntl(desc->accept_fd, F_GETFL);
+       fcntl(desc->accept_fd, F_SETFL, flags | O_NONBLOCK);
+       if (ast_pthread_create_background(&desc->master, NULL, desc->accept_fn, desc)) {
+               ast_log(LOG_NOTICE, "Unable to launch %s on %s:%d: %s\n",
+                       desc->name,
+                       ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
                        strerror(errno));
-               close(httpfd);
-               httpfd = -1;
+               goto error;
+       }
+       return;
+
+error:
+       close(desc->accept_fd);
+       desc->accept_fd = -1;
+}
+
+/*!
+ * \brief Add a new URI redirect
+ * The entries in the redirect list are sorted by length, just like the list
+ * of URI handlers.
+ */
+static void add_redirect(const char *value)
+{
+       char *target, *dest;
+       struct http_uri_redirect *redirect, *cur;
+       unsigned int target_len;
+       unsigned int total_len;
+
+       dest = ast_strdupa(value);
+       dest = ast_skip_blanks(dest);
+       target = strsep(&dest, " ");
+       target = ast_skip_blanks(target);
+       target = strsep(&target, " "); /* trim trailing whitespace */
+
+       if (!dest) {
+               ast_log(LOG_WARNING, "Invalid redirect '%s'\n", value);
                return;
        }
-       if (listen(httpfd, 10)) {
-               ast_log(LOG_NOTICE, "Unable to listen!\n");
-               close(httpfd);
-               httpfd = -1;
+
+       target_len = strlen(target) + 1;
+       total_len = sizeof(*redirect) + target_len + strlen(dest) + 1;
+
+       if (!(redirect = ast_calloc(1, total_len)))
+               return;
+
+       redirect->dest = redirect->target + target_len;
+       strcpy(redirect->target, target);
+       strcpy(redirect->dest, dest);
+
+       AST_RWLIST_WRLOCK(&uri_redirects);
+
+       target_len--; /* So we can compare directly with strlen() */
+       if ( AST_RWLIST_EMPTY(&uri_redirects) 
+               || strlen(AST_RWLIST_FIRST(&uri_redirects)->target) <= target_len ) {
+               AST_RWLIST_INSERT_HEAD(&uri_redirects, redirect, entry);
+               AST_RWLIST_UNLOCK(&uri_redirects);
+               return;
+       }
+
+       AST_RWLIST_TRAVERSE(&uri_redirects, cur, entry) {
+               if ( AST_RWLIST_NEXT(cur, entry) 
+                       && strlen(AST_RWLIST_NEXT(cur, entry)->target) <= target_len ) {
+                       AST_RWLIST_INSERT_AFTER(&uri_redirects, cur, redirect, entry);
+                       AST_RWLIST_UNLOCK(&uri_redirects); 
+                       return;
+               }
+       }
+
+       AST_RWLIST_INSERT_TAIL(&uri_redirects, redirect, entry);
+
+       AST_RWLIST_UNLOCK(&uri_redirects);
+}
+
+static void destroy_post_mapping(struct ast_http_post_mapping *post_map)
+{
+       if (post_map->from)
+               ast_free(post_map->from);
+       if (post_map->to)
+               ast_free(post_map->to);
+       ast_free(post_map);
+}
+
+static void destroy_post_mappings(void)
+{
+       struct ast_http_post_mapping *post_map;
+
+       AST_RWLIST_WRLOCK(&post_mappings);
+       while ((post_map = AST_RWLIST_REMOVE_HEAD(&post_mappings, entry)))
+               destroy_post_mapping(post_map);
+       AST_RWLIST_UNLOCK(&post_mappings);
+}
+
+static void add_post_mapping(const char *from, const char *to)
+{
+       struct ast_http_post_mapping *post_map;
+
+       if (!(post_map = ast_calloc(1, sizeof(*post_map))))
+               return;
+
+       if (!(post_map->from = ast_strdup(from))) {
+               destroy_post_mapping(post_map);
                return;
        }
-       flags = fcntl(httpfd, F_GETFL);
-       fcntl(httpfd, F_SETFL, flags | O_NONBLOCK);
-       if (ast_pthread_create_background(&master, NULL, http_root, NULL)) {
-               ast_log(LOG_NOTICE, "Unable to launch http server on %s:%d: %s\n",
-                               ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
-                               strerror(errno));
-               close(httpfd);
-               httpfd = -1;
+
+       if (!(post_map->to = ast_strdup(to))) {
+               destroy_post_mapping(post_map);
+               return;
        }
+
+       AST_RWLIST_WRLOCK(&post_mappings);
+       AST_RWLIST_INSERT_TAIL(&post_mappings, post_map, entry);
+       AST_RWLIST_UNLOCK(&post_mappings);
 }
 
 static int __ast_http_load(int reload)
@@ -611,27 +1129,67 @@ static int __ast_http_load(int reload)
        struct ast_variable *v;
        int enabled=0;
        int newenablestatic=0;
-       struct sockaddr_in sin;
        struct hostent *hp;
        struct ast_hostent ahp;
        char newprefix[MAX_PREFIX];
+       int have_sslbindaddr = 0;
+       struct http_uri_redirect *redirect;
+
+       /* default values */
+       memset(&http_desc.sin, 0, sizeof(http_desc.sin));
+       http_desc.sin.sin_port = htons(8088);
+
+       memset(&https_desc.sin, 0, sizeof(https_desc.sin));
+       https_desc.sin.sin_port = htons(8089);
 
-       memset(&sin, 0, sizeof(sin));
-       sin.sin_port = 8088;
        strcpy(newprefix, DEFAULT_PREFIX);
+
+       http_tls_cfg.enabled = 0;
+       if (http_tls_cfg.certfile)
+               ast_free(http_tls_cfg.certfile);
+       http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
+       if (http_tls_cfg.cipher)
+               ast_free(http_tls_cfg.cipher);
+       http_tls_cfg.cipher = ast_strdup("");
+
+       AST_RWLIST_WRLOCK(&uri_redirects);
+       while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry)))
+               ast_free(redirect);
+       AST_RWLIST_UNLOCK(&uri_redirects);
+
+       destroy_post_mappings();
+
        cfg = ast_config_load("http.conf");
        if (cfg) {
                v = ast_variable_browse(cfg, "general");
-               while(v) {
+               for (; v; v = v->next) {
                        if (!strcasecmp(v->name, "enabled"))
                                enabled = ast_true(v->value);
+                       else if (!strcasecmp(v->name, "sslenable"))
+                               http_tls_cfg.enabled = ast_true(v->value);
+                       else if (!strcasecmp(v->name, "sslbindport"))
+                               https_desc.sin.sin_port = htons(atoi(v->value));
+                       else if (!strcasecmp(v->name, "sslcert")) {
+                               ast_free(http_tls_cfg.certfile);
+                               http_tls_cfg.certfile = ast_strdup(v->value);
+                       } else if (!strcasecmp(v->name, "sslcipher")) {
+                               ast_free(http_tls_cfg.cipher);
+                               http_tls_cfg.cipher = ast_strdup(v->value);
+                       }
                        else if (!strcasecmp(v->name, "enablestatic"))
                                newenablestatic = ast_true(v->value);
                        else if (!strcasecmp(v->name, "bindport"))
-                               sin.sin_port = ntohs(atoi(v->value));
-                       else if (!strcasecmp(v->name, "bindaddr")) {
+                               http_desc.sin.sin_port = htons(atoi(v->value));
+                       else if (!strcasecmp(v->name, "sslbindaddr")) {
+                               if ((hp = ast_gethostbyname(v->value, &ahp))) {
+                                       memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
+                                       have_sslbindaddr = 1;
+                               } else {
+                                       ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
+                               }
+                       } else if (!strcasecmp(v->name, "bindaddr")) {
                                if ((hp = ast_gethostbyname(v->value, &ahp))) {
-                                       memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
+                                       memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
                                } else {
                                        ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
                                }
@@ -642,44 +1200,81 @@ static int __ast_http_load(int reload)
                                } else {
                                        newprefix[0] = '\0';
                                }
-                                       
+                       } else if (!strcasecmp(v->name, "redirect")) {
+                               add_redirect(v->value);
+                       } else {
+                               ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
                        }
-                       v = v->next;
                }
+
+               for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next)
+                       add_post_mapping(v->name, v->value);
+
                ast_config_destroy(cfg);
        }
+       if (!have_sslbindaddr)
+               https_desc.sin.sin_addr = http_desc.sin.sin_addr;
        if (enabled)
-               sin.sin_family = AF_INET;
-       if (strcmp(prefix, newprefix)) {
+               http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
+       if (strcmp(prefix, newprefix))
                ast_copy_string(prefix, newprefix, sizeof(prefix));
-               prefix_len = strlen(prefix);
-       }
        enablestatic = newenablestatic;
-       http_server_start(&sin);
+       server_start(&http_desc);
+       if (ssl_setup(https_desc.tls_cfg))
+               server_start(&https_desc);
+
        return 0;
 }
 
 static int handle_show_http(int fd, int argc, char *argv[])
 {
        struct ast_http_uri *urih;
+       struct http_uri_redirect *redirect;
+       struct ast_http_post_mapping *post_map;
+
        if (argc != 3)
                return RESULT_SHOWUSAGE;
+
        ast_cli(fd, "HTTP Server Status:\n");
        ast_cli(fd, "Prefix: %s\n", prefix);
-       if (oldsin.sin_family)
-               ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
-                       ast_inet_ntoa(oldsin.sin_addr),
-                       ntohs(oldsin.sin_port));
-       else
+       if (!http_desc.oldsin.sin_family)
                ast_cli(fd, "Server Disabled\n\n");
-       ast_cli(fd, "Enabled URI's:\n");
-       urih = uris;
-       while(urih){
-               ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
-               urih = urih->next;
+       else {
+               ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
+                       ast_inet_ntoa(http_desc.oldsin.sin_addr),
+                       ntohs(http_desc.oldsin.sin_port));
+               if (http_tls_cfg.enabled)
+                       ast_cli(fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
+                               ast_inet_ntoa(https_desc.oldsin.sin_addr),
+                               ntohs(https_desc.oldsin.sin_port));
        }
-       if (!uris)
+
+       ast_cli(fd, "Enabled URI's:\n");
+       AST_RWLIST_RDLOCK(&uris);
+       if (AST_RWLIST_EMPTY(&uris)) {
                ast_cli(fd, "None.\n");
+       } else {
+               AST_RWLIST_TRAVERSE(&uris, urih, entry)
+                       ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
+       }
+       AST_RWLIST_UNLOCK(&uris);
+
+       ast_cli(fd, "\nEnabled Redirects:\n");
+       AST_RWLIST_RDLOCK(&uri_redirects);
+       AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry)
+               ast_cli(fd, "  %s => %s\n", redirect->target, redirect->dest);
+       if (AST_RWLIST_EMPTY(&uri_redirects))
+               ast_cli(fd, "  None.\n");
+       AST_RWLIST_UNLOCK(&uri_redirects);
+
+
+       ast_cli(fd, "\nPOST mappings:\n");
+       AST_RWLIST_RDLOCK(&post_mappings);
+       AST_LIST_TRAVERSE(&post_mappings, post_map, entry)
+               ast_cli(fd, "%s/%s => %s\n", prefix, post_map->from, post_map->to);
+       ast_cli(fd, "%s\n", AST_LIST_EMPTY(&post_mappings) ? "None.\n" : "");
+       AST_RWLIST_UNLOCK(&post_mappings);
+
        return RESULT_SUCCESS;
 }
 
@@ -689,19 +1284,23 @@ int ast_http_reload(void)
 }
 
 static char show_http_help[] =
-"Usage: http list status\n"
+"Usage: http show status\n"
 "       Lists status of internal HTTP engine\n";
 
 static struct ast_cli_entry cli_http[] = {
-       { { "http", "list", "status", NULL },
+       { { "http", "show", "status", NULL },
        handle_show_http, "Display HTTP server status",
        show_http_help },
 };
 
 int ast_http_init(void)
 {
+       mm_library_init();
+       mm_codec_registerdefaultcodecs();
+
        ast_http_uri_link(&statusuri);
        ast_http_uri_link(&staticuri);
        ast_cli_register_multiple(cli_http, sizeof(cli_http) / sizeof(struct ast_cli_entry));
+
        return __ast_http_load(0);
 }