Skip to content
Snippets Groups Projects
http.c 34.4 KiB
Newer Older
  • Learn to ignore specific revisions
  •  * Asterisk -- An open source telephony toolkit.
     *
     * Copyright (C) 1999 - 2006, Digium, Inc.
     *
     * Mark Spencer <markster@digium.com>
     *
     * See http://www.asterisk.org for more information about
     * the Asterisk project. Please do not directly contact
     * any of the maintainers of this project for assistance;
     * the project provides a web site, mailing lists and IRC
     * channels for your use.
     *
     * This program is free software, distributed under the terms of
     * the GNU General Public License Version 2. See the LICENSE file
     * at the top of the source tree.
     */
    
    
    Olle Johansson's avatar
    Olle Johansson committed
     * \brief http server for AMI access
    
    Olle Johansson's avatar
    Olle Johansson committed
     * \author Mark Spencer <markster@digium.com>
    
     *
     * This program implements a tiny http server
     * and was inspired by micro-httpd by Jef Poskanzer 
    
    Olle Johansson's avatar
    Olle Johansson committed
     * \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 <stdlib.h>
    #include <time.h>
    #include <string.h>
    #include <netinet/in.h>
    #include <sys/time.h>
    #include <sys/socket.h>
    
    #include <sys/signal.h>
    #include <arpa/inet.h>
    #include <errno.h>
    #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/version.h"
    
    #include "asterisk/manager.h"
    
    Olle Johansson's avatar
    Olle Johansson committed
    /* 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 */
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    #endif
    
    
    static struct tls_config http_tls_cfg;
    
    static void *httpd_helper_thread(void *arg);
    
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    /*!
     * 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,
    
    	.accept_fn = server_root,
    
    	.worker_fn = httpd_helper_thread,
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    };
    
    static struct server_args https_desc = {
    	.accept_fd = -1,
    	.master = AST_PTHREADT_NULL,
    
    	.accept_fn = server_root,
    
    	.worker_fn = httpd_helper_thread,
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    };
    
    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 AST_RWLIST_HEAD_STATIC(post_mappings, ast_http_post_mapping);
    
    
    /* all valid URIs must be prepended by the string in prefix. */
    
    static int enablestatic;
    
    Olle Johansson's avatar
    Olle Johansson committed
    /*! \brief Limit the kinds of files we're willing to serve up */
    
    	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;
    
    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) {
    		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 struct ast_str *static_callback(struct server_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
    
    	char *ftype;
    	const char *mtype;
    
    	time_t t;
    	char buf[256];
    
    	/* 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;
    
    	mtype = ftype2mtype(ftype, wkspace, sizeof(wkspace));
    
    	len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5;
    
    	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;
    
    	time(&t);
    	strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
    	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)
    
    	*title = ast_strdup("Not Found");
    
    	return ast_http_error(404, "Not Found", NULL, "Nothing to see here.  Move along.");
    
    out403:
    	*status = 403;
    
    	*title = ast_strdup("Access Denied");
    
    	return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
    }
    
    static struct ast_str *httpstatus_callback(struct server_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
    
    	struct ast_str *out = ast_str_create(512);
    
    	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_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",
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    			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",
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    			ntohs(http_desc.oldsin.sin_port));
    
    		ast_str_append(&out, 0, "<tr><td><i>SSL Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    			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) {
    
    			ast_str_append(&out, 0, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
    
    	ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
    	for (v = vars; v; 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_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 = {
    	.callback = httpstatus_callback,
    	.description = "Asterisk HTTP General Status",
    	.uri = "httpstatus",
    	.has_subtree = 0,
    };
    
    static struct ast_http_uri staticuri = {
    	.callback = static_callback,
    	.description = "Asterisk HTTP Static Delivery",
    	.uri = "static",
    	.has_subtree = 1,
    
    	.static_content = 1,
    
    struct ast_str *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
    
    	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"
    		"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
    		"<html><head>\r\n"
    		"<title>%d %s</title>\r\n"
    		"</head><body>\r\n"
    		"<h1>%s</h1>\r\n"
    		"<p>%s</p>\r\n"
    		"<hr />\r\n"
    		"<address>Asterisk Server</address>\r\n"
    		"</body></html>\r\n",
    			(extra_header ? extra_header : ""), status, title, title, text);
    
    Olle Johansson's avatar
    Olle Johansson committed
     * 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)
    {
    
    	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);
    
    	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); 
    
    	AST_RWLIST_INSERT_TAIL(&uris, urih, entry);
    
    	AST_RWLIST_UNLOCK(&uris);
    
    
    void ast_http_uri_unlink(struct ast_http_uri *urih)
    {
    
    	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;
    	}
    	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);
    
    			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;
    		}
    
    		/* 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 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)
    
    	struct ast_str *out = NULL;
    
    	struct ast_variable *vars=NULL, *v, *prev = NULL;
    
    	strsep(&params, "?");
    
    	/* Extract arguments from the request and store them in variables. */
    
    		while ((val = strsep(&params, "&"))) {
    			var = strsep(&val, "=");
    			if (val)
    
    				val = "";
    			ast_uri_decode(var);
    			if ((v = ast_variable_new(var, val))) {
    				if (vars)
    					prev->next = v;
    				else
    					vars = v;
    				prev = v;
    			}
    		}
    	}
    
    	/*
    	 * 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
    
    	if (prev)
    		prev->next = *cookies;
    	else
    		vars = *cookies;
    	*cookies = NULL;
    
    	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");
    
    	AST_RWLIST_UNLOCK(&uri_redirects);
    
    	/* 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;
    
    			AST_RWLIST_UNLOCK(&uris);
    
    		if (urih->static_content)
    			*static_content = 1;
    
    		out = urih->callback(ser, uri, vars, status, title, contentlength);
    
    		AST_RWLIST_UNLOCK(&uris);
    
    		out = ast_http_error(404, "Not Found", NULL,
    
    			"The requested URL was not found on this server.");
    
    		*title = ast_strdup("Not Found");
    
    	ast_variables_destroy(vars);
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    #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
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    /*!
     * 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)
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    {
    
    	int i = SSL_read(cookie, buf, len-1);
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    #if 0
    	if (i >= 0)
    		buf[i] = '\0';
    
    	ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    #endif
    	return i;
    }
    
    
    static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T len)
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    {
    #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);
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    #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;
    }
    
    /*!
     * 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)
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	struct server_instance *ser = data;
    
    	/*
    	 * open a FILE * as appropriate.
    	 */
    
    		ser->f = fdopen(ser->fd, "w+");
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    #ifdef DO_SSL
    
    	else if ( (ser->ssl = SSL_new(ser->parent->tls_cfg->ssl_ctx)) ) {
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    		SSL_set_fd(ser->ssl, ser->fd);
    
    		if (SSL_accept(ser->ssl) == 0)
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    			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 */
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    #endif
    
    		}
    		if (!ser->f)	/* no success opening descriptor stacking */
    			SSL_free(ser->ssl);
    	}
    #endif /* DO_SSL */
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    
    	if (!ser->f) {
    		close(ser->fd);
    
    		ast_log(LOG_WARNING, "FILE * open failed!\n");
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	}
    
    	return ser->parent->worker_fn(ser);
    }
    
    static void *httpd_helper_thread(void *data)
    {
    	char buf[4096];
    	char cookie[4096];
    	struct server_instance *ser = data;
    
    	struct ast_variable *var, *prev=NULL, *vars=NULL, *headers = NULL;
    
    	char *uri, *title=NULL;
    
    	int status = 200, contentlength = 0;
    
    	struct ast_str *out = NULL;
    
    	unsigned int static_content = 0;
    
    	if (!fgets(buf, sizeof(buf), ser->f))
    		goto done;
    
    	uri = ast_skip_nonblanks(buf);	/* Skip method */
    	if (*uri)
    		*uri++ = '\0';
    
    	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;
    
    			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;
    
    		/* 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);
    
    		/* 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;
    
    		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);
    
    
    		time_t t = time(NULL);
    		char timebuf[256];
    
    		strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
    
    		fprintf(ser->f, "HTTP/1.1 %d %s\r\n"
    
    				"Server: Asterisk/%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);
    
    			char *tmp = strstr(out->str, "\r\n\r\n");
    
    				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);
    
    void *server_root(void *data)
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	struct server_args *desc = data;
    
    	socklen_t sinlen;
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	struct server_instance *ser;
    
    		if (desc->periodic_fn)
    			desc->periodic_fn(desc);
    		i = ast_wait_for_input(desc->accept_fd, desc->poll_timeout);
    		if (i <= 0)
    			continue;
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    		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));
    			continue;
    		}
    
    		if (!ser) {
    			ast_log(LOG_WARNING, "No memory for new session: %s\n", strerror(errno));
    			close(fd);
    			continue;
    		}
    		flags = fcntl(fd, F_GETFL);
    		fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
    		ser->fd = fd;
    
    		memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
    			
    
    		if (ast_pthread_create_detached_background(&launched, NULL, make_file_from_fd, ser)) {
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    			ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
    
    int ssl_setup(struct tls_config *cfg)
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    {
    #ifndef DO_SSL
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	return 0;
    #else
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    		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);
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    			sleep(2);
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    			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);
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    			sleep(2);
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    			return 0;
    		}
    	}
    	ast_verbose("ssl cert ok");
    	return 1;
    #endif
    }
    
    /*!
     * 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)
    
    	/* Do nothing if nothing has changed */
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	if (!memcmp(&desc->oldsin, &desc->sin, sizeof(desc->oldsin))) {
    
    		ast_debug(1, "Nothing changed in %s\n", desc->name);
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	desc->oldsin = desc->sin;
    
    	/* Shutdown a running server if there is one */
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	if (desc->master != AST_PTHREADT_NULL) {
    		pthread_cancel(desc->master);
    		pthread_kill(desc->master, SIGURG);
    		pthread_join(desc->master, NULL);
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	if (desc->accept_fd != -1)
    		close(desc->accept_fd);
    
    
    	/* If there's no new server, stop here */
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	if (desc->sin.sin_family == 0)
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	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));
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	setsockopt(desc->accept_fd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
    	if (bind(desc->accept_fd, (struct sockaddr *)&desc->sin, sizeof(desc->sin))) {