Le 10/06/2021 à 14:07, Moritz Muehlenhoff a écrit : > On Thu, Jun 10, 2021 at 02:02:05PM +0200, Yadd wrote: >> Le 10/06/2021 à 12:16, Yadd a écrit : >>> Le 10/06/2021 à 11:51, Yadd a écrit : >>>> Hi, >>>> >>>> Hopefully there is an available-and-simple fix for #989562 >>>> (CVE-2021-31618) ! >>>> >>>> Cheers, >>>> Yadd >>> >>> Here is the debdiff >> >> Updated with all CVE fixes. Thanks to security-tracker and its >> maintainers ;-) >> >> Cheers, >> Yadd > >> diff --git a/debian/changelog b/debian/changelog >> index b6096f7d..41cb8b28 100644 >> --- a/debian/changelog >> +++ b/debian/changelog >> @@ -1,3 +1,12 @@ >> +apache2 (2.4.38-3+deb10u5) buster-security; urgency=medium >> + >> + * Fix "NULL pointer dereference on specially crafted HTTP/2 request" >> + (Closes: #989562, CVE-2021-31618) >> + * Fix various low security issues (Closes: CVE-2020-13950, CVE-2020-35452, >> + CVE-2021-26690, CVE-2021-26691, CVE-2021-30641) > > There's also https://security-tracker.debian.org/tracker/CVE-2019-17567 > https://www.openwall.com/lists/oss-security/2021/06/10/2 > > The CVE ID is from 2019, but it got public yesterday with the other fixes. > > Cheers, > Moritz
Hi, this adds a non trivial patch (attached debdiff shows the difference with 2.4.46-6 which is already proposed in unblock issue (#989683). I had to modify significantly upstream patch. As proposed earlier, I think it should be more safe to upload Apache 2.4.48 in Bullseye instead of this increasingly deviant hybrid (already 7 CVEs patches!). @release-team: please consider this new debdiff as a pre-aproval for 2.4.46-7 Cheers, Yadd
diff --git a/debian/changelog b/debian/changelog index fa775057..25650ac5 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +apache2 (2.4.46-7) UNRELEASED; urgency=medium + + * Fix mod_proxy_wstunnel to avoid HTTP validation bypass + (Closes: CVE-2019-17567) + + -- Yadd <y...@debian.org> Thu, 10 Jun 2021 17:19:55 +0200 + apache2 (2.4.46-6) unstable; urgency=medium * Fix various low security issues (Closes: CVE-2020-13950, CVE-2020-35452, diff --git a/debian/patches/CVE-2019-17567.patch b/debian/patches/CVE-2019-17567.patch new file mode 100644 index 00000000..0d9e3d51 --- /dev/null +++ b/debian/patches/CVE-2019-17567.patch @@ -0,0 +1,1854 @@ +Description: mod_proxy_wstunnel tunneling of non Upgraded connections + mod_proxy_wstunnel configured on an URL that is not necessarily Upgraded + by the origin server was tunneling the whole connection regardless, thus + allowing for subsequent requests on the same connection to pass through + with no HTTP validation, authentication or authorization possibly + configured. +Author: Apache authors +Origin: upstream, http://people.apache.org/~ylavic/patches/2.4.x-mod_proxy_http-upgrade-4on5-v2.patch +Bug: https://httpd.apache.org/security/vulnerabilities_24.html#CVE-2019-17567 +Forwarded: not-needed +Reviewed-By: Yadd <y...@debian.org> +Last-Update: 2021-06-10 + +--- a/modules/proxy/mod_proxy.c ++++ b/modules/proxy/mod_proxy.c +@@ -314,7 +314,8 @@ + } + } + else if (!strcasecmp(key, "upgrade")) { +- if (PROXY_STRNCPY(worker->s->upgrade, val) != APR_SUCCESS) { ++ if (PROXY_STRNCPY(worker->s->upgrade, ++ strcasecmp(val, "ANY") ? val : "*") != APR_SUCCESS) { + return apr_psprintf(p, "upgrade protocol length must be < %d characters", + (int)sizeof(worker->s->upgrade)); + } +--- a/modules/proxy/mod_proxy.h ++++ b/modules/proxy/mod_proxy.h +@@ -725,6 +725,19 @@ + proxy_worker *worker); + + /** ++ * Return whether a worker upgrade configuration matches Upgrade header ++ * @param p memory pool used for displaying worker name ++ * @param worker the worker ++ * @param upgrade the Upgrade header to match ++ * @param dflt default protocol (NULL for none) ++ * @return 1 (true) or 0 (false) ++ */ ++PROXY_DECLARE(int) ap_proxy_worker_can_upgrade(apr_pool_t *p, ++ const proxy_worker *worker, ++ const char *upgrade, ++ const char *dflt); ++ ++/** + * Get the worker from proxy configuration + * @param p memory pool used for finding worker + * @param balancer the balancer that the worker belongs to +@@ -1181,6 +1194,40 @@ + conn_rec *origin, apr_bucket_brigade *bb, + int flush); + ++struct proxy_tunnel_conn; /* opaque */ ++typedef struct { ++ request_rec *r; ++ const char *scheme; ++ apr_pollset_t *pollset; ++ apr_array_header_t *pfds; ++ apr_interval_time_t timeout; ++ struct proxy_tunnel_conn *client, ++ *origin; ++ apr_size_t read_buf_size; ++ int replied; ++} proxy_tunnel_rec; ++ ++/** ++ * Create a tunnel, to be activated by ap_proxy_tunnel_run(). ++ * @param tunnel tunnel created ++ * @param r client request ++ * @param c_o connection to origin ++ * @param scheme caller proxy scheme (connect, ws(s), http(s), ...) ++ * @return APR_SUCCESS or error status ++ */ ++PROXY_DECLARE(apr_status_t) ap_proxy_tunnel_create(proxy_tunnel_rec **tunnel, ++ request_rec *r, conn_rec *c_o, ++ const char *scheme); ++ ++/** ++ * Forward anything from either side of the tunnel to the other, ++ * until one end aborts or a polling timeout/error occurs. ++ * @param tunnel tunnel to run ++ * @return OK if completion is full, HTTP_GATEWAY_TIME_OUT on timeout ++ * or another HTTP_ error otherwise. ++ */ ++PROXY_DECLARE(int) ap_proxy_tunnel_run(proxy_tunnel_rec *tunnel); ++ + /** + * Clear the headers referenced by the Connection header from the given + * table, and remove the Connection header. +@@ -1255,6 +1302,15 @@ + apr_bucket_brigade *from, + apr_bucket_brigade *to); + ++/* ++ * The flags for ap_proxy_transfer_between_connections(), where for legacy and ++ * compatibility reasons FLUSH_EACH and FLUSH_AFTER are boolean values. ++ */ ++#define AP_PROXY_TRANSFER_FLUSH_EACH (0x00) ++#define AP_PROXY_TRANSFER_FLUSH_AFTER (0x01) ++#define AP_PROXY_TRANSFER_YIELD_PENDING (0x02) ++#define AP_PROXY_TRANSFER_YIELD_MAX_READS (0x04) ++ + /* + * Sends all data that can be read non blocking from the input filter chain of + * c_i and send it down the output filter chain of c_o. For reading it uses +@@ -1272,10 +1328,12 @@ + * @param name string for logging from where data was pulled + * @param sent if not NULL will be set to 1 if data was sent through c_o + * @param bsize maximum amount of data pulled in one iteration from c_i +- * @param after if set flush data on c_o only once after the loop ++ * @param flags AP_PROXY_TRANSFER_* bitmask + * @return apr_status_t of the operation. Could be any error returned from + * either the input filter chain of c_i or the output filter chain +- * of c_o. APR_EPIPE if the outgoing connection was aborted. ++ * of c_o, APR_EPIPE if the outgoing connection was aborted, or ++ * APR_INCOMPLETE if AP_PROXY_TRANSFER_YIELD_PENDING was set and ++ * the output stack gets full before the input stack is exhausted. + */ + PROXY_DECLARE(apr_status_t) ap_proxy_transfer_between_connections( + request_rec *r, +@@ -1286,7 +1344,7 @@ + const char *name, + int *sent, + apr_off_t bsize, +- int after); ++ int flags); + + extern module PROXY_DECLARE_DATA proxy_module; + +--- a/modules/proxy/mod_proxy_connect.c ++++ b/modules/proxy/mod_proxy_connect.c +@@ -156,25 +156,19 @@ + apr_socket_t *sock; + conn_rec *c = r->connection; + conn_rec *backconn; +- int done = 0; + +- apr_bucket_brigade *bb_front; +- apr_bucket_brigade *bb_back; + apr_status_t rv; + apr_size_t nbytes; + char buffer[HUGE_STRING_LEN]; +- apr_socket_t *client_socket = ap_get_conn_socket(c); ++ ++ apr_bucket_brigade *bb; ++ proxy_tunnel_rec *tunnel; + int failed, rc; +- apr_pollset_t *pollset; +- apr_pollfd_t pollfd; +- const apr_pollfd_t *signalled; +- apr_int32_t pollcnt, pi; +- apr_int16_t pollevent; +- apr_sockaddr_t *nexthop; + + apr_uri_t uri; + const char *connectname; + apr_port_t connectport = 0; ++ apr_sockaddr_t *nexthop; + + /* is this for us? */ + if (r->method_number != M_CONNECT) { +@@ -261,28 +255,6 @@ + } + } + +- /* setup polling for connection */ +- ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "setting up poll()"); +- +- if ((rv = apr_pollset_create(&pollset, 2, r->pool, 0)) != APR_SUCCESS) { +- apr_socket_close(sock); +- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01020) +- "error apr_pollset_create()"); +- return HTTP_INTERNAL_SERVER_ERROR; +- } +- +- /* Add client side to the poll */ +- pollfd.p = r->pool; +- pollfd.desc_type = APR_POLL_SOCKET; +- pollfd.reqevents = APR_POLLIN | APR_POLLHUP; +- pollfd.desc.s = client_socket; +- pollfd.client_data = NULL; +- apr_pollset_add(pollset, &pollfd); +- +- /* Add the server side to the poll */ +- pollfd.desc.s = sock; +- apr_pollset_add(pollset, &pollfd); +- + /* + * Step Three: Send the Request + * +@@ -305,6 +277,7 @@ + backconn->aborted = 1; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01022) + "pre_connection setup failed (%d)", rc); ++ apr_socket_close(sock); + return HTTP_INTERNAL_SERVER_ERROR; + } + +@@ -314,9 +287,7 @@ + apr_table_setn(r->notes, "proxy-source-port", apr_psprintf(r->pool, "%hu", + backconn->local_addr->port)); + +- +- bb_front = apr_brigade_create(p, c->bucket_alloc); +- bb_back = apr_brigade_create(p, backconn->bucket_alloc); ++ bb = apr_brigade_create(p, c->bucket_alloc); + + /* If we are connecting through a remote proxy, we need to pass + * the CONNECT request on to it. +@@ -326,24 +297,24 @@ + */ + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "sending the CONNECT request to the remote proxy"); +- ap_fprintf(backconn->output_filters, bb_back, ++ ap_fprintf(backconn->output_filters, bb, + "CONNECT %s HTTP/1.0" CRLF, r->uri); +- ap_fprintf(backconn->output_filters, bb_back, ++ ap_fprintf(backconn->output_filters, bb, + "Proxy-agent: %s" CRLF CRLF, ap_get_server_banner()); +- ap_fflush(backconn->output_filters, bb_back); ++ ap_fflush(backconn->output_filters, bb); + } + else { + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "Returning 200 OK"); + nbytes = apr_snprintf(buffer, sizeof(buffer), + "HTTP/1.0 200 Connection Established" CRLF); + ap_xlate_proto_to_ascii(buffer, nbytes); +- ap_fwrite(c->output_filters, bb_front, buffer, nbytes); ++ ap_fwrite(c->output_filters, bb, buffer, nbytes); + nbytes = apr_snprintf(buffer, sizeof(buffer), + "Proxy-agent: %s" CRLF CRLF, + ap_get_server_banner()); + ap_xlate_proto_to_ascii(buffer, nbytes); +- ap_fwrite(c->output_filters, bb_front, buffer, nbytes); +- ap_fflush(c->output_filters, bb_front); ++ ap_fwrite(c->output_filters, bb, buffer, nbytes); ++ ap_fflush(c->output_filters, bb); + #if 0 + /* This is safer code, but it doesn't work yet. I'm leaving it + * here so that I can fix it later. +@@ -354,8 +325,7 @@ + ap_rflush(r); + #endif + } +- +- ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "setting up poll()"); ++ apr_brigade_cleanup(bb); + + /* + * Step Four: Handle Data Transfer +@@ -363,88 +333,30 @@ + * Handle two way transfer of data over the socket (this is a tunnel). + */ + +- /* we are now acting as a tunnel - the input/output filter stacks should +- * not contain any non-connection filters. +- */ +- r->output_filters = c->output_filters; +- r->proto_output_filters = c->output_filters; +- r->input_filters = c->input_filters; +- r->proto_input_filters = c->input_filters; +-/* r->sent_bodyct = 1;*/ +- +- do { /* Loop until done (one side closes the connection, or an error) */ +- rv = apr_pollset_poll(pollset, -1, &pollcnt, &signalled); +- if (rv != APR_SUCCESS) { +- if (APR_STATUS_IS_EINTR(rv)) { +- continue; +- } +- apr_socket_close(sock); +- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01023) "error apr_poll()"); +- return HTTP_INTERNAL_SERVER_ERROR; +- } +-#ifdef DEBUGGING +- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01024) +- "woke from poll(), i=%d", pollcnt); +-#endif +- +- for (pi = 0; pi < pollcnt; pi++) { +- const apr_pollfd_t *cur = &signalled[pi]; ++ /* r->sent_bodyct = 1; */ + +- if (cur->desc.s == sock) { +- pollevent = cur->rtnevents; +- if (pollevent & (APR_POLLIN | APR_POLLHUP)) { +-#ifdef DEBUGGING +- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01025) +- "sock was readable"); +-#endif +- done |= ap_proxy_transfer_between_connections(r, backconn, +- c, bb_back, +- bb_front, +- "sock", NULL, +- CONN_BLKSZ, 1) +- != APR_SUCCESS; +- } +- else if (pollevent & APR_POLLERR) { +- ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(01026) +- "err on backconn"); +- backconn->aborted = 1; +- done = 1; +- } +- } +- else if (cur->desc.s == client_socket) { +- pollevent = cur->rtnevents; +- if (pollevent & (APR_POLLIN | APR_POLLHUP)) { +-#ifdef DEBUGGING +- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01027) +- "client was readable"); +-#endif +- done |= ap_proxy_transfer_between_connections(r, c, +- backconn, +- bb_front, +- bb_back, +- "client", +- NULL, +- CONN_BLKSZ, 1) +- != APR_SUCCESS; +- } +- else if (pollevent & APR_POLLERR) { +- ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02827) +- "err on client"); +- c->aborted = 1; +- done = 1; +- } +- } +- else { +- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01028) +- "unknown socket in pollset"); +- done = 1; +- } ++ rv = ap_proxy_tunnel_create(&tunnel, r, backconn, "CONNECT"); ++ if (rv != APR_SUCCESS) { ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10208) ++ "can't create tunnel for %pI (%s)", ++ nexthop, connectname); ++ return HTTP_INTERNAL_SERVER_ERROR; ++ } + ++ rc = ap_proxy_tunnel_run(tunnel); ++ if (ap_is_HTTP_ERROR(rc)) { ++ if (rc == HTTP_GATEWAY_TIME_OUT) { ++ /* ap_proxy_tunnel_run() didn't log this */ ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10224) ++ "tunnel timed out"); + } +- } while (!done); +- +- ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, +- "finished with poll() - cleaning up"); ++ /* Don't send an error page if we sent data already */ ++ if (proxyport && !tunnel->replied) { ++ return rc; ++ } ++ /* Custom log may need this, still */ ++ r->status = rc; ++ } + + /* + * Step Five: Clean Up +@@ -457,8 +369,6 @@ + else + ap_lingering_close(backconn); + +- c->keepalive = AP_CONN_CLOSE; +- + return OK; + } + +--- a/modules/proxy/mod_proxy_http.c ++++ b/modules/proxy/mod_proxy_http.c +@@ -31,36 +31,71 @@ + static apr_status_t ap_proxygetline(apr_bucket_brigade *bb, char *s, int n, + request_rec *r, int flags, int *read); + ++static const char *get_url_scheme(const char **url, int *is_ssl) ++{ ++ const char *u = *url; ++ ++ switch (u[0]) { ++ case 'h': ++ case 'H': ++ if (strncasecmp(u + 1, "ttp", 3) == 0) { ++ if (u[4] == ':') { ++ *is_ssl = 0; ++ *url = u + 5; ++ return "http"; ++ } ++ if (apr_tolower(u[4]) == 's' && u[5] == ':') { ++ *is_ssl = 1; ++ *url = u + 6; ++ return "https"; ++ } ++ } ++ break; ++ ++ case 'w': ++ case 'W': ++ if (apr_tolower(u[1]) == 's') { ++ if (u[2] == ':') { ++ *is_ssl = 0; ++ *url = u + 3; ++ return "ws"; ++ } ++ if (apr_tolower(u[2]) == 's' && u[3] == ':') { ++ *is_ssl = 1; ++ *url = u + 4; ++ return "wss"; ++ } ++ } ++ break; ++ } ++ ++ *is_ssl = 0; ++ return NULL; ++} ++ + /* + * Canonicalise http-like URLs. + * scheme is the scheme for the URL + * url is the URL starting with the first '/' +- * def_port is the default port for this scheme. + */ + static int proxy_http_canon(request_rec *r, char *url) + { ++ const char *base_url = url; + char *host, *path, sport[7]; + char *search = NULL; + const char *err; + const char *scheme; + apr_port_t port, def_port; ++ int is_ssl = 0; + +- /* ap_port_of_scheme() */ +- if (strncasecmp(url, "http:", 5) == 0) { +- url += 5; +- scheme = "http"; +- } +- else if (strncasecmp(url, "https:", 6) == 0) { +- url += 6; +- scheme = "https"; +- } +- else { ++ scheme = get_url_scheme((const char **)&url, &is_ssl); ++ if (!scheme) { + return DECLINED; + } +- port = def_port = ap_proxy_port_of_scheme(scheme); ++ port = def_port = (is_ssl) ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT; + + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, +- "HTTP: canonicalising URL %s", url); ++ "HTTP: canonicalising URL %s", base_url); + + /* do syntatic check. + * We break the URL into host, port, path, search +@@ -68,7 +103,7 @@ + err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port); + if (err) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01083) +- "error parsing URL %s: %s", url, err); ++ "error parsing URL %s: %s", base_url, err); + return HTTP_BAD_REQUEST; + } + +@@ -108,8 +143,9 @@ + if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */ + host = apr_pstrcat(r->pool, "[", host, "]", NULL); + } ++ + r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "://", host, sport, +- "/", path, (search) ? "?" : "", (search) ? search : "", NULL); ++ "/", path, (search) ? "?" : "", search, NULL); + return OK; + } + +@@ -223,17 +259,6 @@ + #define ZERO_ASCII "\060" + #endif + +-static void terminate_headers(apr_bucket_alloc_t *bucket_alloc, +- apr_bucket_brigade *header_brigade) +-{ +- apr_bucket *e; +- +- /* add empty line at the end of the headers */ +- e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc); +- APR_BRIGADE_INSERT_TAIL(header_brigade, e); +-} +- +- + #define MAX_MEM_SPOOL 16384 + + typedef enum { +@@ -246,6 +271,7 @@ + typedef struct { + apr_pool_t *p; + request_rec *r; ++ const char *proto; + proxy_worker *worker; + proxy_server_conf *sconf; + +@@ -262,8 +288,11 @@ + rb_methods rb_method; + + int expecting_100; +- unsigned int do_100_continue:1, +- prefetch_nonblocking:1; ++ const char *upgrade; ++ ++ unsigned int do_100_continue :1, ++ prefetch_nonblocking :1, ++ force10 :1; + } proxy_http_req_t; + + /* Read what's in the client pipe. If nonblocking is set and read is EAGAIN, +@@ -378,20 +407,26 @@ + } + } + else if (rb_method == RB_STREAM_CL +- && bytes_streamed > req->cl_val) { +- /* C-L < bytes streamed?!? +- * We will error out after the body is completely +- * consumed, but we can't stream more bytes at the +- * back end since they would in part be interpreted +- * as another request! If nothing is sent, then +- * just send nothing. ++ && (bytes_streamed > req->cl_val ++ || (seen_eos && bytes_streamed < req->cl_val))) { ++ /* C-L != bytes streamed?!? ++ * ++ * Prevent HTTP Request/Response Splitting. + * +- * Prevents HTTP Response Splitting. ++ * We can't stream more (or less) bytes at the back end since ++ * they could be interpreted in separate requests (more bytes ++ * now would start a new request, less bytes would make the ++ * first bytes of the next request be part of the current one). ++ * ++ * It can't happen from the client connection here thanks to ++ * ap_http_filter(), but some module's filter may be playing ++ * bad games, hence the HTTP_INTERNAL_SERVER_ERROR. + */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01086) +- "read more bytes of request body than expected " ++ "read %s bytes of request body than expected " + "(got %" APR_OFF_T_FMT ", expected " + "%" APR_OFF_T_FMT ")", ++ bytes_streamed > req->cl_val ? "more" : "less", + bytes_streamed, req->cl_val); + return HTTP_INTERNAL_SERVER_ERROR; + } +@@ -417,13 +452,6 @@ + } + } while (!seen_eos); + +- if (rb_method == RB_STREAM_CL && bytes_streamed != req->cl_val) { +- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01087) +- "client %s given Content-Length did not match" +- " number of body bytes read", r->connection->client_ip); +- return HTTP_BAD_REQUEST; +- } +- + return OK; + } + +@@ -559,6 +587,43 @@ + return OK; + } + ++static void terminate_headers(proxy_http_req_t *req) ++{ ++ apr_bucket_alloc_t *bucket_alloc = req->bucket_alloc; ++ apr_bucket *e; ++ char *buf; ++ ++ /* ++ * Handle Connection: header if we do HTTP/1.1 request: ++ * If we plan to close the backend connection sent Connection: close ++ * otherwise sent Connection: Keep-Alive. ++ */ ++ if (!req->force10) { ++ if (req->upgrade) { ++ buf = apr_pstrdup(req->p, "Connection: Upgrade" CRLF); ++ ap_xlate_proto_to_ascii(buf, strlen(buf)); ++ e = apr_bucket_pool_create(buf, strlen(buf), req->p, bucket_alloc); ++ APR_BRIGADE_INSERT_TAIL(req->header_brigade, e); ++ ++ /* Tell the backend that it can upgrade the connection. */ ++ buf = apr_pstrcat(req->p, "Upgrade: ", req->upgrade, CRLF, NULL); ++ } ++ else if (ap_proxy_connection_reusable(req->backend)) { ++ buf = apr_pstrdup(req->p, "Connection: Keep-Alive" CRLF); ++ } ++ else { ++ buf = apr_pstrdup(req->p, "Connection: close" CRLF); ++ } ++ ap_xlate_proto_to_ascii(buf, strlen(buf)); ++ e = apr_bucket_pool_create(buf, strlen(buf), req->p, bucket_alloc); ++ APR_BRIGADE_INSERT_TAIL(req->header_brigade, e); ++ } ++ ++ /* add empty line at the end of the headers */ ++ e = apr_bucket_immortal_create(CRLF_ASCII, 2, bucket_alloc); ++ APR_BRIGADE_INSERT_TAIL(req->header_brigade, e); ++} ++ + static int ap_proxy_http_prefetch(proxy_http_req_t *req, + apr_uri_t *uri, char *url) + { +@@ -571,20 +636,14 @@ + apr_bucket_brigade *input_brigade = req->input_brigade; + apr_bucket_brigade *temp_brigade; + apr_bucket *e; +- char *buf; + apr_status_t status; + apr_off_t bytes_read = 0; + apr_off_t bytes; +- int force10, rv; ++ int rv; + apr_read_type_e block; + +- if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) { +- if (req->expecting_100) { +- return HTTP_EXPECTATION_FAILED; +- } +- force10 = 1; +- } else { +- force10 = 0; ++ if (req->force10 && r->expecting_100) { ++ return HTTP_EXPECTATION_FAILED; + } + + rv = ap_proxy_create_hdrbrgd(p, header_brigade, r, p_conn, +@@ -755,7 +814,7 @@ + req->rb_method = RB_STREAM_CL; + } + else if (req->old_te_val) { +- if (force10 ++ if (req->force10 + || (apr_table_get(r->subprocess_env, "proxy-sendcl") + && !apr_table_get(r->subprocess_env, "proxy-sendchunks") + && !apr_table_get(r->subprocess_env, "proxy-sendchunked"))) { +@@ -775,7 +834,7 @@ + } + req->rb_method = RB_STREAM_CL; + } +- else if (!force10 ++ else if (!req->force10 + && (apr_table_get(r->subprocess_env, "proxy-sendchunks") + || apr_table_get(r->subprocess_env, "proxy-sendchunked")) + && !apr_table_get(r->subprocess_env, "proxy-sendcl")) { +@@ -819,23 +878,7 @@ + + /* Yes I hate gotos. This is the subrequest shortcut */ + skip_body: +- /* +- * Handle Connection: header if we do HTTP/1.1 request: +- * If we plan to close the backend connection sent Connection: close +- * otherwise sent Connection: Keep-Alive. +- */ +- if (!force10) { +- if (!ap_proxy_connection_reusable(p_conn)) { +- buf = apr_pstrdup(p, "Connection: close" CRLF); +- } +- else { +- buf = apr_pstrdup(p, "Connection: Keep-Alive" CRLF); +- } +- ap_xlate_proto_to_ascii(buf, strlen(buf)); +- e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc); +- APR_BRIGADE_INSERT_TAIL(header_brigade, e); +- } +- terminate_headers(bucket_alloc, header_brigade); ++ terminate_headers(req); + + return OK; + } +@@ -1146,6 +1189,36 @@ + return 1; + } + ++static int send_continue_body(proxy_http_req_t *req) ++{ ++ int status; ++ ++ /* Send the request body (fully). */ ++ switch(req->rb_method) { ++ case RB_SPOOL_CL: ++ case RB_STREAM_CL: ++ case RB_STREAM_CHUNKED: ++ status = stream_reqbody(req); ++ break; ++ default: ++ /* Shouldn't happen */ ++ status = HTTP_INTERNAL_SERVER_ERROR; ++ break; ++ } ++ if (status != OK) { ++ conn_rec *c = req->r->connection; ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req->r, ++ APLOGNO(10154) "pass request body failed " ++ "to %pI (%s) from %s (%s) with status %i", ++ req->backend->addr, ++ req->backend->hostname ? req->backend->hostname : "", ++ c->client_ip, c->remote_host ? c->remote_host : "", ++ status); ++ req->backend->close = 1; ++ } ++ return status; ++} ++ + static + int ap_proxy_http_process_response(proxy_http_req_t *req) + { +@@ -1156,6 +1229,7 @@ + proxy_conn_rec *backend = req->backend; + conn_rec *origin = req->origin; + int do_100_continue = req->do_100_continue; ++ int status; + + char *buffer; + char fixed_buffer[HUGE_STRING_LEN]; +@@ -1227,6 +1301,7 @@ + origin->local_addr->port)); + do { + apr_status_t rc; ++ const char *upgrade = NULL; + int major = 0, minor = 0; + int toclose = 0; + +@@ -1247,7 +1322,8 @@ + apr_table_setn(r->notes, "proxy_timedout", "1"); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01103) "read timeout"); + if (do_100_continue) { +- return ap_proxyerror(r, HTTP_SERVICE_UNAVAILABLE, "Timeout on 100-Continue"); ++ return ap_proxyerror(r, HTTP_SERVICE_UNAVAILABLE, ++ "Timeout on 100-Continue"); + } + } + /* +@@ -1299,12 +1375,12 @@ + /* Need to return OK to avoid sending an error message */ + return OK; + } +- else if (!c->keepalives) { +- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01105) +- "NOT Closing connection to client" +- " although reading from backend server %s:%d" +- " failed.", +- backend->hostname, backend->port); ++ if (!c->keepalives) { ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01105) ++ "NOT Closing connection to client" ++ " although reading from backend server %s:%d" ++ " failed.", ++ backend->hostname, backend->port); + } + return ap_proxyerror(r, HTTP_BAD_GATEWAY, + "Error reading from remote server"); +@@ -1324,8 +1400,8 @@ + */ + if ((major != 1) || (len >= response_field_size - 1)) { + return ap_proxyerror(r, HTTP_BAD_GATEWAY, +- apr_pstrcat(p, "Corrupt status line returned by remote " +- "server: ", buffer, NULL)); ++ apr_pstrcat(p, "Corrupt status line returned " ++ "by remote server: ", buffer, NULL)); + } + backasswards = 0; + +@@ -1422,10 +1498,29 @@ + */ + te = apr_table_get(r->headers_out, "Transfer-Encoding"); + ++ upgrade = apr_table_get(r->headers_out, "Upgrade"); ++ if (proxy_status == HTTP_SWITCHING_PROTOCOLS) { ++ if (!upgrade || !req->upgrade || (strcasecmp(req->upgrade, ++ upgrade) != 0)) { ++ return ap_proxyerror(r, HTTP_BAD_GATEWAY, ++ apr_pstrcat(p, "Unexpected Upgrade: ", ++ upgrade ? upgrade : "n/a", ++ " (expecting ", ++ req->upgrade ? req->upgrade ++ : "n/a", ")", ++ NULL)); ++ } ++ backend->close = 1; ++ } ++ + /* strip connection listed hop-by-hop headers from response */ + toclose = ap_proxy_clear_connection_fn(r, r->headers_out); + if (toclose) { + backend->close = 1; ++ if (toclose < 0) { ++ return ap_proxyerror(r, HTTP_BAD_GATEWAY, ++ "Malformed connection header"); ++ } + } + + if ((buf = apr_table_get(r->headers_out, "Content-Type"))) { +@@ -1485,6 +1580,8 @@ + } + + if (ap_is_HTTP_INFO(proxy_status)) { ++ const char *policy = NULL; ++ + /* RFC2616 tells us to forward this. + * + * OTOH, an interim response here may mean the backend +@@ -1499,18 +1596,32 @@ + * + * We need to set "r->expecting_100 = 1" otherwise origin + * server behaviour will apply. ++ * ++ * 101 Switching Protocol has its own configuration which ++ * shouldn't be interfered by "proxy-interim-response". + */ +- const char *policy = apr_table_get(r->subprocess_env, +- "proxy-interim-response"); ++ if (proxy_status != HTTP_SWITCHING_PROTOCOLS) { ++ policy = apr_table_get(r->subprocess_env, ++ "proxy-interim-response"); ++ } + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, +- "HTTP: received interim %d response", r->status); ++ "HTTP: received interim %d response (policy: %s)", ++ r->status, policy ? policy : "n/a"); + if (!policy + || (!strcasecmp(policy, "RFC") + && (proxy_status != HTTP_CONTINUE + || (req->expecting_100 = 1)))) { +- if (proxy_status == HTTP_CONTINUE) { ++ switch (proxy_status) { ++ case HTTP_SWITCHING_PROTOCOLS: ++ AP_DEBUG_ASSERT(upgrade != NULL); ++ apr_table_setn(r->headers_out, "Connection", "Upgrade"); ++ apr_table_setn(r->headers_out, "Upgrade", ++ apr_pstrdup(p, upgrade)); ++ break; ++ case HTTP_CONTINUE: + r->expecting_100 = req->expecting_100; + req->expecting_100 = 0; ++ break; + } + ap_send_interim_response(r, 1); + } +@@ -1565,30 +1676,8 @@ + major, minor, proxy_status_line); + + if (do_send_body) { +- int status; +- +- /* Send the request body (fully). */ +- switch(req->rb_method) { +- case RB_SPOOL_CL: +- case RB_STREAM_CL: +- case RB_STREAM_CHUNKED: +- status = stream_reqbody(req); +- break; +- default: +- /* Shouldn't happen */ +- status = HTTP_INTERNAL_SERVER_ERROR; +- break; +- } ++ status = send_continue_body(req); + if (status != OK) { +- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, +- APLOGNO(10154) "pass request body failed " +- "to %pI (%s) from %s (%s) with status %i", +- backend->addr, +- backend->hostname ? backend->hostname : "", +- c->client_ip, +- c->remote_host ? c->remote_host : "", +- status); +- backend->close = 1; + return status; + } + } +@@ -1611,6 +1700,62 @@ + do_100_continue = 0; + } + ++ if (proxy_status == HTTP_SWITCHING_PROTOCOLS) { ++ apr_status_t rv; ++ proxy_tunnel_rec *tunnel; ++ apr_interval_time_t client_timeout = -1, ++ backend_timeout = -1; ++ ++ /* If we didn't send the full body yet, do it now */ ++ if (do_100_continue) { ++ r->expecting_100 = 0; ++ status = send_continue_body(req); ++ if (status != OK) { ++ return status; ++ } ++ } ++ ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10239) ++ "HTTP: tunneling protocol %s", upgrade); ++ ++ rv = ap_proxy_tunnel_create(&tunnel, r, origin, "HTTP"); ++ if (rv != APR_SUCCESS) { ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10240) ++ "can't create tunnel for %s", upgrade); ++ return HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ /* Set timeout to the lowest configured for client or backend */ ++ apr_socket_timeout_get(backend->sock, &backend_timeout); ++ apr_socket_timeout_get(ap_get_conn_socket(c), &client_timeout); ++ if (backend_timeout >= 0 && backend_timeout < client_timeout) { ++ tunnel->timeout = backend_timeout; ++ } ++ else { ++ tunnel->timeout = client_timeout; ++ } ++ ++ /* Let proxy tunnel forward everything */ ++ status = ap_proxy_tunnel_run(tunnel); ++ if (ap_is_HTTP_ERROR(status)) { ++ /* Tunnel always return HTTP_GATEWAY_TIME_OUT on timeout, ++ * but we can differentiate between client and backend here. ++ */ ++ if (status == HTTP_GATEWAY_TIME_OUT ++ && tunnel->timeout == client_timeout) { ++ status = HTTP_REQUEST_TIME_OUT; ++ } ++ } ++ else { ++ /* Update r->status for custom log */ ++ status = HTTP_SWITCHING_PROTOCOLS; ++ } ++ r->status = status; ++ ++ /* We are done with both connections */ ++ return DONE; ++ } ++ + if (interim_response) { + /* Already forwarded above, read next response */ + continue; +@@ -1632,6 +1777,17 @@ + } + } + ++ /* Forward back Upgrade header if it matches the configured one(s), it ++ * may be an HTTP_UPGRADE_REQUIRED response or some other status where ++ * Upgrade makes sense to negotiate the protocol by other means. ++ */ ++ if (upgrade && ap_proxy_worker_can_upgrade(p, worker, upgrade, ++ (*req->proto == 'w') ++ ? "WebSocket" : NULL)) { ++ apr_table_setn(r->headers_out, "Connection", "Upgrade"); ++ apr_table_setn(r->headers_out, "Upgrade", apr_pstrdup(p, upgrade)); ++ } ++ + r->sent_bodyct = 1; + /* + * Is it an HTTP/0.9 response or did we maybe preread the 1st line of +@@ -1665,7 +1821,7 @@ + */ + r->status = HTTP_OK; + /* Discard body, if one is expected */ +- if (!r->header_only && !AP_STATUS_IS_HEADER_ONLY(proxy_status)) { ++ if (!r->header_only && !AP_STATUS_IS_HEADER_ONLY(proxy_status)) { + const char *tmp; + /* Add minimal headers needed to allow http_in filter + * detecting end of body without waiting for a timeout. */ +@@ -1868,6 +2024,7 @@ + */ + ap_proxy_release_connection(backend->worker->s->scheme, + backend, r->server); ++ /* Ensure that the backend is not reused */ + req->backend = NULL; + + /* Pass EOS bucket down the filter chain. */ +@@ -1922,9 +2079,8 @@ + apr_port_t proxyport) + { + int status; +- char *scheme; +- const char *proxy_function; +- const char *u; ++ const char *scheme; ++ const char *u = url; + proxy_http_req_t *req = NULL; + proxy_conn_rec *backend = NULL; + apr_bucket_brigade *input_brigade = NULL; +@@ -1941,41 +2097,31 @@ + apr_pool_t *p = r->pool; + apr_uri_t *uri; + +- /* find the scheme */ +- u = strchr(url, ':'); +- if (u == NULL || u[1] != '/' || u[2] != '/' || u[3] == '\0') ++ scheme = get_url_scheme(&u, &is_ssl); ++ if (!scheme && proxyname && strncasecmp(url, "ftp:", 4) == 0) { ++ u = url + 4; ++ scheme = "ftp"; ++ is_ssl = 0; ++ } ++ if (!scheme || u[0] != '/' || u[1] != '/' || u[2] == '\0') { ++ if (!scheme && (u = strchr(url, ':')) && (u - url) > 14) { ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10262) ++ "overlong proxy URL scheme in %s", url); ++ return HTTP_BAD_REQUEST; ++ } ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01113) ++ "HTTP: declining URL %s", url); ++ return DECLINED; /* only interested in HTTP, WS or FTP via proxy */ ++ } ++ if (is_ssl && !ap_proxy_ssl_enable(NULL)) { ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01112) ++ "HTTP: declining URL %s (mod_ssl not configured?)", url); + return DECLINED; +- if ((u - url) > 14) +- return HTTP_BAD_REQUEST; +- scheme = apr_pstrmemdup(p, url, u - url); +- /* scheme is lowercase */ +- ap_str_tolower(scheme); +- /* is it for us? */ +- if (strcmp(scheme, "https") == 0) { +- if (!ap_proxy_ssl_enable(NULL)) { +- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01112) +- "HTTPS: declining URL %s (mod_ssl not configured?)", +- url); +- return DECLINED; +- } +- is_ssl = 1; +- proxy_function = "HTTPS"; +- } +- else if (!(strcmp(scheme, "http") == 0 || (strcmp(scheme, "ftp") == 0 && proxyname))) { +- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01113) "HTTP: declining URL %s", +- url); +- return DECLINED; /* only interested in HTTP, or FTP via proxy */ +- } +- else { +- if (*scheme == 'h') +- proxy_function = "HTTP"; +- else +- proxy_function = "FTP"; + } + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "HTTP: serving URL %s", url); + + /* create space for state information */ +- if ((status = ap_proxy_acquire_connection(proxy_function, &backend, ++ if ((status = ap_proxy_acquire_connection(scheme, &backend, + worker, r->server)) != OK) { + return status; + } +@@ -1988,11 +2134,27 @@ + req->sconf = conf; + req->worker = worker; + req->backend = backend; ++ req->proto = scheme; + req->bucket_alloc = c->bucket_alloc; + req->rb_method = RB_INIT; + + dconf = ap_get_module_config(r->per_dir_config, &proxy_module); + ++ if (apr_table_get(r->subprocess_env, "force-proxy-request-1.0")) { ++ req->force10 = 1; ++ } ++ else if (*worker->s->upgrade || *req->proto == 'w') { ++ /* Forward Upgrade header if it matches the configured one(s), ++ * the default being "WebSocket" for ws[s] schemes. ++ */ ++ const char *upgrade = apr_table_get(r->headers_in, "Upgrade"); ++ if (upgrade && ap_proxy_worker_can_upgrade(p, worker, upgrade, ++ (*req->proto == 'w') ++ ? "WebSocket" : NULL)) { ++ req->upgrade = upgrade; ++ } ++ } ++ + /* We possibly reuse input data prefetched in previous call(s), e.g. for a + * balancer fallback scenario, and in this case the 100 continue settings + * should be consistent between balancer members. If not, we need to ignore +@@ -2016,15 +2178,17 @@ + * req->expecting_100 (i.e. cleared only if mod_proxy_http sent the + * "100 Continue" according to its policy). + */ +- req->do_100_continue = req->prefetch_nonblocking = 1; ++ req->do_100_continue = 1; + req->expecting_100 = r->expecting_100; + r->expecting_100 = 0; + } + /* Should we block while prefetching the body or try nonblocking and flush + * data to the backend ASAP? + */ +- else if (input_brigade || apr_table_get(r->subprocess_env, +- "proxy-prefetch-nonblocking")) { ++ if (input_brigade ++ || req->do_100_continue ++ || apr_table_get(r->subprocess_env, ++ "proxy-prefetch-nonblocking")) { + req->prefetch_nonblocking = 1; + } + +@@ -2100,9 +2264,9 @@ + } + + /* Step Two: Make the Connection */ +- if (ap_proxy_check_connection(proxy_function, backend, r->server, 1, ++ if (ap_proxy_check_connection(scheme, backend, r->server, 1, + PROXY_CHECK_CONN_EMPTY) +- && ap_proxy_connect_backend(proxy_function, backend, worker, ++ && ap_proxy_connect_backend(scheme, backend, worker, + r->server)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01114) + "HTTP: failed to make connection to backend: %s", +@@ -2112,8 +2276,7 @@ + } + + /* Step Three: Create conn_rec */ +- if ((status = ap_proxy_connection_create_ex(proxy_function, +- backend, r)) != OK) ++ if ((status = ap_proxy_connection_create_ex(scheme, backend, r)) != OK) + break; + req->origin = backend->connection; + +@@ -2151,7 +2314,7 @@ + if (req->backend) { + if (status != OK) + req->backend->close = 1; +- ap_proxy_http_cleanup(proxy_function, r, req->backend); ++ ap_proxy_http_cleanup(scheme, r, req->backend); + } + if (req->expecting_100) { + /* Restore r->expecting_100 if we didn't touch it */ +--- a/modules/proxy/mod_proxy_wstunnel.c ++++ b/modules/proxy/mod_proxy_wstunnel.c +@@ -15,9 +15,12 @@ + */ + + #include "mod_proxy.h" ++#include "http_config.h" + + module AP_MODULE_DECLARE_DATA proxy_wstunnel_module; + ++static int fallback_to_mod_proxy_http; ++ + /* + * Canonicalise http-like URLs. + * scheme is the scheme for the URL +@@ -32,6 +35,11 @@ + char *scheme; + apr_port_t port, def_port; + ++ if (fallback_to_mod_proxy_http) { ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "canon fallback"); ++ return DECLINED; ++ } ++ + /* ap_port_of_scheme() */ + if (strncasecmp(url, "ws:", 3) == 0) { + url += 3; +@@ -286,12 +294,17 @@ + int status; + char server_portstr[32]; + proxy_conn_rec *backend = NULL; ++ const char *upgrade; + char *scheme; + apr_pool_t *p = r->pool; + char *locurl = url; + apr_uri_t *uri; + int is_ssl = 0; +- const char *upgrade_method = *worker->s->upgrade ? worker->s->upgrade : "WebSocket"; ++ ++ if (fallback_to_mod_proxy_http) { ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "handler fallback"); ++ return DECLINED; ++ } + + if (strncasecmp(url, "wss:", 4) == 0) { + scheme = "WSS"; +@@ -301,20 +314,24 @@ + scheme = "WS"; + } + else { +- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02450) "declining URL %s", url); ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02450) ++ "declining URL %s", url); + return DECLINED; + } ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "serving URL %s", url); + +- if (ap_cstr_casecmp(upgrade_method, "NONE") != 0) { +- const char *upgrade; +- upgrade = apr_table_get(r->headers_in, "Upgrade"); +- if (!upgrade || (ap_cstr_casecmp(upgrade, upgrade_method) != 0 && +- ap_cstr_casecmp(upgrade_method, "ANY") !=0)) { +- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02900) +- "declining URL %s (not %s, Upgrade: header is %s)", +- url, upgrade_method, upgrade ? upgrade : "missing"); +- return DECLINED; +- } ++ upgrade = apr_table_get(r->headers_in, "Upgrade"); ++ if (!upgrade || !ap_proxy_worker_can_upgrade(p, worker, upgrade, ++ "WebSocket")) { ++ const char *worker_upgrade = *worker->s->upgrade ? worker->s->upgrade ++ : "WebSocket"; ++ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02900) ++ "require upgrade for URL %s " ++ "(Upgrade header is %s, expecting %s)", ++ url, upgrade ? upgrade : "missing", worker_upgrade); ++ apr_table_setn(r->err_headers_out, "Connection", "Upgrade"); ++ apr_table_setn(r->err_headers_out, "Upgrade", worker_upgrade); ++ return HTTP_UPGRADE_REQUIRED; + } + + uri = apr_palloc(p, sizeof(*uri)); +@@ -366,8 +383,18 @@ + return status; + } + +-static void ap_proxy_http_register_hook(apr_pool_t *p) ++static int proxy_wstunnel_post_config(apr_pool_t *pconf, apr_pool_t *plog, ++ apr_pool_t *ptemp, server_rec *s) ++{ ++ fallback_to_mod_proxy_http = ++ (ap_find_linked_module("mod_proxy_http.c") != NULL); ++ ++ return OK; ++} ++ ++static void ws_proxy_hooks(apr_pool_t *p) + { ++ ap_hook_post_config(proxy_wstunnel_post_config, NULL, NULL, APR_HOOK_MIDDLE); + proxy_hook_scheme_handler(proxy_wstunnel_handler, NULL, NULL, APR_HOOK_FIRST); + proxy_hook_canon_handler(proxy_wstunnel_canon, NULL, NULL, APR_HOOK_FIRST); + } +@@ -379,5 +406,5 @@ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + NULL, /* command apr_table_t */ +- ap_proxy_http_register_hook /* register hooks */ ++ ws_proxy_hooks /* register hooks */ + }; +--- a/modules/proxy/proxy_util.c ++++ b/modules/proxy/proxy_util.c +@@ -1644,6 +1644,23 @@ + return APR_SUCCESS; + } + ++PROXY_DECLARE(int) ap_proxy_worker_can_upgrade(apr_pool_t *p, ++ const proxy_worker *worker, ++ const char *upgrade, ++ const char *dflt) ++{ ++ /* Find in worker->s->upgrade list (if any) */ ++ const char *worker_upgrade = worker->s->upgrade; ++ if (*worker_upgrade) { ++ return (strcmp(worker_upgrade, "*") == 0 ++ || ap_cstr_casecmp(worker_upgrade, upgrade) == 0 ++ || ap_find_token(p, worker_upgrade, upgrade)); ++ } ++ ++ /* Compare to the provided default (if any) */ ++ return (dflt && ap_cstr_casecmp(dflt, upgrade) == 0); ++} ++ + /* + * WORKER related... + */ +@@ -3952,6 +3969,28 @@ + return 0; + } + ++static APR_INLINE int ap_filter_should_yield(ap_filter_t *f) ++{ ++ return f->c->data_in_output_filters; ++} ++ ++static APR_INLINE int ap_filter_output_pending(conn_rec *c) ++{ ++ ap_filter_t *f = c->output_filters; ++ while (f->next) { ++ f = f->next; ++ } ++ if (f->frec->filter_func.out_func(f, NULL)) { ++ return AP_FILTER_ERROR; ++ } ++ return c->data_in_output_filters ? OK : DECLINED; ++} ++ ++static APR_INLINE int ap_filter_input_pending(conn_rec *c) ++{ ++ return c->data_in_input_filters ? OK : DECLINED; ++} ++ + PROXY_DECLARE(apr_status_t) ap_proxy_buckets_lifetime_transform(request_rec *r, + apr_bucket_brigade *from, + apr_bucket_brigade *to) +@@ -3990,6 +4029,16 @@ + return rv; + } + ++/* An arbitrary large value to address pathological case where we keep ++ * reading from one side only, without scheduling the other direction for ++ * too long. This can happen with large MTU and small read buffers, like ++ * micro-benchmarking huge files bidirectional transfer with client, proxy ++ * and backend on localhost for instance. Though we could just ignore the ++ * case and let the sender stop by itself at some point when/if it needs to ++ * receive data, or the receiver stop when/if it needs to send... ++ */ ++#define PROXY_TRANSFER_MAX_READS 10000 ++ + PROXY_DECLARE(apr_status_t) ap_proxy_transfer_between_connections( + request_rec *r, + conn_rec *c_i, +@@ -3999,81 +4048,498 @@ + const char *name, + int *sent, + apr_off_t bsize, +- int after) ++ int flags) + { + apr_status_t rv; ++ int flush_each = 0; ++ unsigned int num_reads = 0; + #ifdef DEBUGGING + apr_off_t len; + #endif + +- do { ++ /* ++ * Compat: since FLUSH_EACH is default (and zero) for legacy reasons, we ++ * pretend it's no FLUSH_AFTER nor YIELD_PENDING flags, the latter because ++ * flushing would defeat the purpose of checking for pending data (hence ++ * determine whether or not the output chain/stack is full for stopping). ++ */ ++ if (!(flags & (AP_PROXY_TRANSFER_FLUSH_AFTER | ++ AP_PROXY_TRANSFER_YIELD_PENDING))) { ++ flush_each = 1; ++ } ++ ++ for (;;) { + apr_brigade_cleanup(bb_i); + rv = ap_get_brigade(c_i->input_filters, bb_i, AP_MODE_READBYTES, + APR_NONBLOCK_READ, bsize); +- if (rv == APR_SUCCESS) { +- if (c_o->aborted) { +- return APR_EPIPE; +- } +- if (APR_BRIGADE_EMPTY(bb_i)) { +- break; ++ if (rv != APR_SUCCESS) { ++ if (!APR_STATUS_IS_EAGAIN(rv) && !APR_STATUS_IS_EOF(rv)) { ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03308) ++ "ap_proxy_transfer_between_connections: " ++ "error on %s - ap_get_brigade", ++ name); ++ if (rv == APR_INCOMPLETE) { ++ /* Don't return APR_INCOMPLETE, it'd mean "should yield" ++ * for the caller, while it means "incomplete body" here ++ * from ap_http_filter(), which is an error. ++ */ ++ rv = APR_EGENERAL; ++ } + } ++ break; ++ } ++ ++ if (c_o->aborted) { ++ apr_brigade_cleanup(bb_i); ++ flags &= ~AP_PROXY_TRANSFER_FLUSH_AFTER; ++ rv = APR_EPIPE; ++ break; ++ } ++ if (APR_BRIGADE_EMPTY(bb_i)) { ++ break; ++ } + #ifdef DEBUGGING +- len = -1; +- apr_brigade_length(bb_i, 0, &len); +- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03306) +- "ap_proxy_transfer_between_connections: " +- "read %" APR_OFF_T_FMT +- " bytes from %s", len, name); ++ len = -1; ++ apr_brigade_length(bb_i, 0, &len); ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03306) ++ "ap_proxy_transfer_between_connections: " ++ "read %" APR_OFF_T_FMT ++ " bytes from %s", len, name); + #endif +- if (sent) { +- *sent = 1; +- } +- ap_proxy_buckets_lifetime_transform(r, bb_i, bb_o); +- if (!after) { +- apr_bucket *b; ++ if (sent) { ++ *sent = 1; ++ } ++ ap_proxy_buckets_lifetime_transform(r, bb_i, bb_o); ++ if (flush_each) { ++ apr_bucket *b; ++ /* ++ * Do not use ap_fflush here since this would cause the flush ++ * bucket to be sent in a separate brigade afterwards which ++ * causes some filters to set aside the buckets from the first ++ * brigade and process them when FLUSH arrives in the second ++ * brigade. As set asides of our transformed buckets involve ++ * memory copying we try to avoid this. If we have the flush ++ * bucket in the first brigade they directly process the ++ * buckets without setting them aside. ++ */ ++ b = apr_bucket_flush_create(bb_o->bucket_alloc); ++ APR_BRIGADE_INSERT_TAIL(bb_o, b); ++ } ++ rv = ap_pass_brigade(c_o->output_filters, bb_o); ++ apr_brigade_cleanup(bb_o); ++ if (rv != APR_SUCCESS) { ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(03307) ++ "ap_proxy_transfer_between_connections: " ++ "error on %s - ap_pass_brigade", ++ name); ++ flags &= ~AP_PROXY_TRANSFER_FLUSH_AFTER; ++ break; ++ } + +- /* +- * Do not use ap_fflush here since this would cause the flush +- * bucket to be sent in a separate brigade afterwards which +- * causes some filters to set aside the buckets from the first +- * brigade and process them when the flush arrives in the second +- * brigade. As set asides of our transformed buckets involve +- * memory copying we try to avoid this. If we have the flush +- * bucket in the first brigade they directly process the +- * buckets without setting them aside. +- */ +- b = apr_bucket_flush_create(bb_o->bucket_alloc); +- APR_BRIGADE_INSERT_TAIL(bb_o, b); ++ /* Yield if the output filters stack is full? This is to avoid ++ * blocking and give the caller a chance to POLLOUT async. ++ */ ++ if (flags & AP_PROXY_TRANSFER_YIELD_PENDING) { ++ int rc = OK; ++ ++ if (!ap_filter_should_yield(c_o->output_filters)) { ++ rc = ap_filter_output_pending(c_o); + } +- rv = ap_pass_brigade(c_o->output_filters, bb_o); +- if (rv != APR_SUCCESS) { +- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(03307) ++ if (rc == OK) { ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "ap_proxy_transfer_between_connections: " +- "error on %s - ap_pass_brigade", +- name); ++ "yield (output pending)"); ++ rv = APR_INCOMPLETE; ++ break; ++ } ++ if (rc != DECLINED) { ++ rv = AP_FILTER_ERROR; ++ break; + } +- } else if (!APR_STATUS_IS_EAGAIN(rv) && !APR_STATUS_IS_EOF(rv)) { +- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(03308) ++ } ++ ++ /* Yield if we keep hold of the thread for too long? This gives ++ * the caller a chance to schedule the other direction too. ++ */ ++ if ((flags & AP_PROXY_TRANSFER_YIELD_MAX_READS) ++ && ++num_reads > PROXY_TRANSFER_MAX_READS) { ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "ap_proxy_transfer_between_connections: " +- "error on %s - ap_get_brigade", +- name); ++ "yield (max reads)"); ++ rv = APR_SUCCESS; ++ break; + } +- } while (rv == APR_SUCCESS); ++ } + +- if (after) { ++ if (flags & AP_PROXY_TRANSFER_FLUSH_AFTER) { + ap_fflush(c_o->output_filters, bb_o); ++ apr_brigade_cleanup(bb_o); + } ++ apr_brigade_cleanup(bb_i); + + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, rv, r, +- "ap_proxy_transfer_between_connections complete"); ++ "ap_proxy_transfer_between_connections complete (%s %pI)", ++ (c_i == r->connection) ? "to" : "from", ++ (c_i == r->connection) ? c_o->client_addr ++ : c_i->client_addr); + + if (APR_STATUS_IS_EAGAIN(rv)) { + rv = APR_SUCCESS; + } +- + return rv; + } + ++struct proxy_tunnel_conn { ++ /* the other side of the tunnel */ ++ struct proxy_tunnel_conn *other; ++ ++ conn_rec *c; ++ const char *name; ++ ++ apr_pollfd_t *pfd; ++ apr_bucket_brigade *bb; ++ ++ unsigned int down_in:1, ++ down_out:1; ++}; ++ ++PROXY_DECLARE(apr_status_t) ap_proxy_tunnel_create(proxy_tunnel_rec **ptunnel, ++ request_rec *r, conn_rec *c_o, ++ const char *scheme) ++{ ++ apr_status_t rv; ++ conn_rec *c_i = r->connection; ++ proxy_tunnel_rec *tunnel; ++ ++ *ptunnel = NULL; ++ ++ tunnel = apr_pcalloc(r->pool, sizeof(*tunnel)); ++ ++ rv = apr_pollset_create(&tunnel->pollset, 2, r->pool, APR_POLLSET_NOCOPY); ++ if (rv != APR_SUCCESS) { ++ return rv; ++ } ++ ++ tunnel->r = r; ++ tunnel->scheme = apr_pstrdup(r->pool, scheme); ++ tunnel->client = apr_pcalloc(r->pool, sizeof(struct proxy_tunnel_conn)); ++ tunnel->origin = apr_pcalloc(r->pool, sizeof(struct proxy_tunnel_conn)); ++ tunnel->pfds = apr_array_make(r->pool, 2, sizeof(apr_pollfd_t)); ++ tunnel->read_buf_size = ap_get_read_buf_size(r); ++ tunnel->client->other = tunnel->origin; ++ tunnel->origin->other = tunnel->client; ++ tunnel->timeout = -1; ++ ++ tunnel->client->c = c_i; ++ tunnel->client->name = "client"; ++ tunnel->client->bb = apr_brigade_create(c_i->pool, c_i->bucket_alloc); ++ tunnel->client->pfd = &APR_ARRAY_PUSH(tunnel->pfds, apr_pollfd_t); ++ tunnel->client->pfd->p = r->pool; ++ tunnel->client->pfd->desc_type = APR_POLL_SOCKET; ++ tunnel->client->pfd->desc.s = ap_get_conn_socket(c_i); ++ tunnel->client->pfd->client_data = tunnel->client; ++ ++ tunnel->origin->c = c_o; ++ tunnel->origin->name = "origin"; ++ tunnel->origin->bb = apr_brigade_create(c_o->pool, c_o->bucket_alloc); ++ tunnel->origin->pfd = &APR_ARRAY_PUSH(tunnel->pfds, apr_pollfd_t); ++ tunnel->origin->pfd->p = r->pool; ++ tunnel->origin->pfd->desc_type = APR_POLL_SOCKET; ++ tunnel->origin->pfd->desc.s = ap_get_conn_socket(c_o); ++ tunnel->origin->pfd->client_data = tunnel->origin; ++ ++ /* We should be nonblocking from now on the sockets */ ++ apr_socket_opt_set(tunnel->client->pfd->desc.s, APR_SO_NONBLOCK, 1); ++ apr_socket_opt_set(tunnel->origin->pfd->desc.s, APR_SO_NONBLOCK, 1); ++ ++ /* No coalescing filters */ ++ ap_remove_output_filter_byhandle(c_i->output_filters, ++ "SSL/TLS Coalescing Filter"); ++ ap_remove_output_filter_byhandle(c_o->output_filters, ++ "SSL/TLS Coalescing Filter"); ++ ++ /* Bidirectional non-HTTP stream will confuse mod_reqtimeoout */ ++ ap_remove_input_filter_byhandle(c_i->input_filters, "reqtimeout"); ++ ++ /* The input/output filter stacks should contain connection filters only */ ++ r->input_filters = r->proto_input_filters = c_i->input_filters; ++ r->output_filters = r->proto_output_filters = c_i->output_filters; ++ ++ /* Won't be reused after tunneling */ ++ c_i->keepalive = AP_CONN_CLOSE; ++ c_o->keepalive = AP_CONN_CLOSE; ++ ++ /* Start with POLLOUT and let ap_proxy_tunnel_run() schedule both ++ * directions when there are no output data pending (anymore). ++ */ ++ tunnel->client->pfd->reqevents = APR_POLLOUT | APR_POLLERR; ++ tunnel->origin->pfd->reqevents = APR_POLLOUT | APR_POLLERR; ++ if ((rv = apr_pollset_add(tunnel->pollset, tunnel->client->pfd)) ++ || (rv = apr_pollset_add(tunnel->pollset, tunnel->origin->pfd))) { ++ return rv; ++ } ++ ++ *ptunnel = tunnel; ++ return APR_SUCCESS; ++} ++ ++static void add_pollset(apr_pollset_t *pollset, apr_pollfd_t *pfd, ++ apr_int16_t events) ++{ ++ apr_status_t rv; ++ ++ AP_DEBUG_ASSERT((pfd->reqevents & events) == 0); ++ ++ if (pfd->reqevents) { ++ rv = apr_pollset_remove(pollset, pfd); ++ if (rv != APR_SUCCESS) { ++ AP_DEBUG_ASSERT(1); ++ } ++ } ++ ++ if (events & APR_POLLIN) { ++ events |= APR_POLLHUP; ++ } ++ pfd->reqevents |= events | APR_POLLERR; ++ rv = apr_pollset_add(pollset, pfd); ++ if (rv != APR_SUCCESS) { ++ AP_DEBUG_ASSERT(1); ++ } ++} ++ ++static void del_pollset(apr_pollset_t *pollset, apr_pollfd_t *pfd, ++ apr_int16_t events) ++{ ++ apr_status_t rv; ++ ++ AP_DEBUG_ASSERT((pfd->reqevents & events) != 0); ++ ++ rv = apr_pollset_remove(pollset, pfd); ++ if (rv != APR_SUCCESS) { ++ AP_DEBUG_ASSERT(0); ++ return; ++ } ++ ++ if (events & APR_POLLIN) { ++ events |= APR_POLLHUP; ++ } ++ if (pfd->reqevents & ~(events | APR_POLLERR)) { ++ pfd->reqevents &= ~events; ++ rv = apr_pollset_add(pollset, pfd); ++ if (rv != APR_SUCCESS) { ++ AP_DEBUG_ASSERT(0); ++ return; ++ } ++ } ++ else { ++ pfd->reqevents = 0; ++ } ++} ++ ++static int proxy_tunnel_forward(proxy_tunnel_rec *tunnel, ++ struct proxy_tunnel_conn *in) ++{ ++ struct proxy_tunnel_conn *out = in->other; ++ apr_status_t rv; ++ int sent = 0; ++ ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, tunnel->r, ++ "proxy: %s: %s input ready", ++ tunnel->scheme, in->name); ++ ++ rv = ap_proxy_transfer_between_connections(tunnel->r, ++ in->c, out->c, ++ in->bb, out->bb, ++ in->name, &sent, ++ tunnel->read_buf_size, ++ AP_PROXY_TRANSFER_YIELD_PENDING | ++ AP_PROXY_TRANSFER_YIELD_MAX_READS); ++ if (sent && out == tunnel->client) { ++ tunnel->replied = 1; ++ } ++ if (rv != APR_SUCCESS) { ++ if (APR_STATUS_IS_INCOMPLETE(rv)) { ++ /* Pause POLLIN while waiting for POLLOUT on the other ++ * side, hence avoid filling the output filters even ++ * more to avoid blocking there. ++ */ ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, tunnel->r, ++ "proxy: %s: %s wait writable", ++ tunnel->scheme, out->name); ++ } ++ else if (APR_STATUS_IS_EOF(rv)) { ++ /* Stop POLLIN and wait for POLLOUT (flush) on the ++ * other side to shut it down. ++ */ ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, tunnel->r, ++ "proxy: %s: %s read shutdown", ++ tunnel->scheme, in->name); ++ in->down_in = 1; ++ } ++ else { ++ /* Real failure, bail out */ ++ return HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ del_pollset(tunnel->pollset, in->pfd, APR_POLLIN); ++ add_pollset(tunnel->pollset, out->pfd, APR_POLLOUT); ++ } ++ ++ return OK; ++} ++ ++PROXY_DECLARE(int) ap_proxy_tunnel_run(proxy_tunnel_rec *tunnel) ++{ ++ int rc = OK; ++ request_rec *r = tunnel->r; ++ apr_pollset_t *pollset = tunnel->pollset; ++ struct proxy_tunnel_conn *client = tunnel->client, ++ *origin = tunnel->origin; ++ apr_interval_time_t timeout = tunnel->timeout >= 0 ? tunnel->timeout : -1; ++ const char *scheme = tunnel->scheme; ++ apr_status_t rv; ++ ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(10212) ++ "proxy: %s: tunnel running (timeout %lf)", ++ scheme, timeout >= 0 ? (double)timeout / APR_USEC_PER_SEC ++ : (double)-1.0); ++ ++ /* Loop until both directions of the connection are closed, ++ * or a failure occurs. ++ */ ++ do { ++ const apr_pollfd_t *results; ++ apr_int32_t nresults, i; ++ ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r, ++ "proxy: %s: polling (client=%hx, origin=%hx)", ++ scheme, client->pfd->reqevents, origin->pfd->reqevents); ++ do { ++ rv = apr_pollset_poll(pollset, timeout, &nresults, &results); ++ } while (APR_STATUS_IS_EINTR(rv)); ++ ++ if (rv != APR_SUCCESS) { ++ if (APR_STATUS_IS_TIMEUP(rv)) { ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, APLOGNO(10213) ++ "proxy: %s: polling timed out " ++ "(client=%hx, origin=%hx)", ++ scheme, client->pfd->reqevents, ++ origin->pfd->reqevents); ++ rc = HTTP_GATEWAY_TIME_OUT; ++ } ++ else { ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10214) ++ "proxy: %s: polling failed", scheme); ++ rc = HTTP_INTERNAL_SERVER_ERROR; ++ } ++ return rc; ++ } ++ ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r, APLOGNO(10215) ++ "proxy: %s: woken up, %i result(s)", scheme, nresults); ++ ++ for (i = 0; i < nresults; i++) { ++ const apr_pollfd_t *pfd = &results[i]; ++ struct proxy_tunnel_conn *tc = pfd->client_data; ++ ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r, ++ "proxy: %s: #%i: %s: %hx/%hx", scheme, i, ++ tc->name, pfd->rtnevents, tc->pfd->reqevents); ++ ++ /* sanity check */ ++ if (pfd->desc.s != client->pfd->desc.s ++ && pfd->desc.s != origin->pfd->desc.s) { ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10222) ++ "proxy: %s: unknown socket in pollset", scheme); ++ return HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ if (!(pfd->rtnevents & (APR_POLLIN | APR_POLLOUT | ++ APR_POLLHUP | APR_POLLERR))) { ++ /* this catches POLLNVAL etc.. */ ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10220) ++ "proxy: %s: polling events error (%x)", ++ scheme, pfd->rtnevents); ++ return HTTP_INTERNAL_SERVER_ERROR; ++ } ++ ++ /* Write if we asked for POLLOUT, and got it or POLLERR ++ * alone (i.e. not with POLLIN|HUP). We want the output filters ++ * to know about the socket error if any, by failing the write. ++ */ ++ if ((tc->pfd->reqevents & APR_POLLOUT) ++ && ((pfd->rtnevents & APR_POLLOUT) ++ || !(pfd->rtnevents & (APR_POLLIN | APR_POLLHUP)))) { ++ struct proxy_tunnel_conn *out = tc, *in = tc->other; ++ ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, r, ++ "proxy: %s: %s output ready", ++ scheme, out->name); ++ ++ rc = ap_filter_output_pending(out->c); ++ if (rc == OK) { ++ /* Keep polling out (only) */ ++ continue; ++ } ++ if (rc != DECLINED) { ++ /* Real failure, bail out */ ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10221) ++ "proxy: %s: %s flushing failed (%i)", ++ scheme, out->name, rc); ++ return rc; ++ } ++ ++ /* No more pending data. If the other side is not readable ++ * anymore it's time to shutdown for write (this direction ++ * is over). Otherwise back to normal business. ++ */ ++ del_pollset(pollset, out->pfd, APR_POLLOUT); ++ if (in->down_in) { ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, ++ "proxy: %s: %s write shutdown", ++ scheme, out->name); ++ apr_socket_shutdown(out->pfd->desc.s, 1); ++ out->down_out = 1; ++ } ++ else { ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, ++ "proxy: %s: %s resume writable", ++ scheme, out->name); ++ add_pollset(pollset, in->pfd, APR_POLLIN); ++ ++ /* Flush any pending input data now, we don't know when ++ * the next POLLIN will trigger and retaining data might ++ * block the protocol. ++ */ ++ if (ap_filter_input_pending(in->c) == OK) { ++ rc = proxy_tunnel_forward(tunnel, in); ++ if (rc != OK) { ++ return rc; ++ } ++ } ++ } ++ } ++ ++ /* Read if we asked for POLLIN|HUP, and got it or POLLERR ++ * alone (i.e. not with POLLOUT). We want the input filters ++ * to know about the socket error if any, by failing the read. ++ */ ++ if ((tc->pfd->reqevents & APR_POLLIN) ++ && ((pfd->rtnevents & (APR_POLLIN | APR_POLLHUP)) ++ || !(pfd->rtnevents & APR_POLLOUT))) { ++ rc = proxy_tunnel_forward(tunnel, tc); ++ if (rc != OK) { ++ return rc; ++ } ++ } ++ } ++ } while (!client->down_out || !origin->down_out); ++ ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(10223) ++ "proxy: %s: tunnel finished", scheme); ++ return OK; ++} ++ + PROXY_DECLARE (const char *) ap_proxy_show_hcmethod(hcmethod_t method) + { + proxy_hcmethods_t *m = proxy_hcmethods; diff --git a/debian/patches/series b/debian/patches/series index 8596c419..7655a428 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -17,3 +17,4 @@ CVE-2021-26691.patch CVE-2021-26690.patch CVE-2020-35452.patch CVE-2020-13950.patch +CVE-2019-17567.patch