Bug 6927 - CLI command has 3 args, not 2.
[asterisk/asterisk.git] / http.c
diff --git a/http.c b/http.c
index fca0763..97eb676 100644 (file)
--- a/http.c
+++ b/http.c
 
 /*!
  * \file 
- * \brief http server
+ * \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 
+ * 
+ * \ref AstHTTP - AMI over the http protocol
  */
 
 #include <sys/types.h>
 #include <netinet/in.h>
 #include <sys/time.h>
 #include <sys/socket.h>
+#include <sys/stat.h>
 #include <sys/signal.h>
 #include <arpa/inet.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <pthread.h>
 
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
 #include "asterisk/cli.h"
 #include "asterisk/http.h"
 #include "asterisk/utils.h"
 #include "asterisk/strings.h"
+#include "asterisk/options.h"
+#include "asterisk/config.h"
 
 #define MAX_PREFIX 80
-#define DEFAULT_PREFIX "asterisk"
+#define DEFAULT_PREFIX "/asterisk"
 
 struct ast_http_server_instance {
        FILE *f;
@@ -61,6 +71,100 @@ static pthread_t master = AST_PTHREADT_NULL;
 static char prefix[MAX_PREFIX];
 static int prefix_len = 0;
 static struct sockaddr_in oldsin;
+static int enablestatic=0;
+
+/*! \brief Limit the kinds of files we're willing to serve up */
+static struct {
+       char *ext;
+       char *mtype;
+} mimetypes[] = {
+       { "png", "image/png" },
+       { "jpg", "image/jpeg" },
+       { "js", "application/x-javascript" },
+       { "wav", "audio/x-wav" },
+       { "mp3", "audio/mpeg" },
+};
+
+static char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
+{
+       int x;
+       if (ftype) {
+               for (x=0;x<sizeof(mimetypes) / sizeof(mimetypes[0]); x++) {
+                       if (!strcasecmp(ftype, mimetypes[x].ext))
+                               return mimetypes[x].mtype;
+               }
+       }
+       snprintf(wkspace, wkspacelen, "text/%s", ftype ? ftype : "plain");
+       return wkspace;
+}
+
+static char *static_callback(struct sockaddr_in *req, 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 wkspace[80];
+       struct stat st;
+       int len;
+       int fd;
+       void *blob;
+
+       /* 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. */
+       if (!enablestatic || ast_strlen_zero(uri))
+               goto out403;
+       /* Disallow any funny filenames at all */
+       if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0]))
+               goto out403;
+       if (strstr(uri, "/.."))
+               goto out403;
+               
+       if ((ftype = strrchr(uri, '.')))
+               ftype++;
+       mtype=ftype2mtype(ftype, wkspace, sizeof(wkspace));
+       
+       /* Cap maximum length */
+       len = strlen(uri) + strlen(ast_config_AST_VAR_DIR) + strlen("/static-http/") + 5;
+       if (len > 1024)
+               goto out403;
+               
+       path = alloca(len);
+       sprintf(path, "%s/static-http/%s", ast_config_AST_VAR_DIR, uri);
+       if (stat(path, &st))
+               goto out404;
+       if (S_ISDIR(st.st_mode))
+               goto out404;
+       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;
+               }
+       }
+       return blob;
+
+out404:
+       *status = 404;
+       *title = strdup("Not Found");
+       return ast_http_error(404, "Not Found", NULL, "Nothing to see here.  Move along.");
+
+out403:
+       *status = 403;
+       *title = 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)
@@ -86,7 +190,15 @@ static char *httpstatus_callback(struct sockaddr_in *req, const char *uri, struc
        ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
        v = vars;
        while(v) {
-               ast_build_string(&c, &reslen, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
+               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_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
+       v = vars;
+       while(v) {
+               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_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");
@@ -100,6 +212,13 @@ static struct ast_http_uri statusuri = {
        .has_subtree = 0,
 };
        
+static struct ast_http_uri staticuri = {
+       .callback = static_callback,
+       .description = "Asterisk HTTP Static Delivery",
+       .uri = "static",
+       .has_subtree = 1,
+};
+       
 char *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
 {
        char *c = NULL;
@@ -153,7 +272,7 @@ void ast_http_uri_unlink(struct ast_http_uri *urih)
        }
 }
 
-static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **title, int *contentlength)
+static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **title, int *contentlength, struct ast_variable **cookies)
 {
        char *c;
        char *turi;
@@ -165,8 +284,6 @@ static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **
        struct ast_variable *vars=NULL, *v, *prev = NULL;
        
        
-       if (*uri == '/')
-               uri++;
        params = strchr(uri, '?');
        if (params) {
                *params = '\0';
@@ -176,9 +293,9 @@ static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **
                        if (val) {
                                *val = '\0';
                                val++;
+                               ast_uri_decode(val);
                        } else 
                                val = "";
-                       ast_uri_decode(val);
                        ast_uri_decode(var);
                        if ((v = ast_variable_new(var, val))) {
                                if (vars)
@@ -189,6 +306,11 @@ static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **
                        }
                }
        }
+       if (prev)
+               prev->next = *cookies;
+       else
+               vars = *cookies;
+       *cookies = NULL;
        ast_uri_decode(uri);
        if (!strncasecmp(uri, prefix, prefix_len)) {
                uri += prefix_len;
@@ -216,6 +338,11 @@ static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **
        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");
        } else {
                c = ast_http_error(404, "Not Found", NULL, "The requested URL was not found on this serer.");
                *status = 404;
@@ -227,50 +354,99 @@ static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **
 static void *ast_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;
        int status = 200, contentlength = 0;
        time_t t;
 
        if (fgets(buf, sizeof(buf), ser->f)) {
                /* Skip method */
                uri = buf;
-               while(*uri && (*uri > 32)) uri++;
+               while(*uri && (*uri > 32))
+                       uri++;
                if (*uri) {
                        *uri = '\0';
                        uri++;
                }
 
                /* Skip white space */
-               while (*uri && (*uri < 33)) uri++;
+               while (*uri && (*uri < 33))
+                       uri++;
 
                if (*uri) {
                        c = uri;
-                       while (*c && (*c > 32)) c++;
+                       while (*c && (*c > 32))
+                                c++;
                        if (*c) {
                                *c = '\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)) {
+                               vname = cookie + 8;
+                               vval = strchr(vname, '=');
+                               if (vval) {
+                                       /* Ditch the = and the quotes */
+                                       *vval = '\0';
+                                       vval++;
+                                       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 (*uri) {
                        if (!strcasecmp(buf, "get")) 
-                               c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength);
+                               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 GET %d %s\r\n", status, title ? title : "OK");
+                       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);
-                       if (contentlength)
-                               ast_cli(ser->fd, "Content-length: %d\r\n", contentlength);
                        ast_cli(ser->fd, "Connection: close\r\n");
-                       ast_cli(ser->fd, "%s", c);
+                       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 (title)
@@ -288,6 +464,8 @@ static void *http_root(void *data)
        int sinlen;
        struct ast_http_server_instance *ser;
        pthread_t launched;
+       pthread_attr_t attr;
+       
        for (;;) {
                ast_wait_for_input(httpfd, -1);
                sinlen = sizeof(sin);
@@ -297,11 +475,15 @@ static void *http_root(void *data)
                                ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
                        continue;
                }
-               ser = calloc(1, sizeof(*ser));
+               ser = ast_calloc(1, sizeof(*ser));
                if (ser) {
                        ser->fd = fd;
+                       memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
                        if ((ser->f = fdopen(ser->fd, "w+"))) {
-                               if (ast_pthread_create(&launched, NULL, ast_httpd_helper_thread, ser)) {
+                               pthread_attr_init(&attr);
+                               pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+                               
+                               if (ast_pthread_create(&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);
@@ -312,13 +494,25 @@ static void *http_root(void *data)
                                free(ser);
                        }
                } else {
-                       ast_log(LOG_WARNING, "Out of memory!\n");
-                       close(fd);
+                       close(ser->fd);
+                       free(ser);
                }
        }
        return NULL;
 }
 
+char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, size_t buflen)
+{
+       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;
+}
+
+
 static void http_server_start(struct sockaddr_in *sin)
 {
        char iabuf[INET_ADDRSTRLEN];
@@ -385,10 +579,12 @@ static int __ast_http_load(int reload)
        struct ast_config *cfg;
        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];
+
        memset(&sin, 0, sizeof(sin));
        sin.sin_port = 8088;
        strcpy(newprefix, DEFAULT_PREFIX);
@@ -398,6 +594,8 @@ static int __ast_http_load(int reload)
                while(v) {
                        if (!strcasecmp(v->name, "enabled"))
                                enabled = ast_true(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")) {
@@ -406,8 +604,15 @@ static int __ast_http_load(int reload)
                                } else {
                                        ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
                                }
-                       } else if (!strcasecmp(v->name, "prefix"))
-                               ast_copy_string(newprefix, v->value, sizeof(newprefix));
+                       } else if (!strcasecmp(v->name, "prefix")) {
+                               if (!ast_strlen_zero(v->value)) {
+                                       newprefix[0] = '/';
+                                       ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
+                               } else {
+                                       newprefix[0] = '\0';
+                               }
+                                       
+                       }
                        v = v->next;
                }
                ast_config_destroy(cfg);
@@ -418,6 +623,7 @@ static int __ast_http_load(int reload)
                ast_copy_string(prefix, newprefix, sizeof(prefix));
                prefix_len = strlen(prefix);
        }
+       enablestatic = newenablestatic;
        http_server_start(&sin);
        return 0;
 }
@@ -426,7 +632,7 @@ static int handle_show_http(int fd, int argc, char *argv[])
 {
        char iabuf[INET_ADDRSTRLEN];
        struct ast_http_uri *urih;
-       if (argc != 2)
+       if (argc != 3)
                return RESULT_SHOWUSAGE;
        ast_cli(fd, "HTTP Server Status:\n");
        ast_cli(fd, "Prefix: %s\n", prefix);
@@ -439,7 +645,7 @@ static int handle_show_http(int fd, int argc, char *argv[])
        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);
+               ast_cli(fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
                urih = urih->next;
        }
        if (!uris)
@@ -453,17 +659,18 @@ int ast_http_reload(void)
 }
 
 static char show_http_help[] =
-"Usage: show http\n"
+"Usage: http show status\n"
 "       Shows status of internal HTTP engine\n";
 
 static struct ast_cli_entry http_cli[] = {
-       { { "show", "http", NULL }, handle_show_http,
-         "Display HTTP status", show_http_help },
+       { { "http", "show", "status", NULL }, handle_show_http,
+         "Display HTTP server status", show_http_help },
 };
 
 int ast_http_init(void)
 {
        ast_http_uri_link(&statusuri);
+       ast_http_uri_link(&staticuri);
        ast_cli_register_multiple(http_cli, sizeof(http_cli) / sizeof(http_cli[0]));
        return __ast_http_load(0);
 }