summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJim Jagielski <jim@apache.org>2015-09-28 12:31:37 +0000
committerJim Jagielski <jim@apache.org>2015-09-28 12:31:37 +0000
commit7475aa0909975f8491de64bb08d9793c6e503ae2 (patch)
tree734eba5a8edc0a1095aa3d25503fbd8745238d02
parent809ac57a11e8c7cf9dd2a271a6ff04af5434c046 (diff)
downloadhttpd-7475aa0909975f8491de64bb08d9793c6e503ae2.tar.gz
Merge r1697855, r1697339, r1696428, r1696266, r1696264, r1695874, r1695727, r1692516, r1692486, r1610674, r1685069, r1693918, r1698116, r1698133, r1694950, r1700968, r1701005, r1701145, r1701178 from trunk:
adding ap_get_protocol(c) which safeguards against NULL returns, for use instead of direct calling ap_run_protocol_get changed Protocols to let vhosts override servers, removed old H2Engine example from readme creating ap_array_index in util, forwarding scheme into request processing, enabling SSL vars only when scheme is not http:, delayed connection creation until task worker assignment removed unnecessary lingering_close and sbh update on end of protocol upgrade handling introducing ap_array_index in util, used in protocol and mod_h2 fixes existing protocol missing in selection if not explicitly proposed new directive ProtocolsHonorOrder, added documentation for Protocols feature, changed preference selection and config merging removed accidental code new Protocols directive and core API changes to enable protocol switching on HTTP Upgrade or ALPN, implemented in mod_ssl and mod_h2 SECURITY (CVE-2014-0117): Fix a crash in mod_proxy. In a reverse proxy configuration, a remote attacker could send a carefully crafted request which could crash a server process, resulting in denial of service. Thanks to Marek Kroemeke working with HP's Zero Day Initiative for reporting this issue. * server/util.c (ap_parse_token_list_strict): New function. * modules/proxy/proxy_util.c (find_conn_headers): Use it here. * modules/proxy/mod_proxy_http.c (ap_proxy_http_process_response): Send a 400 for a malformed Connection header. Submitted by: Edward Lu, breser, covener http, mod_ssl: Introduce and return the 421 (Misdirected Request) status code for clients requesting a hostname on a reused connection whose SNI (from the TLS handshake) does not match. PR 5802. This allows HTTP/2 clients to fall back to a new connection as per: https://tools.ietf.org/html/rfc7540#section-9.1.2 Proposed by: Stefan Eissing <stefan eissing.org> Reviewed by: ylavic c89 Allowing protocol_propose hooks to be called with offers=NULL, clarifying semantics as proposed by chaosed0@gmail.com giving ap_array_index a start parameter, adding ap_array_contains ap_process_request needs exportation for use in mod_h2 on Windows final final change to the new ap_array_str_* functions after review changed Protocols default to http/1.1 only, updated documentation, changed ap_select_protocol() to return NULL when no protocol could be agreed upon mod_ssl: fix compiler warning (bad cast). improvements in ap_select_protocol(), supplied by yann ylavic Submitted by: icing, jorton, ylavic, covener, icing, icing, gsmith, icing, icing, ylavic, icing Reviewed/backported by: jim git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1705672 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--STATUS30
-rw-r--r--docs/manual/mod/core.xml68
-rw-r--r--include/http_core.h4
-rw-r--r--include/http_protocol.h133
-rw-r--r--include/http_request.h2
-rw-r--r--include/httpd.h40
-rw-r--r--modules/http/http_protocol.c7
-rw-r--r--modules/http/http_request.c2
-rw-r--r--modules/ssl/ssl_engine_init.c4
-rw-r--r--modules/ssl/ssl_engine_io.c43
-rw-r--r--modules/ssl/ssl_engine_kernel.c112
-rw-r--r--modules/ssl/ssl_private.h11
-rw-r--r--server/core.c114
-rw-r--r--server/protocol.c158
-rw-r--r--server/util.c114
15 files changed, 801 insertions, 41 deletions
diff --git a/STATUS b/STATUS
index ebb0324b0a..7ced6de324 100644
--- a/STATUS
+++ b/STATUS
@@ -110,36 +110,6 @@ RELEASE SHOWSTOPPERS:
PATCHES ACCEPTED TO BACKPORT FROM TRUNK:
[ start all new proposals below, under PATCHES PROPOSED. ]
- *) core/mod_ssl: add Protocols/ProtocolsHonorOrder directives and new
- protocols hooks to control Upgrade: and ALPN protocol switching.
- HTTP_MISDIRECTED_REQUEST addition and handling in mod_ssl
- trunk patch: http://svn.apache.org/r1697855
- http://svn.apache.org/r1697339
- http://svn.apache.org/r1696428
- http://svn.apache.org/r1696266
- http://svn.apache.org/r1696264
- http://svn.apache.org/r1695874
- http://svn.apache.org/r1695727
- http://svn.apache.org/r1692516
- http://svn.apache.org/r1692486
- http://svn.apache.org/r1610674
- http://svn.apache.org/r1685069
- http://svn.apache.org/r1693918
- http://svn.apache.org/r1698116
- http://svn.apache.org/r1698133
- http://svn.apache.org/r1694950
- http://svn.apache.org/r1700968
- http://svn.apache.org/r1701005
- http://svn.apache.org/r1701145
- http://svn.apache.org/r1701178
- All changes to files in modules/http2 need to be ignored.
- v2: added r1698116, r1693918 to patch
- v3: added changes to ap_array_index and ap_array_contains
- 2.4.x patch: https://raw.githubusercontent.com/icing/mod_h2/master/sandbox/httpd/patches/core-protocols-v4.patch
- +1: icing, jim, minfrin
- ylavic: should/could we set the "experimental" bits for the hooks
- protocol_{propose,switch} before backporting?
-
*) mod_h2: add HTTP/2 support to httpd, depends on core/mod_ssl changes above
2.4.x branch for this and core/mod_ssl: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.17-protocols-http2
See diff and merged changelists via:
diff --git a/docs/manual/mod/core.xml b/docs/manual/mod/core.xml
index 2e7812cc8e..1620ccbbc1 100644
--- a/docs/manual/mod/core.xml
+++ b/docs/manual/mod/core.xml
@@ -3590,6 +3590,74 @@ On Windows, from Apache 2.3.3 and later.</compatibility>
<directivesynopsis>
+ <name>Protocols</name>
+ <description>Protocols available for a server/virtual host</description>
+ <syntax>Protocols <var>protocol</var> ...</syntax>
+ <default>Protocols http/1.1</default>
+ <contextlist><context>server config</context><context>virtual host</context></contextlist>
+ <compatibility>Only available from Apache 2.4.17 and later.</compatibility>
+
+ <usage>
+ <p>This directive specifies the list of protocols supported for a
+ server/virtual host. The list determines the allowed protocols
+ a client may negotiate for this server/host.</p>
+
+ <p>You need to set protocols if you want to extend the available
+ protocols for a server/host. By default, only the http/1.1 protocol
+ (which includes the compatibility with 1.0 and 0.9 clients) is
+ allowed.</p>
+
+ <p>For example, if you want to support HTTP/2 for a server with TLS,
+ specify:</p>
+
+ <highlight language="config">
+ Protocols h2 http/1.1
+ </highlight>
+
+ <p>Valid protocols are <code>http/1.1</code> for http and https connections,
+ <code>h2</code> on https connections and <code>h2c</code> for http
+ connections. Modules may enable more protocols.</p>
+
+ <p>It is safe to specify protocols that are unavailable/disabled. Such
+ protocol names will simply be ignored.</p>
+
+ <p>Protocols specified in base servers are inherited for virtual hosts
+ only if the virtual host has no own Protocols directive. Or, the other
+ way around, Protocols directives in virtual hosts replace any
+ such directive in the base server.
+ </p>
+
+ </usage>
+ <seealso><directive module="core">ProtocolsHonorOrder</directive></seealso>
+</directivesynopsis>
+
+
+<directivesynopsis>
+ <name>ProtocolsHonorOrder</name>
+ <description>Protocols available for a server/virtual host</description>
+ <syntax>ProtocolsHonorOrder On|Off</syntax>
+ <default>ProtocolsHonorOrder On</default>
+ <contextlist><context>server config</context><context>virtual host</context></contextlist>
+ <compatibility>Only available from Apache 2.4.17 and later.</compatibility>
+
+ <usage>
+ <p>This directive specifies if the server should honor the order in which
+ the <directive>Protocols</directive> directive lists protocols.</p>
+
+ <p>If configured Off, the client supplied list order of protocols has
+ precedence over the order in the server configuration.</p>
+
+ <p>With <directive>ProtocolsHonorOrder</directive> set to <code>on</code>
+ (default), the client ordering does not matter and only the ordering
+ in the server settings influences the outcome of the protocol
+ negotiation.</p>
+
+ </usage>
+ <seealso><directive module="core">Protocols</directive></seealso>
+</directivesynopsis>
+
+
+<directivesynopsis>
<name>RLimitCPU</name>
<description>Limits the CPU consumption of processes launched
by Apache httpd children</description>
diff --git a/include/http_core.h b/include/http_core.h
index 8171823a08..6ca53f76ed 100644
--- a/include/http_core.h
+++ b/include/http_core.h
@@ -681,6 +681,10 @@ typedef struct {
#define AP_MERGE_TRAILERS_DISABLE 2
int merge_trailers;
+
+
+ apr_array_header_t *protocols;
+ int protocols_honor_order;
} core_server_config;
/* for AddOutputFiltersByType in core.c */
diff --git a/include/http_protocol.h b/include/http_protocol.h
index ee61b68769..64ed01362c 100644
--- a/include/http_protocol.h
+++ b/include/http_protocol.h
@@ -700,6 +700,139 @@ AP_DECLARE_HOOK(const char *,http_scheme,(const request_rec *r))
*/
AP_DECLARE_HOOK(apr_port_t,default_port,(const request_rec *r))
+
+#define AP_PROTOCOL_HTTP1 "http/1.1"
+
+/**
+ * Determine the list of protocols available for a connection/request. This may
+ * be collected with or without any request sent, in which case the request is
+ * NULL. Or it may be triggered by the request received, e.g. through the
+ * "Upgrade" header.
+ *
+ * This hook will be run whenever protocols are being negotiated (ALPN as
+ * one example). It may also be invoked at other times, e.g. when the server
+ * wants to advertise protocols it is capable of switching to.
+ *
+ * The identifiers for protocols are taken from the TLS extension type ALPN:
+ * https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xml
+ *
+ * If no protocols are added to the proposals, the server not perform any
+ * switch. If the protocol selected from the proposals is the protocol
+ * already in place, also no protocol switch will be invoked.
+ *
+ * The client may already have announced the protocols it is willing to
+ * accept. These will then be listed as offers. This parameter may also
+ * be NULL, indicating that offers from the client are not known and
+ * the hooks should propose all protocols that are valid for the
+ * current connection/request.
+ *
+ * All hooks are run, unless one returns an error. Proposals may contain
+ * duplicates. The order in which proposals are added is usually ignored.
+ *
+ * @param c The current connection
+ * @param r The current request or NULL
+ * @param s The server/virtual host selected
+ * @param offers A list of protocol identifiers offered by the client or
+ * NULL to indicated that the hooks are free to propose
+ * @param proposals The list of protocol identifiers proposed by the hooks
+ * @return OK or DECLINED
+ */
+AP_DECLARE_HOOK(int,protocol_propose,(conn_rec *c, request_rec *r,
+ server_rec *s,
+ const apr_array_header_t *offers,
+ apr_array_header_t *proposals))
+
+/**
+ * Perform a protocol switch on the connection. The exact requirements for
+ * that depend on the protocol in place and the one switched to. The first
+ * protocol module to handle the switch is the last module run.
+ *
+ * For a connection level switch (r == NULL), the handler must on return
+ * leave the conn_rec in a state suitable for processing the switched
+ * protocol, e.g. correct filters in place.
+ *
+ * For a request triggered switch (r != NULL), the protocol switch is done
+ * before the response is sent out. When switching from "http/1.1" via Upgrade
+ * header, the 101 intermediate response will have been sent. The
+ * hook needs then to process the connection until it can be closed. Which
+ * the server will enforce on hook return.
+ * Any error the hook might encounter must already be sent by the hook itself
+ * to the client in whatever form the new protocol requires.
+ *
+ * @param c The current connection
+ * @param r The current request or NULL
+ * @param s The server/virtual host selected
+ * @param choices A list of protocol identifiers, normally the clients whishes
+ * @param proposals the list of protocol identifiers proposed by the hooks
+ * @return OK or DECLINED
+ */
+AP_DECLARE_HOOK(int,protocol_switch,(conn_rec *c, request_rec *r,
+ server_rec *s,
+ const char *protocol))
+
+/**
+ * Return the protocol used on the connection. Modules implementing
+ * protocol switching must register here and return the correct protocol
+ * identifier for connections they switched.
+ *
+ * To find out the protocol for the current connection, better call
+ * @see ap_get_protocol which internally uses this hook.
+ *
+ * @param c The current connection
+ * @return The identifier of the protocol in place or NULL
+ */
+AP_DECLARE_HOOK(const char *,protocol_get,(const conn_rec *c))
+
+/**
+ * Select a protocol for the given connection and optional request. Will return
+ * the protocol identifier selected which may be the protocol already in place
+ * on the connection. The selected protocol will be NULL if non of the given
+ * choices could be agreed upon (e.g. no proposal as made).
+ *
+ * A special case is where the choices itself is NULL (instead of empty). In
+ * this case there are no restrictions imposed on protocol selection.
+ *
+ * @param c The current connection
+ * @param r The current request or NULL
+ * @param s The server/virtual host selected
+ * @param choices A list of protocol identifiers, normally the clients whishes
+ * @return The selected protocol or NULL if no protocol could be agreed upon
+ */
+AP_DECLARE(const char *) ap_select_protocol(conn_rec *c, request_rec *r,
+ server_rec *s,
+ const apr_array_header_t *choices);
+
+/**
+ * Perform the actual protocol switch. The protocol given must have been
+ * selected before on the very same connection and request pair.
+ *
+ * @param c The current connection
+ * @param r The current request or NULL
+ * @param s The server/virtual host selected
+ * @param protocol the protocol to switch to
+ * @return APR_SUCCESS, if caller may continue processing as usual
+ * APR_EOF, if caller needs to stop processing the connection
+ * APR_EINVAL, if the protocol is already in place
+ * APR_NOTIMPL, if no module performed the switch
+ * Other errors where appropriate
+ */
+AP_DECLARE(apr_status_t) ap_switch_protocol(conn_rec *c, request_rec *r,
+ server_rec *s,
+ const char *protocol);
+
+/**
+ * Call the protocol_get hook to determine the protocol currently in use
+ * for the given connection.
+ *
+ * Unless another protocol has been switch to, will default to
+ * @see AP_PROTOCOL_HTTP1 and modules implementing a new protocol must
+ * report a switched connection via the protocol_get hook.
+ *
+ * @param c The connection to determine the protocol for
+ * @return the protocol in use, never NULL
+ */
+AP_DECLARE(const char *) ap_get_protocol(conn_rec *c);
+
/** @see ap_bucket_type_error */
typedef struct ap_bucket_error ap_bucket_error;
diff --git a/include/http_request.h b/include/http_request.h
index fabb4c843b..3d0b143e78 100644
--- a/include/http_request.h
+++ b/include/http_request.h
@@ -315,7 +315,7 @@ AP_DECLARE(void) ap_allow_standard_methods(request_rec *r, int reset, ...);
* the response to the client
* @param r The current request
*/
-void ap_process_request(request_rec *r);
+AP_DECLARE(void) ap_process_request(request_rec *r);
/* For post-processing after a handler has finished with a request.
* (Commonly used after it was suspended)
diff --git a/include/httpd.h b/include/httpd.h
index 62afda0f6b..91ddd71c72 100644
--- a/include/httpd.h
+++ b/include/httpd.h
@@ -518,6 +518,7 @@ AP_DECLARE(const char *) ap_get_server_built(void);
#define HTTP_UNSUPPORTED_MEDIA_TYPE 415
#define HTTP_RANGE_NOT_SATISFIABLE 416
#define HTTP_EXPECTATION_FAILED 417
+#define HTTP_MISDIRECTED_REQUEST 421
#define HTTP_UNPROCESSABLE_ENTITY 422
#define HTTP_LOCKED 423
#define HTTP_FAILED_DEPENDENCY 424
@@ -1549,6 +1550,23 @@ AP_DECLARE(int) ap_find_etag_weak(apr_pool_t *p, const char *line, const char *t
AP_DECLARE(int) ap_find_etag_strong(apr_pool_t *p, const char *line, const char *tok);
/**
+ * Retrieve an array of tokens in the format "1#token" defined in RFC2616. Only
+ * accepts ',' as a delimiter, does not accept quoted strings, and errors on
+ * any separator.
+ * @param p The pool to allocate from
+ * @param tok The line to read tokens from
+ * @param tokens Pointer to an array of tokens. If not NULL, must be an array
+ * of char*, otherwise it will be allocated on @a p when a token is found
+ * @param skip_invalid If true, when an invalid separator is encountered, it
+ * will be ignored.
+ * @return NULL on success, an error string otherwise.
+ * @remark *tokens may be NULL on output if NULL in input and no token is found
+ */
+AP_DECLARE(const char *) ap_parse_token_list_strict(apr_pool_t *p, const char *tok,
+ apr_array_header_t **tokens,
+ int skip_invalid);
+
+/**
* Retrieve a token, spacing over it and adjusting the pointer to
* the first non-white byte afterwards. Note that these tokens
* are delimited by semis and commas and can also be delimited
@@ -2265,6 +2283,28 @@ AP_DECLARE(char *) ap_get_exec_line(apr_pool_t *p,
#define AP_NORESTART APR_OS_START_USEERR + 1
+/**
+ * Get the first index of the string in the array or -1 if not found. Start
+ * searching a start.
+ * @param array The array the check
+ * @param s The string to find
+ * @param start Start index for search. If start is out of bounds (negative or
+ equal to array length or greater), -1 will be returned.
+ * @return index of string in array or -1
+ */
+AP_DECLARE(int) ap_array_str_index(const apr_array_header_t *array,
+ const char *s,
+ int start);
+
+/**
+ * Check if the string is member of the given array by strcmp.
+ * @param array The array the check
+ * @param s The string to find
+ * @return !=0 iff string is member of array (via strcmp)
+ */
+AP_DECLARE(int) ap_array_str_contains(const apr_array_header_t *array,
+ const char *s);
+
#ifdef __cplusplus
}
#endif
diff --git a/modules/http/http_protocol.c b/modules/http/http_protocol.c
index fc7ec6ccbe..589611b593 100644
--- a/modules/http/http_protocol.c
+++ b/modules/http/http_protocol.c
@@ -135,7 +135,7 @@ static const char * const status_lines[RESPONSE_CODES] =
NULL, /* 418 */
NULL, /* 419 */
NULL, /* 420 */
- NULL, /* 421 */
+ "421 Misdirected Request",
"422 Unprocessable Entity",
"423 Locked",
"424 Failed Dependency",
@@ -1293,6 +1293,11 @@ static const char *get_canned_error_string(int status,
case HTTP_NETWORK_AUTHENTICATION_REQUIRED:
return("<p>The client needs to authenticate to gain\n"
"network access.</p>\n");
+ case HTTP_MISDIRECTED_REQUEST:
+ return("<p>The client needs a new connection for this\n"
+ "request as the requested host name does not match\n"
+ "the Server Name Indication (SNI) in use for this\n"
+ "connection.</p>\n");
default: /* HTTP_INTERNAL_SERVER_ERROR */
/*
* This comparison to expose error-notes could be modified to
diff --git a/modules/http/http_request.c b/modules/http/http_request.c
index 7b06def940..70bf2937c0 100644
--- a/modules/http/http_request.c
+++ b/modules/http/http_request.c
@@ -363,7 +363,7 @@ void ap_process_async_request(request_rec *r)
ap_process_request_after_handler(r);
}
-void ap_process_request(request_rec *r)
+AP_DECLARE(void) ap_process_request(request_rec *r)
{
apr_bucket_brigade *bb;
apr_bucket *b;
diff --git a/modules/ssl/ssl_engine_init.c b/modules/ssl/ssl_engine_init.c
index 6baf2f514d..72b458e875 100644
--- a/modules/ssl/ssl_engine_init.c
+++ b/modules/ssl/ssl_engine_init.c
@@ -625,6 +625,10 @@ static void ssl_init_ctx_callbacks(server_rec *s,
SSL_CTX_set_tmp_dh_callback(ctx, ssl_callback_TmpDH);
SSL_CTX_set_info_callback(ctx, ssl_callback_Info);
+
+#ifdef HAVE_TLS_ALPN
+ SSL_CTX_set_alpn_select_cb(ctx, ssl_callback_alpn_select, NULL);
+#endif
}
static apr_status_t ssl_init_ctx_verify(server_rec *s,
diff --git a/modules/ssl/ssl_engine_io.c b/modules/ssl/ssl_engine_io.c
index ce8abe5d54..cbbb8f8e9c 100644
--- a/modules/ssl/ssl_engine_io.c
+++ b/modules/ssl/ssl_engine_io.c
@@ -28,6 +28,7 @@
core keeps dumping.''
-- Unknown */
#include "ssl_private.h"
+#include "mod_ssl.h"
#include "apr_date.h"
/* _________________________________________________________________
@@ -297,6 +298,9 @@ typedef struct {
apr_pool_t *pool;
char buffer[AP_IOBUFSIZE];
ssl_filter_ctx_t *filter_ctx;
+#ifdef HAVE_TLS_ALPN
+ int alpn_finished; /* 1 if ALPN has finished, 0 otherwise */
+#endif
} bio_filter_in_ctx_t;
/*
@@ -1412,6 +1416,42 @@ static apr_status_t ssl_io_filter_input(ap_filter_t *f,
APR_BRIGADE_INSERT_TAIL(bb, bucket);
}
+#ifdef HAVE_TLS_ALPN
+ /* By this point, Application-Layer Protocol Negotiation (ALPN) should be
+ * completed (if our version of OpenSSL supports it). If we haven't already,
+ * find out which protocol was decided upon and inform other modules
+ * by calling alpn_proto_negotiated_hook.
+ */
+ if (!inctx->alpn_finished) {
+ SSLConnRec *sslconn = myConnConfig(f->c);
+ const unsigned char *next_proto = NULL;
+ unsigned next_proto_len = 0;
+ const char *protocol;
+ int n;
+
+ SSL_get0_alpn_selected(inctx->ssl, &next_proto, &next_proto_len);
+ if (next_proto && next_proto_len) {
+ protocol = apr_pstrmemdup(f->c->pool, (const char *)next_proto,
+ next_proto_len);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, f->c,
+ APLOGNO(02836) "ALPN selected protocol: '%s'",
+ protocol);
+
+ if (strcmp(protocol, ap_get_protocol(f->c))) {
+ status = ap_switch_protocol(f->c, NULL, sslconn->server,
+ protocol);
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, f->c,
+ APLOGNO(02908) "protocol switch to '%s' failed",
+ protocol);
+ return status;
+ }
+ }
+ }
+ inctx->alpn_finished = 1;
+ }
+#endif
+
return APR_SUCCESS;
}
@@ -1893,6 +1933,9 @@ static void ssl_io_input_add_filter(ssl_filter_ctx_t *filter_ctx, conn_rec *c,
inctx->block = APR_BLOCK_READ;
inctx->pool = c->pool;
inctx->filter_ctx = filter_ctx;
+#ifdef HAVE_TLS_ALPN
+ inctx->alpn_finished = 0;
+#endif
}
/* The request_rec pointer is passed in here only to ensure that the
diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c
index d0f9489a87..0ac64bfeff 100644
--- a/modules/ssl/ssl_engine_kernel.c
+++ b/modules/ssl/ssl_engine_kernel.c
@@ -29,6 +29,7 @@
time I was too famous.''
-- Unknown */
#include "ssl_private.h"
+#include "mod_ssl.h"
#include "util_md5.h"
static void ssl_configure_env(request_rec *r, SSLConnRec *sslconn);
@@ -203,6 +204,9 @@ int ssl_hook_ReadReq(request_rec *r)
ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, APLOGNO(02032)
"Hostname %s provided via SNI and hostname %s provided"
" via HTTP are different", servername, host);
+ if (r->connection->keepalives > 0) {
+ return HTTP_MISDIRECTED_REQUEST;
+ }
return HTTP_BAD_REQUEST;
}
}
@@ -1903,23 +1907,29 @@ void ssl_callback_Info(const SSL *ssl, int where, int rc)
#ifdef HAVE_TLSEXT
/*
- * This callback function is executed when OpenSSL encounters an extended
+ * This function sets the virtual host from an extended
* client hello with a server name indication extension ("SNI", cf. RFC 6066).
*/
-int ssl_callback_ServerNameIndication(SSL *ssl, int *al, modssl_ctx_t *mctx)
+static apr_status_t init_vhost(conn_rec *c, SSL *ssl)
{
- const char *servername =
- SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
- conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
-
+ const char *servername;
+
if (c) {
+ SSLConnRec *sslcon = myConnConfig(c);
+
+ if (sslcon->server != c->base_server) {
+ /* already found the vhost */
+ return APR_SUCCESS;
+ }
+
+ servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (servername) {
if (ap_vhost_iterate_given_conn(c, ssl_find_vhost,
(void *)servername)) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02043)
"SSL virtual host for servername %s found",
servername);
- return SSL_TLSEXT_ERR_OK;
+ return APR_SUCCESS;
}
else {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02044)
@@ -1949,8 +1959,20 @@ int ssl_callback_ServerNameIndication(SSL *ssl, int *al, modssl_ctx_t *mctx)
"(using default/first virtual host)");
}
}
+
+ return APR_NOTFOUND;
+}
- return SSL_TLSEXT_ERR_NOACK;
+/*
+ * This callback function is executed when OpenSSL encounters an extended
+ * client hello with a server name indication extension ("SNI", cf. RFC 6066).
+ */
+int ssl_callback_ServerNameIndication(SSL *ssl, int *al, modssl_ctx_t *mctx)
+{
+ conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
+ apr_status_t status = init_vhost(c, ssl);
+
+ return (status == APR_SUCCESS)? SSL_TLSEXT_ERR_OK : SSL_TLSEXT_ERR_NOACK;
}
/*
@@ -2149,6 +2171,80 @@ int ssl_callback_SessionTicket(SSL *ssl,
}
#endif /* HAVE_TLS_SESSION_TICKETS */
+#ifdef HAVE_TLS_ALPN
+
+/*
+ * This callback function is executed when the TLS Application-Layer
+ * Protocol Negotiation Extension (ALPN, RFC 7301) is triggered by the Client
+ * Hello, giving a list of desired protocol names (in descending preference)
+ * to the server.
+ * The callback has to select a protocol name or return an error if none of
+ * the clients preferences is supported.
+ * The selected protocol does not have to be on the client list, according
+ * to RFC 7301, so no checks are performed.
+ * The client protocol list is serialized as length byte followed by ASCII
+ * characters (not null-terminated), followed by the next protocol name.
+ */
+int ssl_callback_alpn_select(SSL *ssl,
+ const unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen,
+ void *arg)
+{
+ conn_rec *c = (conn_rec*)SSL_get_app_data(ssl);
+ SSLConnRec *sslconn = myConnConfig(c);
+ apr_array_header_t *client_protos;
+ const char *proposed;
+ size_t len;
+ int i;
+
+ /* If the connection object is not available,
+ * then there's nothing for us to do. */
+ if (c == NULL) {
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ if (inlen == 0) {
+ // someone tries to trick us?
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02837)
+ "ALPN client protocol list empty");
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+
+ client_protos = apr_array_make(c->pool, 0, sizeof(char *));
+ for (i = 0; i < inlen; /**/) {
+ unsigned int plen = in[i++];
+ if (plen + i > inlen) {
+ // someone tries to trick us?
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02838)
+ "ALPN protocol identifier too long");
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ APR_ARRAY_PUSH(client_protos, char *) =
+ apr_pstrndup(c->pool, (const char *)in+i, plen);
+ i += plen;
+ }
+
+ /* The order the callbacks are invoked from TLS extensions is, unfortunately
+ * not defined and older openssl versions do call ALPN selection before
+ * they callback the SNI. We need to make sure that we know which vhost
+ * we are dealing with so we respect the correct protocols.
+ */
+ init_vhost(c, ssl);
+
+ proposed = ap_select_protocol(c, NULL, sslconn->server, client_protos);
+ *out = (const unsigned char *)(proposed? proposed : ap_get_protocol(c));
+ len = strlen((const char*)*out);
+ if (len > 255) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02840)
+ "ALPN negotiated protocol name too long");
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
+ }
+ *outlen = (unsigned char)len;
+
+ return SSL_TLSEXT_ERR_OK;
+}
+#endif /* HAVE_TLS_ALPN */
+
#ifdef HAVE_SRP
int ssl_callback_SRPServerParams(SSL *ssl, int *ad, void *arg)
diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h
index 8f889971c1..5d026b238a 100644
--- a/modules/ssl/ssl_private.h
+++ b/modules/ssl/ssl_private.h
@@ -182,6 +182,11 @@
#include <openssl/srp.h>
#endif
+/* ALPN Protocol Negotiation */
+#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
+#define HAVE_TLS_ALPN
+#endif
+
#endif /* !defined(OPENSSL_NO_TLSEXT) && defined(SSL_set_tlsext_host_name) */
/* mod_ssl headers */
@@ -798,6 +803,12 @@ int ssl_callback_SessionTicket(SSL *, unsigned char *, unsigned char *,
EVP_CIPHER_CTX *, HMAC_CTX *, int);
#endif
+#ifdef HAVE_TLS_ALPN
+int ssl_callback_alpn_select(SSL *ssl, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg);
+#endif
+
/** Session Cache Support */
apr_status_t ssl_scache_init(server_rec *, apr_pool_t *);
void ssl_scache_status_register(apr_pool_t *p);
diff --git a/server/core.c b/server/core.c
index 302f90ed4e..de54dc5a42 100644
--- a/server/core.c
+++ b/server/core.c
@@ -423,6 +423,7 @@ static void *merge_core_dir_configs(apr_pool_t *a, void *basev, void *newv)
static void *create_core_server_config(apr_pool_t *a, server_rec *s)
{
core_server_config *conf;
+ const char **np;
int is_virtual = s->is_virtual;
conf = (core_server_config *)apr_pcalloc(a, sizeof(core_server_config));
@@ -468,6 +469,9 @@ static void *create_core_server_config(apr_pool_t *a, server_rec *s)
conf->trace_enable = AP_TRACE_UNSET;
+ conf->protocols = apr_array_make(a, 5, sizeof(const char *));
+ conf->protocols_honor_order = -1;
+
return (void *)conf;
}
@@ -518,6 +522,12 @@ static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv)
? virt->merge_trailers
: base->merge_trailers;
+ conf->protocols = ((virt->protocols->nelts > 0)?
+ virt->protocols : base->protocols);
+ conf->protocols_honor_order = ((virt->protocols_honor_order < 0)?
+ base->protocols_honor_order :
+ virt->protocols_honor_order);
+
return conf;
}
@@ -3692,6 +3702,48 @@ static const char *set_trace_enable(cmd_parms *cmd, void *dummy,
return NULL;
}
+static const char *set_protocols(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ core_server_config *conf =
+ ap_get_core_module_config(cmd->server->module_config);
+ const char **np;
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE);
+
+ if (err) {
+ return err;
+ }
+
+ np = (const char **)apr_array_push(conf->protocols);
+ *np = arg;
+
+ return NULL;
+}
+
+static const char *set_protocols_honor_order(cmd_parms *cmd, void *dummy,
+ const char *arg)
+{
+ core_server_config *conf =
+ ap_get_core_module_config(cmd->server->module_config);
+ const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE);
+
+ if (err) {
+ return err;
+ }
+
+ if (strcasecmp(arg, "on") == 0) {
+ conf->protocols_honor_order = 1;
+ }
+ else if (strcasecmp(arg, "off") == 0) {
+ conf->protocols_honor_order = 0;
+ }
+ else {
+ return "ProtocolsHonorOrder must be 'on' or 'off'";
+ }
+
+ return NULL;
+}
+
static apr_hash_t *errorlog_hash;
static int log_constant_item(const ap_errorlog_info *info, const char *arg,
@@ -4205,6 +4257,11 @@ AP_INIT_TAKE1("TraceEnable", set_trace_enable, NULL, RSRC_CONF,
"'on' (default), 'off' or 'extended' to trace request body content"),
AP_INIT_FLAG("MergeTrailers", set_merge_trailers, NULL, RSRC_CONF,
"merge request trailers into request headers or not"),
+AP_INIT_ITERATE("Protocols", set_protocols, NULL, RSRC_CONF,
+ "Controls which protocols are allowed"),
+AP_INIT_TAKE1("ProtocolsHonorOrder", set_protocols_honor_order, NULL, RSRC_CONF,
+ "'off' (default) or 'on' to respect given order of protocols, "
+ "by default the client specified order determines selection"),
{ NULL }
};
@@ -4936,6 +4993,61 @@ static void core_dump_config(apr_pool_t *p, server_rec *s)
}
}
+static int core_upgrade_handler(request_rec *r)
+{
+ conn_rec *c = r->connection;
+ const char *upgrade = apr_table_get(r->headers_in, "Upgrade");
+
+ if (upgrade && *upgrade) {
+ const char *conn = apr_table_get(r->headers_in, "Connection");
+ if (ap_find_token(r->pool, conn, "upgrade")) {
+ apr_array_header_t *offers = NULL;
+ const char *err;
+
+ err = ap_parse_token_list_strict(r->pool, upgrade, &offers, 0);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02910)
+ "parsing Upgrade header: %s", err);
+ return DECLINED;
+ }
+
+ if (offers && offers->nelts > 0) {
+ const char *protocol = ap_select_protocol(c, r, r->server,
+ offers);
+ if (protocol && strcmp(protocol, ap_get_protocol(c))) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02909)
+ "Upgrade selects '%s'", protocol);
+ /* Let the client know what we are upgrading to. */
+ apr_table_clear(r->headers_out);
+ apr_table_setn(r->headers_out, "Upgrade", protocol);
+ apr_table_setn(r->headers_out, "Connection", "Upgrade");
+
+ r->status = HTTP_SWITCHING_PROTOCOLS;
+ r->status_line = ap_get_status_line(r->status);
+ ap_send_interim_response(r, 1);
+
+ ap_switch_protocol(c, r, r->server, protocol);
+
+ /* make sure httpd closes the connection after this */
+ c->keepalive = AP_CONN_CLOSE;
+ return DONE;
+ }
+ }
+ }
+ }
+
+ return DECLINED;
+}
+
+static int core_upgrade_storage(request_rec *r)
+{
+ if ((r->method_number == M_OPTIONS) && r->uri && (r->uri[0] == '*') &&
+ (r->uri[1] == '\0')) {
+ return core_upgrade_handler(r);
+ }
+ return DECLINED;
+}
+
static void register_hooks(apr_pool_t *p)
{
errorlog_hash = apr_hash_make(p);
@@ -4958,10 +5070,12 @@ static void register_hooks(apr_pool_t *p)
ap_hook_check_config(core_check_config,NULL,NULL,APR_HOOK_FIRST);
ap_hook_test_config(core_dump_config,NULL,NULL,APR_HOOK_FIRST);
ap_hook_translate_name(ap_core_translate,NULL,NULL,APR_HOOK_REALLY_LAST);
+ ap_hook_map_to_storage(core_upgrade_storage,NULL,NULL,APR_HOOK_REALLY_FIRST);
ap_hook_map_to_storage(core_map_to_storage,NULL,NULL,APR_HOOK_REALLY_LAST);
ap_hook_open_logs(ap_open_logs,NULL,NULL,APR_HOOK_REALLY_FIRST);
ap_hook_child_init(core_child_init,NULL,NULL,APR_HOOK_REALLY_FIRST);
ap_hook_child_init(ap_logs_child_init,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_handler(core_upgrade_handler,NULL,NULL,APR_HOOK_REALLY_FIRST);
ap_hook_handler(default_handler,NULL,NULL,APR_HOOK_REALLY_LAST);
/* FIXME: I suspect we can eliminate the need for these do_nothings - Ben */
ap_hook_type_checker(do_nothing,NULL,NULL,APR_HOOK_REALLY_LAST);
diff --git a/server/protocol.c b/server/protocol.c
index 8ebf4f41f6..fc507fa089 100644
--- a/server/protocol.c
+++ b/server/protocol.c
@@ -67,6 +67,9 @@ APR_HOOK_STRUCT(
APR_HOOK_LINK(http_scheme)
APR_HOOK_LINK(default_port)
APR_HOOK_LINK(note_auth_failure)
+ APR_HOOK_LINK(protocol_propose)
+ APR_HOOK_LINK(protocol_switch)
+ APR_HOOK_LINK(protocol_get)
)
AP_DECLARE_DATA ap_filter_rec_t *ap_old_write_func = NULL;
@@ -1790,6 +1793,150 @@ AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers)
apr_brigade_destroy(x.bb);
}
+/*
+ * Compare two protocol identifier. Result is similar to strcmp():
+ * 0 gives same precedence, >0 means proto1 is preferred.
+ */
+static int protocol_cmp(const apr_array_header_t *preferences,
+ const char *proto1,
+ const char *proto2)
+{
+ if (preferences && preferences->nelts > 0) {
+ int index1 = ap_array_str_index(preferences, proto1, 0);
+ int index2 = ap_array_str_index(preferences, proto2, 0);
+ if (index2 > index1) {
+ return (index1 >= 0) ? 1 : -1;
+ }
+ else if (index1 > index2) {
+ return (index2 >= 0) ? -1 : 1;
+ }
+ }
+ /* both have the same index (mabye -1 or no pref configured) and we compare
+ * the names so that spdy3 gets precedence over spdy2. That makes
+ * the outcome at least deterministic. */
+ return strcmp(proto1, proto2);
+}
+
+AP_DECLARE(const char *) ap_get_protocol(conn_rec *c)
+{
+ const char *protocol = ap_run_protocol_get(c);
+ return protocol? protocol : AP_PROTOCOL_HTTP1;
+}
+
+AP_DECLARE(const char *) ap_select_protocol(conn_rec *c, request_rec *r,
+ server_rec *s,
+ const apr_array_header_t *choices)
+{
+ apr_pool_t *pool = r? r->pool : c->pool;
+ core_server_config *conf = ap_get_core_module_config(s->module_config);
+ const char *protocol = NULL, *existing;
+ apr_array_header_t *proposals;
+
+ if (APLOGcdebug(c)) {
+ const char *p = apr_array_pstrcat(pool, conf->protocols, ',');
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ "select protocol from %s, choices=%s for server %s",
+ p, apr_array_pstrcat(pool, choices, ','),
+ s->server_hostname);
+ }
+
+ if (conf->protocols->nelts <= 0) {
+ /* nothing configured, by default, we only allow http/1.1 here.
+ * For now...
+ */
+ if (ap_array_str_contains(choices, AP_PROTOCOL_HTTP1)) {
+ return AP_PROTOCOL_HTTP1;
+ }
+ else {
+ return NULL;
+ }
+ }
+
+ proposals = apr_array_make(pool, choices->nelts + 1, sizeof(char *));
+ ap_run_protocol_propose(c, r, s, choices, proposals);
+
+ /* If the existing protocol has not been proposed, but is a choice,
+ * add it to the proposals implicitly.
+ */
+ existing = ap_get_protocol(c);
+ if (!ap_array_str_contains(proposals, existing)
+ && ap_array_str_contains(choices, existing)) {
+ APR_ARRAY_PUSH(proposals, const char*) = existing;
+ }
+
+ if (proposals->nelts > 0) {
+ int i;
+ const apr_array_header_t *prefs = NULL;
+
+ /* Default for protocols_honor_order is 'on' or != 0 */
+ if (conf->protocols_honor_order == 0 && choices->nelts > 0) {
+ prefs = choices;
+ }
+ else {
+ prefs = conf->protocols;
+ }
+
+ /* Select the most preferred protocol */
+ if (APLOGcdebug(c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+ "select protocol, proposals=%s preferences=%s configured=%s",
+ apr_array_pstrcat(pool, proposals, ','),
+ apr_array_pstrcat(pool, prefs, ','),
+ apr_array_pstrcat(pool, conf->protocols, ','));
+ }
+ for (i = 0; i < proposals->nelts; ++i) {
+ const char *p = APR_ARRAY_IDX(proposals, i, const char *);
+ if (!ap_array_str_contains(conf->protocols, p)) {
+ /* not a configured protocol here */
+ continue;
+ }
+ else if (!protocol
+ || (protocol_cmp(prefs, protocol, p) < 0)) {
+ /* none selected yet or this one has preference */
+ protocol = p;
+ }
+ }
+ }
+ if (APLOGcdebug(c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "selected protocol=%s",
+ protocol? protocol : "(none)");
+ }
+
+ return protocol;
+}
+
+AP_DECLARE(apr_status_t) ap_switch_protocol(conn_rec *c, request_rec *r,
+ server_rec *s,
+ const char *protocol)
+{
+ const char *current = ap_get_protocol(c);
+ int rc;
+
+ if (!strcmp(current, protocol)) {
+ ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(02906)
+ "already at it, protocol_switch to %s",
+ protocol);
+ return APR_SUCCESS;
+ }
+
+ rc = ap_run_protocol_switch(c, r, s, protocol);
+ switch (rc) {
+ case DECLINED:
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02907)
+ "no implementation for protocol_switch to %s",
+ protocol);
+ return APR_ENOTIMPL;
+ case OK:
+ case DONE:
+ return APR_SUCCESS;
+ default:
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02905)
+ "unexpected return code %d from protocol_switch to %s"
+ , rc, protocol);
+ return APR_EOF;
+ }
+}
+
AP_IMPLEMENT_HOOK_VOID(pre_read_request,
(request_rec *r, conn_rec *c),
@@ -1805,3 +1952,14 @@ AP_IMPLEMENT_HOOK_RUN_FIRST(unsigned short,default_port,
AP_IMPLEMENT_HOOK_RUN_FIRST(int, note_auth_failure,
(request_rec *r, const char *auth_type),
(r, auth_type), DECLINED)
+AP_IMPLEMENT_HOOK_RUN_ALL(int,protocol_propose,
+ (conn_rec *c, request_rec *r, server_rec *s,
+ const apr_array_header_t *offers,
+ apr_array_header_t *proposals),
+ (c, r, s, offers, proposals), OK, DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(int,protocol_switch,
+ (conn_rec *c, request_rec *r, server_rec *s,
+ const char *protocol),
+ (c, r, s, protocol), DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(const char *,protocol_get,
+ (const conn_rec *c), (c), NULL)
diff --git a/server/util.c b/server/util.c
index 464b07f490..0bc04b18cc 100644
--- a/server/util.c
+++ b/server/util.c
@@ -1451,6 +1451,95 @@ AP_DECLARE(int) ap_find_etag_weak(apr_pool_t *p, const char *line,
return find_list_item(p, line, tok, AP_ETAG_WEAK);
}
+/* Grab a list of tokens of the format 1#token (from RFC7230) */
+AP_DECLARE(const char *) ap_parse_token_list_strict(apr_pool_t *p,
+ const char *str_in,
+ apr_array_header_t **tokens,
+ int skip_invalid)
+{
+ int in_leading_space = 1;
+ int in_trailing_space = 0;
+ int string_end = 0;
+ const char *tok_begin;
+ const char *cur;
+
+ if (!str_in) {
+ return NULL;
+ }
+
+ tok_begin = cur = str_in;
+
+ while (!string_end) {
+ const unsigned char c = (unsigned char)*cur;
+
+ if (!TEST_CHAR(c, T_HTTP_TOKEN_STOP) && c != '\0') {
+ /* Non-separator character; we are finished with leading
+ * whitespace. We must never have encountered any trailing
+ * whitespace before the delimiter (comma) */
+ in_leading_space = 0;
+ if (in_trailing_space) {
+ return "Encountered illegal whitespace in token";
+ }
+ }
+ else if (c == ' ' || c == '\t') {
+ /* "Linear whitespace" only includes ASCII CRLF, space, and tab;
+ * we can't get a CRLF since headers are split on them already,
+ * so only look for a space or a tab */
+ if (in_leading_space) {
+ /* We're still in leading whitespace */
+ ++tok_begin;
+ }
+ else {
+ /* We must be in trailing whitespace */
+ ++in_trailing_space;
+ }
+ }
+ else if (c == ',' || c == '\0') {
+ if (!in_leading_space) {
+ /* If we're out of the leading space, we know we've read some
+ * characters of a token */
+ if (*tokens == NULL) {
+ *tokens = apr_array_make(p, 4, sizeof(char *));
+ }
+ APR_ARRAY_PUSH(*tokens, char *) =
+ apr_pstrmemdup((*tokens)->pool, tok_begin,
+ (cur - tok_begin) - in_trailing_space);
+ }
+ /* We're allowed to have null elements, just don't add them to the
+ * array */
+
+ tok_begin = cur + 1;
+ in_leading_space = 1;
+ in_trailing_space = 0;
+ string_end = (c == '\0');
+ }
+ else {
+ /* Encountered illegal separator char */
+ if (skip_invalid) {
+ /* Skip to the next separator */
+ const char *temp;
+ temp = ap_strchr_c(cur, ',');
+ if(!temp) {
+ temp = ap_strchr_c(cur, '\0');
+ }
+
+ /* Act like we haven't seen a token so we reset */
+ cur = temp - 1;
+ in_leading_space = 1;
+ in_trailing_space = 0;
+ }
+ else {
+ return apr_psprintf(p, "Encountered illegal separator "
+ "'\\x%.2x'", (unsigned int)c);
+ }
+ }
+
+ ++cur;
+ }
+
+ return NULL;
+}
+
/* Retrieve a token, spacing over it and returning a pointer to
* the first non-white byte afterwards. Note that these tokens
* are delimited by semis and commas; and can also be delimited
@@ -3005,3 +3094,28 @@ AP_DECLARE(char *) ap_get_exec_line(apr_pool_t *p,
return apr_pstrndup(p, buf, k);
}
+
+AP_DECLARE(int) ap_array_str_index(const apr_array_header_t *array,
+ const char *s,
+ int start)
+{
+ if (start >= 0) {
+ int i;
+
+ for (i = start; i < array->nelts; i++) {
+ const char *p = APR_ARRAY_IDX(array, i, const char *);
+ if (!strcmp(p, s)) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+}
+
+AP_DECLARE(int) ap_array_str_contains(const apr_array_header_t *array,
+ const char *s)
+{
+ return (ap_array_str_index(array, s, 0) >= 0);
+}
+