From: Richard Mudgett Date: Thu, 3 Jul 2014 16:16:16 +0000 (+0000) Subject: HTTP: Add persistent connection support. X-Git-Tag: 12.4.0-rc1~12 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=15061dadb9c82c17786dcb5f755eb687a9b3cda3;p=thirdparty%2Fasterisk.git HTTP: Add persistent connection support. Persistent HTTP connection support is needed due to the increased usage of the Asterisk core HTTP transport and the frequency at which REST API calls are going to be issued. * Add http.conf session_keep_alive option to enable persistent connections. * Parse and discard optional chunked body extension information and trailing request headers. * Increased the maximum application/json and application/x-www-form-urlencoded body size allowed to 4k. The previous 1k was kind of small. * Removed a couple inlined versions of ast_http_manid_from_vars() by calling the function. manager.c:generic_http_callback() and res_http_post.c:http_post_callback() * Add missing va_end() in ast_ari_response_error(). * Eliminated unnecessary RAII_VAR() use in http.c:auth_create(). ASTERISK-23552 #close Reported by: Scott Griepentrog Review: https://reviewboard.asterisk.org/r/3691/ git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/12@417880 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- 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 + * + * \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 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. + * \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. + * + * For a need authentication response, the ast_http_auth() function + * should be used. * - * \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 an error response, the ast_http_error() function should be used. * - * For an error response, the ast_http_error() function may 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 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 Get post variables from client Request Entity-Body, if content type is application/x-www-form-urlencoded. +/*! + * \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" - "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 */ @@ -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; - - 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); + char header_line[MAX_HTTP_LINE_LENGTH]; - 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."); + 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"); + + /* + * 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); - handle_uri(ser, uri, http_method, headers); + /* 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; + + 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 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; + 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; + if (!(urih = ast_calloc(sizeof(*urih), 1))) { + ast_config_destroy(cfg); + return -1; + } - ast_http_uri_link(urih); + if (!(ds = ast_str_create(32))) { + ast_free(urih); + ast_config_destroy(cfg); + return -1; } - ast_config_destroy(cfg); + 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