asterisk/main/http.c

2683 lines
70 KiB
C
Raw Normal View History

/*
* 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.
*/
/*!
* \file
* \brief http server for AMI access
*
* \author Mark Spencer <markster@digium.com>
*
* This program implements a tiny http server
* and was inspired by micro-httpd by Jef Poskanzer
*
* GMime http://spruce.sourceforge.net/gmime/
*
* \ref AstHTTP - AMI over the http protocol
*/
/*! \li \ref http.c uses the configuration file \ref http.conf
* \addtogroup configuration_file
*/
/*! \page http.conf http.conf
* \verbinclude http.conf.sample
*/
/*** MODULEINFO
<support_level>core</support_level>
***/
#include "asterisk.h"
#include <time.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <signal.h>
#include <fcntl.h>
#include "asterisk/paths.h" /* use ast_config_AST_DATA_DIR */
#include "asterisk/cli.h"
#include "asterisk/tcptls.h"
#include "asterisk/http.h"
#include "asterisk/utils.h"
#include "asterisk/strings.h"
#include "asterisk/config.h"
#include "asterisk/stringfields.h"
#include "asterisk/ast_version.h"
#include "asterisk/manager.h"
#include "asterisk/module.h"
#include "asterisk/astobj2.h"
#include "asterisk/netsock2.h"
#include "asterisk/json.h"
#define MAX_PREFIX 80
#define DEFAULT_PORT 8088
#define DEFAULT_TLS_PORT 8089
#define DEFAULT_SESSION_LIMIT 100
/*! (ms) Idle time waiting for data. */
#define DEFAULT_SESSION_INACTIVITY 30000
/*! (ms) Min timeout for initial HTTP request to start coming in. */
#define MIN_INITIAL_REQUEST_TIMEOUT 10000
/*! (ms) Idle time between HTTP requests */
#define DEFAULT_SESSION_KEEP_ALIVE 15000
/*! Max size for the http server name */
#define MAX_SERVER_NAME_LENGTH 128
/*! Max size for the http response header */
#define DEFAULT_RESPONSE_HEADER_LENGTH 512
/*! Maximum application/json or application/x-www-form-urlencoded body content length. */
#if !defined(LOW_MEMORY)
#define MAX_CONTENT_LENGTH 40960
#else
#define MAX_CONTENT_LENGTH 1024
#endif /* !defined(LOW_MEMORY) */
/*! Initial response body length. */
#if !defined(LOW_MEMORY)
#define INITIAL_RESPONSE_BODY_BUFFER 1024
#else
#define INITIAL_RESPONSE_BODY_BUFFER 512
#endif /* !defined(LOW_MEMORY) */
/*! Maximum line length for HTTP requests. */
#if !defined(LOW_MEMORY)
#define MAX_HTTP_LINE_LENGTH 4096
#else
#define MAX_HTTP_LINE_LENGTH 1024
#endif /* !defined(LOW_MEMORY) */
static char http_server_name[MAX_SERVER_NAME_LENGTH];
static int session_limit = DEFAULT_SESSION_LIMIT;
AST-2014-007: Fix DOS by consuming the number of allowed HTTP connections. Simply establishing a TCP connection and never sending anything to the configured HTTP port in http.conf will tie up a HTTP connection. Since there is a maximum number of open HTTP sessions allowed at a time you can block legitimate connections. A similar problem exists if a HTTP request is started but never finished. * Added http.conf session_inactivity timer option to close HTTP connections that aren't doing anything. Defaults to 30000 ms. * Removed the undocumented manager.conf block-sockets option. It interferes with TCP/TLS inactivity timeouts. * AMI and SIP TLS connections now have better authentication timeout protection. Though I didn't remove the bizzare TLS timeout polling code from chan_sip. * chan_sip can now handle SSL certificate renegotiations in the middle of a session. It couldn't do that before because the socket was non-blocking and the SSL calls were not restarted as documented by the OpenSSL documentation. * Fixed an off nominal leak of the ssl struct in handle_tcptls_connection() if the FILE stream failed to open and the SSL certificate negotiations failed. The patch creates a custom FILE stream handler to give the created FILE streams inactivity timeout and timeout after a specific moment in time capability. This approach eliminates the need for code using the FILE stream to be redesigned to deal with the timeouts. This patch indirectly fixes most of ASTERISK-18345 by fixing the usage of the SSL_read/SSL_write operations. ASTERISK-23673 #close Reported by: Richard Mudgett ........ Merged revisions 415841 from http://svn.asterisk.org/svn/asterisk/branches/1.8 ........ Merged revisions 415854 from http://svn.asterisk.org/svn/asterisk/branches/11 ........ Merged revisions 415896 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@415907 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2014-06-12 17:00:08 +00:00
static int session_inactivity = DEFAULT_SESSION_INACTIVITY;
static int session_keep_alive = DEFAULT_SESSION_KEEP_ALIVE;
static int session_count = 0;
static struct ast_tls_config http_tls_cfg;
Implement https support. The changes are not large. Most of the diff comes from putting the global variables describing an accept session into a structure, so we can reuse the existing code for running multiple accept threads on different ports. Once this is done, and if your system has the funopen() library function (and ssl, of course), it is just a matter of calling the appropriate functions to set up the ssl connection on the existing socket, and everything works on the secure channel now. At the moment, the code is disabled because i have not implemented yet the autoconf code to detect the presence of funopen(), and add -lssl to main/Makefile if ssl libraries are present. And a bit of documentation on the http.conf arguments, too. If you want to manually enable https support, that is very simple (step 0 1 2 will be eventually detected by ./configure, the rest is something you will have to do anyways). 0. make sure your system has funopen(3). FreeBSD does, linux probably does too, not sure about other systems. 1. uncomment the following line in main/http.c // #define DO_SSL /* comment in/out if you want to support ssl */ 2. add -lssl to AST_LIBS in main/Makefile 3. add the following options to http.conf sslenable=yes sslbindport=4433 ; pick one you like sslcert=/tmp/foo.pem ; path to your certificate file. 4. generate a suitable certificate e.g. (example from mini_httpd's Makefile: openssl req -new -x509 -days 365 -nodes -out /tmp/foo.pem -keyout /tmp/foo.pem and here you go: https://localhost:4433/asterisk/manager now works. git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@45869 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2006-10-22 12:02:35 +00:00
static void *httpd_helper_thread(void *arg);
Implement https support. The changes are not large. Most of the diff comes from putting the global variables describing an accept session into a structure, so we can reuse the existing code for running multiple accept threads on different ports. Once this is done, and if your system has the funopen() library function (and ssl, of course), it is just a matter of calling the appropriate functions to set up the ssl connection on the existing socket, and everything works on the secure channel now. At the moment, the code is disabled because i have not implemented yet the autoconf code to detect the presence of funopen(), and add -lssl to main/Makefile if ssl libraries are present. And a bit of documentation on the http.conf arguments, too. If you want to manually enable https support, that is very simple (step 0 1 2 will be eventually detected by ./configure, the rest is something you will have to do anyways). 0. make sure your system has funopen(3). FreeBSD does, linux probably does too, not sure about other systems. 1. uncomment the following line in main/http.c // #define DO_SSL /* comment in/out if you want to support ssl */ 2. add -lssl to AST_LIBS in main/Makefile 3. add the following options to http.conf sslenable=yes sslbindport=4433 ; pick one you like sslcert=/tmp/foo.pem ; path to your certificate file. 4. generate a suitable certificate e.g. (example from mini_httpd's Makefile: openssl req -new -x509 -days 365 -nodes -out /tmp/foo.pem -keyout /tmp/foo.pem and here you go: https://localhost:4433/asterisk/manager now works. git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@45869 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2006-10-22 12:02:35 +00:00
/*!
* For standard configuration we have up to two accepting threads,
* one for http, one for https. If TEST_FRAMEWORK is enabled it's
* possible to have more than one running http server.
Implement https support. The changes are not large. Most of the diff comes from putting the global variables describing an accept session into a structure, so we can reuse the existing code for running multiple accept threads on different ports. Once this is done, and if your system has the funopen() library function (and ssl, of course), it is just a matter of calling the appropriate functions to set up the ssl connection on the existing socket, and everything works on the secure channel now. At the moment, the code is disabled because i have not implemented yet the autoconf code to detect the presence of funopen(), and add -lssl to main/Makefile if ssl libraries are present. And a bit of documentation on the http.conf arguments, too. If you want to manually enable https support, that is very simple (step 0 1 2 will be eventually detected by ./configure, the rest is something you will have to do anyways). 0. make sure your system has funopen(3). FreeBSD does, linux probably does too, not sure about other systems. 1. uncomment the following line in main/http.c // #define DO_SSL /* comment in/out if you want to support ssl */ 2. add -lssl to AST_LIBS in main/Makefile 3. add the following options to http.conf sslenable=yes sslbindport=4433 ; pick one you like sslcert=/tmp/foo.pem ; path to your certificate file. 4. generate a suitable certificate e.g. (example from mini_httpd's Makefile: openssl req -new -x509 -days 365 -nodes -out /tmp/foo.pem -keyout /tmp/foo.pem and here you go: https://localhost:4433/asterisk/manager now works. git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@45869 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2006-10-22 12:02:35 +00:00
*/
struct ast_http_server {
struct ast_tcptls_session_args args;
char *name;
char *address;
Implement https support. The changes are not large. Most of the diff comes from putting the global variables describing an accept session into a structure, so we can reuse the existing code for running multiple accept threads on different ports. Once this is done, and if your system has the funopen() library function (and ssl, of course), it is just a matter of calling the appropriate functions to set up the ssl connection on the existing socket, and everything works on the secure channel now. At the moment, the code is disabled because i have not implemented yet the autoconf code to detect the presence of funopen(), and add -lssl to main/Makefile if ssl libraries are present. And a bit of documentation on the http.conf arguments, too. If you want to manually enable https support, that is very simple (step 0 1 2 will be eventually detected by ./configure, the rest is something you will have to do anyways). 0. make sure your system has funopen(3). FreeBSD does, linux probably does too, not sure about other systems. 1. uncomment the following line in main/http.c // #define DO_SSL /* comment in/out if you want to support ssl */ 2. add -lssl to AST_LIBS in main/Makefile 3. add the following options to http.conf sslenable=yes sslbindport=4433 ; pick one you like sslcert=/tmp/foo.pem ; path to your certificate file. 4. generate a suitable certificate e.g. (example from mini_httpd's Makefile: openssl req -new -x509 -days 365 -nodes -out /tmp/foo.pem -keyout /tmp/foo.pem and here you go: https://localhost:4433/asterisk/manager now works. git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@45869 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2006-10-22 12:02:35 +00:00
};
/*!
* The default configured HTTP server
*/
struct ast_http_server *global_http_server = NULL;
static struct ast_tcptls_session_args https_desc = {
Implement https support. The changes are not large. Most of the diff comes from putting the global variables describing an accept session into a structure, so we can reuse the existing code for running multiple accept threads on different ports. Once this is done, and if your system has the funopen() library function (and ssl, of course), it is just a matter of calling the appropriate functions to set up the ssl connection on the existing socket, and everything works on the secure channel now. At the moment, the code is disabled because i have not implemented yet the autoconf code to detect the presence of funopen(), and add -lssl to main/Makefile if ssl libraries are present. And a bit of documentation on the http.conf arguments, too. If you want to manually enable https support, that is very simple (step 0 1 2 will be eventually detected by ./configure, the rest is something you will have to do anyways). 0. make sure your system has funopen(3). FreeBSD does, linux probably does too, not sure about other systems. 1. uncomment the following line in main/http.c // #define DO_SSL /* comment in/out if you want to support ssl */ 2. add -lssl to AST_LIBS in main/Makefile 3. add the following options to http.conf sslenable=yes sslbindport=4433 ; pick one you like sslcert=/tmp/foo.pem ; path to your certificate file. 4. generate a suitable certificate e.g. (example from mini_httpd's Makefile: openssl req -new -x509 -days 365 -nodes -out /tmp/foo.pem -keyout /tmp/foo.pem and here you go: https://localhost:4433/asterisk/manager now works. git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@45869 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2006-10-22 12:02:35 +00:00
.accept_fd = -1,
.master = AST_PTHREADT_NULL,
.tls_cfg = &http_tls_cfg,
.poll_timeout = -1,
.name = "https server",
.accept_fn = ast_tcptls_server_root,
.worker_fn = httpd_helper_thread,
Implement https support. The changes are not large. Most of the diff comes from putting the global variables describing an accept session into a structure, so we can reuse the existing code for running multiple accept threads on different ports. Once this is done, and if your system has the funopen() library function (and ssl, of course), it is just a matter of calling the appropriate functions to set up the ssl connection on the existing socket, and everything works on the secure channel now. At the moment, the code is disabled because i have not implemented yet the autoconf code to detect the presence of funopen(), and add -lssl to main/Makefile if ssl libraries are present. And a bit of documentation on the http.conf arguments, too. If you want to manually enable https support, that is very simple (step 0 1 2 will be eventually detected by ./configure, the rest is something you will have to do anyways). 0. make sure your system has funopen(3). FreeBSD does, linux probably does too, not sure about other systems. 1. uncomment the following line in main/http.c // #define DO_SSL /* comment in/out if you want to support ssl */ 2. add -lssl to AST_LIBS in main/Makefile 3. add the following options to http.conf sslenable=yes sslbindport=4433 ; pick one you like sslcert=/tmp/foo.pem ; path to your certificate file. 4. generate a suitable certificate e.g. (example from mini_httpd's Makefile: openssl req -new -x509 -days 365 -nodes -out /tmp/foo.pem -keyout /tmp/foo.pem and here you go: https://localhost:4433/asterisk/manager now works. git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@45869 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2006-10-22 12:02:35 +00:00
};
static AST_RWLIST_HEAD_STATIC(uris, ast_http_uri); /*!< list of supported handlers */
/* all valid URIs must be prepended by the string in prefix. */
static char prefix[MAX_PREFIX];
static int static_uri_enabled;
static int status_uri_enabled;
/*! \brief Limit the kinds of files we're willing to serve up */
static struct {
const char *ext;
const char *mtype;
} mimetypes[] = {
{ "png", "image/png" },
{ "xml", "text/xml" },
{ "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" },
{ "html", "text/html" },
{ "htm", "text/html" },
{ "css", "text/css" },
{ "cnf", "text/plain" },
{ "cfg", "text/plain" },
{ "bin", "application/octet-stream" },
{ "sbn", "application/octet-stream" },
{ "ld", "application/octet-stream" },
};
struct http_uri_redirect {
AST_LIST_ENTRY(http_uri_redirect) entry;
char *dest;
char target[0];
};
static AST_RWLIST_HEAD_STATIC(uri_redirects, http_uri_redirect);
static const struct ast_cfhttp_methods_text {
enum ast_http_method method;
const char *text;
} ast_http_methods_text[] = {
{ AST_HTTP_UNKNOWN, "UNKNOWN" },
{ AST_HTTP_GET, "GET" },
{ AST_HTTP_POST, "POST" },
{ AST_HTTP_HEAD, "HEAD" },
{ AST_HTTP_PUT, "PUT" },
{ AST_HTTP_DELETE, "DELETE" },
{ AST_HTTP_OPTIONS, "OPTIONS" },
};
const char *ast_get_http_method(enum ast_http_method method)
{
int x;
for (x = 0; x < ARRAY_LEN(ast_http_methods_text); x++) {
if (ast_http_methods_text[x].method == method) {
return ast_http_methods_text[x].text;
}
}
return NULL;
}
const char *ast_http_ftype2mtype(const char *ftype)
{
int x;
if (ftype) {
for (x = 0; x < ARRAY_LEN(mimetypes); x++) {
if (!strcasecmp(ftype, mimetypes[x].ext)) {
return mimetypes[x].mtype;
}
}
}
return NULL;
}
uint32_t ast_http_manid_from_vars(struct ast_variable *headers)
{
uint32_t mngid = 0;
struct ast_variable *v, *cookies;
cookies = ast_http_get_cookies(headers);
for (v = cookies; v; v = v->next) {
if (!strcasecmp(v->name, "mansession_id")) {
sscanf(v->value, "%30x", &mngid);
break;
}
}
ast_variables_destroy(cookies);
return mngid;
}
void ast_http_prefix(char *buf, int len)
{
if (buf) {
ast_copy_string(buf, prefix, len);
}
}
static int static_callback(struct ast_tcptls_session_instance *ser,
const struct ast_http_uri *urih, const char *uri,
enum ast_http_method method, struct ast_variable *get_vars,
struct ast_variable *headers)
{
char *path;
const char *ftype;
const char *mtype;
char wkspace[80];
struct stat st;
int len;
int fd;
struct ast_str *http_header;
struct timeval tv;
struct ast_tm tm;
char timebuf[80], etag[23];
struct ast_variable *v;
int not_modified = 0;
if (method != AST_HTTP_GET && method != AST_HTTP_HEAD) {
ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
return 0;
}
/* 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.
*
* XXX: It is not clear to me what this comment means or if it is any longer
* relevant. */
if (ast_strlen_zero(uri)) {
goto out403;
}
/* Disallow any funny filenames at all (checking first character only??) */
if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0])) {
goto out403;
}
if (strstr(uri, "/..")) {
goto out403;
}
if ((ftype = strrchr(uri, '.'))) {
ftype++;
}
if (!(mtype = ast_http_ftype2mtype(ftype))) {
snprintf(wkspace, sizeof(wkspace), "text/%s", S_OR(ftype, "plain"));
mtype = wkspace;
}
/* Cap maximum length */
if ((len = strlen(uri) + strlen(ast_config_AST_DATA_DIR) + strlen("/static-http/") + 5) > 1024) {
goto out403;
}
path = ast_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;
}
if (strstr(path, "/private/") && !astman_is_authed(ast_http_manid_from_vars(headers))) {
goto out403;
}
fd = open(path, O_RDONLY);
if (fd < 0) {
goto out403;
}
/* make "Etag:" http header value */
snprintf(etag, sizeof(etag), "\"%ld\"", (long)st.st_mtime);
/* make "Last-Modified:" http header value */
tv.tv_sec = st.st_mtime;
tv.tv_usec = 0;
ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", ast_localtime(&tv, &tm, "GMT"));
/* check received "If-None-Match" request header and Etag value for file */
for (v = headers; v; v = v->next) {
if (!strcasecmp(v->name, "If-None-Match")) {
if (!strcasecmp(v->value, etag)) {
not_modified = 1;
}
break;
}
}
http_header = ast_str_create(255);
if (!http_header) {
ast_http_request_close_on_completion(ser);
ast_http_error(ser, 500, "Server Error", "Out of memory");
close(fd);
return 0;
}
ast_str_set(&http_header, 0, "Content-type: %s\r\n"
"ETag: %s\r\n"
"Last-Modified: %s\r\n",
mtype,
etag,
timebuf);
/* ast_http_send() frees http_header, so we don't need to do it before returning */
if (not_modified) {
ast_http_send(ser, method, 304, "Not Modified", http_header, NULL, 0, 1);
} else {
ast_http_send(ser, method, 200, NULL, http_header, NULL, fd, 1); /* static content flag is set */
}
close(fd);
return 0;
out404:
ast_http_error(ser, 404, "Not Found", "The requested URL was not found on this server.");
return 0;
out403:
ast_http_request_close_on_completion(ser);
ast_http_error(ser, 403, "Access Denied", "You do not have permission to access the requested URL.");
return 0;
}
static int httpstatus_callback(struct ast_tcptls_session_instance *ser,
const struct ast_http_uri *urih, const char *uri,
enum ast_http_method method, struct ast_variable *get_vars,
struct ast_variable *headers)
{
struct ast_str *out;
struct ast_variable *v, *cookies = NULL;
if (method != AST_HTTP_GET && method != AST_HTTP_HEAD) {
ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
return 0;
}
out = ast_str_create(512);
if (!out) {
ast_http_request_close_on_completion(ser);
ast_http_error(ser, 500, "Server Error", "Out of memory");
return 0;
}
ast_str_append(&out, 0,
"<html><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>Server</i></td><td><b>%s</b></td></tr>\r\n", http_server_name);
ast_str_append(&out, 0, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
if (global_http_server) {
ast_str_append(&out, 0, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
ast_sockaddr_stringify(&global_http_server->args.old_address));
}
if (http_tls_cfg.enabled) {
ast_str_append(&out, 0, "<tr><td><i>TLS Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
ast_sockaddr_stringify(&https_desc.old_address));
}
ast_str_append(&out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
for (v = get_vars; v; v = v->next) {
ast_str_append(&out, 0, "<tr><td><i>Submitted GET 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");
cookies = ast_http_get_cookies(headers);
for (v = cookies; 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_variables_destroy(cookies);
ast_str_append(&out, 0, "</table><center><font size=\"-1\"><i>Asterisk and Digium are registered trademarks of Digium, Inc.</i></font></center></body></html>\r\n");
ast_http_send(ser, method, 200, NULL, NULL, out, 0, 0);
return 0;
}
static struct ast_http_uri status_uri = {
.callback = httpstatus_callback,
.description = "Asterisk HTTP General Status",
.uri = "httpstatus",
.has_subtree = 0,
.data = NULL,
.key = __FILE__,
};
static struct ast_http_uri static_uri = {
.callback = static_callback,
.description = "Asterisk HTTP Static Delivery",
.uri = "static",
.has_subtree = 1,
.data = NULL,
.key= __FILE__,
};
enum http_private_flags {
/*! TRUE if the HTTP request has a body. */
HTTP_FLAG_HAS_BODY = (1 << 0),
/*! TRUE if the HTTP request body has been read. */
HTTP_FLAG_BODY_READ = (1 << 1),
/*! TRUE if the HTTP request must close when completed. */
HTTP_FLAG_CLOSE_ON_COMPLETION = (1 << 2),
};
/*! HTTP tcptls worker_fn private data. */
struct http_worker_private_data {
/*! Body length or -1 if chunked. Valid if HTTP_FLAG_HAS_BODY is TRUE. */
int body_length;
/*! HTTP body tracking flags */
struct ast_flags flags;
};
void ast_http_send(struct ast_tcptls_session_instance *ser,
enum ast_http_method method, int status_code, const char *status_title,
struct ast_str *http_header, struct ast_str *out, int fd,
unsigned int static_content)
{
struct timeval now = ast_tvnow();
struct ast_tm tm;
char timebuf[80];
char buf[256];
int len;
int content_length = 0;
int close_connection;
struct ast_str *server_header_field = ast_str_create(MAX_SERVER_NAME_LENGTH);
int send_content;
if (!ser || !server_header_field) {
/* The connection is not open. */
ast_free(http_header);
ast_free(out);
ast_free(server_header_field);
return;
}
if(!ast_strlen_zero(http_server_name)) {
ast_str_set(&server_header_field,
0,
"Server: %s\r\n",
http_server_name);
}
/*
* We shouldn't be sending non-final status codes to this
* function because we may close the connection before
* returning.
*/
ast_assert(200 <= status_code);
if (session_keep_alive <= 0) {
close_connection = 1;
} else {
struct http_worker_private_data *request;
request = ser->private_data;
if (!request
|| ast_test_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION)
|| ast_http_body_discard(ser)) {
close_connection = 1;
} else {
close_connection = 0;
}
}
ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", ast_localtime(&now, &tm, "GMT"));
/* calc content length */
if (out) {
content_length += ast_str_strlen(out);
}
if (fd) {
content_length += lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
}
send_content = method != AST_HTTP_HEAD || status_code >= 400;
/* send http header */
if (ast_iostream_printf(ser->stream,
"HTTP/1.1 %d %s\r\n"
"%s"
"Date: %s\r\n"
"%s"
"%s"
"%s"
"Content-Length: %d\r\n"
"\r\n"
"%s",
status_code, status_title ? status_title : "OK",
ast_str_buffer(server_header_field),
timebuf,
close_connection ? "Connection: close\r\n" : "",
static_content ? "" : "Cache-Control: no-cache, no-store\r\n",
http_header ? ast_str_buffer(http_header) : "",
content_length,
send_content && out && ast_str_strlen(out) ? ast_str_buffer(out) : ""
) <= 0) {
ast_debug(1, "ast_iostream_printf() failed: %s\n", strerror(errno));
close_connection = 1;
} else if (send_content && fd) {
/* send file content */
while ((len = read(fd, buf, sizeof(buf))) > 0) {
if (ast_iostream_write(ser->stream, buf, len) != len) {
ast_debug(1, "ast_iostream_write() failed: %s\n", strerror(errno));
close_connection = 1;
break;
}
}
}
ast_free(http_header);
ast_free(out);
ast_free(server_header_field);
if (close_connection) {
ast_debug(1, "HTTP closing session. status_code:%d\n", status_code);
ast_tcptls_close_session_file(ser);
} else {
ast_debug(1, "HTTP keeping session open. status_code:%d\n", status_code);
}
}
void ast_http_create_response(struct ast_tcptls_session_instance *ser, int status_code,
const char *status_title, struct ast_str *http_header_data, const char *text)
{
char server_name[MAX_SERVER_NAME_LENGTH];
struct ast_str *server_address = ast_str_create(MAX_SERVER_NAME_LENGTH);
struct ast_str *out = ast_str_create(INITIAL_RESPONSE_BODY_BUFFER);
if (!http_header_data || !server_address || !out) {
ast_free(http_header_data);
ast_free(server_address);
ast_free(out);
if (ser) {
ast_debug(1, "HTTP closing session. OOM.\n");
ast_tcptls_close_session_file(ser);
}
return;
}
if(!ast_strlen_zero(http_server_name)) {
ast_xml_escape(http_server_name, server_name, sizeof(server_name));
ast_str_set(&server_address,
0,
"<address>%s</address>\r\n",
server_name);
}
ast_str_set(&out,
0,
"<!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"
"%s"
"</body></html>\r\n",
status_code,
status_title,
status_title,
text ? text : "",
ast_str_buffer(server_address));
ast_free(server_address);
ast_http_send(ser,
AST_HTTP_UNKNOWN,
status_code,
status_title,
http_header_data,
out,
0,
0);
}
void ast_http_auth(struct ast_tcptls_session_instance *ser, const char *realm,
const unsigned long nonce, const unsigned long opaque, int stale,
const char *text)
{
int status_code = 401;
char *status_title = "Unauthorized";
struct ast_str *http_header_data = ast_str_create(DEFAULT_RESPONSE_HEADER_LENGTH);
if (http_header_data) {
ast_str_set(&http_header_data,
0,
"WWW-authenticate: Digest algorithm=MD5, realm=\"%s\", nonce=\"%08lx\", qop=\"auth\", opaque=\"%08lx\"%s\r\n"
"Content-type: text/html\r\n",
realm ? realm : "Asterisk",
nonce,
opaque,
stale ? ", stale=true" : "");
}
ast_http_create_response(ser,
status_code,
status_title,
http_header_data,
text);
}
void ast_http_error(struct ast_tcptls_session_instance *ser, int status_code,
const char *status_title, const char *text)
{
struct ast_str *http_header_data = ast_str_create(DEFAULT_RESPONSE_HEADER_LENGTH);
if (http_header_data) {
ast_str_set(&http_header_data, 0, "Content-type: text/html\r\n");
}
ast_http_create_response(ser,
status_code,
status_title,
http_header_data,
text);
}
/*!
* \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 *uri;
int len = strlen(urih->uri);
AST_RWLIST_WRLOCK(&uris);
urih->prefix = prefix;
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)
{
AST_RWLIST_WRLOCK(&uris);
AST_RWLIST_REMOVE(&uris, urih, entry);
AST_RWLIST_UNLOCK(&uris);
}
void ast_http_uri_unlink_all_with_key(const char *key)
{
struct ast_http_uri *urih;
AST_RWLIST_WRLOCK(&uris);
AST_RWLIST_TRAVERSE_SAFE_BEGIN(&uris, urih, entry) {
if (!strcmp(urih->key, key)) {
AST_RWLIST_REMOVE_CURRENT(entry);
if (urih->dmallocd) {
ast_free(urih->data);
}
if (urih->mallocd) {
ast_free(urih);
}
}
}
AST_RWLIST_TRAVERSE_SAFE_END;
AST_RWLIST_UNLOCK(&uris);
}
/*!
* \brief Retrieves the header with the given field name.
*
* \param headers Headers to search.
* \param field_name Name of the header to find.
* \return Associated header value.
* \retval NULL if header is not present.
*/
static const char *get_header(struct ast_variable *headers, const char *field_name)
{
struct ast_variable *v;
for (v = headers; v; v = v->next) {
if (!strcasecmp(v->name, field_name)) {
return v->value;
}
}
return NULL;
}
/*!
* \brief Retrieves the content type specified in the "Content-Type" header.
*
* This function only returns the "type/subtype" and any trailing parameter is
* not included.
*
* \note the return value is an allocated string that needs to be freed.
*
* \return the content type/subtype
* \retval NULL if the header is not found.
*/
static char *get_content_type(struct ast_variable *headers)
{
const char *content_type = get_header(headers, "Content-Type");
const char *param;
size_t size;
if (!content_type) {
return NULL;
}
param = strchr(content_type, ';');
size = param ? param - content_type : strlen(content_type);
return ast_strndup(content_type, size);
}
/*!
* \brief Returns the value of the Content-Length header.
*
* \param headers HTTP headers.
*
* \return length Value of the Content-Length header.
* \retval 0 if header is not present.
* \retval -1 if header is invalid.
*/
static int get_content_length(struct ast_variable *headers)
{
const char *content_length = get_header(headers, "Content-Length");
int length;
if (!content_length) {
/* Missing content length; assume zero */
return 0;
}
length = 0;
if (sscanf(content_length, "%30d", &length) != 1) {
/* Invalid Content-Length value */
length = -1;
}
return length;
}
/*!
* \brief Returns the value of the Transfer-Encoding header.
*
* \param headers HTTP headers.
* \return string Value of the Transfer-Encoding header.
* \retval NULL if header is not present.
*/
static const char *get_transfer_encoding(struct ast_variable *headers)
{
return get_header(headers, "Transfer-Encoding");
}
/*!
* \internal
* \brief Determine if the HTTP peer wants the connection closed.
*
* \param headers List of HTTP headers
*
* \retval 0 keep connection open.
* \retval -1 close connection.
*/
static int http_check_connection_close(struct ast_variable *headers)
{
const char *connection = get_header(headers, "Connection");
int close_connection = 0;
if (connection && !strcasecmp(connection, "close")) {
close_connection = -1;
}
return close_connection;
}
void ast_http_request_close_on_completion(struct ast_tcptls_session_instance *ser)
{
struct http_worker_private_data *request = ser->private_data;
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
}
/*!
* \internal
* \brief Initialize the request tracking information in case of early failure.
* \since 12.4.0
*
* \param request Request tracking information.
*/
static void http_request_tracking_init(struct http_worker_private_data *request)
{
ast_set_flags_to(&request->flags,
HTTP_FLAG_HAS_BODY | HTTP_FLAG_BODY_READ | HTTP_FLAG_CLOSE_ON_COMPLETION,
/* Assume close in case request fails early */
HTTP_FLAG_CLOSE_ON_COMPLETION);
}
/*!
* \internal
* \brief Setup the HTTP request tracking information.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
* \param headers List of HTTP headers.
*
* \retval 0 on success.
* \retval -1 on error.
*/
static int http_request_tracking_setup(struct ast_tcptls_session_instance *ser, struct ast_variable *headers)
{
struct http_worker_private_data *request = ser->private_data;
const char *transfer_encoding;
ast_set_flags_to(&request->flags,
HTTP_FLAG_HAS_BODY | HTTP_FLAG_BODY_READ | HTTP_FLAG_CLOSE_ON_COMPLETION,
http_check_connection_close(headers) ? HTTP_FLAG_CLOSE_ON_COMPLETION : 0);
transfer_encoding = get_transfer_encoding(headers);
if (transfer_encoding && !strcasecmp(transfer_encoding, "chunked")) {
request->body_length = -1;
ast_set_flag(&request->flags, HTTP_FLAG_HAS_BODY);
return 0;
}
request->body_length = get_content_length(headers);
if (0 < request->body_length) {
ast_set_flag(&request->flags, HTTP_FLAG_HAS_BODY);
} else if (request->body_length < 0) {
/* Invalid Content-Length */
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
ast_http_error(ser, 400, "Bad Request", "Invalid Content-Length in request!");
return -1;
}
return 0;
}
void ast_http_body_read_status(struct ast_tcptls_session_instance *ser, int read_success)
{
struct http_worker_private_data *request;
request = ser->private_data;
if (!ast_test_flag(&request->flags, HTTP_FLAG_HAS_BODY)
|| ast_test_flag(&request->flags, HTTP_FLAG_BODY_READ)) {
/* No body to read. */
return;
}
ast_set_flag(&request->flags, HTTP_FLAG_BODY_READ);
if (!read_success) {
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
}
}
/*!
* \internal
* \brief Read the next length bytes from the HTTP body.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
* \param buf Where to put the contents reading.
* \param length How much contents to read.
* \param what_getting Name of the contents reading.
*
* \retval 0 on success.
* \retval -1 on error.
*/
static int http_body_read_contents(struct ast_tcptls_session_instance *ser, char *buf, int length, const char *what_getting)
{
int res;
int total = 0;
/* Stream is in exclusive mode so we get it all if possible. */
while (total != length) {
res = ast_iostream_read(ser->stream, buf + total, length - total);
if (res <= 0) {
break;
}
total += res;
}
if (total != length) {
ast_log(LOG_WARNING, "Wrong HTTP content read. Request %s (Wanted %d, Read %d)\n",
what_getting, length, res);
return -1;
}
return 0;
}
/*!
* \internal
* \brief Read and discard the next length bytes from the HTTP body.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
* \param length How much contents to discard
* \param what_getting Name of the contents discarding.
*
* \retval 0 on success.
* \retval -1 on error.
*/
static int http_body_discard_contents(struct ast_tcptls_session_instance *ser, int length, const char *what_getting)
{
ssize_t res;
res = ast_iostream_discard(ser->stream, length);
if (res < length) {
ast_log(LOG_WARNING, "Short HTTP request %s (Wanted %d but got %zd)\n",
what_getting, length, res);
return -1;
}
return 0;
}
/*!
* \internal
* \brief decode chunked mode hexadecimal value
*
* \param s string to decode
* \param len length of string
*
* \return length on success.
* \retval -1 on error.
*/
static int chunked_atoh(const char *s, int len)
{
int value = 0;
char c;
if (*s < '0') {
/* zero value must be 0\n not just \n */
return -1;
}
while (len--) {
c = *s++;
if (c == '\x0D') {
return value;
}
if (c == ';') {
/* We have a chunk-extension that we don't care about. */
while (len--) {
if (*s++ == '\x0D') {
return value;
}
}
break;
}
value <<= 4;
if (c >= '0' && c <= '9') {
value += c - '0';
continue;
}
if (c >= 'a' && c <= 'f') {
value += 10 + c - 'a';
continue;
}
if (c >= 'A' && c <= 'F') {
value += 10 + c - 'A';
continue;
}
/* invalid character */
return -1;
}
/* end of string */
return -1;
}
/*!
* \internal
* \brief Read and convert the chunked body header length.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
*
* \return length Size of chunk to expect.
* \retval -1 on error.
*/
static int http_body_get_chunk_length(struct ast_tcptls_session_instance *ser)
{
int length;
char header_line[MAX_HTTP_LINE_LENGTH];
/* get the line of hexadecimal giving chunk-size w/ optional chunk-extension */
if (ast_iostream_gets(ser->stream, header_line, sizeof(header_line)) <= 0) {
ast_log(LOG_WARNING, "Short HTTP read of chunked header\n");
return -1;
}
length = chunked_atoh(header_line, strlen(header_line));
if (length < 0) {
ast_log(LOG_WARNING, "Invalid HTTP chunk size\n");
return -1;
}
return length;
}
/*!
* \internal
* \brief Read and check the chunk contents line termination.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
*
* \retval 0 on success.
* \retval -1 on error.
*/
static int http_body_check_chunk_sync(struct ast_tcptls_session_instance *ser)
{
int res;
char chunk_sync[2];
/* Stay in fread until get the expected CRLF or timeout. */
res = ast_iostream_read(ser->stream, chunk_sync, sizeof(chunk_sync));
if (res < sizeof(chunk_sync)) {
ast_log(LOG_WARNING, "Short HTTP chunk sync read (Wanted %zu)\n",
sizeof(chunk_sync));
return -1;
}
if (chunk_sync[0] != 0x0D || chunk_sync[1] != 0x0A) {
ast_log(LOG_WARNING, "HTTP chunk sync bytes wrong (0x%02hhX, 0x%02hhX)\n",
(unsigned char) chunk_sync[0], (unsigned char) chunk_sync[1]);
return -1;
}
return 0;
}
/*!
* \internal
* \brief Read and discard any chunked trailer entity-header lines.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
*
* \retval 0 on success.
* \retval -1 on error.
*/
static int http_body_discard_chunk_trailer_headers(struct ast_tcptls_session_instance *ser)
{
char header_line[MAX_HTTP_LINE_LENGTH];
for (;;) {
if (ast_iostream_gets(ser->stream, header_line, sizeof(header_line)) <= 0) {
ast_log(LOG_WARNING, "Short HTTP read of chunked trailer header\n");
return -1;
}
/* Trim trailing whitespace */
ast_trim_blanks(header_line);
if (ast_strlen_zero(header_line)) {
/* A blank line ends the chunked-body */
break;
}
}
return 0;
}
int ast_http_body_discard(struct ast_tcptls_session_instance *ser)
{
struct http_worker_private_data *request;
request = ser->private_data;
if (!ast_test_flag(&request->flags, HTTP_FLAG_HAS_BODY)
|| ast_test_flag(&request->flags, HTTP_FLAG_BODY_READ)) {
/* No body to read or it has already been read. */
return 0;
}
ast_set_flag(&request->flags, HTTP_FLAG_BODY_READ);
ast_debug(1, "HTTP discarding unused request body\n");
ast_assert(request->body_length != 0);
if (0 < request->body_length) {
if (http_body_discard_contents(ser, request->body_length, "body")) {
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
return -1;
}
return 0;
}
/* parse chunked-body */
for (;;) {
int length;
length = http_body_get_chunk_length(ser);
if (length < 0) {
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
return -1;
}
if (length == 0) {
/* parsed last-chunk */
break;
}
if (http_body_discard_contents(ser, length, "chunk-data")
|| http_body_check_chunk_sync(ser)) {
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
return -1;
}
}
/* Read and discard any trailer entity-header lines. */
if (http_body_discard_chunk_trailer_headers(ser)) {
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
return -1;
}
return 0;
}
/*!
* \brief Returns the contents (body) of the HTTP request
*
* \param return_length ptr to int that returns content length
* \param ser HTTP TCP/TLS session object
* \param headers List of HTTP headers
* \return ptr to content (zero terminated)
* \retval NULL on failure
* \note Since returned ptr is malloc'd, it should be free'd by caller
*/
static char *ast_http_get_contents(int *return_length,
struct ast_tcptls_session_instance *ser, struct ast_variable *headers)
{
struct http_worker_private_data *request;
int content_length;
int bufsize;
char *buf;
request = ser->private_data;
if (!ast_test_flag(&request->flags, HTTP_FLAG_HAS_BODY)) {
/* no content - not an error */
return NULL;
}
if (ast_test_flag(&request->flags, HTTP_FLAG_BODY_READ)) {
/* Already read the body. Cannot read again. Assume no content. */
ast_assert(0);
return NULL;
}
ast_set_flag(&request->flags, HTTP_FLAG_BODY_READ);
ast_debug(2, "HTTP consuming request body\n");
ast_assert(request->body_length != 0);
if (0 < request->body_length) {
/* handle regular non-chunked content */
content_length = request->body_length;
if (content_length > MAX_CONTENT_LENGTH) {
ast_log(LOG_WARNING, "Excessively long HTTP content. (%d > %d)\n",
content_length, MAX_CONTENT_LENGTH);
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
errno = EFBIG;
return NULL;
}
buf = ast_malloc(content_length + 1);
if (!buf) {
/* Malloc sets ENOMEM */
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
return NULL;
}
if (http_body_read_contents(ser, buf, content_length, "body")) {
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
errno = EIO;
ast_free(buf);
return NULL;
}
buf[content_length] = 0;
*return_length = content_length;
return buf;
}
/* pre-allocate buffer */
bufsize = 250;
buf = ast_malloc(bufsize);
if (!buf) {
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
return NULL;
}
/* parse chunked-body */
content_length = 0;
for (;;) {
int chunk_length;
chunk_length = http_body_get_chunk_length(ser);
if (chunk_length < 0) {
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
errno = EIO;
ast_free(buf);
return NULL;
}
if (chunk_length == 0) {
/* parsed last-chunk */
break;
}
if (content_length + chunk_length > MAX_CONTENT_LENGTH) {
ast_log(LOG_WARNING,
"Excessively long HTTP accumulated chunked body. (%d + %d > %d)\n",
content_length, chunk_length, MAX_CONTENT_LENGTH);
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
errno = EFBIG;
ast_free(buf);
return NULL;
}
/* insure buffer is large enough +1 */
if (content_length + chunk_length >= bufsize) {
char *new_buf;
/* Increase bufsize until it can handle the expected data. */
do {
bufsize *= 2;
} while (content_length + chunk_length >= bufsize);
new_buf = ast_realloc(buf, bufsize);
if (!new_buf) {
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
ast_free(buf);
return NULL;
}
buf = new_buf;
}
if (http_body_read_contents(ser, buf + content_length, chunk_length, "chunk-data")
|| http_body_check_chunk_sync(ser)) {
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
errno = EIO;
ast_free(buf);
return NULL;
}
content_length += chunk_length;
}
/*
* Read and discard any trailer entity-header lines
* which we don't care about.
*
* XXX In the future we may need to add the trailer headers
* to the passed in headers list rather than discarding them.
*/
if (http_body_discard_chunk_trailer_headers(ser)) {
ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
errno = EIO;
ast_free(buf);
return NULL;
}
buf[content_length] = 0;
*return_length = content_length;
return buf;
}
struct ast_json *ast_http_get_json(
struct ast_tcptls_session_instance *ser, struct ast_variable *headers)
{
int content_length = 0;
struct ast_json *body;
RAII_VAR(char *, buf, NULL, ast_free);
RAII_VAR(char *, type, get_content_type(headers), ast_free);
/* Use errno to distinguish errors from no body */
errno = 0;
if (ast_strlen_zero(type) || strcasecmp(type, "application/json")) {
/* Content type is not JSON. Don't read the body. */
return NULL;
}
buf = ast_http_get_contents(&content_length, ser, headers);
if (!buf || !content_length) {
/*
* errno already set
* or it is not an error to have zero content
*/
return NULL;
}
body = ast_json_load_buf(buf, content_length, NULL);
if (!body) {
/* Failed to parse JSON; treat as an I/O error */
errno = EIO;
return NULL;
}
return body;
}
/*
* get post variables from client Request Entity-Body, if content type is
* application/x-www-form-urlencoded
*/
struct ast_variable *ast_http_get_post_vars(
struct ast_tcptls_session_instance *ser, struct ast_variable *headers)
{
int content_length = 0;
struct ast_variable *v, *post_vars=NULL, *prev = NULL;
char *var, *val;
RAII_VAR(char *, buf, NULL, ast_free);
RAII_VAR(char *, type, get_content_type(headers), ast_free);
/* Use errno to distinguish errors from no params */
errno = 0;
if (ast_strlen_zero(type) ||
strcasecmp(type, "application/x-www-form-urlencoded")) {
/* Content type is not form data. Don't read the body. */
return NULL;
}
buf = ast_http_get_contents(&content_length, ser, headers);
if (!buf || !content_length) {
/*
* errno already set
* or it is not an error to have zero content
*/
return NULL;
Resolve crashes due to large stack allocations when using TCP Asterisk had several places where messages received over various network transports may be copied in a single stack allocation. In the case of TCP, since multiple packets in a stream may be concatenated together, this can lead to large allocations that overflow the stack. This patch modifies those portions of Asterisk using TCP to either favor heap allocations or use an upper bound to ensure that the stack will not overflow: * For SIP, the allocation now has an upper limit * For HTTP, the allocation is now a heap allocation instead of a stack allocation * For XMPP (in res_jabber), the allocation has been eliminated since it was unnecesary. Note that the HTTP portion of this issue was independently found by Brandon Edwards of Exodus Intelligence. (issue ASTERISK-20658) Reported by: wdoekes, Brandon Edwards Tested by: mmichelson, wdoekes patches: ASTERISK-20658_res_jabber.c.patch uploaded by mmichelson (license 5049) issueA20658_http_postvars_use_malloc2.patch uploaded by wdoekes (license 5674) issueA20658_limit_sip_packet_size3.patch uploaded by wdoekes (license 5674) ........ Merged revisions 378269 from http://svn.asterisk.org/svn/asterisk/branches/1.8 ........ Merged revisions 378286 from http://svn.asterisk.org/svn/asterisk/branches/10 ........ Merged revisions 378287 from http://svn.asterisk.org/svn/asterisk/branches/11 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@378288 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2013-01-02 15:39:42 +00:00
}
while ((val = strsep(&buf, "&"))) {
var = strsep(&val, "=");
if (val) {
ast_uri_decode(val, ast_uri_http_legacy);
} else {
val = "";
}
ast_uri_decode(var, ast_uri_http_legacy);
if ((v = ast_variable_new(var, val, ""))) {
if (post_vars) {
prev->next = v;
} else {
post_vars = v;
}
prev = v;
}
}
return post_vars;
}
static int handle_uri(struct ast_tcptls_session_instance *ser, char *uri,
enum ast_http_method method, struct ast_variable *headers)
{
char *c;
int res = 0;
char *params = uri;
struct ast_http_uri *urih = NULL;
int l;
struct ast_variable *get_vars = NULL, *v, *prev = NULL;
struct http_uri_redirect *redirect;
ast_debug(2, "HTTP Request URI is %s \n", uri);
strsep(&params, "?");
/* Extract arguments from the request and store them in variables. */
if (params) {
char *var, *val;
while ((val = strsep(&params, "&"))) {
var = strsep(&val, "=");
if (val) {
ast_uri_decode(val, ast_uri_http_legacy);
} else {
val = "";
}
ast_uri_decode(var, ast_uri_http_legacy);
if ((v = ast_variable_new(var, val, ""))) {
if (get_vars) {
prev->next = v;
} else {
get_vars = v;
}
prev = v;
}
}
}
AST_RWLIST_RDLOCK(&uri_redirects);
AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
if (!strcasecmp(uri, redirect->target)) {
struct ast_str *http_header = ast_str_create(128);
if (!http_header) {
ast_http_request_close_on_completion(ser);
ast_http_error(ser, 500, "Server Error", "Out of memory");
break;
}
ast_str_set(&http_header, 0, "Location: %s\r\n", redirect->dest);
ast_http_send(ser, method, 302, "Moved Temporarily", http_header, NULL, 0, 0);
break;
}
}
AST_RWLIST_UNLOCK(&uri_redirects);
if (redirect) {
goto cleanup;
}
/* We want requests to start with the (optional) prefix and '/' */
l = strlen(prefix);
if (!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 */
ast_debug(2, "match request [%s] with handler [%s] len %d\n", uri, urih->uri, l);
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) {
ast_debug(1, "Match made with [%s]\n", urih->uri);
if (!urih->no_decode_uri) {
ast_uri_decode(uri, ast_uri_http_legacy);
}
res = urih->callback(ser, urih, uri, method, get_vars, headers);
} else {
ast_debug(1, "Requested URI [%s] has no handler\n", uri);
ast_http_error(ser, 404, "Not Found", "The requested URL was not found on this server.");
}
cleanup:
ast_variables_destroy(get_vars);
return res;
}
static struct ast_variable *parse_cookies(const char *cookies)
{
char *parse = ast_strdupa(cookies);
char *cur;
struct ast_variable *vars = NULL, *var;
while ((cur = strsep(&parse, ";"))) {
char *name, *val;
name = val = cur;
strsep(&val, "=");
if (ast_strlen_zero(name) || ast_strlen_zero(val)) {
continue;
}
name = ast_strip(name);
val = ast_strip_quoted(val, "\"", "\"");
if (ast_strlen_zero(name) || ast_strlen_zero(val)) {
continue;
}
ast_debug(1, "HTTP Cookie, Name: '%s' Value: '%s'\n", name, val);
var = ast_variable_new(name, val, __FILE__);
var->next = vars;
vars = var;
}
return vars;
}
/* get cookie from Request headers */
struct ast_variable *ast_http_get_cookies(struct ast_variable *headers)
{
struct ast_variable *v, *cookies = NULL;
for (v = headers; v; v = v->next) {
if (!strcasecmp(v->name, "Cookie")) {
ast_variables_destroy(cookies);
cookies = parse_cookies(v->value);
}
}
return cookies;
}
static struct ast_http_auth *auth_create(const char *userid, const char *password)
{
struct ast_http_auth *auth;
size_t userid_len;
size_t password_len;
if (!userid || !password) {
ast_log(LOG_ERROR, "Invalid userid/password\n");
return NULL;
}
userid_len = strlen(userid) + 1;
password_len = strlen(password) + 1;
/* Allocate enough room to store everything in one memory block */
auth = ao2_alloc(sizeof(*auth) + userid_len + password_len, NULL);
if (!auth) {
return NULL;
}
/* Put the userid right after the struct */
auth->userid = (char *)(auth + 1);
strcpy(auth->userid, userid);
/* Put the password right after the userid */
auth->password = auth->userid + userid_len;
strcpy(auth->password, password);
return auth;
}
#define BASIC_PREFIX "Basic "
#define BASIC_LEN 6 /*!< strlen(BASIC_PREFIX) */
struct ast_http_auth *ast_http_get_auth(struct ast_variable *headers)
{
struct ast_variable *v;
for (v = headers; v; v = v->next) {
const char *base64;
char decoded[256] = {};
char *username;
char *password;
#ifdef AST_DEVMODE
int cnt;
#endif /* AST_DEVMODE */
if (strcasecmp("Authorization", v->name) != 0) {
continue;
}
if (!ast_begins_with(v->value, BASIC_PREFIX)) {
ast_log(LOG_DEBUG,
"Unsupported Authorization scheme\n");
continue;
}
/* Basic auth header parsing. RFC 2617, section 2.
* credentials = "Basic" basic-credentials
* basic-credentials = base64-user-pass
* base64-user-pass = <base64 encoding of user-pass,
* except not limited to 76 char/line>
* user-pass = userid ":" password
*/
base64 = v->value + BASIC_LEN;
/* This will truncate "userid:password" lines to
* sizeof(decoded). The array is long enough that this shouldn't
* be a problem */
#ifdef AST_DEVMODE
cnt =
#endif /* AST_DEVMODE */
ast_base64decode((unsigned char*)decoded, base64,
sizeof(decoded) - 1);
ast_assert(cnt < sizeof(decoded));
/* Split the string at the colon */
password = decoded;
username = strsep(&password, ":");
if (!password) {
ast_log(LOG_WARNING, "Invalid Authorization header\n");
return NULL;
}
return auth_create(username, password);
}
return NULL;
}
int ast_http_response_status_line(const char *buf, const char *version, int code)
{
int status_code;
size_t size = strlen(version);
if (strncmp(buf, version, size) || buf[size] != ' ') {
ast_log(LOG_ERROR, "HTTP version not supported - "
"expected %s\n", version);
return -1;
}
/* skip to status code (version + space) */
buf += size + 1;
if (sscanf(buf, "%d", &status_code) != 1) {
ast_log(LOG_ERROR, "Could not read HTTP status code - "
"%s\n", buf);
return -1;
}
return status_code;
}
static void remove_excess_lws(char *s)
{
char *p, *res = s;
char *buf = ast_malloc(strlen(s) + 1);
char *buf_end;
if (!buf) {
return;
}
buf_end = buf;
while (*s && *(s = ast_skip_blanks(s))) {
p = s;
s = ast_skip_nonblanks(s);
if (buf_end != buf) {
*buf_end++ = ' ';
}
memcpy(buf_end, p, s - p);
buf_end += s - p;
}
*buf_end = '\0';
/* safe since buf will always be less than or equal to res */
strcpy(res, buf);
ast_free(buf);
}
int ast_http_header_parse(char *buf, char **name, char **value)
{
ast_trim_blanks(buf);
if (ast_strlen_zero(buf)) {
return -1;
}
*value = buf;
*name = strsep(value, ":");
if (!*value) {
return 1;
}
*value = ast_skip_blanks(*value);
if (ast_strlen_zero(*value) || ast_strlen_zero(*name)) {
return 1;
}
remove_excess_lws(*value);
return 0;
}
int ast_http_header_match(const char *name, const char *expected_name,
const char *value, const char *expected_value)
{
if (strcasecmp(name, expected_name)) {
/* no value to validate if names don't match */
return 0;
}
if (strcasecmp(value, expected_value)) {
ast_log(LOG_ERROR, "Invalid header value - expected %s "
"received %s", value, expected_value);
return -1;
}
return 1;
}
int ast_http_header_match_in(const char *name, const char *expected_name,
const char *value, const char *expected_value)
{
if (strcasecmp(name, expected_name)) {
/* no value to validate if names don't match */
return 0;
}
if (!strcasestr(expected_value, value)) {
ast_log(LOG_ERROR, "Header '%s' - could not locate '%s' "
"in '%s'\n", name, value, expected_value);
return -1;
}
return 1;
}
/*! Limit the number of request headers in case the sender is being ridiculous. */
#define MAX_HTTP_REQUEST_HEADERS 100
/*!
* \internal
* \brief Read the request headers.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
* \param headers Where to put the request headers list pointer.
*
* \retval 0 on success.
* \retval -1 on error.
*/
static int http_request_headers_get(struct ast_tcptls_session_instance *ser, struct ast_variable **headers)
{
struct ast_variable *tail = *headers;
int remaining_headers;
char header_line[MAX_HTTP_LINE_LENGTH];
AST-2014-007: Fix DOS by consuming the number of allowed HTTP connections. Simply establishing a TCP connection and never sending anything to the configured HTTP port in http.conf will tie up a HTTP connection. Since there is a maximum number of open HTTP sessions allowed at a time you can block legitimate connections. A similar problem exists if a HTTP request is started but never finished. * Added http.conf session_inactivity timer option to close HTTP connections that aren't doing anything. Defaults to 30000 ms. * Removed the undocumented manager.conf block-sockets option. It interferes with TCP/TLS inactivity timeouts. * AMI and SIP TLS connections now have better authentication timeout protection. Though I didn't remove the bizzare TLS timeout polling code from chan_sip. * chan_sip can now handle SSL certificate renegotiations in the middle of a session. It couldn't do that before because the socket was non-blocking and the SSL calls were not restarted as documented by the OpenSSL documentation. * Fixed an off nominal leak of the ssl struct in handle_tcptls_connection() if the FILE stream failed to open and the SSL certificate negotiations failed. The patch creates a custom FILE stream handler to give the created FILE streams inactivity timeout and timeout after a specific moment in time capability. This approach eliminates the need for code using the FILE stream to be redesigned to deal with the timeouts. This patch indirectly fixes most of ASTERISK-18345 by fixing the usage of the SSL_read/SSL_write operations. ASTERISK-23673 #close Reported by: Richard Mudgett ........ Merged revisions 415841 from http://svn.asterisk.org/svn/asterisk/branches/1.8 ........ Merged revisions 415854 from http://svn.asterisk.org/svn/asterisk/branches/11 ........ Merged revisions 415896 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@415907 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2014-06-12 17:00:08 +00:00
remaining_headers = MAX_HTTP_REQUEST_HEADERS;
AST-2014-007: Fix DOS by consuming the number of allowed HTTP connections. Simply establishing a TCP connection and never sending anything to the configured HTTP port in http.conf will tie up a HTTP connection. Since there is a maximum number of open HTTP sessions allowed at a time you can block legitimate connections. A similar problem exists if a HTTP request is started but never finished. * Added http.conf session_inactivity timer option to close HTTP connections that aren't doing anything. Defaults to 30000 ms. * Removed the undocumented manager.conf block-sockets option. It interferes with TCP/TLS inactivity timeouts. * AMI and SIP TLS connections now have better authentication timeout protection. Though I didn't remove the bizzare TLS timeout polling code from chan_sip. * chan_sip can now handle SSL certificate renegotiations in the middle of a session. It couldn't do that before because the socket was non-blocking and the SSL calls were not restarted as documented by the OpenSSL documentation. * Fixed an off nominal leak of the ssl struct in handle_tcptls_connection() if the FILE stream failed to open and the SSL certificate negotiations failed. The patch creates a custom FILE stream handler to give the created FILE streams inactivity timeout and timeout after a specific moment in time capability. This approach eliminates the need for code using the FILE stream to be redesigned to deal with the timeouts. This patch indirectly fixes most of ASTERISK-18345 by fixing the usage of the SSL_read/SSL_write operations. ASTERISK-23673 #close Reported by: Richard Mudgett ........ Merged revisions 415841 from http://svn.asterisk.org/svn/asterisk/branches/1.8 ........ Merged revisions 415854 from http://svn.asterisk.org/svn/asterisk/branches/11 ........ Merged revisions 415896 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@415907 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2014-06-12 17:00:08 +00:00
for (;;) {
ssize_t len;
AST-2014-007: Fix DOS by consuming the number of allowed HTTP connections. Simply establishing a TCP connection and never sending anything to the configured HTTP port in http.conf will tie up a HTTP connection. Since there is a maximum number of open HTTP sessions allowed at a time you can block legitimate connections. A similar problem exists if a HTTP request is started but never finished. * Added http.conf session_inactivity timer option to close HTTP connections that aren't doing anything. Defaults to 30000 ms. * Removed the undocumented manager.conf block-sockets option. It interferes with TCP/TLS inactivity timeouts. * AMI and SIP TLS connections now have better authentication timeout protection. Though I didn't remove the bizzare TLS timeout polling code from chan_sip. * chan_sip can now handle SSL certificate renegotiations in the middle of a session. It couldn't do that before because the socket was non-blocking and the SSL calls were not restarted as documented by the OpenSSL documentation. * Fixed an off nominal leak of the ssl struct in handle_tcptls_connection() if the FILE stream failed to open and the SSL certificate negotiations failed. The patch creates a custom FILE stream handler to give the created FILE streams inactivity timeout and timeout after a specific moment in time capability. This approach eliminates the need for code using the FILE stream to be redesigned to deal with the timeouts. This patch indirectly fixes most of ASTERISK-18345 by fixing the usage of the SSL_read/SSL_write operations. ASTERISK-23673 #close Reported by: Richard Mudgett ........ Merged revisions 415841 from http://svn.asterisk.org/svn/asterisk/branches/1.8 ........ Merged revisions 415854 from http://svn.asterisk.org/svn/asterisk/branches/11 ........ Merged revisions 415896 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@415907 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2014-06-12 17:00:08 +00:00
char *name;
char *value;
len = ast_iostream_gets(ser->stream, header_line, sizeof(header_line));
if (len <= 0) {
AST-2014-007: Fix DOS by consuming the number of allowed HTTP connections. Simply establishing a TCP connection and never sending anything to the configured HTTP port in http.conf will tie up a HTTP connection. Since there is a maximum number of open HTTP sessions allowed at a time you can block legitimate connections. A similar problem exists if a HTTP request is started but never finished. * Added http.conf session_inactivity timer option to close HTTP connections that aren't doing anything. Defaults to 30000 ms. * Removed the undocumented manager.conf block-sockets option. It interferes with TCP/TLS inactivity timeouts. * AMI and SIP TLS connections now have better authentication timeout protection. Though I didn't remove the bizzare TLS timeout polling code from chan_sip. * chan_sip can now handle SSL certificate renegotiations in the middle of a session. It couldn't do that before because the socket was non-blocking and the SSL calls were not restarted as documented by the OpenSSL documentation. * Fixed an off nominal leak of the ssl struct in handle_tcptls_connection() if the FILE stream failed to open and the SSL certificate negotiations failed. The patch creates a custom FILE stream handler to give the created FILE streams inactivity timeout and timeout after a specific moment in time capability. This approach eliminates the need for code using the FILE stream to be redesigned to deal with the timeouts. This patch indirectly fixes most of ASTERISK-18345 by fixing the usage of the SSL_read/SSL_write operations. ASTERISK-23673 #close Reported by: Richard Mudgett ........ Merged revisions 415841 from http://svn.asterisk.org/svn/asterisk/branches/1.8 ........ Merged revisions 415854 from http://svn.asterisk.org/svn/asterisk/branches/11 ........ Merged revisions 415896 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@415907 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2014-06-12 17:00:08 +00:00
ast_http_error(ser, 400, "Bad Request", "Timeout");
return -1;
AST-2014-007: Fix DOS by consuming the number of allowed HTTP connections. Simply establishing a TCP connection and never sending anything to the configured HTTP port in http.conf will tie up a HTTP connection. Since there is a maximum number of open HTTP sessions allowed at a time you can block legitimate connections. A similar problem exists if a HTTP request is started but never finished. * Added http.conf session_inactivity timer option to close HTTP connections that aren't doing anything. Defaults to 30000 ms. * Removed the undocumented manager.conf block-sockets option. It interferes with TCP/TLS inactivity timeouts. * AMI and SIP TLS connections now have better authentication timeout protection. Though I didn't remove the bizzare TLS timeout polling code from chan_sip. * chan_sip can now handle SSL certificate renegotiations in the middle of a session. It couldn't do that before because the socket was non-blocking and the SSL calls were not restarted as documented by the OpenSSL documentation. * Fixed an off nominal leak of the ssl struct in handle_tcptls_connection() if the FILE stream failed to open and the SSL certificate negotiations failed. The patch creates a custom FILE stream handler to give the created FILE streams inactivity timeout and timeout after a specific moment in time capability. This approach eliminates the need for code using the FILE stream to be redesigned to deal with the timeouts. This patch indirectly fixes most of ASTERISK-18345 by fixing the usage of the SSL_read/SSL_write operations. ASTERISK-23673 #close Reported by: Richard Mudgett ........ Merged revisions 415841 from http://svn.asterisk.org/svn/asterisk/branches/1.8 ........ Merged revisions 415854 from http://svn.asterisk.org/svn/asterisk/branches/11 ........ Merged revisions 415896 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@415907 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2014-06-12 17:00:08 +00:00
}
if (header_line[len - 1] != '\n') {
/* We didn't get a full line */
ast_http_error(ser, 400, "Bad Request",
(len == sizeof(header_line) - 1) ? "Header line too long" : "Timeout");
return -1;
}
/* Trim trailing characters */
ast_trim_blanks(header_line);
if (ast_strlen_zero(header_line)) {
AST-2014-007: Fix DOS by consuming the number of allowed HTTP connections. Simply establishing a TCP connection and never sending anything to the configured HTTP port in http.conf will tie up a HTTP connection. Since there is a maximum number of open HTTP sessions allowed at a time you can block legitimate connections. A similar problem exists if a HTTP request is started but never finished. * Added http.conf session_inactivity timer option to close HTTP connections that aren't doing anything. Defaults to 30000 ms. * Removed the undocumented manager.conf block-sockets option. It interferes with TCP/TLS inactivity timeouts. * AMI and SIP TLS connections now have better authentication timeout protection. Though I didn't remove the bizzare TLS timeout polling code from chan_sip. * chan_sip can now handle SSL certificate renegotiations in the middle of a session. It couldn't do that before because the socket was non-blocking and the SSL calls were not restarted as documented by the OpenSSL documentation. * Fixed an off nominal leak of the ssl struct in handle_tcptls_connection() if the FILE stream failed to open and the SSL certificate negotiations failed. The patch creates a custom FILE stream handler to give the created FILE streams inactivity timeout and timeout after a specific moment in time capability. This approach eliminates the need for code using the FILE stream to be redesigned to deal with the timeouts. This patch indirectly fixes most of ASTERISK-18345 by fixing the usage of the SSL_read/SSL_write operations. ASTERISK-23673 #close Reported by: Richard Mudgett ........ Merged revisions 415841 from http://svn.asterisk.org/svn/asterisk/branches/1.8 ........ Merged revisions 415854 from http://svn.asterisk.org/svn/asterisk/branches/11 ........ Merged revisions 415896 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@415907 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2014-06-12 17:00:08 +00:00
/* A blank line ends the request header section. */
break;
}
value = header_line;
name = strsep(&value, ":");
if (!value) {
continue;
}
value = ast_skip_blanks(value);
if (ast_strlen_zero(value) || ast_strlen_zero(name)) {
continue;
}
ast_trim_blanks(name);
if (!remaining_headers--) {
/* Too many headers. */
ast_http_error(ser, 413, "Request Entity Too Large", "Too many headers");
return -1;
}
if (!*headers) {
*headers = ast_variable_new(name, value, __FILE__);
tail = *headers;
} else {
tail->next = ast_variable_new(name, value, __FILE__);
tail = tail->next;
}
if (!tail) {
/*
* Variable allocation failure.
* Try to make some room.
*/
ast_variables_destroy(*headers);
*headers = NULL;
ast_http_error(ser, 500, "Server Error", "Out of memory");
return -1;
}
}
return 0;
}
/*!
* \internal
* \brief Process a HTTP request.
* \since 12.4.0
*
* \param ser HTTP TCP/TLS session object.
*
* \retval 0 Continue and process the next HTTP request.
* \retval -1 Fatal HTTP connection error. Force the HTTP connection closed.
*/
static int httpd_process_request(struct ast_tcptls_session_instance *ser)
{
RAII_VAR(struct ast_variable *, headers, NULL, ast_variables_destroy);
char *uri;
char *method;
const char *transfer_encoding;
struct http_worker_private_data *request;
enum ast_http_method http_method = AST_HTTP_UNKNOWN;
int res;
ssize_t len;
char request_line[MAX_HTTP_LINE_LENGTH];
len = ast_iostream_gets(ser->stream, request_line, sizeof(request_line));
if (len <= 0) {
return -1;
}
/* Re-initialize the request body tracking data. */
request = ser->private_data;
http_request_tracking_init(request);
if (request_line[len - 1] != '\n') {
/* We didn't get a full line */
ast_http_error(ser, 400, "Bad Request",
(len == sizeof(request_line) - 1) ? "Request line too long" : "Timeout");
return -1;
}
/* Get method */
method = ast_skip_blanks(request_line);
uri = ast_skip_nonblanks(method);
if (*uri) {
*uri++ = '\0';
}
if (!strcasecmp(method,"GET")) {
http_method = AST_HTTP_GET;
} else if (!strcasecmp(method,"POST")) {
http_method = AST_HTTP_POST;
} else if (!strcasecmp(method,"HEAD")) {
http_method = AST_HTTP_HEAD;
} else if (!strcasecmp(method,"PUT")) {
http_method = AST_HTTP_PUT;
} else if (!strcasecmp(method,"DELETE")) {
http_method = AST_HTTP_DELETE;
} else if (!strcasecmp(method,"OPTIONS")) {
http_method = AST_HTTP_OPTIONS;
}
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';
}
} else {
ast_http_error(ser, 400, "Bad Request", "Invalid Request");
return -1;
}
HTTP: Stop accepting requests on final system shutdown. There are three CLI commands to stop and restart Asterisk each. 1) core stop/restart now - Hangup all calls and stop or restart Asterisk. New channels are prevented while the shutdown request is pending. 2) core stop/restart gracefully - Stop or restart Asterisk when there are no calls remaining in the system. New channels are prevented while the shutdown request is pending. 3) core stop/restart when convenient - Stop or restart Asterisk when there are no calls in the system. New calls are not prevented while the shutdown request is pending. ARI has made stopping/restarting Asterisk more problematic. While a shutdown request is pending it is desirable to continue to process ARI HTTP requests for current calls. To handle the current calls while a shutdown request is pending, a new committed to shutdown phase is needed so ARI applications can deal with the calls until the system is fully committed to shutdown. * Added a new shutdown committed phase so ARI applications can deal with calls until the final committed to shutdown phase is reached. * Made refuse new HTTP requests when the system has reached the final system shutdown phase. Starting anything while the system is actively releasing resources and unloading modules is not a good thing. * Split the bridging framework shutdown to not cleanup the global bridging containers when shutting down in a hurry. This is similar to how other modules prevent crashes on rapid system shutdown. * Moved ast_begin_shutdown(), ast_cancel_shutdown(), and ast_shutting_down(). You should not have to include channel.h just to access these system functions. ASTERISK-24752 #close Reported by: Matthew Jordan Review: https://reviewboard.asterisk.org/r/4399/ ........ Merged revisions 431692 from http://svn.asterisk.org/svn/asterisk/branches/13 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@431694 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2015-02-11 17:39:13 +00:00
if (ast_shutdown_final()) {
ast_http_error(ser, 503, "Service Unavailable", "Shutdown in progress");
return -1;
}
/* process "Request Headers" lines */
if (http_request_headers_get(ser, &headers)) {
return -1;
}
transfer_encoding = get_transfer_encoding(headers);
/* Transfer encoding defaults to identity */
if (!transfer_encoding) {
transfer_encoding = "identity";
}
/*
* RFC 2616, section 3.6, we should respond with a 501 for any transfer-
* codings we don't understand.
*/
if (strcasecmp(transfer_encoding, "identity") != 0 &&
strcasecmp(transfer_encoding, "chunked") != 0) {
/* Transfer encodings not supported */
ast_http_error(ser, 501, "Unimplemented", "Unsupported Transfer-Encoding.");
return -1;
}
if (http_request_tracking_setup(ser, headers)
|| handle_uri(ser, uri, http_method, headers)
|| ast_test_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION)) {
res = -1;
} else {
res = 0;
}
return res;
}
static void *httpd_helper_thread(void *data)
{
struct ast_tcptls_session_instance *ser = data;
int timeout;
int arg = 1;
if (!ser) {
ao2_cleanup(ser);
return NULL;
}
if (ast_atomic_fetchadd_int(&session_count, +1) >= session_limit) {
ast_log(LOG_WARNING, "HTTP session count exceeded %d sessions.\n",
session_limit);
goto done;
}
ast_debug(1, "HTTP opening session. Top level\n");
/*
* Here we set TCP_NODELAY on the socket to disable Nagle's algorithm.
* This is necessary to prevent delays (caused by buffering) as we
* write to the socket in bits and pieces.
*/
if (setsockopt(ast_iostream_get_fd(ser->stream), IPPROTO_TCP, TCP_NODELAY, (char *) &arg, sizeof(arg)) < 0) {
ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on HTTP connection: %s\n", strerror(errno));
}
ast_iostream_nonblock(ser->stream);
/* Setup HTTP worker private data to keep track of request body reading. */
ao2_cleanup(ser->private_data);
ser->private_data = ao2_alloc_options(sizeof(struct http_worker_private_data), NULL,
AO2_ALLOC_OPT_LOCK_NOLOCK);
if (!ser->private_data) {
ast_http_error(ser, 500, "Server Error", "Out of memory");
goto done;
}
http_request_tracking_init(ser->private_data);
/* Determine initial HTTP request wait timeout. */
timeout = session_keep_alive;
if (timeout <= 0) {
/* Persistent connections not enabled. */
timeout = session_inactivity;
}
if (timeout < MIN_INITIAL_REQUEST_TIMEOUT) {
timeout = MIN_INITIAL_REQUEST_TIMEOUT;
}
/* We can let the stream wait for data to arrive. */
ast_iostream_set_exclusive_input(ser->stream, 1);
for (;;) {
/* Wait for next potential HTTP request message. */
ast_iostream_set_timeout_idle_inactivity(ser->stream, timeout, session_inactivity);
if (httpd_process_request(ser)) {
/* Break the connection or the connection closed */
break;
}
if (!ser->stream) {
/* Web-socket or similar that took the connection */
break;
}
timeout = session_keep_alive;
if (timeout <= 0) {
/* Persistent connections not enabled. */
break;
}
}
done:
ast_atomic_fetchadd_int(&session_count, -1);
ast_debug(1, "HTTP closing session. Top level\n");
ast_tcptls_close_session_file(ser);
ao2_ref(ser, -1);
return NULL;
}
/*!
* \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;
size_t dest_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;
}
target_len = strlen(target) + 1;
dest_len = strlen(dest) + 1;
total_len = sizeof(*redirect) + target_len + dest_len;
if (!(redirect = ast_calloc(1, total_len))) {
return;
}
redirect->dest = redirect->target + target_len;
strcpy(redirect->target, target);
ast_copy_string(redirect->dest, dest, dest_len);
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);
}
/*! \brief Number of HTTP server buckets */
#define HTTP_SERVER_BUCKETS 5
struct ao2_container *http_servers = NULL;
AO2_STRING_FIELD_HASH_FN(ast_http_server, address);
AO2_STRING_FIELD_CMP_FN(ast_http_server, address);
static void http_server_destroy(void *obj)
{
struct ast_http_server *server = obj;
ast_tcptls_server_stop(&server->args);
ast_verb(1, "Stopped http server '%s' listening at '%s'\n", server->name, server->address);
ast_free(server->name);
ast_free(server->address);
}
static struct ast_http_server *http_server_create(const char *name, const char *address,
const struct ast_sockaddr *addr)
{
struct ast_http_server *server;
server = ao2_alloc(sizeof(*server), http_server_destroy);
if (!server) {
ast_log(LOG_ERROR, "Unable to allocate HTTP server '%s' at address '%s'\n",
name, address);
return NULL;
}
if (!(server->address = ast_strdup(address)) || !(server->name = ast_strdup(name))) {
ast_log(LOG_ERROR, "Unable to complete setup for HTTP server '%s' at address '%s'\n",
name, address);
ao2_ref(server, -1);
return NULL;
}
server->args.accept_fd = -1;
server->args.master = AST_PTHREADT_NULL;
server->args.tls_cfg = NULL;
server->args.poll_timeout = -1;
server->args.name = server->name;
server->args.accept_fn = ast_tcptls_server_root;
server->args.worker_fn = httpd_helper_thread;
ast_sockaddr_copy(&server->args.local_address, addr);
return server;
}
static int http_server_start(struct ast_http_server *server)
{
if (server->args.accept_fd != -1) {
/* Already running */
return 0;
}
ast_tcptls_server_start(&server->args);
if (server->args.accept_fd == -1) {
ast_log(LOG_WARNING, "Failed to start HTTP server '%s' at address '%s'\n",
server->name, server->address);
return -1;
}
if (!ao2_link_flags(http_servers, server, OBJ_NOLOCK)) {
ast_log(LOG_WARNING, "Failed to link HTTP server '%s' at address '%s'\n",
server->name, server->address);
return -1;
}
ast_verb(1, "Bound HTTP server '%s' to address %s\n", server->name, server->address);
return 0;
}
/*!
* \brief Discard/Drop a HTTP server
*
* Decrements the reference to the given object, and unlinks it from the servers
* container if it's the last reference.
*
* After a server object has been added to the container this method should always
* be called to decrement the object's reference instead of the regular ao2 methods.
*
* \note NULL tolerant
*
* \param server The server object
*/
static void http_server_discard(struct ast_http_server *server)
{
if (!server) {
return;
}
/*
* If only two references were on the object then the last one is from
* the servers container, so remove from container now.
*/
if (ao2_ref(server, -1) == 2) {
ao2_unlink(http_servers, server);
}
}
/*!
* \brief Retrieve, or create a HTTP server object by sock address
*
* Look for, and return a matching server object by addr. If an object is not found
* then create a new one.
*
* \note This method should be called with the http_servers container already locked.
*
* \param name The name of the server
* \param addr The address to match on, or create a new object with
*
* \return a HTTP server object, or NULL on error
*/
static struct ast_http_server *http_server_get_by_addr(
const char *name, const struct ast_sockaddr *addr)
{
struct ast_http_server *server;
const char *address;
address = ast_sockaddr_stringify(addr);
if (ast_strlen_zero(address)) {
return NULL;
}
server = ao2_find(http_servers, address, OBJ_KEY | OBJ_NOLOCK);
return server ?: http_server_create(name, address, addr);
}
/*!
* \brief Retrieve, or create a HTTP server object by host
*
* Resolve the given host, and then look for, and return a matching server object.
* If an object is not found then create a new one.
*
* \note This method should be called with the http_servers container already locked.
*
* \param name The name of the server
* \param host The host to resolve, and match on or create a new object with
* \param port Optional port used if one is not specified with the host (default 8088)
*
* \return a HTTP server object, or NULL on error
*/
static struct ast_http_server *http_server_get_by_host(const char *name, const char *host,
uint32_t port)
{
struct ast_sockaddr *addrs = NULL;
int num_addrs;
int i;
if (!(num_addrs = ast_sockaddr_resolve(&addrs, host, 0, AST_AF_UNSPEC))) {
ast_log(LOG_WARNING, "Unable to resolve host '%s'\n", host);
return NULL;
}
if (port == 0) {
port = DEFAULT_PORT;
}
for (i = 0; i < num_addrs; ++i) {
struct ast_http_server *server;
/* Use the given port if one was not specified already */
if (!ast_sockaddr_port(&addrs[i])) {
ast_sockaddr_set_port(&addrs[i], port);
}
server = http_server_get_by_addr(name, &addrs[i]);
if (server) {
ast_free(addrs);
return server;
}
}
ast_free(addrs);
return NULL;
}
/*!
* \brief Retrieve, or create and start a HTTP server
*
* Resolve the given host, and retrieve a listening server object. If the server is
* not already listening then start it. If a replace_me parameter is given, and it
* points to a non-NULL value then that server is discarded and replaced.
*
* \param name The name of the server
* \param host The host to resolve, and match on or create a new object with
* \param port Optional port used if one is not specified with the host (default 8088)
* \param[out] replace_me Optional server to be replaced
*
* \note If replace_me is specified the returned value is always the same as what's
* passed back out in the variable.
*
* \return a HTTP server object, or NULL on error
*/
static struct ast_http_server *http_server_get(const char *name, const char *host,
uint32_t port, struct ast_http_server **replace_me)
{
struct ast_http_server *server;
ao2_lock(http_servers);
server = http_server_get_by_host(name, host, port);
if (replace_me) {
/* Only replace if different */
if (*replace_me == server) {
ao2_cleanup(server);
ao2_unlock(http_servers);
return *replace_me;
}
if (*replace_me) {
http_server_discard(*replace_me);
}
*replace_me = server;
}
if (server && http_server_start(server)) {
if (replace_me) {
*replace_me = NULL;
}
ao2_ref(server, -1);
server = NULL;
}
ao2_unlock(http_servers);
return server;
}
#ifdef TEST_FRAMEWORK
struct ast_http_server *ast_http_test_server_get(const char *name, const char *host)
{
struct ast_http_server *server;
/*
* Currently multiple HTTP servers are only allowed when the TEST_FRAMEWORK
* is enabled, leaving the follow 'todos' if they become a problem or if this
* ability moves outside the TEST_FRAMEWORK.
*
* TODO: Add locking around global_http_server use. If this module is reloading
* it's possible for the global_http_server to exist here, and then become
* NULL between the check and return.
*
* TODO: Make it so 'localhost' and 'any' addresses equate.
*/
if (ast_strlen_zero(host)) {
/* Use configured server if one available */
if (global_http_server) {
ast_module_ref(ast_module_info->self);
return ao2_bump(global_http_server);
}
host = "localhost:8088";
}
if (!name) {
name = "http test server";
}
server = http_server_get(name, host, 0, NULL);
if (server) {
ast_module_ref(ast_module_info->self);
}
return server;
}
void ast_http_test_server_discard(struct ast_http_server *server)
{
if (server) {
http_server_discard(server);
ast_module_unref(ast_module_info->self);
}
}
#endif
static int __ast_http_load(int reload)
{
struct ast_config *cfg;
struct ast_variable *v;
int enabled = 0;
int new_static_uri_enabled = 0;
int new_status_uri_enabled = 1; /* Default to enabled for BC */
char newprefix[MAX_PREFIX] = "";
char server_name[MAX_SERVER_NAME_LENGTH];
struct http_uri_redirect *redirect;
struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
uint32_t bindport = DEFAULT_PORT;
int http_tls_was_enabled = 0;
char *bindaddr = NULL;
cfg = ast_config_load2("http.conf", "http", config_flags);
if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
return 0;
}
/* Even if the http.conf hasn't been updated, the TLS certs/keys may have been */
if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
if (http_tls_cfg.enabled && ast_ssl_setup(https_desc.tls_cfg)) {
ast_tcptls_server_start(&https_desc);
}
return 0;
}
http_tls_was_enabled = (reload && http_tls_cfg.enabled);
http_tls_cfg.enabled = 0;
ast_free(http_tls_cfg.certfile);
http_tls_cfg.certfile = ast_strdup(AST_CERTFILE);
ast_free(http_tls_cfg.capath);
http_tls_cfg.capath = ast_strdup("");
ast_free(http_tls_cfg.pvtfile);
http_tls_cfg.pvtfile = ast_strdup("");
/* Apply modern intermediate settings according to the Mozilla OpSec team as of July 30th, 2015 but disable TLSv1 */
ast_set_flag(&http_tls_cfg.flags, AST_SSL_DISABLE_TLSV1 | AST_SSL_SERVER_CIPHER_ORDER);
ast_free(http_tls_cfg.cipher);
http_tls_cfg.cipher = ast_strdup("ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA");
Implement https support. The changes are not large. Most of the diff comes from putting the global variables describing an accept session into a structure, so we can reuse the existing code for running multiple accept threads on different ports. Once this is done, and if your system has the funopen() library function (and ssl, of course), it is just a matter of calling the appropriate functions to set up the ssl connection on the existing socket, and everything works on the secure channel now. At the moment, the code is disabled because i have not implemented yet the autoconf code to detect the presence of funopen(), and add -lssl to main/Makefile if ssl libraries are present. And a bit of documentation on the http.conf arguments, too. If you want to manually enable https support, that is very simple (step 0 1 2 will be eventually detected by ./configure, the rest is something you will have to do anyways). 0. make sure your system has funopen(3). FreeBSD does, linux probably does too, not sure about other systems. 1. uncomment the following line in main/http.c // #define DO_SSL /* comment in/out if you want to support ssl */ 2. add -lssl to AST_LIBS in main/Makefile 3. add the following options to http.conf sslenable=yes sslbindport=4433 ; pick one you like sslcert=/tmp/foo.pem ; path to your certificate file. 4. generate a suitable certificate e.g. (example from mini_httpd's Makefile: openssl req -new -x509 -days 365 -nodes -out /tmp/foo.pem -keyout /tmp/foo.pem and here you go: https://localhost:4433/asterisk/manager now works. git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@45869 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2006-10-22 12:02:35 +00:00
AST_RWLIST_WRLOCK(&uri_redirects);
while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry))) {
ast_free(redirect);
}
AST_RWLIST_UNLOCK(&uri_redirects);
ast_sockaddr_setnull(&https_desc.local_address);
AST-2014-007: Fix DOS by consuming the number of allowed HTTP connections. Simply establishing a TCP connection and never sending anything to the configured HTTP port in http.conf will tie up a HTTP connection. Since there is a maximum number of open HTTP sessions allowed at a time you can block legitimate connections. A similar problem exists if a HTTP request is started but never finished. * Added http.conf session_inactivity timer option to close HTTP connections that aren't doing anything. Defaults to 30000 ms. * Removed the undocumented manager.conf block-sockets option. It interferes with TCP/TLS inactivity timeouts. * AMI and SIP TLS connections now have better authentication timeout protection. Though I didn't remove the bizzare TLS timeout polling code from chan_sip. * chan_sip can now handle SSL certificate renegotiations in the middle of a session. It couldn't do that before because the socket was non-blocking and the SSL calls were not restarted as documented by the OpenSSL documentation. * Fixed an off nominal leak of the ssl struct in handle_tcptls_connection() if the FILE stream failed to open and the SSL certificate negotiations failed. The patch creates a custom FILE stream handler to give the created FILE streams inactivity timeout and timeout after a specific moment in time capability. This approach eliminates the need for code using the FILE stream to be redesigned to deal with the timeouts. This patch indirectly fixes most of ASTERISK-18345 by fixing the usage of the SSL_read/SSL_write operations. ASTERISK-23673 #close Reported by: Richard Mudgett ........ Merged revisions 415841 from http://svn.asterisk.org/svn/asterisk/branches/1.8 ........ Merged revisions 415854 from http://svn.asterisk.org/svn/asterisk/branches/11 ........ Merged revisions 415896 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@415907 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2014-06-12 17:00:08 +00:00
session_limit = DEFAULT_SESSION_LIMIT;
session_inactivity = DEFAULT_SESSION_INACTIVITY;
session_keep_alive = DEFAULT_SESSION_KEEP_ALIVE;
snprintf(server_name, sizeof(server_name), "Asterisk/%s", ast_get_version());
v = ast_variable_browse(cfg, "general");
for (; v; v = v->next) {
/* read tls config options while preventing unsupported options from being set */
if (strcasecmp(v->name, "tlscafile")
&& strcasecmp(v->name, "tlscapath")
&& strcasecmp(v->name, "tlscadir")
&& strcasecmp(v->name, "tlsverifyclient")
&& strcasecmp(v->name, "tlsdontverifyserver")
&& strcasecmp(v->name, "tlsclientmethod")
&& strcasecmp(v->name, "sslclientmethod")
&& !ast_tls_read_conf(&http_tls_cfg, &https_desc, v->name, v->value)) {
continue;
}
AST-2014-007: Fix DOS by consuming the number of allowed HTTP connections. Simply establishing a TCP connection and never sending anything to the configured HTTP port in http.conf will tie up a HTTP connection. Since there is a maximum number of open HTTP sessions allowed at a time you can block legitimate connections. A similar problem exists if a HTTP request is started but never finished. * Added http.conf session_inactivity timer option to close HTTP connections that aren't doing anything. Defaults to 30000 ms. * Removed the undocumented manager.conf block-sockets option. It interferes with TCP/TLS inactivity timeouts. * AMI and SIP TLS connections now have better authentication timeout protection. Though I didn't remove the bizzare TLS timeout polling code from chan_sip. * chan_sip can now handle SSL certificate renegotiations in the middle of a session. It couldn't do that before because the socket was non-blocking and the SSL calls were not restarted as documented by the OpenSSL documentation. * Fixed an off nominal leak of the ssl struct in handle_tcptls_connection() if the FILE stream failed to open and the SSL certificate negotiations failed. The patch creates a custom FILE stream handler to give the created FILE streams inactivity timeout and timeout after a specific moment in time capability. This approach eliminates the need for code using the FILE stream to be redesigned to deal with the timeouts. This patch indirectly fixes most of ASTERISK-18345 by fixing the usage of the SSL_read/SSL_write operations. ASTERISK-23673 #close Reported by: Richard Mudgett ........ Merged revisions 415841 from http://svn.asterisk.org/svn/asterisk/branches/1.8 ........ Merged revisions 415854 from http://svn.asterisk.org/svn/asterisk/branches/11 ........ Merged revisions 415896 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@415907 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2014-06-12 17:00:08 +00:00
if (!strcasecmp(v->name, "servername")) {
if (!ast_strlen_zero(v->value)) {
ast_copy_string(server_name, v->value, sizeof(server_name));
} else {
server_name[0] = '\0';
}
} else if (!strcasecmp(v->name, "enabled")) {
enabled = ast_true(v->value);
} else if (!strcasecmp(v->name, "enablestatic") || !strcasecmp(v->name, "enable_static")) {
new_static_uri_enabled = ast_true(v->value);
} else if (!strcasecmp(v->name, "enable_status")) {
new_status_uri_enabled = ast_true(v->value);
} else if (!strcasecmp(v->name, "bindport")) {
if (ast_parse_arg(v->value, PARSE_UINT32 | PARSE_IN_RANGE | PARSE_DEFAULT,
&bindport, DEFAULT_PORT, 0, 65535)) {
ast_log(LOG_WARNING, "Invalid port %s specified. Using default port %" PRId32 "\n",
v->value, DEFAULT_PORT);
}
} else if (!strcasecmp(v->name, "bindaddr")) {
bindaddr = ast_strdupa(v->value);
} 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';
}
} else if (!strcasecmp(v->name, "redirect")) {
add_redirect(v->value);
} else if (!strcasecmp(v->name, "sessionlimit")) {
if (ast_parse_arg(v->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE,
&session_limit, DEFAULT_SESSION_LIMIT, 1, INT_MAX)) {
ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of http.conf\n",
v->name, v->value, v->lineno);
}
} else if (!strcasecmp(v->name, "session_inactivity")) {
if (ast_parse_arg(v->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE,
&session_inactivity, DEFAULT_SESSION_INACTIVITY, 1, INT_MAX)) {
ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of http.conf\n",
v->name, v->value, v->lineno);
}
} else if (!strcasecmp(v->name, "session_keep_alive")) {
if (sscanf(v->value, "%30d", &session_keep_alive) != 1
|| session_keep_alive < 0) {
session_keep_alive = DEFAULT_SESSION_KEEP_ALIVE;
ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of http.conf\n",
v->name, v->value, v->lineno);
}
} else {
ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
}
}
ast_config_destroy(cfg);
if (strcmp(prefix, newprefix)) {
ast_copy_string(prefix, newprefix, sizeof(prefix));
}
ast_copy_string(http_server_name, server_name, sizeof(http_server_name));
if (enabled) {
http_server_get("http server", bindaddr, bindport, &global_http_server);
} else if (global_http_server) {
http_server_discard(global_http_server);
global_http_server = NULL;
}
/* When no specific TLS bindaddr is specified, we just use
* the non-TLS bindaddress here.
*/
if (global_http_server && ast_sockaddr_isnull(&https_desc.local_address) &&
global_http_server->args.accept_fd != -1) {
ast_sockaddr_copy(&https_desc.local_address, &global_http_server->args.local_address);
/* Of course, we can't use the same port though.
* Since no bind address was specified, we just use the
* default TLS port
*/
ast_sockaddr_set_port(&https_desc.local_address, DEFAULT_TLS_PORT);
}
if (http_tls_was_enabled && !http_tls_cfg.enabled) {
ast_tcptls_server_stop(&https_desc);
} else if (http_tls_cfg.enabled && !ast_sockaddr_isnull(&https_desc.local_address)) {
/* We can get here either because a TLS-specific address was specified
* or because we copied the non-TLS address here. In the case where
* we read an explicit address from the config, there may have been
* no port specified, so we'll just use the default TLS port.
*/
if (!ast_sockaddr_port(&https_desc.local_address)) {
ast_sockaddr_set_port(&https_desc.local_address, DEFAULT_TLS_PORT);
}
if (ast_ssl_setup(https_desc.tls_cfg)) {
ast_tcptls_server_start(&https_desc);
}
}
if (static_uri_enabled && !new_static_uri_enabled) {
ast_http_uri_unlink(&static_uri);
} else if (!static_uri_enabled && new_static_uri_enabled) {
ast_http_uri_link(&static_uri);
}
static_uri_enabled = new_static_uri_enabled;
if (status_uri_enabled && !new_status_uri_enabled) {
ast_http_uri_unlink(&status_uri);
} else if (!status_uri_enabled && new_status_uri_enabled) {
ast_http_uri_link(&status_uri);
}
status_uri_enabled = new_status_uri_enabled;
return 0;
}
static char *handle_show_http(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct ast_http_uri *urih;
struct http_uri_redirect *redirect;
switch (cmd) {
case CLI_INIT:
e->command = "http show status";
e->usage =
"Usage: http show status\n"
" Lists status of internal HTTP engine\n";
return NULL;
case CLI_GENERATE:
return NULL;
}
if (a->argc != 3) {
return CLI_SHOWUSAGE;
}
ast_cli(a->fd, "HTTP Server Status:\n");
ast_cli(a->fd, "Prefix: %s\n", prefix);
ast_cli(a->fd, "Server: %s\n", http_server_name);
if (!global_http_server) {
ast_cli(a->fd, "Server Disabled\n\n");
} else {
ast_cli(a->fd, "Server Enabled and Bound to %s\n\n",
ast_sockaddr_stringify(&global_http_server->args.old_address));
if (http_tls_cfg.enabled) {
ast_cli(a->fd, "HTTPS Server Enabled and Bound to %s\n\n",
ast_sockaddr_stringify(&https_desc.old_address));
}
Implement https support. The changes are not large. Most of the diff comes from putting the global variables describing an accept session into a structure, so we can reuse the existing code for running multiple accept threads on different ports. Once this is done, and if your system has the funopen() library function (and ssl, of course), it is just a matter of calling the appropriate functions to set up the ssl connection on the existing socket, and everything works on the secure channel now. At the moment, the code is disabled because i have not implemented yet the autoconf code to detect the presence of funopen(), and add -lssl to main/Makefile if ssl libraries are present. And a bit of documentation on the http.conf arguments, too. If you want to manually enable https support, that is very simple (step 0 1 2 will be eventually detected by ./configure, the rest is something you will have to do anyways). 0. make sure your system has funopen(3). FreeBSD does, linux probably does too, not sure about other systems. 1. uncomment the following line in main/http.c // #define DO_SSL /* comment in/out if you want to support ssl */ 2. add -lssl to AST_LIBS in main/Makefile 3. add the following options to http.conf sslenable=yes sslbindport=4433 ; pick one you like sslcert=/tmp/foo.pem ; path to your certificate file. 4. generate a suitable certificate e.g. (example from mini_httpd's Makefile: openssl req -new -x509 -days 365 -nodes -out /tmp/foo.pem -keyout /tmp/foo.pem and here you go: https://localhost:4433/asterisk/manager now works. git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@45869 65c4cc65-6c06-0410-ace0-fbb531ad65f3
2006-10-22 12:02:35 +00:00
}
ast_cli(a->fd, "Enabled URI's:\n");
AST_RWLIST_RDLOCK(&uris);
if (AST_RWLIST_EMPTY(&uris)) {
ast_cli(a->fd, "None.\n");
} else {
AST_RWLIST_TRAVERSE(&uris, urih, entry)
ast_cli(a->fd, "%s/%s%s => %s\n", prefix, urih->uri, (urih->has_subtree ? "/..." : "" ), urih->description);
}
AST_RWLIST_UNLOCK(&uris);
ast_cli(a->fd, "\nEnabled Redirects:\n");
AST_RWLIST_RDLOCK(&uri_redirects);
AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry)
ast_cli(a->fd, " %s => %s\n", redirect->target, redirect->dest);
if (AST_RWLIST_EMPTY(&uri_redirects)) {
ast_cli(a->fd, " None.\n");
}
AST_RWLIST_UNLOCK(&uri_redirects);
return CLI_SUCCESS;
}
static int reload_module(void)
{
return __ast_http_load(1);
}
static struct ast_cli_entry cli_http[] = {
AST_CLI_DEFINE(handle_show_http, "Display HTTP server status"),
};
static int unload_module(void)
{
struct http_uri_redirect *redirect;
ast_cli_unregister_multiple(cli_http, ARRAY_LEN(cli_http));
ao2_cleanup(global_http_server);
ao2_cleanup(http_servers);
if (http_tls_cfg.enabled) {
ast_tcptls_server_stop(&https_desc);
}
ast_free(http_tls_cfg.certfile);
ast_free(http_tls_cfg.capath);
ast_free(http_tls_cfg.pvtfile);
ast_free(http_tls_cfg.cipher);
if (status_uri_enabled) {
ast_http_uri_unlink(&status_uri);
}
if (static_uri_enabled) {
ast_http_uri_unlink(&static_uri);
}
AST_RWLIST_WRLOCK(&uri_redirects);
while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry))) {
ast_free(redirect);
}
AST_RWLIST_UNLOCK(&uri_redirects);
return 0;
}
static int load_module(void)
{
ast_cli_register_multiple(cli_http, ARRAY_LEN(cli_http));
http_servers = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0,
HTTP_SERVER_BUCKETS, ast_http_server_hash_fn, NULL, ast_http_server_cmp_fn);
if (!http_servers) {
return AST_MODULE_LOAD_FAILURE;
}
if (__ast_http_load(0)) {
ao2_cleanup(http_servers);
http_servers = NULL;
return AST_MODULE_LOAD_FAILURE;
}
return AST_MODULE_LOAD_SUCCESS;
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Built-in HTTP Server",
.support_level = AST_MODULE_SUPPORT_CORE,
.load = load_module,
.unload = unload_module,
.reload = reload_module,
.load_pri = AST_MODPRI_CORE,
.requires = "extconfig",
);