diff --git a/UPGRADE.txt b/UPGRADE.txt index abd435f8de..87256ef25a 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -21,7 +21,7 @@ === =========================================================== -From 12.3.0 to 12.4.0: +From 12.3.2 to 12.4.0: - The safe_asterisk script was previously not installed on top of an existing version. This caused bug-fixes in that script not to be deployed. If your @@ -41,6 +41,11 @@ From 12.3.0 to 12.4.0: client is slow to process the received data, the socket may be disconnected. In such cases, it may be necessary to adjust this value. Default is 100 ms. + - Added support for persistent HTTP connections. To enable persistent + HTTP connections configure the keep alive time between HTTP requests. The + keep alive time between HTTP requests is configured in http.conf with the + session_keep_alive parameter. + - Added a 'force_avp' option to chan_pjsip which will force the usage of 'RTP/AVP', 'RTP/AVPF', 'RTP/SAVP', or 'RTP/SAVPF' as the media transport type in SDP offers depending on settings, even when DTLS is used for media diff --git a/configs/http.conf.sample b/configs/http.conf.sample index 98c672b2ae..c59a67e1c2 100644 --- a/configs/http.conf.sample +++ b/configs/http.conf.sample @@ -45,7 +45,13 @@ bindaddr=127.0.0.1 ; Default: 30000 ;session_inactivity=30000 ; -; Whether Asterisk should serve static content from http-static +; session_keep_alive specifies the number of milliseconds to wait for +; the next HTTP request over a persistent connection. +; +; Default: 0 (Disables persistent HTTP connections.) +;session_keep_alive=15000 +; +; Whether Asterisk should serve static content from static-http ; Default is no. ; ;enablestatic=yes @@ -80,6 +86,9 @@ bindaddr=127.0.0.1 ; ;[post_mappings] ; +; NOTE: You need a valid HTTP AMI mansession_id cookie with the manager +; config permission to POST files. +; ; In this example, if the prefix option is set to "asterisk", then using the ; POST URL: /asterisk/uploads will put files in /var/lib/asterisk/uploads/. ;uploads = /var/lib/asterisk/uploads/ diff --git a/include/asterisk/http.h b/include/asterisk/http.h index 0642cfa9bf..ed0e0dfb6f 100644 --- a/include/asterisk/http.h +++ b/include/asterisk/http.h @@ -66,28 +66,34 @@ enum ast_http_method { struct ast_http_uri; -/*! \brief HTTP Callbacks +/*! + * \brief HTTP Callbacks * - * \note The callback function receives server instance, uri, http method, - * get method (if present in URI), and http headers as arguments and should - * use the ast_http_send() function for sending content allocated with ast_str - * and/or content from an opened file descriptor. + * \param ser TCP/TLS session object + * \param urih Registered URI handler struct for the URI. + * \param uri Remaining request URI path (also with the get_params removed). + * \param method enum ast_http_method GET, POST, etc. + * \param get_params URI argument list passed with the HTTP request. + * \param headers HTTP request header-name/value pair list + * + * \note Should use the ast_http_send() function for sending content + * allocated with ast_str and/or content from an opened file descriptor. * * Status and status text should be sent as arguments to the ast_http_send() * function to reflect the status of the request (200 or 304, for example). * Content length is calculated by ast_http_send() automatically. * - * Static content may be indicated to the ast_http_send() function, to indicate - * that it may be cached. + * Static content may be indicated to the ast_http_send() function, + * to indicate that it may be cached. * - * \verbatim - * The return value may include additional headers at the front and MUST - * include a blank line with \r\n to provide separation between user headers - * and content (even if no content is specified) - * \endverbatim + * For a need authentication response, the ast_http_auth() function + * should be used. * - * For an error response, the ast_http_error() function may be used. -*/ + * For an error response, the ast_http_error() function should be used. + * + * \retval 0 Continue and process the next HTTP request. + * \retval -1 Fatal HTTP connection error. Force the HTTP connection closed. + */ typedef int (*ast_http_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_params, struct ast_variable *headers); /*! \brief Definition of a URI handler */ @@ -141,26 +147,30 @@ void ast_http_uri_unlink(struct ast_http_uri *urihandler); /*! \brief Unregister all handlers with matching key */ void ast_http_uri_unlink_all_with_key(const char *key); -/*!\brief Return http method name string +/*! + * \brief Return http method name string * \since 1.8 */ const char *ast_get_http_method(enum ast_http_method method) attribute_pure; -/*!\brief Return mime type based on extension +/*! + * \brief Return mime type based on extension * \param ftype filename extension * \return String containing associated MIME type * \since 1.8 */ const char *ast_http_ftype2mtype(const char *ftype) attribute_pure; -/*!\brief Return manager id, if exist, from request headers +/*! + * \brief Return manager id, if exist, from request headers * \param headers List of HTTP headers * \return 32-bit associated manager session identifier * \since 1.8 */ uint32_t ast_http_manid_from_vars(struct ast_variable *headers) attribute_pure; -/*! \brief Generic function for sending http/1.1 response. +/*! + * \brief Generic function for sending HTTP/1.1 response. * \param ser TCP/TLS session object * \param method GET/POST/HEAD * \param status_code HTTP response code (200/401/403/404/500) @@ -186,12 +196,14 @@ uint32_t ast_http_manid_from_vars(struct ast_variable *headers) attribute_pure; * * \since 1.8 */ -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, unsigned int static_content); +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); -/*!\brief Send http "401 Unauthorized" response and close socket */ +/*! \brief Send http "401 Unauthorized" response 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); -/*!\brief Send HTTP error message and close socket */ +/*! \brief Send HTTP error message and close socket */ void ast_http_error(struct ast_tcptls_session_instance *ser, int status, const char *title, const char *text); /*! @@ -202,8 +214,42 @@ void ast_http_error(struct ast_tcptls_session_instance *ser, int status, const c */ void ast_http_prefix(char *buf, int len); +/*! + * \brief Request the HTTP connection be closed after this HTTP request. + * \since 12.4.0 + * + * \param ser HTTP TCP/TLS session object. + * + * \note Call before ast_http_error() to make the connection close. + * + * \return Nothing + */ +void ast_http_request_close_on_completion(struct ast_tcptls_session_instance *ser); -/*!\brief Get post variables from client Request Entity-Body, if content type is application/x-www-form-urlencoded. +/*! + * \brief Update the body read success status. + * \since 12.4.0 + * + * \param ser HTTP TCP/TLS session object. + * \param read_success TRUE if body was read successfully. + * + * \return Nothing + */ +void ast_http_body_read_status(struct ast_tcptls_session_instance *ser, int read_success); + +/*! + * \brief Read and discard any unread HTTP request body. + * \since 12.4.0 + * + * \param ser HTTP TCP/TLS session object. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int ast_http_body_discard(struct ast_tcptls_session_instance *ser); + +/*! + * \brief Get post variables from client Request Entity-Body, if content type is application/x-www-form-urlencoded. * \param ser TCP/TLS session object * \param headers List of HTTP headers * \return List of variables within the POST body @@ -214,7 +260,8 @@ struct ast_variable *ast_http_get_post_vars(struct ast_tcptls_session_instance * struct ast_json; -/*!\brief Get JSON from client Request Entity-Body, if content type is +/*! + * \brief Get JSON from client Request Entity-Body, if content type is * application/json. * \param ser TCP/TLS session object * \param headers List of HTTP headers diff --git a/include/asterisk/tcptls.h b/include/asterisk/tcptls.h index 3356a92ccd..0e8d9d042f 100644 --- a/include/asterisk/tcptls.h +++ b/include/asterisk/tcptls.h @@ -210,7 +210,6 @@ struct ast_tcptls_session_instance { FILE *f; /*!< fopen/funopen result */ int fd; /*!< the socket returned by accept() */ SSL *ssl; /*!< ssl state */ -/* iint (*ssl_setup)(SSL *); */ int client; struct ast_sockaddr remote_address; struct ast_tcptls_session_args *parent; @@ -222,6 +221,8 @@ struct ast_tcptls_session_instance { struct ast_str *overflow_buf; /*! ao2 FILE stream cookie object associated with f. */ struct ast_tcptls_stream *stream_cookie; + /*! ao2 object private data of parent->worker_fn */ + void *private_data; }; #if defined(HAVE_FUNOPEN) diff --git a/main/http.c b/main/http.c index 9eac086e6a..9ac8cec26a 100644 --- a/main/http.c +++ b/main/http.c @@ -71,7 +71,26 @@ 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 0 + +/*! 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) */ /* See http.h for more information about the SSL implementation */ #if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE)) @@ -80,6 +99,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") 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; @@ -231,7 +251,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 @@ -300,9 +320,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" @@ -323,11 +346,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, @@ -340,11 +364,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, @@ -397,23 +424,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 */ @@ -427,20 +494,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" + "%s" "%s" "Content-Length: %d\r\n" - "%s" "\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 */ @@ -448,33 +517,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) @@ -485,6 +556,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; } @@ -509,10 +584,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); @@ -521,6 +594,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; } @@ -536,14 +613,13 @@ void ast_http_error(struct ast_tcptls_session_instance *ser, int status_code, co "
\r\n" "
Asterisk Server
\r\n" "\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, @@ -607,8 +683,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. * @@ -617,8 +691,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; @@ -660,29 +733,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) { @@ -690,11 +769,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) { @@ -706,13 +950,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; @@ -732,11 +984,152 @@ static int chunked_atoh(const char *s, int len) return -1; } +/*! + * \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 @@ -744,122 +1137,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; @@ -878,23 +1279,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; @@ -913,7 +1312,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 */ @@ -921,12 +1320,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; } @@ -955,7 +1358,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; @@ -992,9 +1395,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; } } @@ -1135,10 +1543,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; @@ -1164,7 +1571,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; } @@ -1231,96 +1637,31 @@ struct ast_http_auth *ast_http_get_auth(struct ast_variable *headers) /*! 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; + char header_line[MAX_HTTP_LINE_LENGTH]; - 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); - - 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 */ @@ -1346,11 +1687,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; @@ -1360,14 +1701,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) { @@ -1382,22 +1793,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."); - goto done; + return -1; } - handle_uri(ser, uri, http_method, headers); + 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"); + + /* + * 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; } @@ -1474,7 +1980,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; } @@ -1506,66 +2012,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; - 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, 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 { - ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name); - } + 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; } - ast_config_destroy(cfg); + 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); + } + } 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, "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)); } diff --git a/main/manager.c b/main/manager.c index 076db1e6e0..7b8fe2f85f 100644 --- a/main/manager.c +++ b/main/manager.c @@ -6820,9 +6820,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 }; @@ -6831,19 +6832,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))) { @@ -6852,18 +6844,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); } @@ -6875,6 +6870,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; } @@ -6896,19 +6892,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; + } } } @@ -6945,7 +6944,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], @@ -7010,7 +7008,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); @@ -7045,7 +7044,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; @@ -7065,7 +7064,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 */ @@ -7081,8 +7080,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)) { @@ -7109,8 +7109,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 */ @@ -7159,8 +7160,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); @@ -7240,6 +7242,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) { @@ -7268,15 +7287,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; } @@ -7306,7 +7324,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 912b33e0cd..fc55fe2278 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 diff --git a/res/res_ari.c b/res/res_ari.c index acdbbfe9aa..0d80babbb2 100644 --- a/res/res_ari.c +++ b/res/res_ari.c @@ -262,6 +262,7 @@ void ast_ari_response_error(struct ast_ari_response *response, va_start(ap, message_fmt); message = ast_json_vstringf(message_fmt, ap); + va_end(ap); response->message = ast_json_pack("{s: o}", "message", ast_json_ref(message)); response->response_code = response_code; @@ -884,23 +885,25 @@ static int ast_ari_callback(struct ast_tcptls_session_instance *ser, * with us. */ post_vars = ast_http_get_post_vars(ser, headers); - if (get_params == NULL) { + if (!post_vars) { switch (errno) { case EFBIG: ast_ari_response_error(&response, 413, "Request Entity Too Large", "Request body too large"); - break; + goto request_failed; case ENOMEM: ast_ari_response_error(&response, 500, "Internal Server Error", "Error processing request"); - break; + goto request_failed; case EIO: ast_ari_response_error(&response, 400, "Bad Request", "Error parsing request body"); - break; + goto request_failed; } + } + if (get_params == NULL) { get_params = post_vars; } else if (get_params && post_vars) { /* Has both post_vars and get_params */ @@ -963,6 +966,7 @@ static int ast_ari_callback(struct ast_tcptls_session_instance *ser, return 0; } +request_failed: /* If you explicitly want to have no content, set message to * ast_json_null(). */ diff --git a/res/res_http_post.c b/res/res_http_post.c index c05c228e1e..03d407f258 100644 --- a/res/res_http_post.c +++ b/res/res_http_post.c @@ -213,7 +213,7 @@ static int find_sequence(char * inbuf, int inlen, char * matchbuf, int matchlen) * This function has two modes. The first to find a boundary marker. The * second is to find the filename immediately after the boundary. */ -static int readmimefile(FILE * fin, FILE * fout, char * boundary, int contentlen) +static int readmimefile(FILE *fin, FILE *fout, char *boundary, int contentlen) { int find_filename = 0; char buf[4096]; @@ -313,53 +313,41 @@ static int readmimefile(FILE * fin, FILE * fout, char * boundary, int contentlen static int http_post_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_variable *var, *cookies; - unsigned long ident = 0; + struct ast_variable *var; + uint32_t ident; FILE *f; int content_len = 0; struct ast_str *post_dir; GMimeMessage *message; - char * boundary_marker = NULL; + char *boundary_marker = NULL; if (method != AST_HTTP_POST) { ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method"); - return -1; - } - - if (!astman_is_authed(ast_http_manid_from_vars(headers))) { - ast_http_error(ser, 403, "Access Denied", "Sorry, I cannot let you do that, Dave."); - return -1; + return 0; } if (!urih) { ast_http_error(ser, 400, "Missing URI handle", "There was an error parsing the request"); - return -1; + return 0; } - cookies = ast_http_get_cookies(headers); - for (var = cookies; var; var = var->next) { - if (!strcasecmp(var->name, "mansession_id")) { - sscanf(var->value, "%30lx", &ident); - break; - } - } - if (cookies) { - ast_variables_destroy(cookies); + ident = ast_http_manid_from_vars(headers); + if (!ident || !astman_is_authed(ident)) { + ast_http_request_close_on_completion(ser); + ast_http_error(ser, 403, "Access Denied", "Sorry, I cannot let you do that, Dave."); + return 0; } - if (ident == 0) { - ast_http_error(ser, 401, "Unauthorized", "You are not authorized to make this request."); - return -1; - } if (!astman_verify_session_writepermissions(ident, EVENT_FLAG_CONFIG)) { + ast_http_request_close_on_completion(ser); ast_http_error(ser, 401, "Unauthorized", "You are not authorized to make this request."); - return -1; + return 0; } if (!(f = tmpfile())) { ast_log(LOG_ERROR, "Could not create temp file.\n"); ast_http_error(ser, 500, "Internal server error", "Could not create temp file."); - return -1; + return 0; } for (var = headers; var; var = var->next) { @@ -369,8 +357,9 @@ static int http_post_callback(struct ast_tcptls_session_instance *ser, const str if ((sscanf(var->value, "%30u", &content_len)) != 1) { ast_log(LOG_ERROR, "Invalid Content-Length in POST request!\n"); fclose(f); - ast_http_error(ser, 500, "Internal server error", "Invalid Content-Length in POST request!"); - return -1; + ast_http_request_close_on_completion(ser); + ast_http_error(ser, 400, "Bad Request", "Invalid Content-Length in POST request!"); + return 0; } ast_debug(1, "Got a Content-Length of %d\n", content_len); } else if (!strcasecmp(var->name, "Content-Type")) { @@ -380,42 +369,50 @@ static int http_post_callback(struct ast_tcptls_session_instance *ser, const str } } } - fprintf(f, "\r\n"); + /* + * Always mark the body read as failed. + * + * XXX Should change readmimefile() to always be sure to read + * the entire body so we can update the read status and + * potentially keep the connection open. + */ + ast_http_body_read_status(ser, 0); + if (0 > readmimefile(ser->f, f, boundary_marker, content_len)) { ast_debug(1, "Cannot find boundary marker in POST request.\n"); fclose(f); - - return -1; + ast_http_error(ser, 400, "Bad Request", "Cannot find boundary marker in POST request."); + return 0; } if (fseek(f, SEEK_SET, 0)) { ast_log(LOG_ERROR, "Failed to seek temp file back to beginning.\n"); fclose(f); ast_http_error(ser, 500, "Internal server error", "Failed to seek temp file back to beginning."); - return -1; + return 0; } post_dir = urih->data; message = parse_message(f); /* Takes ownership and will close f */ - if (!message) { ast_log(LOG_ERROR, "Error parsing MIME data\n"); - ast_http_error(ser, 400, "Bad Request", "The was an error parsing the request."); - return -1; + ast_http_error(ser, 400, "Bad Request", "There was an error parsing the request."); + return 0; } if (!process_message(message, ast_str_buffer(post_dir))) { ast_log(LOG_ERROR, "Invalid MIME data, found no parts!\n"); g_object_unref(message); - ast_http_error(ser, 400, "Bad Request", "The was an error parsing the request."); - return -1; + ast_http_error(ser, 400, "Bad Request", "There was an error parsing the request."); + return 0; } g_object_unref(message); + /* XXX Passing 200 to the error response routine? */ ast_http_error(ser, 200, "OK", "File successfully uploaded."); return 0; } @@ -427,7 +424,7 @@ static int __ast_http_post_load(int reload) struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 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; } @@ -435,45 +432,43 @@ static int __ast_http_post_load(int reload) ast_http_uri_unlink_all_with_key(__FILE__); } - if (cfg) { - for (v = ast_variable_browse(cfg, "general"); v; v = v->next) { - if (!strcasecmp(v->name, "prefix")) { - ast_copy_string(prefix, v->value, sizeof(prefix)); - if (prefix[strlen(prefix)] == '/') { - prefix[strlen(prefix)] = '\0'; - } + for (v = ast_variable_browse(cfg, "general"); v; v = v->next) { + if (!strcasecmp(v->name, "prefix")) { + ast_copy_string(prefix, v->value, sizeof(prefix)); + if (prefix[strlen(prefix)] == '/') { + prefix[strlen(prefix)] = '\0'; } } - - for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next) { - struct ast_http_uri *urih; - struct ast_str *ds; - - if (!(urih = ast_calloc(sizeof(*urih), 1))) { - ast_config_destroy(cfg); - return -1; - } - - if (!(ds = ast_str_create(32))) { - ast_free(urih); - ast_config_destroy(cfg); - return -1; - } - - urih->description = ast_strdup("HTTP POST mapping"); - urih->uri = ast_strdup(v->name); - ast_str_set(&ds, 0, "%s", v->value); - urih->data = ds; - urih->has_subtree = 0; - urih->callback = http_post_callback; - urih->key = __FILE__; - urih->mallocd = urih->dmallocd = 1; - - ast_http_uri_link(urih); - } - - ast_config_destroy(cfg); } + + for (v = ast_variable_browse(cfg, "post_mappings"); v; v = v->next) { + struct ast_http_uri *urih; + struct ast_str *ds; + + if (!(urih = ast_calloc(sizeof(*urih), 1))) { + ast_config_destroy(cfg); + return -1; + } + + if (!(ds = ast_str_create(32))) { + ast_free(urih); + ast_config_destroy(cfg); + return -1; + } + + urih->description = ast_strdup("HTTP POST mapping"); + urih->uri = ast_strdup(v->name); + ast_str_set(&ds, 0, "%s", v->value); + urih->data = ds; + urih->has_subtree = 0; + urih->callback = http_post_callback; + urih->key = __FILE__; + urih->mallocd = urih->dmallocd = 1; + + ast_http_uri_link(urih); + } + + ast_config_destroy(cfg); return 0; } diff --git a/res/res_http_websocket.c b/res/res_http_websocket.c index 23fe92075b..4d822208a3 100644 --- a/res/res_http_websocket.c +++ b/res/res_http_websocket.c @@ -587,6 +587,19 @@ static struct websocket_protocol *one_protocol( return ao2_callback(server->protocols, OBJ_NOLOCK, NULL, NULL); } +static void websocket_bad_request(struct ast_tcptls_session_instance *ser) +{ + struct ast_str *http_header = ast_str_create(64); + + if (!http_header) { + ast_http_request_close_on_completion(ser); + ast_http_error(ser, 500, "Server Error", "Out of memory"); + return; + } + ast_str_set(&http_header, 0, "Sec-WebSocket-Version: 7, 8, 13\r\n"); + ast_http_send(ser, AST_HTTP_UNKNOWN, 400, "Bad Request", http_header, NULL, 0, 0); +} + int AST_OPTIONAL_API_NAME(ast_websocket_uri_cb)(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_variable *v; @@ -601,7 +614,7 @@ int AST_OPTIONAL_API_NAME(ast_websocket_uri_cb)(struct ast_tcptls_session_instan /* Upgrade requests are only permitted on GET methods */ if (method != AST_HTTP_GET) { ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method"); - return -1; + return 0; } server = urih->data; @@ -631,7 +644,7 @@ int AST_OPTIONAL_API_NAME(ast_websocket_uri_cb)(struct ast_tcptls_session_instan ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - did not request WebSocket\n", ast_sockaddr_stringify(&ser->remote_address)); ast_http_error(ser, 426, "Upgrade Required", NULL); - return -1; + return 0; } else if (ast_strlen_zero(requested_protocols)) { /* If there's only a single protocol registered, and the * client doesn't specify what protocol it's using, go ahead @@ -641,17 +654,15 @@ int AST_OPTIONAL_API_NAME(ast_websocket_uri_cb)(struct ast_tcptls_session_instan /* Multiple registered subprotocols; client must specify */ ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - no protocols requested\n", ast_sockaddr_stringify(&ser->remote_address)); - fputs("HTTP/1.1 400 Bad Request\r\n" - "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f); - return -1; + websocket_bad_request(ser); + return 0; } } else if (key1 && key2) { /* Specification defined in http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 and * http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 -- not currently supported*/ ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - unsupported version '00/76' chosen\n", ast_sockaddr_stringify(&ser->remote_address)); - fputs("HTTP/1.1 400 Bad Request\r\n" - "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f); + websocket_bad_request(ser); return 0; } @@ -664,8 +675,7 @@ int AST_OPTIONAL_API_NAME(ast_websocket_uri_cb)(struct ast_tcptls_session_instan if (!protocol_handler) { ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - no protocols out of '%s' supported\n", ast_sockaddr_stringify(&ser->remote_address), protos); - fputs("HTTP/1.1 400 Bad Request\r\n" - "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f); + websocket_bad_request(ser); return 0; } @@ -680,8 +690,13 @@ int AST_OPTIONAL_API_NAME(ast_websocket_uri_cb)(struct ast_tcptls_session_instan combined_length = (key ? strlen(key) : 0) + strlen(WEBSOCKET_GUID) + 1; if (!key || combined_length > 8192) { /* no stack overflows please */ - fputs("HTTP/1.1 400 Bad Request\r\n" - "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f); + websocket_bad_request(ser); + ao2_ref(protocol_handler, -1); + return 0; + } + + if (ast_http_body_discard(ser)) { + websocket_bad_request(ser); ao2_ref(protocol_handler, -1); return 0; } @@ -689,8 +704,7 @@ int AST_OPTIONAL_API_NAME(ast_websocket_uri_cb)(struct ast_tcptls_session_instan if (!(session = ao2_alloc(sizeof(*session), session_destroy_fn))) { ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted\n", ast_sockaddr_stringify(&ser->remote_address)); - fputs("HTTP/1.1 400 Bad Request\r\n" - "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f); + websocket_bad_request(ser); ao2_ref(protocol_handler, -1); return 0; } @@ -729,8 +743,7 @@ int AST_OPTIONAL_API_NAME(ast_websocket_uri_cb)(struct ast_tcptls_session_instan /* Specification defined in http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 or completely unknown */ ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - unsupported version '%d' chosen\n", ast_sockaddr_stringify(&ser->remote_address), version ? version : 75); - fputs("HTTP/1.1 400 Bad Request\r\n" - "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f); + websocket_bad_request(ser); ao2_ref(protocol_handler, -1); return 0; } @@ -739,8 +752,7 @@ int AST_OPTIONAL_API_NAME(ast_websocket_uri_cb)(struct ast_tcptls_session_instan if (setsockopt(ser->fd, SOL_SOCKET, SO_KEEPALIVE, &flags, sizeof(flags))) { ast_log(LOG_WARNING, "WebSocket connection from '%s' could not be accepted - failed to enable keepalive\n", ast_sockaddr_stringify(&ser->remote_address)); - fputs("HTTP/1.1 400 Bad Request\r\n" - "Sec-WebSocket-Version: 7, 8, 13\r\n\r\n", ser->f); + websocket_bad_request(ser); ao2_ref(session, -1); ao2_ref(protocol_handler, -1); return 0; @@ -757,6 +769,7 @@ int AST_OPTIONAL_API_NAME(ast_websocket_uri_cb)(struct ast_tcptls_session_instan session->secure = ser->ssl ? 1 : 0; /* Give up ownership of the socket and pass it to the protocol handler */ + ast_tcptls_stream_set_exclusive_input(ser->stream_cookie, 0); protocol_handler->callback(session, get_vars, headers); ao2_ref(protocol_handler, -1); diff --git a/res/res_phoneprov.c b/res/res_phoneprov.c index 6c7e3835b8..5fda0a945b 100644 --- a/res/res_phoneprov.c +++ b/res/res_phoneprov.c @@ -428,7 +428,7 @@ static int phoneprov_callback(struct ast_tcptls_session_instance *ser, const str 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 (!(route = ao2_find(http_routes, &search_route, OBJ_POINTER))) { @@ -542,12 +542,12 @@ static int phoneprov_callback(struct ast_tcptls_session_instance *ser, const str out404: ast_http_error(ser, 404, "Not Found", "Nothing to see here. Move along."); - return -1; + return 0; out500: route = unref_route(route); ast_http_error(ser, 500, "Internal Error", "An internal error has occured."); - return -1; + return 0; } /*! \brief Build a route structure and add it to the list of available http routes