+
Available Languages: en |
+diff --git a/modules/proxy/mod_proxy_wstunnel.c b/modules/proxy/mod_proxy_wstunnel.c
+index 4aadbab..ca3ed3a 100644
+--- a/modules/proxy/mod_proxy_wstunnel.c
++++ b/modules/proxy/mod_proxy_wstunnel.c
+@@ -18,6 +18,10 @@
+
+ module AP_MODULE_DECLARE_DATA proxy_wstunnel_module;
+
++typedef struct {
++ apr_time_t idle_timeout;
++} proxyws_dir_conf;
++
+ /*
+ * Canonicalise http-like URLs.
+ * scheme is the scheme for the URL
+@@ -108,6 +112,8 @@ static int proxy_wstunnel_request(apr_pool_t *p, request_rec *r,
+ conn_rec *c = r->connection;
+ apr_socket_t *sock = conn->sock;
+ conn_rec *backconn = conn->connection;
++ proxyws_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
++ &proxy_wstunnel_module);
+ char *buf;
+ apr_bucket_brigade *header_brigade;
+ apr_bucket *e;
+@@ -185,10 +191,13 @@ static int proxy_wstunnel_request(apr_pool_t *p, request_rec *r,
+ c->keepalive = AP_CONN_CLOSE;
+
+ do { /* Loop until done (one side closes the connection, or an error) */
+- rv = apr_pollset_poll(pollset, -1, &pollcnt, &signalled);
++ rv = apr_pollset_poll(pollset, dconf->idle_timeout, &pollcnt, &signalled);
+ if (rv != APR_SUCCESS) {
+ if (APR_STATUS_IS_EINTR(rv)) {
+ continue;
++ } else if(APR_STATUS_IS_TIMEUP(rv)){
++ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "RH: the connection has timed out");
++ return HTTP_REQUEST_TIME_OUT;
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02444) "error apr_poll()");
+ return HTTP_INTERNAL_SERVER_ERROR;
+@@ -366,6 +375,38 @@ cleanup:
+ return status;
+ }
+
++static const char * proxyws_set_idle(cmd_parms *cmd, void *conf, const char *val)
++{
++ proxyws_dir_conf *dconf = conf;
++ if (ap_timeout_parameter_parse(val, &(dconf->idle_timeout), "s") != APR_SUCCESS)
++ return "ProxyWebsocketIdleTimeout timeout has wrong format";
++
++ if (dconf->idle_timeout < 0)
++ return "ProxyWebsocketIdleTimeout timeout has to be a non-negative number";
++
++ if (!dconf->idle_timeout) dconf->idle_timeout = -1; /* loop indefinitely */
++
++ return NULL;
++}
++
++static void *create_proxyws_dir_config(apr_pool_t *p, char *dummy)
++{
++ proxyws_dir_conf *new =
++ (proxyws_dir_conf *) apr_pcalloc(p, sizeof(proxyws_dir_conf));
++
++ new->idle_timeout = -1; /* no timeout */
++
++ return (void *) new;
++}
++
++static const command_rec ws_proxy_cmds[] =
++{
++ AP_INIT_TAKE1("ProxyWebsocketIdleTimeout", proxyws_set_idle, NULL, RSRC_CONF|ACCESS_CONF,
++ "timeout for activity in either direction, unlimited by default."),
++
++ {NULL}
++};
++
+ static void ap_proxy_http_register_hook(apr_pool_t *p)
+ {
+ proxy_hook_scheme_handler(proxy_wstunnel_handler, NULL, NULL, APR_HOOK_FIRST);
+@@ -374,10 +415,10 @@ static void ap_proxy_http_register_hook(apr_pool_t *p)
+
+ AP_DECLARE_MODULE(proxy_wstunnel) = {
+ STANDARD20_MODULE_STUFF,
+- NULL, /* create per-directory config structure */
++ create_proxyws_dir_config, /* create per-directory config structure */
+ NULL, /* merge per-directory config structures */
+ NULL, /* create per-server config structure */
+ NULL, /* merge per-server config structures */
+- NULL, /* command apr_table_t */
++ ws_proxy_cmds, /* command apr_table_t */
+ ap_proxy_http_register_hook /* register hooks */
+ };
diff --git a/httpd-2.4.37-r1828172+.patch b/httpd-2.4.37-r1828172+.patch
new file mode 100644
index 0000000..72b124b
--- /dev/null
+++ b/httpd-2.4.37-r1828172+.patch
@@ -0,0 +1,1420 @@
+# ./pullrev.sh 1828172 1862968 1863191 1867878 1867882 1867968 1867970 1867971
+http://svn.apache.org/viewvc?view=revision&revision=1828172
+http://svn.apache.org/viewvc?view=revision&revision=1862968
+http://svn.apache.org/viewvc?view=revision&revision=1863191
+http://svn.apache.org/viewvc?view=revision&revision=1867878
+http://svn.apache.org/viewvc?view=revision&revision=1867882
+http://svn.apache.org/viewvc?view=revision&revision=1867968
+http://svn.apache.org/viewvc?view=revision&revision=1867970
+http://svn.apache.org/viewvc?view=revision&revision=1867971
+
+--- httpd-2.4.41/modules/generators/mod_cgi.c
++++ httpd-2.4.41/modules/generators/mod_cgi.c
+@@ -92,6 +92,10 @@
+ apr_size_t bufbytes;
+ } cgi_server_conf;
+
++typedef struct {
++ apr_interval_time_t timeout;
++} cgi_dirconf;
++
+ static void *create_cgi_config(apr_pool_t *p, server_rec *s)
+ {
+ cgi_server_conf *c =
+@@ -112,6 +116,12 @@
+ return overrides->logname ? overrides : base;
+ }
+
++static void *create_cgi_dirconf(apr_pool_t *p, char *dummy)
++{
++ cgi_dirconf *c = (cgi_dirconf *) apr_pcalloc(p, sizeof(cgi_dirconf));
++ return c;
++}
++
+ static const char *set_scriptlog(cmd_parms *cmd, void *dummy, const char *arg)
+ {
+ server_rec *s = cmd->server;
+@@ -150,6 +160,17 @@
+ return NULL;
+ }
+
++static const char *set_script_timeout(cmd_parms *cmd, void *dummy, const char *arg)
++{
++ cgi_dirconf *dc = dummy;
++
++ if (ap_timeout_parameter_parse(arg, &dc->timeout, "s") != APR_SUCCESS) {
++ return "CGIScriptTimeout has wrong format";
++ }
++
++ return NULL;
++}
++
+ static const command_rec cgi_cmds[] =
+ {
+ AP_INIT_TAKE1("ScriptLog", set_scriptlog, NULL, RSRC_CONF,
+@@ -158,6 +179,9 @@
+ "the maximum length (in bytes) of the script debug log"),
+ AP_INIT_TAKE1("ScriptLogBuffer", set_scriptlog_buffer, NULL, RSRC_CONF,
+ "the maximum size (in bytes) to record of a POST request"),
++AP_INIT_TAKE1("CGIScriptTimeout", set_script_timeout, NULL, RSRC_CONF | ACCESS_CONF,
++ "The amount of time to wait between successful reads from "
++ "the CGI script, in seconds."),
+ {NULL}
+ };
+
+@@ -471,23 +495,26 @@
+ apr_filepath_name_get(r->filename));
+ }
+ else {
++ cgi_dirconf *dc = ap_get_module_config(r->per_dir_config, &cgi_module);
++ apr_interval_time_t timeout = dc->timeout > 0 ? dc->timeout : r->server->timeout;
++
+ apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT);
+
+ *script_in = procnew->out;
+ if (!*script_in)
+ return APR_EBADF;
+- apr_file_pipe_timeout_set(*script_in, r->server->timeout);
++ apr_file_pipe_timeout_set(*script_in, timeout);
+
+ if (e_info->prog_type == RUN_AS_CGI) {
+ *script_out = procnew->in;
+ if (!*script_out)
+ return APR_EBADF;
+- apr_file_pipe_timeout_set(*script_out, r->server->timeout);
++ apr_file_pipe_timeout_set(*script_out, timeout);
+
+ *script_err = procnew->err;
+ if (!*script_err)
+ return APR_EBADF;
+- apr_file_pipe_timeout_set(*script_err, r->server->timeout);
++ apr_file_pipe_timeout_set(*script_err, timeout);
+ }
+ }
+ }
+@@ -541,212 +568,10 @@
+ return APR_SUCCESS;
+ }
+
+-static void discard_script_output(apr_bucket_brigade *bb)
+-{
+- apr_bucket *e;
+- const char *buf;
+- apr_size_t len;
+- apr_status_t rv;
+-
+- for (e = APR_BRIGADE_FIRST(bb);
+- e != APR_BRIGADE_SENTINEL(bb);
+- e = APR_BUCKET_NEXT(e))
+- {
+- if (APR_BUCKET_IS_EOS(e)) {
+- break;
+- }
+- rv = apr_bucket_read(e, &buf, &len, APR_BLOCK_READ);
+- if (rv != APR_SUCCESS) {
+- break;
+- }
+- }
+-}
+-
+ #if APR_FILES_AS_SOCKETS
+-
+-/* A CGI bucket type is needed to catch any output to stderr from the
+- * script; see PR 22030. */
+-static const apr_bucket_type_t bucket_type_cgi;
+-
+-struct cgi_bucket_data {
+- apr_pollset_t *pollset;
+- request_rec *r;
+-};
+-
+-/* Create a CGI bucket using pipes from script stdout 'out'
+- * and stderr 'err', for request 'r'. */
+-static apr_bucket *cgi_bucket_create(request_rec *r,
+- apr_file_t *out, apr_file_t *err,
+- apr_bucket_alloc_t *list)
+-{
+- apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+- apr_status_t rv;
+- apr_pollfd_t fd;
+- struct cgi_bucket_data *data = apr_palloc(r->pool, sizeof *data);
+-
+- APR_BUCKET_INIT(b);
+- b->free = apr_bucket_free;
+- b->list = list;
+- b->type = &bucket_type_cgi;
+- b->length = (apr_size_t)(-1);
+- b->start = -1;
+-
+- /* Create the pollset */
+- rv = apr_pollset_create(&data->pollset, 2, r->pool, 0);
+- if (rv != APR_SUCCESS) {
+- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01217)
+- "apr_pollset_create(); check system or user limits");
+- return NULL;
+- }
+-
+- fd.desc_type = APR_POLL_FILE;
+- fd.reqevents = APR_POLLIN;
+- fd.p = r->pool;
+- fd.desc.f = out; /* script's stdout */
+- fd.client_data = (void *)1;
+- rv = apr_pollset_add(data->pollset, &fd);
+- if (rv != APR_SUCCESS) {
+- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01218)
+- "apr_pollset_add(); check system or user limits");
+- return NULL;
+- }
+-
+- fd.desc.f = err; /* script's stderr */
+- fd.client_data = (void *)2;
+- rv = apr_pollset_add(data->pollset, &fd);
+- if (rv != APR_SUCCESS) {
+- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01219)
+- "apr_pollset_add(); check system or user limits");
+- return NULL;
+- }
+-
+- data->r = r;
+- b->data = data;
+- return b;
+-}
+-
+-/* Create a duplicate CGI bucket using given bucket data */
+-static apr_bucket *cgi_bucket_dup(struct cgi_bucket_data *data,
+- apr_bucket_alloc_t *list)
+-{
+- apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+- APR_BUCKET_INIT(b);
+- b->free = apr_bucket_free;
+- b->list = list;
+- b->type = &bucket_type_cgi;
+- b->length = (apr_size_t)(-1);
+- b->start = -1;
+- b->data = data;
+- return b;
+-}
+-
+-/* Handle stdout from CGI child. Duplicate of logic from the _read
+- * method of the real APR pipe bucket implementation. */
+-static apr_status_t cgi_read_stdout(apr_bucket *a, apr_file_t *out,
+- const char **str, apr_size_t *len)
+-{
+- char *buf;
+- apr_status_t rv;
+-
+- *str = NULL;
+- *len = APR_BUCKET_BUFF_SIZE;
+- buf = apr_bucket_alloc(*len, a->list); /* XXX: check for failure? */
+-
+- rv = apr_file_read(out, buf, len);
+-
+- if (rv != APR_SUCCESS && rv != APR_EOF) {
+- apr_bucket_free(buf);
+- return rv;
+- }
+-
+- if (*len > 0) {
+- struct cgi_bucket_data *data = a->data;
+- apr_bucket_heap *h;
+-
+- /* Change the current bucket to refer to what we read */
+- a = apr_bucket_heap_make(a, buf, *len, apr_bucket_free);
+- h = a->data;
+- h->alloc_len = APR_BUCKET_BUFF_SIZE; /* note the real buffer size */
+- *str = buf;
+- APR_BUCKET_INSERT_AFTER(a, cgi_bucket_dup(data, a->list));
+- }
+- else {
+- apr_bucket_free(buf);
+- a = apr_bucket_immortal_make(a, "", 0);
+- *str = a->data;
+- }
+- return rv;
+-}
+-
+-/* Read method of CGI bucket: polls on stderr and stdout of the child,
+- * sending any stderr output immediately away to the error log. */
+-static apr_status_t cgi_bucket_read(apr_bucket *b, const char **str,
+- apr_size_t *len, apr_read_type_e block)
+-{
+- struct cgi_bucket_data *data = b->data;
+- apr_interval_time_t timeout;
+- apr_status_t rv;
+- int gotdata = 0;
+-
+- timeout = block == APR_NONBLOCK_READ ? 0 : data->r->server->timeout;
+-
+- do {
+- const apr_pollfd_t *results;
+- apr_int32_t num;
+-
+- rv = apr_pollset_poll(data->pollset, timeout, &num, &results);
+- if (APR_STATUS_IS_TIMEUP(rv)) {
+- if (timeout) {
+- ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, data->r, APLOGNO(01220)
+- "Timeout waiting for output from CGI script %s",
+- data->r->filename);
+- return rv;
+- }
+- else {
+- return APR_EAGAIN;
+- }
+- }
+- else if (APR_STATUS_IS_EINTR(rv)) {
+- continue;
+- }
+- else if (rv != APR_SUCCESS) {
+- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, data->r, APLOGNO(01221)
+- "poll failed waiting for CGI child");
+- return rv;
+- }
+-
+- for (; num; num--, results++) {
+- if (results[0].client_data == (void *)1) {
+- /* stdout */
+- rv = cgi_read_stdout(b, results[0].desc.f, str, len);
+- if (APR_STATUS_IS_EOF(rv)) {
+- rv = APR_SUCCESS;
+- }
+- gotdata = 1;
+- } else {
+- /* stderr */
+- apr_status_t rv2 = log_script_err(data->r, results[0].desc.f);
+- if (APR_STATUS_IS_EOF(rv2)) {
+- apr_pollset_remove(data->pollset, &results[0]);
+- }
+- }
+- }
+-
+- } while (!gotdata);
+-
+- return rv;
+-}
+-
+-static const apr_bucket_type_t bucket_type_cgi = {
+- "CGI", 5, APR_BUCKET_DATA,
+- apr_bucket_destroy_noop,
+- cgi_bucket_read,
+- apr_bucket_setaside_notimpl,
+- apr_bucket_split_notimpl,
+- apr_bucket_copy_notimpl
+-};
+-
++#define WANT_CGI_BUCKET
+ #endif
++#include "cgi_common.h"
+
+ static int cgi_handler(request_rec *r)
+ {
+@@ -766,6 +591,8 @@
+ apr_status_t rv;
+ cgi_exec_info_t e_info;
+ conn_rec *c;
++ cgi_dirconf *dc = ap_get_module_config(r->per_dir_config, &cgi_module);
++ apr_interval_time_t timeout = dc->timeout > 0 ? dc->timeout : r->server->timeout;
+
+ if (strcmp(r->handler, CGI_MAGIC_TYPE) && strcmp(r->handler, "cgi-script")) {
+ return DECLINED;
+@@ -925,10 +752,7 @@
+ AP_DEBUG_ASSERT(script_in != NULL);
+
+ #if APR_FILES_AS_SOCKETS
+- apr_file_pipe_timeout_set(script_in, 0);
+- apr_file_pipe_timeout_set(script_err, 0);
+-
+- b = cgi_bucket_create(r, script_in, script_err, c->bucket_alloc);
++ b = cgi_bucket_create(r, dc->timeout, script_in, script_err, c->bucket_alloc);
+ if (b == NULL)
+ return HTTP_INTERNAL_SERVER_ERROR;
+ #else
+@@ -938,111 +762,7 @@
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+- /* Handle script return... */
+- if (!nph) {
+- const char *location;
+- char sbuf[MAX_STRING_LEN];
+- int ret;
+-
+- if ((ret = ap_scan_script_header_err_brigade_ex(r, bb, sbuf,
+- APLOG_MODULE_INDEX)))
+- {
+- ret = log_script(r, conf, ret, dbuf, sbuf, bb, script_err);
+-
+- /*
+- * ret could be HTTP_NOT_MODIFIED in the case that the CGI script
+- * does not set an explicit status and ap_meets_conditions, which
+- * is called by ap_scan_script_header_err_brigade, detects that
+- * the conditions of the requests are met and the response is
+- * not modified.
+- * In this case set r->status and return OK in order to prevent
+- * running through the error processing stack as this would
+- * break with mod_cache, if the conditions had been set by
+- * mod_cache itself to validate a stale entity.
+- * BTW: We circumvent the error processing stack anyway if the
+- * CGI script set an explicit status code (whatever it is) and
+- * the only possible values for ret here are:
+- *
+- * HTTP_NOT_MODIFIED (set by ap_meets_conditions)
+- * HTTP_PRECONDITION_FAILED (set by ap_meets_conditions)
+- * HTTP_INTERNAL_SERVER_ERROR (if something went wrong during the
+- * processing of the response of the CGI script, e.g broken headers
+- * or a crashed CGI process).
+- */
+- if (ret == HTTP_NOT_MODIFIED) {
+- r->status = ret;
+- return OK;
+- }
+-
+- return ret;
+- }
+-
+- location = apr_table_get(r->headers_out, "Location");
+-
+- if (location && r->status == 200) {
+- /* For a redirect whether internal or not, discard any
+- * remaining stdout from the script, and log any remaining
+- * stderr output, as normal. */
+- discard_script_output(bb);
+- apr_brigade_destroy(bb);
+- apr_file_pipe_timeout_set(script_err, r->server->timeout);
+- log_script_err(r, script_err);
+- }
+-
+- if (location && location[0] == '/' && r->status == 200) {
+- /* This redirect needs to be a GET no matter what the original
+- * method was.
+- */
+- r->method = "GET";
+- r->method_number = M_GET;
+-
+- /* We already read the message body (if any), so don't allow
+- * the redirected request to think it has one. We can ignore
+- * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR.
+- */
+- apr_table_unset(r->headers_in, "Content-Length");
+-
+- ap_internal_redirect_handler(location, r);
+- return OK;
+- }
+- else if (location && r->status == 200) {
+- /* XXX: Note that if a script wants to produce its own Redirect
+- * body, it now has to explicitly *say* "Status: 302"
+- */
+- return HTTP_MOVED_TEMPORARILY;
+- }
+-
+- rv = ap_pass_brigade(r->output_filters, bb);
+- }
+- else /* nph */ {
+- struct ap_filter_t *cur;
+-
+- /* get rid of all filters up through protocol... since we
+- * haven't parsed off the headers, there is no way they can
+- * work
+- */
+-
+- cur = r->proto_output_filters;
+- while (cur && cur->frec->ftype < AP_FTYPE_CONNECTION) {
+- cur = cur->next;
+- }
+- r->output_filters = r->proto_output_filters = cur;
+-
+- rv = ap_pass_brigade(r->output_filters, bb);
+- }
+-
+- /* don't soak up script output if errors occurred writing it
+- * out... otherwise, we prolong the life of the script when the
+- * connection drops or we stopped sending output for some other
+- * reason */
+- if (rv == APR_SUCCESS && !r->connection->aborted) {
+- apr_file_pipe_timeout_set(script_err, r->server->timeout);
+- log_script_err(r, script_err);
+- }
+-
+- apr_file_close(script_err);
+-
+- return OK; /* NOT r->status, even if it has changed. */
++ return cgi_handle_response(r, nph, bb, timeout, conf, dbuf, script_err);
+ }
+
+ /*============================================================================
+@@ -1277,7 +997,7 @@
+ AP_DECLARE_MODULE(cgi) =
+ {
+ STANDARD20_MODULE_STUFF,
+- NULL, /* dir config creater */
++ create_cgi_dirconf, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ create_cgi_config, /* server config */
+ merge_cgi_config, /* merge server config */
+--- httpd-2.4.41/modules/generators/config5.m4
++++ httpd-2.4.41/modules/generators/config5.m4
+@@ -78,4 +78,15 @@
+
+ APR_ADDTO(INCLUDES, [-I\$(top_srcdir)/$modpath_current])
+
++AC_ARG_ENABLE(cgid-fdpassing,
++ [APACHE_HELP_STRING(--enable-cgid-fdpassing,Enable experimental mod_cgid support for fd passing)],
++ [if test "$enableval" = "yes"; then
++ AC_CHECK_DECL(CMSG_DATA,
++ [AC_DEFINE([HAVE_CGID_FDPASSING], 1, [Enable FD passing support in mod_cgid])],
++ [AC_MSG_ERROR([cannot support mod_cgid fd-passing on this system])], [
++#include
++#include ])
++ fi
++])
++
+ APACHE_MODPATH_FINISH
+--- httpd-2.4.41/modules/generators/mod_cgid.c
++++ httpd-2.4.41/modules/generators/mod_cgid.c
+@@ -342,15 +342,19 @@
+ return close(fd);
+ }
+
+-/* deal with incomplete reads and signals
+- * assume you really have to read buf_size bytes
+- */
+-static apr_status_t sock_read(int fd, void *vbuf, size_t buf_size)
++/* Read from the socket dealing with incomplete messages and signals.
++ * Returns 0 on success or errno on failure. Stderr fd passed as
++ * auxiliary data from other end is written to *errfd, or else stderr
++ * fileno if not present. */
++static apr_status_t sock_readhdr(int fd, int *errfd, void *vbuf, size_t buf_size)
+ {
+- char *buf = vbuf;
+ int rc;
++#ifndef HAVE_CGID_FDPASSING
++ char *buf = vbuf;
+ size_t bytes_read = 0;
+
++ if (errfd) *errfd = 0;
++
+ do {
+ do {
+ rc = read(fd, buf + bytes_read, buf_size - bytes_read);
+@@ -365,9 +369,60 @@
+ }
+ } while (bytes_read < buf_size);
+
++
++#else /* with FD passing */
++ struct msghdr msg = {0};
++ struct iovec vec = {vbuf, buf_size};
++ struct cmsghdr *cmsg;
++ union { /* union to ensure alignment */
++ struct cmsghdr cm;
++ char buf[CMSG_SPACE(sizeof(int))];
++ } u;
++
++ msg.msg_iov = &vec;
++ msg.msg_iovlen = 1;
++
++ if (errfd) {
++ msg.msg_control = u.buf;
++ msg.msg_controllen = sizeof(u.buf);
++ *errfd = 0;
++ }
++
++ /* use MSG_WAITALL to skip loop on truncated reads */
++ do {
++ rc = recvmsg(fd, &msg, MSG_WAITALL);
++ } while (rc < 0 && errno == EINTR);
++
++ if (rc == 0) {
++ return ECONNRESET;
++ }
++ else if (rc < 0) {
++ return errno;
++ }
++ else if (rc != buf_size) {
++ /* MSG_WAITALL should ensure the recvmsg blocks until the
++ * entire length is read, but let's be paranoid. */
++ return APR_INCOMPLETE;
++ }
++
++ if (errfd
++ && (cmsg = CMSG_FIRSTHDR(&msg)) != NULL
++ && cmsg->cmsg_len == CMSG_LEN(sizeof(*errfd))
++ && cmsg->cmsg_level == SOL_SOCKET
++ && cmsg->cmsg_type == SCM_RIGHTS) {
++ *errfd = *((int *) CMSG_DATA(cmsg));
++ }
++#endif
++
+ return APR_SUCCESS;
+ }
+
++/* As sock_readhdr but without auxiliary fd passing. */
++static apr_status_t sock_read(int fd, void *vbuf, size_t buf_size)
++{
++ return sock_readhdr(fd, NULL, vbuf, buf_size);
++}
++
+ /* deal with signals
+ */
+ static apr_status_t sock_write(int fd, const void *buf, size_t buf_size)
+@@ -384,7 +439,7 @@
+ return APR_SUCCESS;
+ }
+
+-static apr_status_t sock_writev(int fd, request_rec *r, int count, ...)
++static apr_status_t sock_writev(int fd, int auxfd, request_rec *r, int count, ...)
+ {
+ va_list ap;
+ int rc;
+@@ -399,9 +454,39 @@
+ }
+ va_end(ap);
+
++#ifndef HAVE_CGID_FDPASSING
+ do {
+ rc = writev(fd, vec, count);
+ } while (rc < 0 && errno == EINTR);
++#else
++ {
++ struct msghdr msg = { 0 };
++ struct cmsghdr *cmsg;
++ union { /* union for alignment */
++ char buf[CMSG_SPACE(sizeof(int))];
++ struct cmsghdr align;
++ } u;
++
++ msg.msg_iov = vec;
++ msg.msg_iovlen = count;
++
++ if (auxfd) {
++ msg.msg_control = u.buf;
++ msg.msg_controllen = sizeof(u.buf);
++
++ cmsg = CMSG_FIRSTHDR(&msg);
++ cmsg->cmsg_level = SOL_SOCKET;
++ cmsg->cmsg_type = SCM_RIGHTS;
++ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
++ *((int *) CMSG_DATA(cmsg)) = auxfd;
++ }
++
++ do {
++ rc = sendmsg(fd, &msg, 0);
++ } while (rc < 0 && errno == EINTR);
++ }
++#endif
++
+ if (rc < 0) {
+ return errno;
+ }
+@@ -410,7 +495,7 @@
+ }
+
+ static apr_status_t get_req(int fd, request_rec *r, char **argv0, char ***env,
+- cgid_req_t *req)
++ int *errfd, cgid_req_t *req)
+ {
+ int i;
+ char **environ;
+@@ -421,7 +506,7 @@
+ r->server = apr_pcalloc(r->pool, sizeof(server_rec));
+
+ /* read the request header */
+- stat = sock_read(fd, req, sizeof(*req));
++ stat = sock_readhdr(fd, errfd, req, sizeof(*req));
+ if (stat != APR_SUCCESS) {
+ return stat;
+ }
+@@ -479,14 +564,15 @@
+ return APR_SUCCESS;
+ }
+
+-static apr_status_t send_req(int fd, request_rec *r, char *argv0, char **env,
+- int req_type)
++static apr_status_t send_req(int fd, apr_file_t *errpipe, request_rec *r,
++ char *argv0, char **env, int req_type)
+ {
+ int i;
+ cgid_req_t req = {0};
+ apr_status_t stat;
+ ap_unix_identity_t * ugid = ap_run_get_suexec_identity(r);
+ core_dir_config *core_conf = ap_get_core_module_config(r->per_dir_config);
++ int errfd;
+
+
+ if (ugid == NULL) {
+@@ -507,16 +593,21 @@
+ req.args_len = r->args ? strlen(r->args) : 0;
+ req.loglevel = r->server->log.level;
+
++ if (errpipe)
++ apr_os_file_get(&errfd, errpipe);
++ else
++ errfd = 0;
++
+ /* Write the request header */
+ if (req.args_len) {
+- stat = sock_writev(fd, r, 5,
++ stat = sock_writev(fd, errfd, r, 5,
+ &req, sizeof(req),
+ r->filename, req.filename_len,
+ argv0, req.argv0_len,
+ r->uri, req.uri_len,
+ r->args, req.args_len);
+ } else {
+- stat = sock_writev(fd, r, 4,
++ stat = sock_writev(fd, errfd, r, 4,
+ &req, sizeof(req),
+ r->filename, req.filename_len,
+ argv0, req.argv0_len,
+@@ -531,7 +622,7 @@
+ for (i = 0; i < req.env_count; i++) {
+ apr_size_t curlen = strlen(env[i]);
+
+- if ((stat = sock_writev(fd, r, 2, &curlen, sizeof(curlen),
++ if ((stat = sock_writev(fd, 0, r, 2, &curlen, sizeof(curlen),
+ env[i], curlen)) != APR_SUCCESS) {
+ return stat;
+ }
+@@ -582,20 +673,34 @@
+ }
+ }
+
++/* Callback executed in the forked child process if exec of the CGI
++ * script fails. For the fd-passing case, output to stderr goes to
++ * the client (request handling thread) and is logged via
++ * ap_log_rerror there. For the non-fd-passing case, the "fake"
++ * request_rec passed via userdata is used to log. */
+ static void cgid_child_errfn(apr_pool_t *pool, apr_status_t err,
+ const char *description)
+ {
+- request_rec *r;
+ void *vr;
+
+ apr_pool_userdata_get(&vr, ERRFN_USERDATA_KEY, pool);
+- r = vr;
+-
+- /* sure we got r, but don't call ap_log_rerror() because we don't
+- * have r->headers_in and possibly other storage referenced by
+- * ap_log_rerror()
+- */
+- ap_log_error(APLOG_MARK, APLOG_ERR, err, r->server, APLOGNO(01241) "%s", description);
++ if (vr) {
++ request_rec *r = vr;
++
++ /* sure we got r, but don't call ap_log_rerror() because we don't
++ * have r->headers_in and possibly other storage referenced by
++ * ap_log_rerror()
++ */
++ ap_log_error(APLOG_MARK, APLOG_ERR, err, r->server, APLOGNO(01241) "%s", description);
++ }
++ else {
++ const char *logstr;
++
++ logstr = apr_psprintf(pool, APLOGNO(01241) "error spawning CGI child: %s (%pm)\n",
++ description, &err);
++ fputs(logstr, stderr);
++ fflush(stderr);
++ }
+ }
+
+ static int cgid_server(void *data)
+@@ -669,7 +774,7 @@
+ }
+
+ while (!daemon_should_exit) {
+- int errfileno = STDERR_FILENO;
++ int errfileno;
+ char *argv0 = NULL;
+ char **env = NULL;
+ const char * const *argv;
+@@ -709,7 +814,7 @@
+ r = apr_pcalloc(ptrans, sizeof(request_rec));
+ procnew = apr_pcalloc(ptrans, sizeof(*procnew));
+ r->pool = ptrans;
+- stat = get_req(sd2, r, &argv0, &env, &cgid_req);
++ stat = get_req(sd2, r, &argv0, &env, &errfileno, &cgid_req);
+ if (stat != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, stat,
+ main_server, APLOGNO(01248)
+@@ -741,6 +846,16 @@
+ continue;
+ }
+
++ if (errfileno == 0) {
++ errfileno = STDERR_FILENO;
++ }
++ else {
++ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, main_server,
++ "using passed fd %d as stderr", errfileno);
++ /* Limit the received fd lifetime to pool lifetime */
++ apr_pool_cleanup_register(ptrans, (void *)((long)errfileno),
++ close_unix_socket, close_unix_socket);
++ }
+ apr_os_file_put(&r->server->error_log, &errfileno, 0, r->pool);
+ apr_os_file_put(&inout, &sd2, 0, r->pool);
+
+@@ -800,7 +915,10 @@
+ close(sd2);
+ }
+ else {
+- apr_pool_userdata_set(r, ERRFN_USERDATA_KEY, apr_pool_cleanup_null, ptrans);
++ if (errfileno == STDERR_FILENO) {
++ /* Used by cgid_child_errfn without fd-passing. */
++ apr_pool_userdata_set(r, ERRFN_USERDATA_KEY, apr_pool_cleanup_null, ptrans);
++ }
+
+ argv = (const char * const *)create_argv(r->pool, NULL, NULL, NULL, argv0, r->args);
+
+@@ -1099,6 +1217,33 @@
+ return ret;
+ }
+
++/* Soak up stderr from a script and redirect it to the error log.
++ * TODO: log_scripterror() and this could move to cgi_common.h. */
++static apr_status_t log_script_err(request_rec *r, apr_file_t *script_err)
++{
++ char argsbuffer[HUGE_STRING_LEN];
++ char *newline;
++ apr_status_t rv;
++ cgid_server_conf *conf = ap_get_module_config(r->server->module_config, &cgid_module);
++
++ while ((rv = apr_file_gets(argsbuffer, HUGE_STRING_LEN,
++ script_err)) == APR_SUCCESS) {
++
++ newline = strchr(argsbuffer, '\n');
++ if (newline) {
++ char *prev = newline - 1;
++ if (prev >= argsbuffer && *prev == '\r') {
++ newline = prev;
++ }
++
++ *newline = '\0';
++ }
++ log_scripterror(r, conf, r->status, 0, argsbuffer);
++ }
++
++ return rv;
++}
++
+ static int log_script(request_rec *r, cgid_server_conf * conf, int ret,
+ char *dbuf, const char *sbuf, apr_bucket_brigade *bb,
+ apr_file_t *script_err)
+@@ -1204,6 +1349,13 @@
+ return ret;
+ }
+
++/* Pull in CGI bucket implementation. */
++#define cgi_server_conf cgid_server_conf
++#ifdef HAVE_CGID_FDPASSING
++#define WANT_CGI_BUCKET
++#endif
++#include "cgi_common.h"
++
+ static int connect_to_daemon(int *sdptr, request_rec *r,
+ cgid_server_conf *conf)
+ {
+@@ -1270,27 +1422,6 @@
+ return OK;
+ }
+
+-static void discard_script_output(apr_bucket_brigade *bb)
+-{
+- apr_bucket *e;
+- const char *buf;
+- apr_size_t len;
+- apr_status_t rv;
+-
+- for (e = APR_BRIGADE_FIRST(bb);
+- e != APR_BRIGADE_SENTINEL(bb);
+- e = APR_BUCKET_NEXT(e))
+- {
+- if (APR_BUCKET_IS_EOS(e)) {
+- break;
+- }
+- rv = apr_bucket_read(e, &buf, &len, APR_BLOCK_READ);
+- if (rv != APR_SUCCESS) {
+- break;
+- }
+- }
+-}
+-
+ /****************************************************************
+ *
+ * Actual cgid handling...
+@@ -1395,6 +1526,7 @@
+
+ static int cgid_handler(request_rec *r)
+ {
++ conn_rec *c = r->connection;
+ int retval, nph, dbpos;
+ char *argv0, *dbuf;
+ apr_bucket_brigade *bb;
+@@ -1404,10 +1536,11 @@
+ int seen_eos, child_stopped_reading;
+ int sd;
+ char **env;
+- apr_file_t *tempsock;
++ apr_file_t *tempsock, *script_err, *errpipe_out;
+ struct cleanup_script_info *info;
+ apr_status_t rv;
+ cgid_dirconf *dc;
++ apr_interval_time_t timeout;
+
+ if (strcmp(r->handler, CGI_MAGIC_TYPE) && strcmp(r->handler, "cgi-script")) {
+ return DECLINED;
+@@ -1416,7 +1549,7 @@
+ conf = ap_get_module_config(r->server->module_config, &cgid_module);
+ dc = ap_get_module_config(r->per_dir_config, &cgid_module);
+
+-
++ timeout = dc->timeout > 0 ? dc->timeout : r->server->timeout;
+ is_included = !strcmp(r->protocol, "INCLUDED");
+
+ if ((argv0 = strrchr(r->filename, '/')) != NULL) {
+@@ -1469,6 +1602,17 @@
+ }
+ */
+
++#ifdef HAVE_CGID_FDPASSING
++ rv = apr_file_pipe_create(&script_err, &errpipe_out, r->pool);
++ if (rv) {
++ return log_scripterror(r, conf, HTTP_SERVICE_UNAVAILABLE, rv, APLOGNO(10176)
++ "could not create pipe for stderr");
++ }
++#else
++ script_err = NULL;
++ errpipe_out = NULL;
++#endif
++
+ /*
+ * httpd core function used to add common environment variables like
+ * DOCUMENT_ROOT.
+@@ -1481,12 +1625,16 @@
+ return retval;
+ }
+
+- rv = send_req(sd, r, argv0, env, CGI_REQ);
++ rv = send_req(sd, errpipe_out, r, argv0, env, CGI_REQ);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01268)
+ "write to cgi daemon process");
+ }
+
++ /* The write-end of the pipe is only used by the server, so close
++ * it here. */
++ if (errpipe_out) apr_file_close(errpipe_out);
++
+ info = apr_palloc(r->pool, sizeof(struct cleanup_script_info));
+ info->conf = conf;
+ info->r = r;
+@@ -1508,12 +1656,7 @@
+ */
+
+ apr_os_pipe_put_ex(&tempsock, &sd, 1, r->pool);
+- if (dc->timeout > 0) {
+- apr_file_pipe_timeout_set(tempsock, dc->timeout);
+- }
+- else {
+- apr_file_pipe_timeout_set(tempsock, r->server->timeout);
+- }
++ apr_file_pipe_timeout_set(tempsock, timeout);
+ apr_pool_cleanup_kill(r->pool, (void *)((long)sd), close_unix_socket);
+
+ /* Transfer any put/post args, CERN style...
+@@ -1605,114 +1748,19 @@
+ */
+ shutdown(sd, 1);
+
+- /* Handle script return... */
+- if (!nph) {
+- conn_rec *c = r->connection;
+- const char *location;
+- char sbuf[MAX_STRING_LEN];
+- int ret;
+-
+- bb = apr_brigade_create(r->pool, c->bucket_alloc);
+- b = apr_bucket_pipe_create(tempsock, c->bucket_alloc);
+- APR_BRIGADE_INSERT_TAIL(bb, b);
+- b = apr_bucket_eos_create(c->bucket_alloc);
+- APR_BRIGADE_INSERT_TAIL(bb, b);
+-
+- if ((ret = ap_scan_script_header_err_brigade_ex(r, bb, sbuf,
+- APLOG_MODULE_INDEX)))
+- {
+- ret = log_script(r, conf, ret, dbuf, sbuf, bb, NULL);
+-
+- /*
+- * ret could be HTTP_NOT_MODIFIED in the case that the CGI script
+- * does not set an explicit status and ap_meets_conditions, which
+- * is called by ap_scan_script_header_err_brigade, detects that
+- * the conditions of the requests are met and the response is
+- * not modified.
+- * In this case set r->status and return OK in order to prevent
+- * running through the error processing stack as this would
+- * break with mod_cache, if the conditions had been set by
+- * mod_cache itself to validate a stale entity.
+- * BTW: We circumvent the error processing stack anyway if the
+- * CGI script set an explicit status code (whatever it is) and
+- * the only possible values for ret here are:
+- *
+- * HTTP_NOT_MODIFIED (set by ap_meets_conditions)
+- * HTTP_PRECONDITION_FAILED (set by ap_meets_conditions)
+- * HTTP_INTERNAL_SERVER_ERROR (if something went wrong during the
+- * processing of the response of the CGI script, e.g broken headers
+- * or a crashed CGI process).
+- */
+- if (ret == HTTP_NOT_MODIFIED) {
+- r->status = ret;
+- return OK;
+- }
+-
+- return ret;
+- }
+-
+- location = apr_table_get(r->headers_out, "Location");
+-
+- if (location && location[0] == '/' && r->status == 200) {
+-
+- /* Soak up all the script output */
+- discard_script_output(bb);
+- apr_brigade_destroy(bb);
+- /* This redirect needs to be a GET no matter what the original
+- * method was.
+- */
+- r->method = "GET";
+- r->method_number = M_GET;
+-
+- /* We already read the message body (if any), so don't allow
+- * the redirected request to think it has one. We can ignore
+- * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR.
+- */
+- apr_table_unset(r->headers_in, "Content-Length");
+-
+- ap_internal_redirect_handler(location, r);
+- return OK;
+- }
+- else if (location && r->status == 200) {
+- /* XXX: Note that if a script wants to produce its own Redirect
+- * body, it now has to explicitly *say* "Status: 302"
+- */
+- discard_script_output(bb);
+- apr_brigade_destroy(bb);
+- return HTTP_MOVED_TEMPORARILY;
+- }
+-
+- rv = ap_pass_brigade(r->output_filters, bb);
+- if (rv != APR_SUCCESS) {
+- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
+- "Failed to flush CGI output to client");
+- }
+- }
+-
+- if (nph) {
+- conn_rec *c = r->connection;
+- struct ap_filter_t *cur;
+-
+- /* get rid of all filters up through protocol... since we
+- * haven't parsed off the headers, there is no way they can
+- * work
+- */
+-
+- cur = r->proto_output_filters;
+- while (cur && cur->frec->ftype < AP_FTYPE_CONNECTION) {
+- cur = cur->next;
+- }
+- r->output_filters = r->proto_output_filters = cur;
+-
+- bb = apr_brigade_create(r->pool, c->bucket_alloc);
+- b = apr_bucket_pipe_create(tempsock, c->bucket_alloc);
+- APR_BRIGADE_INSERT_TAIL(bb, b);
+- b = apr_bucket_eos_create(c->bucket_alloc);
+- APR_BRIGADE_INSERT_TAIL(bb, b);
+- ap_pass_brigade(r->output_filters, bb);
+- }
++ bb = apr_brigade_create(r->pool, c->bucket_alloc);
++#ifdef HAVE_CGID_FDPASSING
++ b = cgi_bucket_create(r, dc->timeout, tempsock, script_err, c->bucket_alloc);
++ if (b == NULL)
++ return HTTP_INTERNAL_SERVER_ERROR; /* should call log_scripterror() w/ _UNAVAILABLE? */
++#else
++ b = apr_bucket_pipe_create(tempsock, c->bucket_alloc);
++#endif
++ APR_BRIGADE_INSERT_TAIL(bb, b);
++ b = apr_bucket_eos_create(c->bucket_alloc);
++ APR_BRIGADE_INSERT_TAIL(bb, b);
+
+- return OK; /* NOT r->status, even if it has changed. */
++ return cgi_handle_response(r, nph, bb, timeout, conf, dbuf, script_err);
+ }
+
+
+@@ -1829,7 +1877,7 @@
+ return retval;
+ }
+
+- send_req(sd, r, command, env, SSI_REQ);
++ send_req(sd, NULL, r, command, env, SSI_REQ);
+
+ info = apr_palloc(r->pool, sizeof(struct cleanup_script_info));
+ info->conf = conf;
+--- httpd-2.4.41/modules/generators/cgi_common.h
++++ httpd-2.4.41/modules/generators/cgi_common.h
+@@ -0,0 +1,359 @@
++/* Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++#include "apr.h"
++#include "apr_strings.h"
++#include "apr_buckets.h"
++#include "apr_lib.h"
++#include "apr_poll.h"
++
++#define APR_WANT_STRFUNC
++#define APR_WANT_MEMFUNC
++#include "apr_want.h"
++
++#include "httpd.h"
++#include "util_filter.h"
++
++static void discard_script_output(apr_bucket_brigade *bb)
++{
++ apr_bucket *e;
++ const char *buf;
++ apr_size_t len;
++
++ for (e = APR_BRIGADE_FIRST(bb);
++ e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e);
++ e = APR_BRIGADE_FIRST(bb))
++ {
++ if (apr_bucket_read(e, &buf, &len, APR_BLOCK_READ)) {
++ break;
++ }
++ apr_bucket_delete(e);
++ }
++}
++
++#ifdef WANT_CGI_BUCKET
++/* A CGI bucket type is needed to catch any output to stderr from the
++ * script; see PR 22030. */
++static const apr_bucket_type_t bucket_type_cgi;
++
++struct cgi_bucket_data {
++ apr_pollset_t *pollset;
++ request_rec *r;
++ apr_interval_time_t timeout;
++};
++
++/* Create a CGI bucket using pipes from script stdout 'out'
++ * and stderr 'err', for request 'r'. */
++static apr_bucket *cgi_bucket_create(request_rec *r,
++ apr_interval_time_t timeout,
++ apr_file_t *out, apr_file_t *err,
++ apr_bucket_alloc_t *list)
++{
++ apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
++ apr_status_t rv;
++ apr_pollfd_t fd;
++ struct cgi_bucket_data *data = apr_palloc(r->pool, sizeof *data);
++
++ /* Disable APR timeout handling since we'll use poll() entirely. */
++ apr_file_pipe_timeout_set(out, 0);
++ apr_file_pipe_timeout_set(err, 0);
++
++ APR_BUCKET_INIT(b);
++ b->free = apr_bucket_free;
++ b->list = list;
++ b->type = &bucket_type_cgi;
++ b->length = (apr_size_t)(-1);
++ b->start = -1;
++
++ /* Create the pollset */
++ rv = apr_pollset_create(&data->pollset, 2, r->pool, 0);
++ if (rv != APR_SUCCESS) {
++ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01217)
++ "apr_pollset_create(); check system or user limits");
++ return NULL;
++ }
++
++ fd.desc_type = APR_POLL_FILE;
++ fd.reqevents = APR_POLLIN;
++ fd.p = r->pool;
++ fd.desc.f = out; /* script's stdout */
++ fd.client_data = (void *)1;
++ rv = apr_pollset_add(data->pollset, &fd);
++ if (rv != APR_SUCCESS) {
++ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01218)
++ "apr_pollset_add(); check system or user limits");
++ return NULL;
++ }
++
++ fd.desc.f = err; /* script's stderr */
++ fd.client_data = (void *)2;
++ rv = apr_pollset_add(data->pollset, &fd);
++ if (rv != APR_SUCCESS) {
++ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01219)
++ "apr_pollset_add(); check system or user limits");
++ return NULL;
++ }
++
++ data->r = r;
++ data->timeout = timeout;
++ b->data = data;
++ return b;
++}
++
++/* Create a duplicate CGI bucket using given bucket data */
++static apr_bucket *cgi_bucket_dup(struct cgi_bucket_data *data,
++ apr_bucket_alloc_t *list)
++{
++ apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
++ APR_BUCKET_INIT(b);
++ b->free = apr_bucket_free;
++ b->list = list;
++ b->type = &bucket_type_cgi;
++ b->length = (apr_size_t)(-1);
++ b->start = -1;
++ b->data = data;
++ return b;
++}
++
++/* Handle stdout from CGI child. Duplicate of logic from the _read
++ * method of the real APR pipe bucket implementation. */
++static apr_status_t cgi_read_stdout(apr_bucket *a, apr_file_t *out,
++ const char **str, apr_size_t *len)
++{
++ char *buf;
++ apr_status_t rv;
++
++ *str = NULL;
++ *len = APR_BUCKET_BUFF_SIZE;
++ buf = apr_bucket_alloc(*len, a->list); /* XXX: check for failure? */
++
++ rv = apr_file_read(out, buf, len);
++
++ if (rv != APR_SUCCESS && rv != APR_EOF) {
++ apr_bucket_free(buf);
++ return rv;
++ }
++
++ if (*len > 0) {
++ struct cgi_bucket_data *data = a->data;
++ apr_bucket_heap *h;
++
++ /* Change the current bucket to refer to what we read */
++ a = apr_bucket_heap_make(a, buf, *len, apr_bucket_free);
++ h = a->data;
++ h->alloc_len = APR_BUCKET_BUFF_SIZE; /* note the real buffer size */
++ *str = buf;
++ APR_BUCKET_INSERT_AFTER(a, cgi_bucket_dup(data, a->list));
++ }
++ else {
++ apr_bucket_free(buf);
++ a = apr_bucket_immortal_make(a, "", 0);
++ *str = a->data;
++ }
++ return rv;
++}
++
++/* Read method of CGI bucket: polls on stderr and stdout of the child,
++ * sending any stderr output immediately away to the error log. */
++static apr_status_t cgi_bucket_read(apr_bucket *b, const char **str,
++ apr_size_t *len, apr_read_type_e block)
++{
++ struct cgi_bucket_data *data = b->data;
++ apr_interval_time_t timeout = 0;
++ apr_status_t rv;
++ int gotdata = 0;
++
++ if (block != APR_NONBLOCK_READ) {
++ timeout = data->timeout > 0 ? data->timeout : data->r->server->timeout;
++ }
++
++ do {
++ const apr_pollfd_t *results;
++ apr_int32_t num;
++
++ rv = apr_pollset_poll(data->pollset, timeout, &num, &results);
++ if (APR_STATUS_IS_TIMEUP(rv)) {
++ if (timeout) {
++ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, data->r, APLOGNO(01220)
++ "Timeout waiting for output from CGI script %s",
++ data->r->filename);
++ return rv;
++ }
++ else {
++ return APR_EAGAIN;
++ }
++ }
++ else if (APR_STATUS_IS_EINTR(rv)) {
++ continue;
++ }
++ else if (rv != APR_SUCCESS) {
++ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, data->r, APLOGNO(01221)
++ "poll failed waiting for CGI child");
++ return rv;
++ }
++
++ for (; num; num--, results++) {
++ if (results[0].client_data == (void *)1) {
++ /* stdout */
++ rv = cgi_read_stdout(b, results[0].desc.f, str, len);
++ if (APR_STATUS_IS_EOF(rv)) {
++ rv = APR_SUCCESS;
++ }
++ gotdata = 1;
++ } else {
++ /* stderr */
++ apr_status_t rv2 = log_script_err(data->r, results[0].desc.f);
++ if (APR_STATUS_IS_EOF(rv2)) {
++ apr_pollset_remove(data->pollset, &results[0]);
++ }
++ }
++ }
++
++ } while (!gotdata);
++
++ return rv;
++}
++
++static const apr_bucket_type_t bucket_type_cgi = {
++ "CGI", 5, APR_BUCKET_DATA,
++ apr_bucket_destroy_noop,
++ cgi_bucket_read,
++ apr_bucket_setaside_notimpl,
++ apr_bucket_split_notimpl,
++ apr_bucket_copy_notimpl
++};
++
++#endif /* WANT_CGI_BUCKET */
++
++/* Handle the CGI response output, having set up the brigade with the
++ * CGI or PIPE bucket as appropriate. */
++static int cgi_handle_response(request_rec *r, int nph, apr_bucket_brigade *bb,
++ apr_interval_time_t timeout, cgi_server_conf *conf,
++ char *logdata, apr_file_t *script_err)
++{
++ apr_status_t rv;
++
++ /* Handle script return... */
++ if (!nph) {
++ const char *location;
++ char sbuf[MAX_STRING_LEN];
++ int ret;
++
++ if ((ret = ap_scan_script_header_err_brigade_ex(r, bb, sbuf,
++ APLOG_MODULE_INDEX)))
++ {
++ ret = log_script(r, conf, ret, logdata, sbuf, bb, script_err);
++
++ /*
++ * ret could be HTTP_NOT_MODIFIED in the case that the CGI script
++ * does not set an explicit status and ap_meets_conditions, which
++ * is called by ap_scan_script_header_err_brigade, detects that
++ * the conditions of the requests are met and the response is
++ * not modified.
++ * In this case set r->status and return OK in order to prevent
++ * running through the error processing stack as this would
++ * break with mod_cache, if the conditions had been set by
++ * mod_cache itself to validate a stale entity.
++ * BTW: We circumvent the error processing stack anyway if the
++ * CGI script set an explicit status code (whatever it is) and
++ * the only possible values for ret here are:
++ *
++ * HTTP_NOT_MODIFIED (set by ap_meets_conditions)
++ * HTTP_PRECONDITION_FAILED (set by ap_meets_conditions)
++ * HTTP_INTERNAL_SERVER_ERROR (if something went wrong during the
++ * processing of the response of the CGI script, e.g broken headers
++ * or a crashed CGI process).
++ */
++ if (ret == HTTP_NOT_MODIFIED) {
++ r->status = ret;
++ return OK;
++ }
++
++ return ret;
++ }
++
++ location = apr_table_get(r->headers_out, "Location");
++
++ if (location && r->status == 200) {
++ /* For a redirect whether internal or not, discard any
++ * remaining stdout from the script, and log any remaining
++ * stderr output, as normal. */
++ discard_script_output(bb);
++ apr_brigade_destroy(bb);
++
++ if (script_err) {
++ apr_file_pipe_timeout_set(script_err, timeout);
++ log_script_err(r, script_err);
++ }
++ }
++
++ if (location && location[0] == '/' && r->status == 200) {
++ /* This redirect needs to be a GET no matter what the original
++ * method was.
++ */
++ r->method = "GET";
++ r->method_number = M_GET;
++
++ /* We already read the message body (if any), so don't allow
++ * the redirected request to think it has one. We can ignore
++ * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR.
++ */
++ apr_table_unset(r->headers_in, "Content-Length");
++
++ ap_internal_redirect_handler(location, r);
++ return OK;
++ }
++ else if (location && r->status == 200) {
++ /* XXX: Note that if a script wants to produce its own Redirect
++ * body, it now has to explicitly *say* "Status: 302"
++ */
++ discard_script_output(bb);
++ apr_brigade_destroy(bb);
++ return HTTP_MOVED_TEMPORARILY;
++ }
++
++ rv = ap_pass_brigade(r->output_filters, bb);
++ }
++ else /* nph */ {
++ struct ap_filter_t *cur;
++
++ /* get rid of all filters up through protocol... since we
++ * haven't parsed off the headers, there is no way they can
++ * work
++ */
++
++ cur = r->proto_output_filters;
++ while (cur && cur->frec->ftype < AP_FTYPE_CONNECTION) {
++ cur = cur->next;
++ }
++ r->output_filters = r->proto_output_filters = cur;
++
++ rv = ap_pass_brigade(r->output_filters, bb);
++ }
++
++ /* don't soak up script output if errors occurred writing it
++ * out... otherwise, we prolong the life of the script when the
++ * connection drops or we stopped sending output for some other
++ * reason */
++ if (script_err && rv == APR_SUCCESS && !r->connection->aborted) {
++ apr_file_pipe_timeout_set(script_err, timeout);
++ log_script_err(r, script_err);
++ }
++
++ if (script_err) apr_file_close(script_err);
++
++ return OK; /* NOT r->status, even if it has changed. */
++}
diff --git a/httpd-2.4.37-r1840554.patch b/httpd-2.4.37-r1840554.patch
new file mode 100644
index 0000000..7b379e1
--- /dev/null
+++ b/httpd-2.4.37-r1840554.patch
@@ -0,0 +1,35 @@
+diff --git a/modules/arch/unix/mod_systemd.c b/modules/arch/unix/mod_systemd.c
+index 7a82a90..6c244b6 100644
+--- a/modules/arch/unix/mod_systemd.c
++++ b/modules/arch/unix/mod_systemd.c
+@@ -100,6 +100,21 @@ static int systemd_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ return OK;
+ }
+
++/* Report the service is ready in post_config, which could be during
++ * startup or after a reload. The server could still hit a fatal
++ * startup error after this point during ap_run_mpm(), so this is
++ * perhaps too early, but by post_config listen() has been called on
++ * the TCP ports so new connections will not be rejected. There will
++ * always be a possible async failure event simultaneous to the
++ * service reporting "ready", so this should be good enough. */
++static int systemd_post_config_last(apr_pool_t *p, apr_pool_t *plog,
++ apr_pool_t *ptemp, server_rec *main_server)
++{
++ sd_notify(0, "READY=1\n"
++ "STATUS=Configuration loaded.\n");
++ return OK;
++}
++
+ static int systemd_pre_mpm(apr_pool_t *p, ap_scoreboard_e sb_type)
+ {
+ int rv;
+@@ -187,6 +202,8 @@ static void systemd_register_hooks(apr_pool_t *p)
+ ap_hook_pre_config(systemd_pre_config, NULL, NULL, APR_HOOK_LAST);
+ /* Grab the listener config. */
+ ap_hook_post_config(systemd_post_config, NULL, NULL, APR_HOOK_LAST);
++ /* Signal service is ready. */
++ ap_hook_post_config(systemd_post_config_last, NULL, NULL, APR_HOOK_REALLY_LAST);
+ /* We know the PID in this hook ... */
+ ap_hook_pre_mpm(systemd_pre_mpm, NULL, NULL, APR_HOOK_LAST);
+ /* Used to update httpd's status line using sd_notifyf */
diff --git a/httpd-2.4.37-r1842929+.patch b/httpd-2.4.37-r1842929+.patch
new file mode 100644
index 0000000..ab5bba6
--- /dev/null
+++ b/httpd-2.4.37-r1842929+.patch
@@ -0,0 +1,272 @@
+# ./pullrev.sh 1842929 1842931 1852982 1853631 1857731
+http://svn.apache.org/viewvc?view=revision&revision=1842929
+http://svn.apache.org/viewvc?view=revision&revision=1842931
+http://svn.apache.org/viewvc?view=revision&revision=1852982
+http://svn.apache.org/viewvc?view=revision&revision=1857731
+http://svn.apache.org/viewvc?view=revision&revision=1853631
+
+diff --git a/Makefile.in b/Makefile.in
+index 06b8c5a..9eeb5c7 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -213,6 +213,7 @@ install-cgi:
+ install-other:
+ @test -d $(DESTDIR)$(logfiledir) || $(MKINSTALLDIRS) $(DESTDIR)$(logfiledir)
+ @test -d $(DESTDIR)$(runtimedir) || $(MKINSTALLDIRS) $(DESTDIR)$(runtimedir)
++ @test -d $(DESTDIR)$(statedir) || $(MKINSTALLDIRS) $(DESTDIR)$(statedir)
+ @for ext in dll x; do \
+ file=apachecore.$$ext; \
+ if test -f $$file; then \
+diff --git a/acinclude.m4 b/acinclude.m4
+index 0ad0c13..a8c2804 100644
+--- a/acinclude.m4
++++ b/acinclude.m4
+@@ -45,6 +45,7 @@ AC_DEFUN([APACHE_GEN_CONFIG_VARS],[
+ APACHE_SUBST(installbuilddir)
+ APACHE_SUBST(runtimedir)
+ APACHE_SUBST(proxycachedir)
++ APACHE_SUBST(statedir)
+ APACHE_SUBST(other_targets)
+ APACHE_SUBST(progname)
+ APACHE_SUBST(prefix)
+@@ -663,6 +664,7 @@ AC_DEFUN([APACHE_EXPORT_ARGUMENTS],[
+ APACHE_SUBST_EXPANDED_ARG(runtimedir)
+ APACHE_SUBST_EXPANDED_ARG(logfiledir)
+ APACHE_SUBST_EXPANDED_ARG(proxycachedir)
++ APACHE_SUBST_EXPANDED_ARG(statedir)
+ ])
+
+ dnl
+diff --git a/configure.in b/configure.in
+index a208b53..de6a8ad 100644
+--- a/configure.in
++++ b/configure.in
+@@ -41,7 +41,7 @@ dnl Something seems broken here.
+ AC_PREFIX_DEFAULT(/usr/local/apache2)
+
+ dnl Get the layout here, so we can pass the required variables to apr
+-APR_ENABLE_LAYOUT(Apache, [errordir iconsdir htdocsdir cgidir])
++APR_ENABLE_LAYOUT(Apache, [errordir iconsdir htdocsdir cgidir statedir])
+
+ dnl reparse the configure arguments.
+ APR_PARSE_ARGUMENTS
+diff --git a/include/ap_config_layout.h.in b/include/ap_config_layout.h.in
+index 2b4a70c..e076f41 100644
+--- a/include/ap_config_layout.h.in
++++ b/include/ap_config_layout.h.in
+@@ -60,5 +60,7 @@
+ #define DEFAULT_REL_LOGFILEDIR "@rel_logfiledir@"
+ #define DEFAULT_EXP_PROXYCACHEDIR "@exp_proxycachedir@"
+ #define DEFAULT_REL_PROXYCACHEDIR "@rel_proxycachedir@"
++#define DEFAULT_EXP_STATEDIR "@exp_statedir@"
++#define DEFAULT_REL_STATEDIR "@rel_statedir@"
+
+ #endif /* AP_CONFIG_LAYOUT_H */
+diff --git a/include/http_config.h b/include/http_config.h
+index adc5825..effccc1 100644
+--- a/include/http_config.h
++++ b/include/http_config.h
+@@ -757,6 +757,14 @@ AP_DECLARE(char *) ap_server_root_relative(apr_pool_t *p, const char *fname);
+ */
+ AP_DECLARE(char *) ap_runtime_dir_relative(apr_pool_t *p, const char *fname);
+
++/**
++ * Compute the name of a persistent state file (e.g. a database or
++ * long-lived cache) relative to the appropriate state directory.
++ * Absolute paths are returned as-is. The state directory is
++ * configured via the DefaultStateDir directive or at build time.
++ */
++AP_DECLARE(char *) ap_state_dir_relative(apr_pool_t *p, const char *fname);
++
+ /* Finally, the hook for dynamically loading modules in... */
+
+ /**
+diff --git a/modules/dav/fs/mod_dav_fs.c b/modules/dav/fs/mod_dav_fs.c
+index addfd7e..2389f8f 100644
+--- a/modules/dav/fs/mod_dav_fs.c
++++ b/modules/dav/fs/mod_dav_fs.c
+@@ -29,6 +29,10 @@ typedef struct {
+
+ extern module AP_MODULE_DECLARE_DATA dav_fs_module;
+
++#ifndef DEFAULT_DAV_LOCKDB
++#define DEFAULT_DAV_LOCKDB "davlockdb"
++#endif
++
+ const char *dav_get_lockdb_path(const request_rec *r)
+ {
+ dav_fs_server_conf *conf;
+@@ -57,6 +61,24 @@ static void *dav_fs_merge_server_config(apr_pool_t *p,
+ return newconf;
+ }
+
++static apr_status_t dav_fs_post_config(apr_pool_t *p, apr_pool_t *plog,
++ apr_pool_t *ptemp, server_rec *base_server)
++{
++ server_rec *s;
++
++ for (s = base_server; s; s = s->next) {
++ dav_fs_server_conf *conf;
++
++ conf = ap_get_module_config(s->module_config, &dav_fs_module);
++
++ if (!conf->lockdb_path) {
++ conf->lockdb_path = ap_state_dir_relative(p, DEFAULT_DAV_LOCKDB);
++ }
++ }
++
++ return OK;
++}
++
+ /*
+ * Command handler for the DAVLockDB directive, which is TAKE1
+ */
+@@ -87,6 +109,8 @@ static const command_rec dav_fs_cmds[] =
+
+ static void register_hooks(apr_pool_t *p)
+ {
++ ap_hook_post_config(dav_fs_post_config, NULL, NULL, APR_HOOK_MIDDLE);
++
+ dav_hook_gather_propsets(dav_fs_gather_propsets, NULL, NULL,
+ APR_HOOK_MIDDLE);
+ dav_hook_find_liveprop(dav_fs_find_liveprop, NULL, NULL, APR_HOOK_MIDDLE);
+diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c
+index 336a21b..4d50e26 100644
+--- a/modules/md/mod_md_config.c
++++ b/modules/md/mod_md_config.c
+@@ -54,10 +54,18 @@
+
+ #define DEF_VAL (-1)
+
++#ifndef MD_DEFAULT_BASE_DIR
++#define MD_DEFAULT_BASE_DIR "md"
++#endif
++
+ /* Default settings for the global conf */
+ static md_mod_conf_t defmc = {
+ NULL,
+- "md",
++#if 1
++ NULL, /* apply default state-dir-relative */
++#else
++ MD_DEFAULT_BASE_DIR,
++#endif
+ NULL,
+ NULL,
+ 80,
+@@ -864,6 +872,12 @@ apr_status_t md_config_post_config(server_rec *s, apr_pool_t *p)
+ if (mc->hsts_max_age > 0) {
+ mc->hsts_header = apr_psprintf(p, "max-age=%d", mc->hsts_max_age);
+ }
++
++#if 1
++ if (mc->base_dir == NULL) {
++ mc->base_dir = ap_state_dir_relative(p, MD_DEFAULT_BASE_DIR);
++ }
++#endif
+
+ return APR_SUCCESS;
+ }
+diff --git a/server/core.c b/server/core.c
+index bbe52e0..b5ab429 100644
+--- a/server/core.c
++++ b/server/core.c
+@@ -133,6 +133,8 @@ AP_DECLARE_DATA int ap_main_state = AP_SQ_MS_INITIAL_STARTUP;
+ AP_DECLARE_DATA int ap_run_mode = AP_SQ_RM_UNKNOWN;
+ AP_DECLARE_DATA int ap_config_generation = 0;
+
++static const char *core_state_dir;
++
+ static void *create_core_dir_config(apr_pool_t *a, char *dir)
+ {
+ core_dir_config *conf;
+@@ -1411,12 +1413,15 @@ AP_DECLARE(const char *) ap_resolve_env(apr_pool_t *p, const char * word)
+ return res_buf;
+ }
+
+-static int reset_config_defines(void *dummy)
++/* pconf cleanup - clear global variables set from config here. */
++static apr_status_t reset_config(void *dummy)
+ {
+ ap_server_config_defines = saved_server_config_defines;
+ saved_server_config_defines = NULL;
+ server_config_defined_vars = NULL;
+- return OK;
++ core_state_dir = NULL;
++
++ return APR_SUCCESS;
+ }
+
+ /*
+@@ -3108,6 +3113,24 @@ static const char *set_runtime_dir(cmd_parms *cmd, void *dummy, const char *arg)
+ return NULL;
+ }
+
++static const char *set_state_dir(cmd_parms *cmd, void *dummy, const char *arg)
++{
++ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
++
++ if (err != NULL) {
++ return err;
++ }
++
++ if ((apr_filepath_merge((char**)&core_state_dir, NULL,
++ ap_server_root_relative(cmd->temp_pool, arg),
++ APR_FILEPATH_TRUENAME, cmd->pool) != APR_SUCCESS)
++ || !ap_is_directory(cmd->temp_pool, core_state_dir)) {
++ return "DefaultStateDir must be a valid directory, absolute or relative to ServerRoot";
++ }
++
++ return NULL;
++}
++
+ static const char *set_timeout(cmd_parms *cmd, void *dummy, const char *arg)
+ {
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_CONTEXT);
+@@ -4409,6 +4432,8 @@ AP_INIT_TAKE1("ServerRoot", set_server_root, NULL, RSRC_CONF | EXEC_ON_READ,
+ "Common directory of server-related files (logs, confs, etc.)"),
+ AP_INIT_TAKE1("DefaultRuntimeDir", set_runtime_dir, NULL, RSRC_CONF | EXEC_ON_READ,
+ "Common directory for run-time files (shared memory, locks, etc.)"),
++AP_INIT_TAKE1("DefaultStateDir", set_state_dir, NULL, RSRC_CONF | EXEC_ON_READ,
++ "Common directory for persistent state (databases, long-lived caches, etc.)"),
+ AP_INIT_TAKE1("ErrorLog", set_server_string_slot,
+ (void *)APR_OFFSETOF(server_rec, error_fname), RSRC_CONF,
+ "The filename of the error log"),
+@@ -4932,8 +4957,7 @@ static int core_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptem
+
+ if (!saved_server_config_defines)
+ init_config_defines(pconf);
+- apr_pool_cleanup_register(pconf, NULL, reset_config_defines,
+- apr_pool_cleanup_null);
++ apr_pool_cleanup_register(pconf, NULL, reset_config, apr_pool_cleanup_null);
+
+ ap_regcomp_set_default_cflags(AP_REG_DOLLAR_ENDONLY);
+
+@@ -5202,6 +5226,27 @@ AP_DECLARE(int) ap_state_query(int query)
+ }
+ }
+
++AP_DECLARE(char *) ap_state_dir_relative(apr_pool_t *p, const char *file)
++{
++ char *newpath = NULL;
++ apr_status_t rv;
++ const char *state_dir;
++
++ state_dir = core_state_dir
++ ? core_state_dir
++ : ap_server_root_relative(p, DEFAULT_REL_STATEDIR);
++
++ rv = apr_filepath_merge(&newpath, state_dir, file, APR_FILEPATH_TRUENAME, p);
++ if (newpath && (rv == APR_SUCCESS || APR_STATUS_IS_EPATHWILD(rv)
++ || APR_STATUS_IS_ENOENT(rv)
++ || APR_STATUS_IS_ENOTDIR(rv))) {
++ return newpath;
++ }
++ else {
++ return NULL;
++ }
++}
++
+ static apr_random_t *rng = NULL;
+ #if APR_HAS_THREADS
+ static apr_thread_mutex_t *rng_mutex = NULL;
diff --git a/httpd-2.4.37-r1845768+.patch b/httpd-2.4.37-r1845768+.patch
new file mode 100644
index 0000000..a51934f
--- /dev/null
+++ b/httpd-2.4.37-r1845768+.patch
@@ -0,0 +1,48 @@
+diff --git a/modules/ssl/ssl_engine_init.c b/modules/ssl/ssl_engine_init.c
+index 70d151e..e4f5fc8 100644
+--- a/modules/ssl/ssl_engine_init.c
++++ b/modules/ssl/ssl_engine_init.c
+@@ -1095,7 +1095,9 @@ static apr_status_t ssl_init_ctx_crl(server_rec *s,
+ /*
+ * Read a file that optionally contains the server certificate in PEM
+ * format, possibly followed by a sequence of CA certificates that
+- * should be sent to the peer in the SSL Certificate message.
++ * should be sent to the peer in the SSL Certificate message. Returns
++ * 0 on success, otherwise the OpenSSL error stack contents should be
++ * reported.
+ */
+ static int use_certificate_chain(
+ SSL_CTX *ctx, char *file, int skipfirst, pem_password_cb *cb)
+@@ -1128,8 +1130,10 @@ static int use_certificate_chain(
+ ctx->extra_certs = NULL;
+ }
+ #endif
++
+ /* create new extra chain by loading the certs */
+ n = 0;
++ ERR_clear_error();
+ while ((x509 = PEM_read_bio_X509(bio, NULL, cb, NULL)) != NULL) {
+ if (!SSL_CTX_add_extra_chain_cert(ctx, x509)) {
+ X509_free(x509);
+@@ -1190,6 +1194,7 @@ static apr_status_t ssl_init_ctx_cert_chain(server_rec *s,
+ if (n < 0) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(01903)
+ "Failed to configure CA certificate chain!");
++ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return ssl_die(s);
+ }
+
+diff --git a/modules/ssl/ssl_util_ocsp.c b/modules/ssl/ssl_util_ocsp.c
+index b11a6e9..b66e151 100644
+--- a/modules/ssl/ssl_util_ocsp.c
++++ b/modules/ssl/ssl_util_ocsp.c
+@@ -363,7 +363,9 @@ static STACK_OF(X509) *modssl_read_ocsp_certificates(const char *file)
+ BIO_free(bio);
+ return NULL;
+ }
++
+ /* create new extra chain by loading the certs */
++ ERR_clear_error();
+ while ((x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) {
+ if (!other_certs) {
+ other_certs = sk_X509_new_null();
diff --git a/httpd-2.4.37-r1851471.patch b/httpd-2.4.37-r1851471.patch
new file mode 100644
index 0000000..03e3301
--- /dev/null
+++ b/httpd-2.4.37-r1851471.patch
@@ -0,0 +1,44 @@
+diff --git a/modules/ssl/ssl_engine_io.c b/modules/ssl/ssl_engine_io.c
+index 0958135..018b667 100644
+--- a/modules/ssl/ssl_engine_io.c
++++ b/modules/ssl/ssl_engine_io.c
+@@ -200,6 +200,8 @@ static int bio_filter_out_write(BIO *bio, const char *in, int inl)
+ apr_bucket *e;
+ int need_flush;
+
++ BIO_clear_retry_flags(bio);
++
+ #ifndef SSL_OP_NO_RENEGOTIATION
+ /* Abort early if the client has initiated a renegotiation. */
+ if (outctx->filter_ctx->config->reneg_state == RENEG_ABORT) {
+@@ -208,12 +210,6 @@ static int bio_filter_out_write(BIO *bio, const char *in, int inl)
+ }
+ #endif
+
+- /* when handshaking we'll have a small number of bytes.
+- * max size SSL will pass us here is about 16k.
+- * (16413 bytes to be exact)
+- */
+- BIO_clear_retry_flags(bio);
+-
+ /* Use a transient bucket for the output data - any downstream
+ * filter must setaside if necessary. */
+ e = apr_bucket_transient_create(in, inl, outctx->bb->bucket_alloc);
+@@ -460,6 +456,8 @@ static int bio_filter_in_read(BIO *bio, char *in, int inlen)
+ if (!in)
+ return 0;
+
++ BIO_clear_retry_flags(bio);
++
+ #ifndef SSL_OP_NO_RENEGOTIATION
+ /* Abort early if the client has initiated a renegotiation. */
+ if (inctx->filter_ctx->config->reneg_state == RENEG_ABORT) {
+@@ -468,8 +466,6 @@ static int bio_filter_in_read(BIO *bio, char *in, int inlen)
+ }
+ #endif
+
+- BIO_clear_retry_flags(bio);
+-
+ if (!inctx->bb) {
+ inctx->rc = APR_EOF;
+ return -1;
diff --git a/httpd-2.4.37-r1861793+.patch b/httpd-2.4.37-r1861793+.patch
new file mode 100644
index 0000000..4ac9c2d
--- /dev/null
+++ b/httpd-2.4.37-r1861793+.patch
@@ -0,0 +1,209 @@
+diff --git a/configure.in b/configure.in
+index de6a8ad..4ca489d 100644
+--- a/configure.in
++++ b/configure.in
+@@ -465,6 +465,28 @@ LIBS=""
+ AC_SEARCH_LIBS(crypt, crypt)
+ CRYPT_LIBS="$LIBS"
+ APACHE_SUBST(CRYPT_LIBS)
++
++if test "$ac_cv_search_crypt" != "no"; then
++ # Test crypt() with the SHA-512 test vector from https://akkadia.org/drepper/SHA-crypt.txt
++ AC_CACHE_CHECK([whether crypt() supports SHA-2], [ap_cv_crypt_sha2], [
++ AC_RUN_IFELSE([AC_LANG_PROGRAM([[
++#include
++#include
++#include
++
++#define PASSWD_0 "Hello world!"
++#define SALT_0 "\$6\$saltstring"
++#define EXPECT_0 "\$6\$saltstring\$svn8UoSVapNtMuq1ukKS4tPQd8iKwSMHWjl/O817G3uBnIFNjnQJu" \
++ "esI68u4OTLiBFdcbYEdFCoEOfaS35inz1"
++]], [char *result = crypt(PASSWD_0, SALT_0);
++ if (!result) return 1;
++ if (strcmp(result, EXPECT_0)) return 2;
++])], [ap_cv_crypt_sha2=yes], [ap_cv_crypt_sha2=no])])
++ if test "$ap_cv_crypt_sha2" = yes; then
++ AC_DEFINE([HAVE_CRYPT_SHA2], 1, [Define if crypt() supports SHA-2 hashes])
++ fi
++fi
++
+ LIBS="$saved_LIBS"
+
+ dnl See Comment #Spoon
+diff --git a/support/htpasswd.c b/support/htpasswd.c
+index 660a27c..136f62a 100644
+--- a/support/htpasswd.c
++++ b/support/htpasswd.c
+@@ -98,28 +98,32 @@ static int mkrecord(struct passwd_ctx *ctx, char *user)
+ static void usage(void)
+ {
+ apr_file_printf(errfile, "Usage:" NL
+- "\thtpasswd [-cimBdpsDv] [-C cost] passwordfile username" NL
+- "\thtpasswd -b[cmBdpsDv] [-C cost] passwordfile username password" NL
++ "\thtpasswd [-cimB25dpsDv] [-C cost] [-r rounds] passwordfile username" NL
++ "\thtpasswd -b[cmB25dpsDv] [-C cost] [-r rounds] passwordfile username password" NL
+ NL
+- "\thtpasswd -n[imBdps] [-C cost] username" NL
+- "\thtpasswd -nb[mBdps] [-C cost] username password" NL
++ "\thtpasswd -n[imB25dps] [-C cost] [-r rounds] username" NL
++ "\thtpasswd -nb[mB25dps] [-C cost] [-r rounds] username password" NL
+ " -c Create a new file." NL
+ " -n Don't update file; display results on stdout." NL
+ " -b Use the password from the command line rather than prompting "
+ "for it." NL
+ " -i Read password from stdin without verification (for script usage)." NL
+ " -m Force MD5 encryption of the password (default)." NL
+- " -B Force bcrypt encryption of the password (very secure)." NL
++ " -2 Force SHA-256 crypt() hash of the password (secure)." NL
++ " -5 Force SHA-512 crypt() hash of the password (secure)." NL
++ " -B Force bcrypt encryption of the password (very secure)." NL
+ " -C Set the computing time used for the bcrypt algorithm" NL
+ " (higher is more secure but slower, default: %d, valid: 4 to 31)." NL
++ " -r Set the number of rounds used for the SHA-256, SHA-512 algorithms" NL
++ " (higher is more secure but slower, default: 5000)." NL
+ " -d Force CRYPT encryption of the password (8 chars max, insecure)." NL
+- " -s Force SHA encryption of the password (insecure)." NL
++ " -s Force SHA-1 encryption of the password (insecure)." NL
+ " -p Do not encrypt the password (plaintext, insecure)." NL
+ " -D Delete the specified user." NL
+ " -v Verify password for the specified user." NL
+ "On other systems than Windows and NetWare the '-p' flag will "
+ "probably not work." NL
+- "The SHA algorithm does not use a salt and is less secure than the "
++ "The SHA-1 algorithm does not use a salt and is less secure than the "
+ "MD5 algorithm." NL,
+ BCRYPT_DEFAULT_COST
+ );
+@@ -178,7 +182,7 @@ static void check_args(int argc, const char *const argv[],
+ if (rv != APR_SUCCESS)
+ exit(ERR_SYNTAX);
+
+- while ((rv = apr_getopt(state, "cnmspdBbDiC:v", &opt, &opt_arg)) == APR_SUCCESS) {
++ while ((rv = apr_getopt(state, "cnmspdBbDi25C:r:v", &opt, &opt_arg)) == APR_SUCCESS) {
+ switch (opt) {
+ case 'c':
+ *mask |= APHTP_NEWFILE;
+diff --git a/support/passwd_common.c b/support/passwd_common.c
+index 664e509..d45657c 100644
+--- a/support/passwd_common.c
++++ b/support/passwd_common.c
+@@ -185,10 +185,15 @@ int mkhash(struct passwd_ctx *ctx)
+ #if CRYPT_ALGO_SUPPORTED
+ char *cbuf;
+ #endif
++#ifdef HAVE_CRYPT_SHA2
++ const char *setting;
++ char method;
++#endif
+
+- if (ctx->cost != 0 && ctx->alg != ALG_BCRYPT) {
++ if (ctx->cost != 0 && ctx->alg != ALG_BCRYPT
++ && ctx->alg != ALG_CRYPT_SHA256 && ctx->alg != ALG_CRYPT_SHA512 ) {
+ apr_file_printf(errfile,
+- "Warning: Ignoring -C argument for this algorithm." NL);
++ "Warning: Ignoring -C/-r argument for this algorithm." NL);
+ }
+
+ if (ctx->passwd == NULL) {
+@@ -246,6 +251,34 @@ int mkhash(struct passwd_ctx *ctx)
+ break;
+ #endif /* CRYPT_ALGO_SUPPORTED */
+
++#ifdef HAVE_CRYPT_SHA2
++ case ALG_CRYPT_SHA256:
++ case ALG_CRYPT_SHA512:
++ ret = generate_salt(salt, 16, &ctx->errstr, ctx->pool);
++ if (ret != 0)
++ break;
++
++ method = ctx->alg == ALG_CRYPT_SHA256 ? '5': '6';
++
++ if (ctx->cost)
++ setting = apr_psprintf(ctx->pool, "$%c$rounds=%d$%s",
++ method, ctx->cost, salt);
++ else
++ setting = apr_psprintf(ctx->pool, "$%c$%s",
++ method, salt);
++
++ cbuf = crypt(pw, setting);
++ if (cbuf == NULL) {
++ rv = APR_FROM_OS_ERROR(errno);
++ ctx->errstr = apr_psprintf(ctx->pool, "crypt() failed: %pm", &rv);
++ ret = ERR_PWMISMATCH;
++ break;
++ }
++
++ apr_cpystrn(ctx->out, cbuf, ctx->out_len - 1);
++ break;
++#endif /* HAVE_CRYPT_SHA2 */
++
+ #if BCRYPT_ALGO_SUPPORTED
+ case ALG_BCRYPT:
+ rv = apr_generate_random_bytes((unsigned char*)salt, 16);
+@@ -294,6 +327,19 @@ int parse_common_options(struct passwd_ctx *ctx, char opt,
+ case 's':
+ ctx->alg = ALG_APSHA;
+ break;
++#ifdef HAVE_CRYPT_SHA2
++ case '2':
++ ctx->alg = ALG_CRYPT_SHA256;
++ break;
++ case '5':
++ ctx->alg = ALG_CRYPT_SHA512;
++ break;
++#else
++ case '2':
++ case '5':
++ ctx->errstr = "SHA-2 crypt() algorithms are not supported on this platform.";
++ return ERR_ALG_NOT_SUPP;
++#endif
+ case 'p':
+ ctx->alg = ALG_PLAIN;
+ #if !PLAIN_ALGO_SUPPORTED
+@@ -324,11 +370,12 @@ int parse_common_options(struct passwd_ctx *ctx, char opt,
+ return ERR_ALG_NOT_SUPP;
+ #endif
+ break;
+- case 'C': {
++ case 'C':
++ case 'r': {
+ char *endptr;
+ long num = strtol(opt_arg, &endptr, 10);
+ if (*endptr != '\0' || num <= 0) {
+- ctx->errstr = "argument to -C must be a positive integer";
++ ctx->errstr = "argument to -C/-r must be a positive integer";
+ return ERR_SYNTAX;
+ }
+ ctx->cost = num;
+diff --git a/support/passwd_common.h b/support/passwd_common.h
+index 660081e..f1b3cd7 100644
+--- a/support/passwd_common.h
++++ b/support/passwd_common.h
+@@ -28,6 +28,8 @@
+ #include "apu_version.h"
+ #endif
+
++#include "ap_config_auto.h"
++
+ #define MAX_STRING_LEN 256
+
+ #define ALG_PLAIN 0
+@@ -35,6 +37,8 @@
+ #define ALG_APMD5 2
+ #define ALG_APSHA 3
+ #define ALG_BCRYPT 4
++#define ALG_CRYPT_SHA256 5
++#define ALG_CRYPT_SHA512 6
+
+ #define BCRYPT_DEFAULT_COST 5
+
+@@ -84,7 +88,7 @@ struct passwd_ctx {
+ apr_size_t out_len;
+ char *passwd;
+ int alg;
+- int cost;
++ int cost; /* cost for bcrypt, rounds for SHA-2 */
+ enum {
+ PW_PROMPT = 0,
+ PW_ARG,
diff --git a/httpd-2.4.37-r1864000.patch b/httpd-2.4.37-r1864000.patch
new file mode 100644
index 0000000..8adecfa
--- /dev/null
+++ b/httpd-2.4.37-r1864000.patch
@@ -0,0 +1,40 @@
+--- a/modules/proxy/mod_proxy_hcheck.c 2019/07/30 13:01:08 1863999
++++ b/modules/proxy/mod_proxy_hcheck.c 2019/07/30 13:01:21 1864000
+@@ -110,6 +110,10 @@
+ if (!worker && !v) {
+ return "Bad call to set_worker_hc_param()";
+ }
++ if (!ctx) {
++ ctx = hc_create_config(p, s);
++ ap_set_module_config(s->module_config, &proxy_hcheck_module, ctx);
++ }
+ temp = (hc_template_t *)v;
+ if (!strcasecmp(key, "hctemplate")) {
+ hc_template_t *template;
+@@ -1059,6 +1063,8 @@
+ int i;
+ sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
+ &proxy_hcheck_module);
++ if (!ctx)
++ return;
+ if (apr_is_empty_table(ctx->conditions))
+ return;
+
+@@ -1088,6 +1094,8 @@
+ int i;
+ sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
+ &proxy_hcheck_module);
++ if (!ctx)
++ return;
+ if (apr_is_empty_table(ctx->conditions))
+ return;
+
+@@ -1111,6 +1119,8 @@
+ int i;
+ sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
+ &proxy_hcheck_module);
++ if (!ctx)
++ return 0;
+ if (apr_is_empty_table(ctx->conditions))
+ return 0;
+
diff --git a/httpd-2.4.37-r1870095+.patch b/httpd-2.4.37-r1870095+.patch
new file mode 100644
index 0000000..bd43c5c
--- /dev/null
+++ b/httpd-2.4.37-r1870095+.patch
@@ -0,0 +1,117 @@
+# ./pullrev.sh 1870095 1870097
+http://svn.apache.org/viewvc?view=revision&revision=1870095
+http://svn.apache.org/viewvc?view=revision&revision=1870097
+
+--- httpd-2.4.37/modules/ssl/ssl_engine_kernel.c
++++ httpd-2.4.37/modules/ssl/ssl_engine_kernel.c
+@@ -114,6 +114,45 @@
+ return result;
+ }
+
++/* If a renegotiation is required for the location, and the request
++ * includes a message body (and the client has not requested a "100
++ * Continue" response), then the client will be streaming the request
++ * body over the wire already. In that case, it is not possible to
++ * stop and perform a new SSL handshake immediately; once the SSL
++ * library moves to the "accept" state, it will reject the SSL packets
++ * which the client is sending for the request body.
++ *
++ * To allow authentication to complete in the hook, the solution used
++ * here is to fill a (bounded) buffer with the request body, and then
++ * to reinject that request body later.
++ *
++ * This function is called to fill the renegotiation buffer for the
++ * location as required, or fail. Returns zero on success or HTTP_
++ * error code on failure.
++ */
++static int fill_reneg_buffer(request_rec *r, SSLDirConfigRec *dc)
++{
++ int rv;
++ apr_size_t rsize;
++
++ /* ### this is HTTP/1.1 specific, special case for protocol? */
++ if (r->expecting_100 || !ap_request_has_body(r)) {
++ return 0;
++ }
++
++ rsize = dc->nRenegBufferSize == UNSET ? DEFAULT_RENEG_BUFFER_SIZE : dc->nRenegBufferSize;
++ if (rsize > 0) {
++ /* Fill the I/O buffer with the request body if possible. */
++ rv = ssl_io_buffer_fill(r, rsize);
++ }
++ else {
++ /* If the reneg buffer size is set to zero, just fail. */
++ rv = HTTP_REQUEST_ENTITY_TOO_LARGE;
++ }
++
++ return rv;
++}
++
+ #ifdef HAVE_TLSEXT
+ static int ap_array_same_str_set(apr_array_header_t *s1, apr_array_header_t *s2)
+ {
+@@ -814,41 +853,14 @@
+ }
+ }
+
+- /* If a renegotiation is now required for this location, and the
+- * request includes a message body (and the client has not
+- * requested a "100 Continue" response), then the client will be
+- * streaming the request body over the wire already. In that
+- * case, it is not possible to stop and perform a new SSL
+- * handshake immediately; once the SSL library moves to the
+- * "accept" state, it will reject the SSL packets which the client
+- * is sending for the request body.
+- *
+- * To allow authentication to complete in this auth hook, the
+- * solution used here is to fill a (bounded) buffer with the
+- * request body, and then to reinject that request body later.
+- */
+- if (renegotiate && !renegotiate_quick
+- && !r->expecting_100
+- && ap_request_has_body(r)) {
+- int rv;
+- apr_size_t rsize;
+-
+- rsize = dc->nRenegBufferSize == UNSET ? DEFAULT_RENEG_BUFFER_SIZE :
+- dc->nRenegBufferSize;
+- if (rsize > 0) {
+- /* Fill the I/O buffer with the request body if possible. */
+- rv = ssl_io_buffer_fill(r, rsize);
+- }
+- else {
+- /* If the reneg buffer size is set to zero, just fail. */
+- rv = HTTP_REQUEST_ENTITY_TOO_LARGE;
+- }
+-
+- if (rv) {
++ /* Fill reneg buffer if required. */
++ if (renegotiate && !renegotiate_quick) {
++ rc = fill_reneg_buffer(r, dc);
++ if (rc) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02257)
+ "could not buffer message body to allow "
+ "SSL renegotiation to proceed");
+- return rv;
++ return rc;
+ }
+ }
+
+@@ -1132,6 +1144,17 @@
+ }
+ }
+
++ /* Fill reneg buffer if required. */
++ if (change_vmode) {
++ rc = fill_reneg_buffer(r, dc);
++ if (rc) {
++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10228)
++ "could not buffer message body to allow "
++ "TLS Post-Handshake Authentication to proceed");
++ return rc;
++ }
++ }
++
+ if (change_vmode) {
+ char peekbuf[1];
+
diff --git a/httpd-2.4.37-r1872790.patch b/httpd-2.4.37-r1872790.patch
new file mode 100644
index 0000000..2cab606
--- /dev/null
+++ b/httpd-2.4.37-r1872790.patch
@@ -0,0 +1,636 @@
+diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h
+index 57cc92f..fbbd508 100644
+--- a/modules/proxy/mod_proxy.h
++++ b/modules/proxy/mod_proxy.h
+@@ -288,12 +288,15 @@ typedef struct {
+
+ /* Connection pool */
+ struct proxy_conn_pool {
+- apr_pool_t *pool; /* The pool used in constructor and destructor calls */
+- apr_sockaddr_t *addr; /* Preparsed remote address info */
+- apr_reslist_t *res; /* Connection resource list */
+- proxy_conn_rec *conn; /* Single connection for prefork mpm */
++ apr_pool_t *pool; /* The pool used in constructor and destructor calls */
++ apr_sockaddr_t *addr; /* Preparsed remote address info */
++ apr_reslist_t *res; /* Connection resource list */
++ proxy_conn_rec *conn; /* Single connection for prefork mpm */
++ apr_pool_t *dns_pool; /* The pool used for worker scoped DNS resolutions */
+ };
+
++#define AP_VOLATILIZE_T(T, x) (*(T volatile *)&(x))
++
+ /* worker status bits */
+ /*
+ * NOTE: Keep up-to-date w/ proxy_wstat_tbl[]
+@@ -475,7 +478,9 @@ struct proxy_worker {
+ proxy_conn_pool *cp; /* Connection pool to use */
+ proxy_worker_shared *s; /* Shared data */
+ proxy_balancer *balancer; /* which balancer am I in? */
++#if APR_HAS_THREADS
+ apr_thread_mutex_t *tmutex; /* Thread lock for updating address cache */
++#endif
+ void *context; /* general purpose storage */
+ ap_conf_vector_t *section_config; /* -section wherein defined */
+ };
+@@ -534,7 +539,9 @@ struct proxy_balancer {
+ apr_time_t wupdated; /* timestamp of last change to workers list */
+ proxy_balancer_method *lbmethod;
+ apr_global_mutex_t *gmutex; /* global lock for updating list of workers */
++#if APR_HAS_THREADS
+ apr_thread_mutex_t *tmutex; /* Thread lock for updating shm */
++#endif
+ proxy_server_conf *sconf;
+ void *context; /* general purpose storage */
+ proxy_balancer_shared *s; /* Shared data */
+diff --git a/modules/proxy/mod_proxy_balancer.c b/modules/proxy/mod_proxy_balancer.c
+index c59f5e9..3a28038 100644
+--- a/modules/proxy/mod_proxy_balancer.c
++++ b/modules/proxy/mod_proxy_balancer.c
+@@ -346,23 +346,27 @@ static proxy_worker *find_best_worker(proxy_balancer *balancer,
+ proxy_worker *candidate = NULL;
+ apr_status_t rv;
+
++#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_LOCK(balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01163)
+ "%s: Lock failed for find_best_worker()",
+ balancer->s->name);
+ return NULL;
+ }
++#endif
+
+ candidate = (*balancer->lbmethod->finder)(balancer, r);
+
+ if (candidate)
+ candidate->s->elected++;
+
++#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_UNLOCK(balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01164)
+ "%s: Unlock failed for find_best_worker()",
+ balancer->s->name);
+ }
++#endif
+
+ if (candidate == NULL) {
+ /* All the workers are in error state or disabled.
+@@ -492,11 +496,13 @@ static int proxy_balancer_pre_request(proxy_worker **worker,
+ /* Step 2: Lock the LoadBalancer
+ * XXX: perhaps we need the process lock here
+ */
++#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_LOCK(*balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01166)
+ "%s: Lock failed for pre_request", (*balancer)->s->name);
+ return DECLINED;
+ }
++#endif
+
+ /* Step 3: force recovery */
+ force_recovery(*balancer, r->server);
+@@ -557,20 +563,24 @@ static int proxy_balancer_pre_request(proxy_worker **worker,
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01167)
+ "%s: All workers are in error state for route (%s)",
+ (*balancer)->s->name, route);
++#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_UNLOCK(*balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01168)
+ "%s: Unlock failed for pre_request",
+ (*balancer)->s->name);
+ }
++#endif
+ return HTTP_SERVICE_UNAVAILABLE;
+ }
+ }
+
++#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_UNLOCK(*balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01169)
+ "%s: Unlock failed for pre_request",
+ (*balancer)->s->name);
+ }
++#endif
+ if (!*worker) {
+ runtime = find_best_worker(*balancer, r);
+ if (!runtime) {
+@@ -644,12 +654,14 @@ static int proxy_balancer_post_request(proxy_worker *worker,
+
+ apr_status_t rv;
+
++#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_LOCK(balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01173)
+ "%s: Lock failed for post_request",
+ balancer->s->name);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
++#endif
+
+ if (!apr_is_empty_array(balancer->errstatuses)
+ && !(worker->s->status & PROXY_WORKER_IGNORE_ERRORS)) {
+@@ -681,11 +693,12 @@ static int proxy_balancer_post_request(proxy_worker *worker,
+ worker->s->error_time = apr_time_now();
+
+ }
+-
++#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_UNLOCK(balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01175)
+ "%s: Unlock failed for post_request", balancer->s->name);
+ }
++#endif
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01176)
+ "proxy_balancer_post_request for (%s)", balancer->s->name);
+
+@@ -945,7 +958,6 @@ static int balancer_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ PROXY_STRNCPY(balancer->s->sname, sname); /* We know this will succeed */
+
+ balancer->max_workers = balancer->workers->nelts + balancer->growth;
+-
+ /* Create global mutex */
+ rv = ap_global_mutex_create(&(balancer->gmutex), NULL, balancer_mutex_type,
+ balancer->s->sname, s, pconf, 0);
+@@ -955,7 +967,6 @@ static int balancer_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ balancer->s->sname);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+-
+ apr_pool_cleanup_register(pconf, (void *)s, lock_remove,
+ apr_pool_cleanup_null);
+
+@@ -1135,17 +1146,21 @@ static int balancer_handler(request_rec *r)
+
+ balancer = (proxy_balancer *)conf->balancers->elts;
+ for (i = 0; i < conf->balancers->nelts; i++, balancer++) {
++#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_LOCK(balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01189)
+ "%s: Lock failed for balancer_handler",
+ balancer->s->name);
+ }
++#endif
+ ap_proxy_sync_balancer(balancer, r->server, conf);
++#if APR_HAS_THREADS
+ if ((rv = PROXY_THREAD_UNLOCK(balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01190)
+ "%s: Unlock failed for balancer_handler",
+ balancer->s->name);
+ }
++#endif
+ }
+
+ if (r->args && (r->method_number == M_GET)) {
+@@ -1359,11 +1374,13 @@ static int balancer_handler(request_rec *r)
+ proxy_worker *nworker;
+ nworker = ap_proxy_get_worker(r->pool, bsel, conf, val);
+ if (!nworker && storage->num_free_slots(bsel->wslot)) {
++#if APR_HAS_THREADS
+ if ((rv = PROXY_GLOBAL_LOCK(bsel)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01194)
+ "%s: Lock failed for adding worker",
+ bsel->s->name);
+ }
++#endif
+ ret = ap_proxy_define_worker(conf->pool, &nworker, bsel, conf, val, 0);
+ if (!ret) {
+ unsigned int index;
+@@ -1372,53 +1389,76 @@ static int balancer_handler(request_rec *r)
+ if ((rv = storage->grab(bsel->wslot, &index)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, APLOGNO(01195)
+ "worker slotmem_grab failed");
++#if APR_HAS_THREADS
+ if ((rv = PROXY_GLOBAL_UNLOCK(bsel)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01196)
+ "%s: Unlock failed for adding worker",
+ bsel->s->name);
+ }
++#endif
+ return HTTP_BAD_REQUEST;
+ }
+ if ((rv = storage->dptr(bsel->wslot, index, (void *)&shm)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, APLOGNO(01197)
+ "worker slotmem_dptr failed");
++#if APR_HAS_THREADS
+ if ((rv = PROXY_GLOBAL_UNLOCK(bsel)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01198)
+ "%s: Unlock failed for adding worker",
+ bsel->s->name);
+ }
++#endif
+ return HTTP_BAD_REQUEST;
+ }
+ if ((rv = ap_proxy_share_worker(nworker, shm, index)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, APLOGNO(01199)
+ "Cannot share worker");
++#if APR_HAS_THREADS
+ if ((rv = PROXY_GLOBAL_UNLOCK(bsel)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01200)
+ "%s: Unlock failed for adding worker",
+ bsel->s->name);
+ }
++#endif
+ return HTTP_BAD_REQUEST;
+ }
+ if ((rv = ap_proxy_initialize_worker(nworker, r->server, conf->pool)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_EMERG, rv, r, APLOGNO(01201)
+ "Cannot init worker");
++#if APR_HAS_THREADS
+ if ((rv = PROXY_GLOBAL_UNLOCK(bsel)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01202)
+ "%s: Unlock failed for adding worker",
+ bsel->s->name);
+ }
++#endif
+ return HTTP_BAD_REQUEST;
+ }
+ /* sync all timestamps */
+ bsel->wupdated = bsel->s->wupdated = nworker->s->updated = apr_time_now();
+ /* by default, all new workers are disabled */
+ ap_proxy_set_wstatus(PROXY_WORKER_DISABLED_FLAG, 1, nworker);
++ } else {
++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10163)
++ "%s: failed to add worker %s",
++ bsel->s->name, val);
++#if APR_HAS_THREADS
++ PROXY_GLOBAL_UNLOCK(bsel);
++#endif
++ return HTTP_BAD_REQUEST;
+ }
++#if APR_HAS_THREADS
+ if ((rv = PROXY_GLOBAL_UNLOCK(bsel)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01203)
+ "%s: Unlock failed for adding worker",
+ bsel->s->name);
+ }
++#endif
++ } else {
++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10164)
++ "%s: failed to add worker %s",
++ bsel->s->name, val);
++ return HTTP_BAD_REQUEST;
+ }
+
+ }
+diff --git a/modules/proxy/mod_proxy_ftp.c b/modules/proxy/mod_proxy_ftp.c
+index 5d9175e..5c4d641 100644
+--- a/modules/proxy/mod_proxy_ftp.c
++++ b/modules/proxy/mod_proxy_ftp.c
+@@ -979,8 +979,10 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
+ apr_status_t rv;
+ conn_rec *origin, *data = NULL;
+ apr_status_t err = APR_SUCCESS;
++#if APR_HAS_THREADS
+ apr_status_t uerr = APR_SUCCESS;
+- apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc);
++#endif
++ apr_bucket_brigade *bb;
+ char *buf, *connectname;
+ apr_port_t connectport;
+ char *ftpmessage = NULL;
+@@ -1120,13 +1122,15 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
+
+ if (worker->s->is_address_reusable) {
+ if (!worker->cp->addr) {
++#if APR_HAS_THREADS
+ if ((err = PROXY_THREAD_LOCK(worker->balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, err, r, APLOGNO(01037) "lock");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
++#endif
+ }
+- connect_addr = worker->cp->addr;
+- address_pool = worker->cp->pool;
++ connect_addr = AP_VOLATILIZE_T(apr_sockaddr_t *, worker->cp->addr);
++ address_pool = worker->cp->dns_pool;
+ }
+ else
+ address_pool = r->pool;
+@@ -1139,9 +1143,11 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
+ address_pool);
+ if (worker->s->is_address_reusable && !worker->cp->addr) {
+ worker->cp->addr = connect_addr;
++#if APR_HAS_THREADS
+ if ((uerr = PROXY_THREAD_UNLOCK(worker->balancer)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, uerr, r, APLOGNO(01038) "unlock");
+ }
++#endif
+ }
+ /*
+ * get all the possible IP addresses for the destname and loop through
+@@ -1212,6 +1218,7 @@ static int proxy_ftp_handler(request_rec *r, proxy_worker *worker,
+ * correct directory...
+ */
+
++ bb = apr_brigade_create(p, c->bucket_alloc);
+
+ /* possible results: */
+ /* 120 Service ready in nnn minutes. */
+diff --git a/modules/proxy/proxy_util.c b/modules/proxy/proxy_util.c
+index 2bfc8f0..7714b6c 100644
+--- a/modules/proxy/proxy_util.c
++++ b/modules/proxy/proxy_util.c
+@@ -1167,8 +1167,10 @@ PROXY_DECLARE(char *) ap_proxy_define_balancer(apr_pool_t *p,
+ lbmethod = ap_lookup_provider(PROXY_LBMETHOD, "byrequests", "0");
+
+ (*balancer)->workers = apr_array_make(p, 5, sizeof(proxy_worker *));
++#if APR_HAS_THREADS
+ (*balancer)->gmutex = NULL;
+ (*balancer)->tmutex = NULL;
++#endif
+ (*balancer)->lbmethod = lbmethod;
+
+ if (do_malloc)
+@@ -1257,7 +1259,9 @@ PROXY_DECLARE(apr_status_t) ap_proxy_share_balancer(proxy_balancer *balancer,
+
+ PROXY_DECLARE(apr_status_t) ap_proxy_initialize_balancer(proxy_balancer *balancer, server_rec *s, apr_pool_t *p)
+ {
++#if APR_HAS_THREADS
+ apr_status_t rv = APR_SUCCESS;
++#endif
+ ap_slotmem_provider_t *storage = balancer->storage;
+ apr_size_t size;
+ unsigned int num;
+@@ -1297,6 +1301,7 @@ PROXY_DECLARE(apr_status_t) ap_proxy_initialize_balancer(proxy_balancer *balance
+ if (balancer->lbmethod && balancer->lbmethod->reset)
+ balancer->lbmethod->reset(balancer, s);
+
++#if APR_HAS_THREADS
+ if (balancer->tmutex == NULL) {
+ rv = apr_thread_mutex_create(&(balancer->tmutex), APR_THREAD_MUTEX_DEFAULT, p);
+ if (rv != APR_SUCCESS) {
+@@ -1305,6 +1310,7 @@ PROXY_DECLARE(apr_status_t) ap_proxy_initialize_balancer(proxy_balancer *balance
+ return rv;
+ }
+ }
++#endif
+ return APR_SUCCESS;
+ }
+
+@@ -1446,16 +1452,14 @@ static void socket_cleanup(proxy_conn_rec *conn)
+
+ static apr_status_t conn_pool_cleanup(void *theworker)
+ {
+- proxy_worker *worker = (proxy_worker *)theworker;
+- if (worker->cp->res) {
+- worker->cp->pool = NULL;
+- }
++ ((proxy_worker *)theworker)->cp = NULL;
+ return APR_SUCCESS;
+ }
+
+ static void init_conn_pool(apr_pool_t *p, proxy_worker *worker)
+ {
+ apr_pool_t *pool;
++ apr_pool_t *dns_pool;
+ proxy_conn_pool *cp;
+
+ /*
+@@ -1466,12 +1470,21 @@ static void init_conn_pool(apr_pool_t *p, proxy_worker *worker)
+ */
+ apr_pool_create(&pool, p);
+ apr_pool_tag(pool, "proxy_worker_cp");
++ /*
++ * Create a subpool of the connection pool for worker
++ * scoped DNS resolutions. This is needed to avoid race
++ * conditions in using the connection pool by multiple
++ * threads during ramp up.
++ */
++ apr_pool_create(&dns_pool, pool);
++ apr_pool_tag(dns_pool, "proxy_worker_dns");
+ /*
+ * Alloc from the same pool as worker.
+ * proxy_conn_pool is permanently attached to the worker.
+ */
+ cp = (proxy_conn_pool *)apr_pcalloc(p, sizeof(proxy_conn_pool));
+ cp->pool = pool;
++ cp->dns_pool = dns_pool;
+ worker->cp = cp;
+ }
+
+@@ -1487,14 +1500,6 @@ static apr_status_t connection_cleanup(void *theconn)
+ proxy_conn_rec *conn = (proxy_conn_rec *)theconn;
+ proxy_worker *worker = conn->worker;
+
+- /*
+- * If the connection pool is NULL the worker
+- * cleanup has been run. Just return.
+- */
+- if (!worker->cp->pool) {
+- return APR_SUCCESS;
+- }
+-
+ if (conn->r) {
+ apr_pool_destroy(conn->r->pool);
+ conn->r = NULL;
+@@ -1616,7 +1621,7 @@ static apr_status_t connection_destructor(void *resource, void *params,
+ proxy_worker *worker = params;
+
+ /* Destroy the pool only if not called from reslist_destroy */
+- if (worker->cp->pool) {
++ if (worker->cp) {
+ proxy_conn_rec *conn = resource;
+ apr_pool_destroy(conn->pool);
+ }
+@@ -1972,67 +1977,73 @@ PROXY_DECLARE(apr_status_t) ap_proxy_initialize_worker(proxy_worker *worker, ser
+ ap_proxy_worker_name(p, worker));
+ }
+ else {
+- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00927)
+- "initializing worker %s local",
+- ap_proxy_worker_name(p, worker));
+ apr_global_mutex_lock(proxy_mutex);
+- /* Now init local worker data */
+- if (worker->tmutex == NULL) {
+- rv = apr_thread_mutex_create(&(worker->tmutex), APR_THREAD_MUTEX_DEFAULT, p);
+- if (rv != APR_SUCCESS) {
+- ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00928)
+- "can not create worker thread mutex");
++ /* Check again after we got the lock if we are still uninitialized */
++ if (!(AP_VOLATILIZE_T(unsigned int, worker->local_status) & PROXY_WORKER_INITIALIZED)) {
++ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00927)
++ "initializing worker %s local",
++ ap_proxy_worker_name(p, worker));
++ /* Now init local worker data */
++#if APR_HAS_THREADS
++ if (worker->tmutex == NULL) {
++ rv = apr_thread_mutex_create(&(worker->tmutex), APR_THREAD_MUTEX_DEFAULT, p);
++ if (rv != APR_SUCCESS) {
++ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00928)
++ "can not create worker thread mutex");
++ apr_global_mutex_unlock(proxy_mutex);
++ return rv;
++ }
++ }
++#endif
++ if (worker->cp == NULL)
++ init_conn_pool(p, worker);
++ if (worker->cp == NULL) {
++ ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00929)
++ "can not create connection pool");
+ apr_global_mutex_unlock(proxy_mutex);
+- return rv;
++ return APR_EGENERAL;
+ }
+- }
+- if (worker->cp == NULL)
+- init_conn_pool(p, worker);
+- if (worker->cp == NULL) {
+- ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00929)
+- "can not create connection pool");
+- apr_global_mutex_unlock(proxy_mutex);
+- return APR_EGENERAL;
+- }
+
+- if (worker->s->hmax) {
+- rv = apr_reslist_create(&(worker->cp->res),
+- worker->s->min, worker->s->smax,
+- worker->s->hmax, worker->s->ttl,
+- connection_constructor, connection_destructor,
+- worker, worker->cp->pool);
++ if (worker->s->hmax) {
++ rv = apr_reslist_create(&(worker->cp->res),
++ worker->s->min, worker->s->smax,
++ worker->s->hmax, worker->s->ttl,
++ connection_constructor, connection_destructor,
++ worker, worker->cp->pool);
+
+- apr_pool_cleanup_register(worker->cp->pool, (void *)worker,
+- conn_pool_cleanup,
+- apr_pool_cleanup_null);
++ apr_pool_pre_cleanup_register(worker->cp->pool, worker,
++ conn_pool_cleanup);
+
+- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00930)
+- "initialized pool in child %" APR_PID_T_FMT " for (%s) min=%d max=%d smax=%d",
+- getpid(), worker->s->hostname_ex, worker->s->min,
+- worker->s->hmax, worker->s->smax);
++ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00930)
++ "initialized pool in child %" APR_PID_T_FMT " for (%s) min=%d max=%d smax=%d",
++ getpid(), worker->s->hostname_ex, worker->s->min,
++ worker->s->hmax, worker->s->smax);
+
+- /* Set the acquire timeout */
+- if (rv == APR_SUCCESS && worker->s->acquire_set) {
+- apr_reslist_timeout_set(worker->cp->res, worker->s->acquire);
+- }
++ /* Set the acquire timeout */
++ if (rv == APR_SUCCESS && worker->s->acquire_set) {
++ apr_reslist_timeout_set(worker->cp->res, worker->s->acquire);
++ }
+
+- }
+- else {
+- void *conn;
++ }
++ else {
++ void *conn;
+
+- rv = connection_constructor(&conn, worker, worker->cp->pool);
+- worker->cp->conn = conn;
++ rv = connection_constructor(&conn, worker, worker->cp->pool);
++ worker->cp->conn = conn;
+
+- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00931)
+- "initialized single connection worker in child %" APR_PID_T_FMT " for (%s)",
+- getpid(), worker->s->hostname_ex);
++ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00931)
++ "initialized single connection worker in child %" APR_PID_T_FMT " for (%s)",
++ getpid(), worker->s->hostname_ex);
++ }
++ if (rv == APR_SUCCESS) {
++ worker->local_status |= (PROXY_WORKER_INITIALIZED);
++ }
+ }
+ apr_global_mutex_unlock(proxy_mutex);
+
+ }
+ if (rv == APR_SUCCESS) {
+ worker->s->status |= (PROXY_WORKER_INITIALIZED);
+- worker->local_status |= (PROXY_WORKER_INITIALIZED);
+ }
+ return rv;
+ }
+@@ -2292,13 +2303,13 @@ PROXY_DECLARE(int) ap_proxy_acquire_connection(const char *proxy_function,
+ else {
+ /* create the new connection if the previous was destroyed */
+ if (!worker->cp->conn) {
+- connection_constructor((void **)conn, worker, worker->cp->pool);
++ rv = connection_constructor((void **)conn, worker, worker->cp->pool);
+ }
+ else {
+ *conn = worker->cp->conn;
+ worker->cp->conn = NULL;
++ rv = APR_SUCCESS;
+ }
+- rv = APR_SUCCESS;
+ }
+
+ if (rv != APR_SUCCESS) {
+@@ -2344,7 +2355,9 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r,
+ {
+ int server_port;
+ apr_status_t err = APR_SUCCESS;
++#if APR_HAS_THREADS
+ apr_status_t uerr = APR_SUCCESS;
++#endif
+ const char *uds_path;
+
+ /*
+@@ -2481,25 +2494,39 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r,
+ * we can reuse the address.
+ */
+ if (!worker->cp->addr) {
++#if APR_HAS_THREADS
+ if ((err = PROXY_THREAD_LOCK(worker)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, err, r, APLOGNO(00945) "lock");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
++#endif
+
+ /*
+- * Worker can have the single constant backend address.
+- * The single DNS lookup is used once per worker.
+- * If dynamic change is needed then set the addr to NULL
+- * inside dynamic config to force the lookup.
++ * Recheck addr after we got the lock. This may have changed
++ * while waiting for the lock.
+ */
+- err = apr_sockaddr_info_get(&(worker->cp->addr),
+- conn->hostname, APR_UNSPEC,
+- conn->port, 0,
+- worker->cp->pool);
++ if (!AP_VOLATILIZE_T(apr_sockaddr_t *, worker->cp->addr)) {
++
++ apr_sockaddr_t *addr;
++
++ /*
++ * Worker can have the single constant backend address.
++ * The single DNS lookup is used once per worker.
++ * If dynamic change is needed then set the addr to NULL
++ * inside dynamic config to force the lookup.
++ */
++ err = apr_sockaddr_info_get(&addr,
++ conn->hostname, APR_UNSPEC,
++ conn->port, 0,
++ worker->cp->dns_pool);
++ worker->cp->addr = addr;
++ }
+ conn->addr = worker->cp->addr;
++#if APR_HAS_THREADS
+ if ((uerr = PROXY_THREAD_UNLOCK(worker)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, uerr, r, APLOGNO(00946) "unlock");
+ }
++#endif
+ }
+ else {
+ conn->addr = worker->cp->addr;
+@@ -3422,7 +3449,9 @@ PROXY_DECLARE(apr_status_t) ap_proxy_sync_balancer(proxy_balancer *b, server_rec
+ (*runtime)->cp = NULL;
+ (*runtime)->balancer = b;
+ (*runtime)->s = shm;
++#if APR_HAS_THREADS
+ (*runtime)->tmutex = NULL;
++#endif
+ rv = ap_proxy_initialize_worker(*runtime, s, conf->pool);
+ if (rv != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(00966) "Cannot init worker");
diff --git a/httpd-2.4.37-r1873907.patch b/httpd-2.4.37-r1873907.patch
new file mode 100644
index 0000000..4ec0cfa
--- /dev/null
+++ b/httpd-2.4.37-r1873907.patch
@@ -0,0 +1,265 @@
+diff --git a/docs/manual/mod/mod_ssl.html.en b/docs/manual/mod/mod_ssl.html.en
+index b543150..ab72d4f 100644
+--- a/docs/manual/mod/mod_ssl.html.en
++++ b/docs/manual/mod/mod_ssl.html.en
+@@ -1524,6 +1524,32 @@ The available (case-insensitive) protocols are:
+
Example
SSLProtocol TLSv1
+
+
++
++
SSLProtocol
for name-based virtual hosts
++
++Before OpenSSL 1.1.1, even though the Server Name Indication (SNI) allowed to
++determine the targeted virtual host early in the TLS handshake, it was not
++possible to switch the TLS protocol version of the connection at this point,
++and thus the SSLProtocol
negotiated was always based off
++the one of the base virtual host (first virtual host declared on the
++listening IP:port
of the connection).
++
++
++Beginning with Apache HTTP server version 2.4.42, when built/linked against
++OpenSSL 1.1.1 or later, and when the SNI is provided by the client in the TLS
++handshake, the SSLProtocol
of each (name-based) virtual
++host can and will be honored.
++
++
++For compatibility with previous versions, if no
++SSLProtocol
is configured in a name-based virtual host,
++the one from the base virtual host still applies, unless
++SSLProtocol
is configured globally in which case the
++global value applies (this latter exception is more sensible than compatible,
++though).
++
++
++
+
+