summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
Diffstat (limited to 'main')
-rw-r--r--main/http.c1077
-rw-r--r--main/manager.c101
-rw-r--r--main/tcptls.c1
3 files changed, 856 insertions, 323 deletions
diff --git a/main/http.c b/main/http.c
index 7b3a3cee4..c465d5394 100644
--- a/main/http.c
+++ b/main/http.c
@@ -71,10 +71,30 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#define DEFAULT_PORT 8088
#define DEFAULT_TLS_PORT 8089
#define DEFAULT_SESSION_LIMIT 100
-#define DEFAULT_SESSION_INACTIVITY 30000 /* (ms) Idle time waiting for data. */
+/*! (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
+
+/*! Maximum application/json or application/x-www-form-urlencoded body content length. */
+#if !defined(LOW_MEMORY)
+#define MAX_CONTENT_LENGTH 4096
+#else
+#define MAX_CONTENT_LENGTH 1024
+#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 int session_limit = DEFAULT_SESSION_LIMIT;
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;
@@ -226,7 +246,7 @@ static int static_callback(struct ast_tcptls_session_instance *ser,
if (method != AST_HTTP_GET && method != AST_HTTP_HEAD) {
ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
- return -1;
+ return 0;
}
/* Yuck. I'm not really sold on this, but if you don't deliver static content it makes your configuration
@@ -295,9 +315,12 @@ static int static_callback(struct ast_tcptls_session_instance *ser,
}
}
- if ( (http_header = ast_str_create(255)) == NULL) {
+ 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 -1;
+ return 0;
}
ast_str_set(&http_header, 0, "Content-type: %s\r\n"
@@ -318,11 +341,12 @@ static int static_callback(struct ast_tcptls_session_instance *ser,
out404:
ast_http_error(ser, 404, "Not Found", "The requested URL was not found on this server.");
- return -1;
+ 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 -1;
+ return 0;
}
static int httpstatus_callback(struct ast_tcptls_session_instance *ser,
@@ -335,11 +359,14 @@ static int httpstatus_callback(struct ast_tcptls_session_instance *ser,
if (method != AST_HTTP_GET && method != AST_HTTP_HEAD) {
ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
- return -1;
+ return 0;
}
- if ( (out = ast_str_create(512)) == NULL) {
- return -1;
+ 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,
@@ -392,23 +419,63 @@ static struct ast_http_uri staticuri = {
.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;
+};
-/* send http/1.1 response */
-/* free content variable and close socket*/
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, const int fd,
+ 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];
int content_length = 0;
+ int close_connection;
- if (!ser || 0 == ser->f) {
+ if (!ser || !ser->f) {
+ /* The connection is not open. */
+ ast_free(http_header);
+ ast_free(out);
return;
}
+ /*
+ * 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 */
@@ -422,20 +489,22 @@ void ast_http_send(struct ast_tcptls_session_instance *ser,
}
/* send http header */
- fprintf(ser->f, "HTTP/1.1 %d %s\r\n"
+ fprintf(ser->f,
+ "HTTP/1.1 %d %s\r\n"
"Server: Asterisk/%s\r\n"
"Date: %s\r\n"
- "Connection: close\r\n"
"%s"
- "Content-Length: %d\r\n"
"%s"
+ "%s"
+ "Content-Length: %d\r\n"
"\r\n",
status_code, status_title ? status_title : "OK",
ast_get_version(),
timebuf,
+ close_connection ? "Connection: close\r\n" : "",
static_content ? "" : "Cache-Control: no-cache, no-store\r\n",
- content_length,
- http_header ? ast_str_buffer(http_header) : ""
+ http_header ? ast_str_buffer(http_header) : "",
+ content_length
);
/* send content */
@@ -443,33 +512,35 @@ void ast_http_send(struct ast_tcptls_session_instance *ser,
if (out && ast_str_strlen(out)) {
if (fwrite(ast_str_buffer(out), ast_str_strlen(out), 1, ser->f) != 1) {
ast_log(LOG_ERROR, "fwrite() failed: %s\n", strerror(errno));
+ close_connection = 1;
}
}
if (fd) {
char buf[256];
int len;
+
while ((len = read(fd, buf, sizeof(buf))) > 0) {
if (fwrite(buf, len, 1, ser->f) != 1) {
ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
+ close_connection = 1;
break;
}
}
}
}
- if (http_header) {
- ast_free(http_header);
- }
- if (out) {
- ast_free(out);
- }
+ ast_free(http_header);
+ ast_free(out);
- ast_tcptls_close_session_file(ser);
- return;
+ 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);
+ }
}
-/* Send http "401 Unauthorized" responce and close socket*/
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)
@@ -480,6 +551,10 @@ void ast_http_auth(struct ast_tcptls_session_instance *ser, const char *realm,
if (!http_headers || !out) {
ast_free(http_headers);
ast_free(out);
+ if (ser && ser->f) {
+ ast_debug(1, "HTTP closing session. Auth OOM\n");
+ ast_tcptls_close_session_file(ser);
+ }
return;
}
@@ -504,10 +579,8 @@ void ast_http_auth(struct ast_tcptls_session_instance *ser, const char *realm,
text ? text : "");
ast_http_send(ser, AST_HTTP_UNKNOWN, 401, "Unauthorized", http_headers, out, 0, 0);
- return;
}
-/* send http error response and close socket*/
void ast_http_error(struct ast_tcptls_session_instance *ser, int status_code, const char *status_title, const char *text)
{
struct ast_str *http_headers = ast_str_create(40);
@@ -516,6 +589,10 @@ void ast_http_error(struct ast_tcptls_session_instance *ser, int status_code, co
if (!http_headers || !out) {
ast_free(http_headers);
ast_free(out);
+ if (ser && ser->f) {
+ ast_debug(1, "HTTP closing session. error OOM\n");
+ ast_tcptls_close_session_file(ser);
+ }
return;
}
@@ -531,14 +608,13 @@ void ast_http_error(struct ast_tcptls_session_instance *ser, int status_code, co
"<hr />\r\n"
"<address>Asterisk Server</address>\r\n"
"</body></html>\r\n",
- status_code, status_title, status_title, text);
+ status_code, status_title, status_title, text);
ast_http_send(ser, AST_HTTP_UNKNOWN, status_code, status_title, http_headers, out, 0, 0);
- return;
}
-/*! \brief
- * Link the new uri into the list.
+/*!
+ * \brief Link the new uri into the list.
*
* They are sorted by length of
* the string, not alphabetically. Duplicate entries are not replaced,
@@ -602,8 +678,6 @@ void ast_http_uri_unlink_all_with_key(const char *key)
AST_RWLIST_UNLOCK(&uris);
}
-#define MAX_POST_CONTENT 1025
-
/*!
* \brief Retrieves the header with the given field name.
*
@@ -612,8 +686,7 @@ void ast_http_uri_unlink_all_with_key(const char *key)
* \return Associated header value.
* \return \c NULL if header is not present.
*/
-static const char *get_header(struct ast_variable *headers,
- const char *field_name)
+static const char *get_header(struct ast_variable *headers, const char *field_name)
{
struct ast_variable *v;
@@ -655,29 +728,35 @@ static char *get_content_type(struct ast_variable *headers)
* \brief Returns the value of the Content-Length header.
*
* \param headers HTTP headers.
- * \return Value of the Content-Length header.
- * \return 0 if header is not present, or is invalid.
+ *
+ * \retval 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;
}
- /* atoi() will return 0 for invalid inputs, which is good enough for
- * the HTTP parsing. */
- return atoi(content_length);
+ 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 Value of the Transfer-Encoding header.
- * \return 0 if header is not present, or is invalid.
+ * \retval string Value of the Transfer-Encoding header.
+ * \retval NULL if header is not present.
*/
static const char *get_transfer_encoding(struct ast_variable *headers)
{
@@ -685,11 +764,176 @@ static const char *get_transfer_encoding(struct ast_variable *headers)
}
/*!
+ * \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.
+ *
+ * \return Nothing
+ */
+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;
+
+ /* Stay in fread until get all the expected data or timeout. */
+ res = fread(buf, length, 1, ser->f);
+ if (res < 1) {
+ ast_log(LOG_WARNING, "Short HTTP request %s (Wanted %d)\n",
+ what_getting, length);
+ 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)
+{
+ int res;
+ char buf[MAX_HTTP_LINE_LENGTH];/* Discard buffer */
+
+ /* Stay in fread until get all the expected data or timeout. */
+ while (sizeof(buf) < length) {
+ res = fread(buf, sizeof(buf), 1, ser->f);
+ if (res < 1) {
+ ast_log(LOG_WARNING, "Short HTTP request %s (Wanted %zu of remaining %d)\n",
+ what_getting, sizeof(buf), length);
+ return -1;
+ }
+ length -= sizeof(buf);
+ }
+ res = fread(buf, length, 1, ser->f);
+ if (res < 1) {
+ ast_log(LOG_WARNING, "Short HTTP request %s (Wanted %d of remaining %d)\n",
+ what_getting, length, length);
+ return -1;
+ }
+ return 0;
+}
+
+/*!
+ * \internal
* \brief decode chunked mode hexadecimal value
*
* \param s string to decode
* \param len length of string
- * \return integer value or -1 for decode error
+ *
+ * \retval length on success.
+ * \retval -1 on error.
*/
static int chunked_atoh(const char *s, int len)
{
@@ -701,13 +945,21 @@ static int chunked_atoh(const char *s, int len)
return -1;
}
- while (len--)
- {
- if (*s == '\x0D') {
+ 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;
- c = *s++;
if (c >= '0' && c <= '9') {
value += c - '0';
continue;
@@ -728,10 +980,151 @@ static int chunked_atoh(const char *s, int len)
}
/*!
+ * \internal
+ * \brief Read and convert the chunked body header length.
+ * \since 12.4.0
+ *
+ * \param ser HTTP TCP/TLS session object.
+ *
+ * \retval 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 (!fgets(header_line, sizeof(header_line), ser->f)) {
+ 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 = fread(chunk_sync, sizeof(chunk_sync), 1, ser->f);
+ if (res < 1) {
+ 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%02X, 0x%02X)\n",
+ chunk_sync[0], 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 (!fgets(header_line, sizeof(header_line), ser->f)) {
+ 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 aser HTTP TCP/TLS session object
+ * \param ser HTTP TCP/TLS session object
* \param headers List of HTTP headers
* \return ptr to content (zero terminated) or NULL on failure
* \note Since returned ptr is malloc'd, it should be free'd by caller
@@ -739,122 +1132,130 @@ static int chunked_atoh(const char *s, int len)
static char *ast_http_get_contents(int *return_length,
struct ast_tcptls_session_instance *ser, struct ast_variable *headers)
{
- const char *transfer_encoding;
- int res;
- int content_length = 0;
- int chunk_length;
- char chunk_header[8];
- int bufsize = 250;
+ struct http_worker_private_data *request;
+ int content_length;
+ int bufsize;
char *buf;
- transfer_encoding = get_transfer_encoding(headers);
+ 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);
- if (ast_strlen_zero(transfer_encoding) ||
- strcasecmp(transfer_encoding, "chunked") != 0) {
+ 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 = get_content_length(headers);
- if (content_length <= 0) {
- /* no content - not an error */
- return NULL;
- }
- if (content_length > MAX_POST_CONTENT - 1) {
- ast_log(LOG_WARNING,
- "Excessively long HTTP content. (%d > %d)\n",
- content_length, MAX_POST_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;
}
- res = fread(buf, 1, content_length, ser->f);
- if (res < content_length) {
- /* Error, distinguishable by ferror() or feof(), but neither
- * is good. Treat either one as I/O error */
- ast_log(LOG_WARNING, "Short HTTP request body (%d < %d)\n",
- res, content_length);
+
+ 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;
}
- /* handled chunked content */
- do {
- /* get the line of hexadecimal giving chunk size */
- if (!fgets(chunk_header, sizeof(chunk_header), ser->f)) {
- ast_log(LOG_WARNING,
- "Short HTTP read of chunked header\n");
- errno = EIO;
- ast_free(buf);
- return NULL;
- }
- chunk_length = chunked_atoh(chunk_header, sizeof(chunk_header));
+ /* parse chunked-body */
+ content_length = 0;
+ for (;;) {
+ int chunk_length;
+
+ chunk_length = http_body_get_chunk_length(ser);
if (chunk_length < 0) {
- ast_log(LOG_WARNING, "Invalid HTTP chunk size\n");
+ ast_set_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION);
errno = EIO;
ast_free(buf);
return NULL;
}
- if (content_length + chunk_length > MAX_POST_CONTENT - 1) {
+ if (chunk_length == 0) {
+ /* parsed last-chunk */
+ break;
+ }
+ if (content_length + chunk_length > MAX_CONTENT_LENGTH) {
ast_log(LOG_WARNING,
- "Excessively long HTTP chunk. (%d + %d > %d)\n",
- content_length, chunk_length, MAX_POST_CONTENT);
+ "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)
- {
- bufsize *= 2;
- buf = ast_realloc(buf, bufsize);
- if (!buf) {
+ 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;
}
- /* read the chunk */
- res = fread(buf + content_length, 1, chunk_length, ser->f);
- if (res < chunk_length) {
- ast_log(LOG_WARNING, "Short HTTP chunk read (%d < %d)\n",
- res, chunk_length);
+ 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;
+ }
- /* insure the next 2 bytes are CRLF */
- res = fread(chunk_header, 1, 2, ser->f);
- if (res < 2) {
- ast_log(LOG_WARNING,
- "Short HTTP chunk sync read (%d < 2)\n", res);
- errno = EIO;
- ast_free(buf);
- return NULL;
- }
- if (chunk_header[0] != 0x0D || chunk_header[1] != 0x0A) {
- ast_log(LOG_WARNING,
- "Post HTTP chunk sync bytes wrong (%d, %d)\n",
- chunk_header[0], chunk_header[1]);
- errno = EIO;
- ast_free(buf);
- return NULL;
- }
- } while (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;
@@ -873,23 +1274,21 @@ struct ast_json *ast_http_get_json(
errno = 0;
if (ast_strlen_zero(type) || strcasecmp(type, "application/json")) {
- /* Content type is not JSON */
+ /* Content type is not JSON. Don't read the body. */
return NULL;
}
buf = ast_http_get_contents(&content_length, ser, headers);
- if (buf == NULL) {
- /* errno already set */
- return NULL;
- }
-
- if (!content_length) {
- /* it is not an error to have zero content */
+ 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 == NULL) {
+ if (!body) {
/* Failed to parse JSON; treat as an I/O error */
errno = EIO;
return NULL;
@@ -908,7 +1307,7 @@ struct ast_variable *ast_http_get_post_vars(
int content_length = 0;
struct ast_variable *v, *post_vars=NULL, *prev = NULL;
char *var, *val;
- RAII_VAR(char *, buf, NULL, ast_free_ptr);
+ 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 */
@@ -916,12 +1315,16 @@ struct ast_variable *ast_http_get_post_vars(
if (ast_strlen_zero(type) ||
strcasecmp(type, "application/x-www-form-urlencoded")) {
- /* Content type is not form data */
+ /* Content type is not form data. Don't read the body. */
return NULL;
}
buf = ast_http_get_contents(&content_length, ser, headers);
- if (buf == NULL) {
+ if (!buf || !content_length) {
+ /*
+ * errno already set
+ * or it is not an error to have zero content
+ */
return NULL;
}
@@ -950,7 +1353,7 @@ 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 = -1;
+ int res = 0;
char *params = uri;
struct ast_http_uri *urih = NULL;
int l;
@@ -987,9 +1390,14 @@ static int handle_uri(struct ast_tcptls_session_instance *ser, char *uri,
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;
}
}
@@ -1085,10 +1493,9 @@ struct ast_variable *ast_http_get_cookies(struct ast_variable *headers)
return cookies;
}
-static struct ast_http_auth *auth_create(const char *userid,
- const char *password)
+static struct ast_http_auth *auth_create(const char *userid, const char *password)
{
- RAII_VAR(struct ast_http_auth *, auth, NULL, ao2_cleanup);
+ struct ast_http_auth *auth;
size_t userid_len;
size_t password_len;
@@ -1114,7 +1521,6 @@ static struct ast_http_auth *auth_create(const char *userid,
auth->password = auth->userid + userid_len;
strcpy(auth->password, password);
- ao2_ref(auth, +1);
return auth;
}
@@ -1288,96 +1694,31 @@ int ast_http_header_match_in(const char *name, const char *expected_name,
/*! Limit the number of request headers in case the sender is being ridiculous. */
#define MAX_HTTP_REQUEST_HEADERS 100
-static void *httpd_helper_thread(void *data)
+/*!
+ * \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)
{
- char buf[4096];
- char header_line[4096];
- struct ast_tcptls_session_instance *ser = data;
- struct ast_variable *headers = NULL;
- struct ast_variable *tail = headers;
- char *uri, *method;
- enum ast_http_method http_method = AST_HTTP_UNKNOWN;
- const char *transfer_encoding;
+ struct ast_variable *tail = *headers;
int remaining_headers;
- int flags;
- struct protoent *p;
-
- if (ast_atomic_fetchadd_int(&session_count, +1) >= session_limit) {
- goto done;
- }
-
- /* 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. */
- p = getprotobyname("tcp");
- if (p) {
- int arg = 1;
- if( setsockopt(ser->fd, p->p_proto, TCP_NODELAY, (char *)&arg, sizeof(arg) ) < 0 ) {
- ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on HTTP connection: %s\n", strerror(errno));
- ast_log(LOG_WARNING, "Some HTTP requests may be slow to respond.\n");
- }
- } else {
- ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on HTTP connection, getprotobyname(\"tcp\") failed\n");
- ast_log(LOG_WARNING, "Some HTTP requests may be slow to respond.\n");
- }
-
- /* make sure socket is non-blocking */
- flags = fcntl(ser->fd, F_GETFL);
- flags |= O_NONBLOCK;
- fcntl(ser->fd, F_SETFL, flags);
-
- /* We can let the stream wait for data to arrive. */
- ast_tcptls_stream_set_exclusive_input(ser->stream_cookie, 1);
-
- ast_tcptls_stream_set_timeout_inactivity(ser->stream_cookie, session_inactivity);
+ char header_line[MAX_HTTP_LINE_LENGTH];
- if (!fgets(buf, sizeof(buf), ser->f) || feof(ser->f)) {
- goto done;
- }
-
- /* Get method */
- method = ast_skip_blanks(buf);
- 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");
- goto done;
- }
-
- /* process "Request Headers" lines */
remaining_headers = MAX_HTTP_REQUEST_HEADERS;
for (;;) {
char *name;
char *value;
- if (!fgets(header_line, sizeof(header_line), ser->f) || feof(ser->f)) {
+ if (!fgets(header_line, sizeof(header_line), ser->f)) {
ast_http_error(ser, 400, "Bad Request", "Timeout");
- goto done;
+ return -1;
}
/* Trim trailing characters */
@@ -1403,11 +1744,11 @@ static void *httpd_helper_thread(void *data)
if (!remaining_headers--) {
/* Too many headers. */
ast_http_error(ser, 413, "Request Entity Too Large", "Too many headers");
- goto done;
+ return -1;
}
- if (!headers) {
- headers = ast_variable_new(name, value, __FILE__);
- tail = headers;
+ if (!*headers) {
+ *headers = ast_variable_new(name, value, __FILE__);
+ tail = *headers;
} else {
tail->next = ast_variable_new(name, value, __FILE__);
tail = tail->next;
@@ -1417,14 +1758,84 @@ static void *httpd_helper_thread(void *data)
* Variable allocation failure.
* Try to make some room.
*/
- ast_variables_destroy(headers);
- headers = NULL;
+ ast_variables_destroy(*headers);
+ *headers = NULL;
ast_http_error(ser, 500, "Server Error", "Out of memory");
- goto done;
+ 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;
+ char request_line[MAX_HTTP_LINE_LENGTH];
+
+ if (!fgets(request_line, sizeof(request_line), ser->f)) {
+ return -1;
+ }
+
+ /* Re-initialize the request body tracking data. */
+ request = ser->private_data;
+ http_request_tracking_init(request);
+
+ /* 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;
+ }
+
+ /* 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) {
@@ -1439,22 +1850,117 @@ static void *httpd_helper_thread(void *data)
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;
+ struct protoent *p;
+ int flags;
+ int timeout;
+
+ if (!ser || !ser->f) {
+ 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");
- handle_uri(ser, uri, http_method, headers);
+ /*
+ * 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.
+ */
+ p = getprotobyname("tcp");
+ if (p) {
+ int arg = 1;
+
+ if (setsockopt(ser->fd, p->p_proto, TCP_NODELAY, (char *) &arg, sizeof(arg) ) < 0) {
+ ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on HTTP connection: %s\n", strerror(errno));
+ ast_log(LOG_WARNING, "Some HTTP requests may be slow to respond.\n");
+ }
+ } else {
+ ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on HTTP connection, getprotobyname(\"tcp\") failed\n");
+ ast_log(LOG_WARNING, "Some HTTP requests may be slow to respond.\n");
+ }
+
+ /* make sure socket is non-blocking */
+ flags = fcntl(ser->fd, F_GETFL);
+ flags |= O_NONBLOCK;
+ fcntl(ser->fd, F_SETFL, flags);
+
+ /* 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_tcptls_stream_set_exclusive_input(ser->stream_cookie, 1);
+
+ for (;;) {
+ int ch;
+
+ /* Wait for next potential HTTP request message. */
+ ast_tcptls_stream_set_timeout_inactivity(ser->stream_cookie, timeout);
+ ch = fgetc(ser->f);
+ if (ch == EOF || ungetc(ch, ser->f) == EOF) {
+ /* Between request idle timeout */
+ ast_debug(1, "HTTP idle timeout or peer closed connection.\n");
+ break;
+ }
+
+ ast_tcptls_stream_set_timeout_inactivity(ser->stream_cookie, session_inactivity);
+ if (httpd_process_request(ser) || !ser->f || feof(ser->f)) {
+ /* Break the connection or the connection closed */
+ break;
+ }
+
+ timeout = session_keep_alive;
+ if (timeout <= 0) {
+ /* Persistent connections not enabled. */
+ break;
+ }
+ }
done:
ast_atomic_fetchadd_int(&session_count, -1);
- /* clean up all the header information */
- ast_variables_destroy(headers);
-
if (ser->f) {
+ ast_debug(1, "HTTP closing session. Top level\n");
ast_tcptls_close_session_file(ser);
}
ao2_ref(ser, -1);
- ser = NULL;
return NULL;
}
@@ -1531,7 +2037,7 @@ static int __ast_http_load(int reload)
int http_tls_was_enabled = 0;
cfg = ast_config_load2("http.conf", "http", config_flags);
- if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
+ if (!cfg || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
return 0;
}
@@ -1563,66 +2069,73 @@ static int __ast_http_load(int reload)
session_limit = DEFAULT_SESSION_LIMIT;
session_inactivity = DEFAULT_SESSION_INACTIVITY;
+ session_keep_alive = DEFAULT_SESSION_KEEP_ALIVE;
+
+ 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")
+ && strcasecmp(v->name, "tlscipher")
+ && strcasecmp(v->name, "sslcipher")
+ && !ast_tls_read_conf(&http_tls_cfg, &https_desc, v->name, v->value)) {
+ continue;
+ }
- if (cfg) {
- 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")
- && strcasecmp(v->name, "tlscipher")
- && strcasecmp(v->name, "sslcipher")
- && !ast_tls_read_conf(&http_tls_cfg, &https_desc, v->name, v->value)) {
- continue;
+ if (!strcasecmp(v->name, "enabled")) {
+ enabled = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "enablestatic")) {
+ newenablestatic = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "bindport")) {
+ 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);
}
-
- if (!strcasecmp(v->name, "enabled")) {
- enabled = ast_true(v->value);
- } else if (!strcasecmp(v->name, "enablestatic")) {
- newenablestatic = ast_true(v->value);
- } else if (!strcasecmp(v->name, "bindport")) {
- 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, v->value, DEFAULT_PORT);
- }
- } else if (!strcasecmp(v->name, "bindaddr")) {
- if (!(num_addrs = ast_sockaddr_resolve(&addrs, v->value, 0, AST_AF_UNSPEC))) {
- ast_log(LOG_WARNING, "Invalid bind address %s\n", 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, "bindaddr")) {
+ if (!(num_addrs = ast_sockaddr_resolve(&addrs, v->value, 0, AST_AF_UNSPEC))) {
+ ast_log(LOG_WARNING, "Invalid bind address %s\n", 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 {
- ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
+ 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);
}
+ ast_config_destroy(cfg);
+
if (strcmp(prefix, newprefix)) {
ast_copy_string(prefix, newprefix, sizeof(prefix));
}
diff --git a/main/manager.c b/main/manager.c
index 253517d96..5fcd34716 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -6848,9 +6848,10 @@ static int generic_http_callback(struct ast_tcptls_session_instance *ser,
{
struct mansession s = { .session = NULL, .tcptls_session = ser };
struct mansession_session *session = NULL;
- uint32_t ident = 0;
+ uint32_t ident;
int blastaway = 0;
- struct ast_variable *v, *cookies, *params = get_params;
+ struct ast_variable *v;
+ struct ast_variable *params = get_params;
char template[] = "/tmp/ast-http-XXXXXX"; /* template for temporary file */
struct ast_str *http_header = NULL, *out = NULL;
struct message m = { 0 };
@@ -6859,19 +6860,10 @@ static int generic_http_callback(struct ast_tcptls_session_instance *ser,
if (method != AST_HTTP_GET && method != AST_HTTP_HEAD && method != AST_HTTP_POST) {
ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
- return -1;
+ return 0;
}
- cookies = ast_http_get_cookies(headers);
- for (v = cookies; v; v = v->next) {
- if (!strcasecmp(v->name, "mansession_id")) {
- sscanf(v->value, "%30x", &ident);
- break;
- }
- }
- if (cookies) {
- ast_variables_destroy(cookies);
- }
+ ident = ast_http_manid_from_vars(headers);
if (!(session = find_session(ident, 1))) {
@@ -6880,18 +6872,21 @@ static int generic_http_callback(struct ast_tcptls_session_instance *ser,
* While it is not in the list we don't need any locking
*/
if (!(session = build_mansession(remote_address))) {
+ ast_http_request_close_on_completion(ser);
ast_http_error(ser, 500, "Server Error", "Internal Server Error (out of memory)\n");
- return -1;
+ return 0;
}
ao2_lock(session);
session->send_events = 0;
session->inuse = 1;
- /*!\note There is approximately a 1 in 1.8E19 chance that the following
+ /*!
+ * \note There is approximately a 1 in 1.8E19 chance that the following
* calculation will produce 0, which is an invalid ID, but due to the
* properties of the rand() function (and the constantcy of s), that
* won't happen twice in a row.
*/
- while ((session->managerid = ast_random() ^ (unsigned long) session) == 0);
+ while ((session->managerid = ast_random() ^ (unsigned long) session) == 0) {
+ }
session->last_ev = grab_last();
AST_LIST_HEAD_INIT_NOLOCK(&session->datastores);
}
@@ -6903,6 +6898,7 @@ static int generic_http_callback(struct ast_tcptls_session_instance *ser,
ast_mutex_init(&s.lock);
if (http_header == NULL || out == NULL) {
+ ast_http_request_close_on_completion(ser);
ast_http_error(ser, 500, "Server Error", "Internal Server Error (ast_str_create() out of memory)\n");
goto generic_callback_out;
}
@@ -6924,19 +6920,22 @@ static int generic_http_callback(struct ast_tcptls_session_instance *ser,
if (method == AST_HTTP_POST) {
params = ast_http_get_post_vars(ser, headers);
- }
-
- if (!params) {
- switch (errno) {
- case EFBIG:
- ast_http_send(ser, AST_HTTP_POST, 413, "Request Entity Too Large", NULL, NULL, 0, 0);
- break;
- case ENOMEM:
- ast_http_send(ser, AST_HTTP_POST, 500, "Internal Server Error", NULL, NULL, 0, 0);
- break;
- case EIO:
- ast_http_send(ser, AST_HTTP_POST, 400, "Bad Request", NULL, NULL, 0, 0);
- break;
+ if (!params) {
+ switch (errno) {
+ case EFBIG:
+ ast_http_error(ser, 413, "Request Entity Too Large", "Body too large");
+ close_mansession_file(&s);
+ goto generic_callback_out;
+ case ENOMEM:
+ ast_http_request_close_on_completion(ser);
+ ast_http_error(ser, 500, "Server Error", "Out of memory");
+ close_mansession_file(&s);
+ goto generic_callback_out;
+ case EIO:
+ ast_http_error(ser, 400, "Bad Request", "Error parsing request body");
+ close_mansession_file(&s);
+ goto generic_callback_out;
+ }
}
}
@@ -6973,7 +6972,6 @@ static int generic_http_callback(struct ast_tcptls_session_instance *ser,
ast_str_append(&http_header, 0,
"Content-type: text/%s\r\n"
- "Cache-Control: no-cache;\r\n"
"Set-Cookie: mansession_id=\"%08x\"; Version=1; Max-Age=%d\r\n"
"Pragma: SuppressEvents\r\n",
contenttype[format],
@@ -7038,7 +7036,8 @@ static int generic_http_callback(struct ast_tcptls_session_instance *ser,
ao2_unlock(session);
ast_http_send(ser, method, 200, NULL, http_header, out, 0, 0);
- http_header = out = NULL;
+ http_header = NULL;
+ out = NULL;
generic_callback_out:
ast_mutex_destroy(&s.lock);
@@ -7073,7 +7072,7 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
struct ast_variable *v, *params = get_params;
char template[] = "/tmp/ast-http-XXXXXX"; /* template for temporary file */
struct ast_str *http_header = NULL, *out = NULL;
- size_t result_size = 512;
+ size_t result_size;
struct message m = { 0 };
unsigned int idx;
size_t hdrlen;
@@ -7093,7 +7092,7 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
if (method != AST_HTTP_GET && method != AST_HTTP_HEAD && method != AST_HTTP_POST) {
ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
- return -1;
+ return 0;
}
/* Find "Authorization: " header */
@@ -7109,8 +7108,9 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
/* Digest found - parse */
if (ast_string_field_init(&d, 128)) {
+ ast_http_request_close_on_completion(ser);
ast_http_error(ser, 500, "Server Error", "Internal Server Error (out of memory)\n");
- return -1;
+ return 0;
}
if (ast_parse_digest(v->value, &d, 0, 1)) {
@@ -7137,8 +7137,9 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
if (user->acl && !ast_apply_acl(user->acl, remote_address, "Manager User ACL:")) {
AST_RWLIST_UNLOCK(&users);
ast_log(LOG_NOTICE, "%s failed to pass IP ACL as '%s'\n", ast_sockaddr_stringify_addr(&session->addr), d.username);
+ ast_http_request_close_on_completion(ser);
ast_http_error(ser, 403, "Permission denied", "Permission denied\n");
- return -1;
+ return 0;
}
/* --- We have auth, so check it */
@@ -7187,8 +7188,9 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
* While it is not in the list we don't need any locking
*/
if (!(session = build_mansession(remote_address))) {
+ ast_http_request_close_on_completion(ser);
ast_http_error(ser, 500, "Server Error", "Internal Server Error (out of memory)\n");
- return -1;
+ return 0;
}
ao2_lock(session);
@@ -7268,6 +7270,23 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
if (method == AST_HTTP_POST) {
params = ast_http_get_post_vars(ser, headers);
+ if (!params) {
+ switch (errno) {
+ case EFBIG:
+ ast_http_error(ser, 413, "Request Entity Too Large", "Body too large");
+ close_mansession_file(&s);
+ goto auth_callback_out;
+ case ENOMEM:
+ ast_http_request_close_on_completion(ser);
+ ast_http_error(ser, 500, "Server Error", "Out of memory");
+ close_mansession_file(&s);
+ goto auth_callback_out;
+ case EIO:
+ ast_http_error(ser, 400, "Bad Request", "Error parsing request body");
+ close_mansession_file(&s);
+ goto auth_callback_out;
+ }
+ }
}
for (v = params; v && m.hdrcount < ARRAY_LEN(m.headers); v = v->next) {
@@ -7296,15 +7315,14 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
m.headers[idx] = NULL;
}
- if (s.f) {
- result_size = ftell(s.f); /* Calculate approx. size of result */
- }
+ result_size = ftell(s.f); /* Calculate approx. size of result */
http_header = ast_str_create(80);
out = ast_str_create(result_size * 2 + 512);
-
if (http_header == NULL || out == NULL) {
+ ast_http_request_close_on_completion(ser);
ast_http_error(ser, 500, "Server Error", "Internal Server Error (ast_str_create() out of memory)\n");
+ close_mansession_file(&s);
goto auth_callback_out;
}
@@ -7334,7 +7352,8 @@ static int auth_http_callback(struct ast_tcptls_session_instance *ser,
}
ast_http_send(ser, method, 200, NULL, http_header, out, 0, 0);
- http_header = out = NULL;
+ http_header = NULL;
+ out = NULL;
auth_callback_out:
ast_mutex_destroy(&s.lock);
diff --git a/main/tcptls.c b/main/tcptls.c
index 609023440..012376cfe 100644
--- a/main/tcptls.c
+++ b/main/tcptls.c
@@ -543,6 +543,7 @@ static void session_instance_destructor(void *obj)
i->stream_cookie = NULL;
}
ast_free(i->overflow_buf);
+ ao2_cleanup(i->private_data);
}
/*! \brief