/*!
* \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 "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
#include <sys/types.h>
#include <stdio.h>
#include <unistd.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 "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;
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_DATA_DIR) + strlen("/static-http/") + 5;
+ if (len > 1024)
+ goto out403;
+
+ path = alloca(len);
+ sprintf(path, "%s/static-http/%s", ast_config_AST_DATA_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)
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");
.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;
}
}
-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;
struct ast_variable *vars=NULL, *v, *prev = NULL;
- if (*uri == '/')
- uri++;
params = strchr(uri, '?');
if (params) {
*params = '\0';
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)
}
}
}
+ if (prev)
+ prev->next = *cookies;
+ else
+ vars = *cookies;
+ *cookies = NULL;
ast_uri_decode(uri);
if (!strncasecmp(uri, prefix, prefix_len)) {
uri += prefix_len;
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;
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)
{
int fd;
struct sockaddr_in sin;
- int sinlen;
+ socklen_t sinlen;
struct ast_http_server_instance *ser;
pthread_t launched;
+ pthread_attr_t attr;
+
for (;;) {
ast_wait_for_input(httpfd, -1);
sinlen = sizeof(sin);
ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
continue;
}
- if (!(ser = ast_calloc(1, sizeof(*ser)))) {
- close(fd);
- continue;
- }
- ser->fd = fd;
- if ((ser->f = fdopen(ser->fd, "w+"))) {
- if (ast_pthread_create(&launched, NULL, ast_httpd_helper_thread, ser)) {
- ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
- fclose(ser->f);
+ 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+"))) {
+ 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);
+ }
+ } else {
+ ast_log(LOG_WARNING, "fdopen failed!\n");
+ close(ser->fd);
free(ser);
}
- } else {
- ast_log(LOG_WARNING, "fdopen failed!\n");
- 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];
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);
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")) {
} 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);
ast_copy_string(prefix, newprefix, sizeof(prefix));
prefix_len = strlen(prefix);
}
+ enablestatic = newenablestatic;
http_server_start(&sin);
return 0;
}
{
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);
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)
}
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);
}