summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/core.c31
-rw-r--r--server/protocol.c117
-rw-r--r--server/util.c10
-rw-r--r--server/vhost.c168
4 files changed, 279 insertions, 47 deletions
diff --git a/server/core.c b/server/core.c
index 58b95ccf2b..c277af3bb7 100644
--- a/server/core.c
+++ b/server/core.c
@@ -522,6 +522,9 @@ static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv)
if (virt->http09_enable != AP_HTTP09_UNSET)
conf->http09_enable = virt->http09_enable;
+ if (virt->http_conformance != AP_HTTP_CONFORMANCE_UNSET)
+ conf->http_conformance = virt->http_conformance;
+
/* no action for virt->accf_map, not allowed per-vhost */
if (virt->protocol)
@@ -3911,10 +3914,27 @@ static const char *set_http_protocol(cmd_parms *cmd, void *dummy,
else if (strcmp(arg, "1.0") == 0)
conf->http09_enable = AP_HTTP09_DISABLE;
else
- return "HttpProtocol min must be one of '0.9' and '1.0'";
- return NULL;
+ return "HttpProtocol 'min' must be one of '0.9' and '1.0'";
+ return NULL;
+ }
+
+ if (strcmp(arg, "strict") == 0)
+ conf->http_conformance = AP_HTTP_CONFORMANCE_STRICT;
+ else if (strcmp(arg, "strict,log-only") == 0)
+ conf->http_conformance = AP_HTTP_CONFORMANCE_STRICT|
+ AP_HTTP_CONFORMANCE_LOGONLY;
+ else if (strcmp(arg, "liberal") == 0)
+ conf->http_conformance = AP_HTTP_CONFORMANCE_LIBERAL;
+ else
+ return "HttpProtocol accepts 'min=0.9', 'min=1.0', 'liberal', "
+ "'strict', 'strict,log-only'";
+
+ if ((conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT) &&
+ (conf->http_conformance & AP_HTTP_CONFORMANCE_LIBERAL)) {
+ return "HttpProtocol 'strict' and 'liberal' are mutually exclusive";
}
- return "HttpProtocol must be min=0.9|1.0";
+
+ return NULL;
}
static const char *set_http_method(cmd_parms *cmd, void *conf, const char *arg)
@@ -4450,8 +4470,9 @@ AP_INIT_ITERATE("Protocols", set_protocols, NULL, RSRC_CONF,
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"),
-AP_INIT_TAKE1("HttpProtocol", set_http_protocol, NULL, RSRC_CONF,
- "'min=0.9' (default) or 'min=1.0' to allow/deny HTTP/0.9"),
+AP_INIT_ITERATE("HttpProtocol", set_http_protocol, NULL, RSRC_CONF,
+ "'min=0.9' (default) or 'min=1.0' to allow/deny HTTP/0.9; "
+ "'liberal', 'strict', 'strict,log-only'"),
AP_INIT_ITERATE("RegisterHttpMethod", set_http_method, NULL, RSRC_CONF,
"Registers non-standard HTTP methods"),
{ NULL }
diff --git a/server/protocol.c b/server/protocol.c
index 558d70f32b..a1a3845a7e 100644
--- a/server/protocol.c
+++ b/server/protocol.c
@@ -562,6 +562,9 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb)
char http[5];
apr_size_t len;
int num_blank_lines = DEFAULT_LIMIT_BLANK_LINES;
+ core_server_config *conf = ap_get_core_module_config(r->server->module_config);
+ int strict = conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT;
+ int enforce_strict = !(conf->http_conformance & AP_HTTP_CONFORMANCE_LOGONLY);
/* Read past empty lines until we get a real request line,
* a read error, the connection closes (EOF), or we timeout.
@@ -648,8 +651,6 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb)
pro = ll;
len = strlen(ll);
} else {
- core_server_config *conf;
- conf = ap_get_core_module_config(r->server->module_config);
r->assbackwards = 1;
pro = "HTTP/0.9";
len = 8;
@@ -674,12 +675,59 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb)
&& apr_isdigit(pro[7])) {
r->proto_num = HTTP_VERSION(pro[5] - '0', pro[7] - '0');
}
- else if (3 == sscanf(r->protocol, "%4s/%u.%u", http, &major, &minor)
- && (strcasecmp("http", http) == 0)
- && (minor < HTTP_VERSION(1, 0)) ) /* don't allow HTTP/0.1000 */
- r->proto_num = HTTP_VERSION(major, minor);
- else
- r->proto_num = HTTP_VERSION(1, 0);
+ else {
+ if (strict) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02418)
+ "Invalid protocol '%s'", r->protocol);
+ if (enforce_strict) {
+ r->status = HTTP_BAD_REQUEST;
+ return 0;
+ }
+ }
+ if (3 == sscanf(r->protocol, "%4s/%u.%u", http, &major, &minor)
+ && (strcasecmp("http", http) == 0)
+ && (minor < HTTP_VERSION(1, 0)) ) { /* don't allow HTTP/0.1000 */
+ r->proto_num = HTTP_VERSION(major, minor);
+ }
+ else {
+ r->proto_num = HTTP_VERSION(1, 0);
+ }
+ }
+
+ if (strict) {
+ int err = 0;
+ if (ap_has_cntrl(r->the_request)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02420)
+ "Request line must not contain control characters");
+ err = HTTP_BAD_REQUEST;
+ }
+ if (r->parsed_uri.fragment) {
+ /* RFC3986 3.5: no fragment */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02421)
+ "URI must not contain a fragment");
+ err = HTTP_BAD_REQUEST;
+ }
+ else if (r->parsed_uri.user || r->parsed_uri.password) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02422)
+ "URI must not contain a username/password");
+ err = HTTP_BAD_REQUEST;
+ }
+ else if (r->method_number == M_INVALID) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02423)
+ "Invalid HTTP method string: %s", r->method);
+ err = HTTP_NOT_IMPLEMENTED;
+ }
+ else if (r->assbackwards == 0 && r->proto_num < HTTP_VERSION(1, 0)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02424)
+ "HTTP/0.x does not take a protocol");
+ err = HTTP_BAD_REQUEST;
+ }
+
+ if (err && enforce_strict) {
+ r->status = err;
+ return 0;
+ }
+ }
return 1;
}
@@ -723,6 +771,7 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb
apr_size_t len;
int fields_read = 0;
char *tmp_field;
+ core_server_config *conf = ap_get_core_module_config(r->server->module_config);
/*
* Read header lines until we get the empty separator line, a read error,
@@ -876,6 +925,33 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb
*tmp_field-- = '\0';
}
+ if (conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT) {
+ int err = 0;
+
+ if (*last_field == '\0') {
+ err = HTTP_BAD_REQUEST;
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02425)
+ "Empty request header field name not allowed");
+ }
+ else if (ap_has_cntrl(last_field)) {
+ err = HTTP_BAD_REQUEST;
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02426)
+ "[HTTP strict] Request header field name contains "
+ "control character: %.*s",
+ (int)LOG_NAME_MAX_LEN, last_field);
+ }
+ else if (ap_has_cntrl(value)) {
+ err = HTTP_BAD_REQUEST;
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02427)
+ "Request header field '%.*s' contains"
+ "control character", (int)LOG_NAME_MAX_LEN,
+ last_field);
+ }
+ if (err && !(conf->http_conformance & AP_HTTP_CONFORMANCE_LOGONLY)) {
+ r->status = err;
+ return;
+ }
+ }
apr_table_addn(r->headers_in, last_field, value);
/* reset the alloc_len so that we'll allocate a new
@@ -925,7 +1001,7 @@ request_rec *ap_read_request(conn_rec *conn)
request_rec *r;
apr_pool_t *p;
const char *expect;
- int access_status = HTTP_OK;
+ int access_status;
apr_bucket_brigade *tmp_bb;
apr_socket_t *csd;
apr_interval_time_t cur_timeout;
@@ -984,9 +1060,11 @@ request_rec *ap_read_request(conn_rec *conn)
/* Get the request... */
if (!read_request_line(r, tmp_bb)) {
- if (r->status == HTTP_REQUEST_URI_TOO_LARGE
- || r->status == HTTP_BAD_REQUEST
- || r->status == HTTP_VERSION_NOT_SUPPORTED) {
+ switch (r->status) {
+ case HTTP_REQUEST_URI_TOO_LARGE:
+ case HTTP_BAD_REQUEST:
+ case HTTP_VERSION_NOT_SUPPORTED:
+ case HTTP_NOT_IMPLEMENTED:
if (r->status == HTTP_REQUEST_URI_TOO_LARGE) {
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00565)
"request failed: client's request-line exceeds LimitRequestLine (longer than %d)",
@@ -1004,19 +1082,17 @@ request_rec *ap_read_request(conn_rec *conn)
r = NULL;
apr_brigade_destroy(tmp_bb);
goto traceout;
- }
- else if (r->status == HTTP_REQUEST_TIME_OUT) {
+ case HTTP_REQUEST_TIME_OUT:
ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, NULL);
- if (!r->connection->keepalives) {
+ if (!r->connection->keepalives)
ap_run_log_transaction(r);
- }
apr_brigade_destroy(tmp_bb);
goto traceout;
+ default:
+ apr_brigade_destroy(tmp_bb);
+ r = NULL;
+ goto traceout;
}
-
- apr_brigade_destroy(tmp_bb);
- r = NULL;
- goto traceout;
}
/* We may have been in keep_alive_timeout mode, so toggle back
@@ -1101,6 +1177,7 @@ request_rec *ap_read_request(conn_rec *conn)
* now read. may update status.
*/
ap_update_vhost_from_headers(r);
+ access_status = r->status;
/* Toggle to the Host:-based vhost's timeout mode to fetch the
* request body and send the response body, if needed.
diff --git a/server/util.c b/server/util.c
index 06da789718..2fe71a13c5 100644
--- a/server/util.c
+++ b/server/util.c
@@ -2189,6 +2189,16 @@ AP_DECLARE(void) ap_bin2hex(const void *src, apr_size_t srclen, char *dest)
*dest = '\0';
}
+AP_DECLARE(int) ap_has_cntrl(const char *str)
+{
+ while (*str) {
+ if (apr_iscntrl(*str))
+ return 1;
+ str++;
+ }
+ return 0;
+}
+
AP_DECLARE(int) ap_is_directory(apr_pool_t *p, const char *path)
{
apr_finfo_t finfo;
diff --git a/server/vhost.c b/server/vhost.c
index 90ed1398e0..227081c473 100644
--- a/server/vhost.c
+++ b/server/vhost.c
@@ -747,6 +747,59 @@ static apr_status_t fix_hostname_non_v6(request_rec *r, char *host)
return APR_SUCCESS;
}
+/*
+ * If strict mode ever becomes the default, this should be folded into
+ * fix_hostname_non_v6()
+ */
+static apr_status_t strict_hostname_check(request_rec *r, char *host,
+ int logonly)
+{
+ char *ch;
+ int is_dotted_decimal = 1, leading_zeroes = 0, dots = 0;
+
+ for (ch = host; *ch; ch++) {
+ if (!apr_isascii(*ch)) {
+ goto bad;
+ }
+ else if (apr_isalpha(*ch) || *ch == '-') {
+ is_dotted_decimal = 0;
+ }
+ else if (ch[0] == '.') {
+ dots++;
+ if (ch[1] == '0' && apr_isdigit(ch[2]))
+ leading_zeroes = 1;
+ }
+ else if (!apr_isdigit(*ch)) {
+ /* also takes care of multiple Host headers by denying commas */
+ goto bad;
+ }
+ }
+ if (is_dotted_decimal) {
+ if (host[0] == '.' || (host[0] == '0' && apr_isdigit(host[1])))
+ leading_zeroes = 1;
+ if (leading_zeroes || dots != 3) {
+ /* RFC 3986 7.4 */
+ goto bad;
+ }
+ }
+ else {
+ /* The top-level domain must start with a letter (RFC 1123 2.1) */
+ while (ch > host && *ch != '.')
+ ch--;
+ if (ch[0] == '.' && ch[1] != '\0' && !apr_isalpha(ch[1]))
+ goto bad;
+ }
+ return APR_SUCCESS;
+
+bad:
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02415)
+ "[strict] Invalid host name '%s'%s%.6s",
+ host, *ch ? ", problem near: " : "", ch);
+ if (logonly)
+ return APR_SUCCESS;
+ return APR_EINVAL;
+}
+
/* Lowercase and remove any trailing dot and/or :port from the hostname,
* and check that it is sane.
*
@@ -760,22 +813,23 @@ static apr_status_t fix_hostname_non_v6(request_rec *r, char *host)
* Instead we just check for filesystem metacharacters: directory
* separators / and \ and sequences of more than one dot.
*/
-static void fix_hostname(request_rec *r, const char *host_header)
+static int fix_hostname(request_rec *r, const char *host_header,
+ unsigned http_conformance)
{
const char *src;
char *host, *scope_id;
apr_port_t port;
apr_status_t rv;
const char *c;
+ int is_v6literal = 0;
+ int strict = http_conformance & AP_HTTP_CONFORMANCE_STRICT;
+ int strict_logonly = http_conformance & AP_HTTP_CONFORMANCE_LOGONLY;
src = host_header ? host_header : r->hostname;
- /* According to RFC 2616, Host header field CAN be blank.
- * XXX But only 'if the requested URI does not include an Internet host
- * XXX name'. Can this happen?
- */
+ /* According to RFC 2616, Host header field CAN be blank */
if (!*src) {
- return;
+ return is_v6literal;
}
/* apr_parse_addr_port will interpret a bare integer as a port
@@ -784,8 +838,16 @@ static void fix_hostname(request_rec *r, const char *host_header)
for (c = src; apr_isdigit(*c); ++c);
if (!*c) {
/* pure integer */
+ if (strict) {
+ /* RFC 3986 7.4 */
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02416)
+ "[strict] purely numeric host names not allowed: %s",
+ src);
+ if (!strict_logonly)
+ goto bad_nolog;
+ }
r->hostname = src;
- return;
+ return is_v6literal;
}
if (host_header) {
@@ -802,9 +864,7 @@ static void fix_hostname(request_rec *r, const char *host_header)
r->parsed_uri.port_str = apr_itoa(r->pool, (int)port);
}
if (host_header[0] == '[')
- rv = fix_hostname_v6_literal(r, host);
- else
- rv = fix_hostname_non_v6(r, host);
+ is_v6literal = 1;
}
else {
/*
@@ -813,24 +873,32 @@ static void fix_hostname(request_rec *r, const char *host_header)
*/
host = apr_pstrdup(r->pool, r->hostname);
if (ap_strchr(host, ':') != NULL)
- rv = fix_hostname_v6_literal(r, host);
- else
- rv = fix_hostname_non_v6(r, host);
+ is_v6literal = 1;
+ }
+
+ if (is_v6literal) {
+ rv = fix_hostname_v6_literal(r, host);
+ }
+ else {
+ rv = fix_hostname_non_v6(r, host);
+ if (strict && rv == APR_SUCCESS)
+ rv = strict_hostname_check(r, host, strict_logonly);
}
if (rv != APR_SUCCESS)
goto bad;
+
r->hostname = host;
- return;
+ return is_v6literal;
bad:
- r->status = HTTP_BAD_REQUEST;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00550)
"Client sent malformed Host header: %s",
- r->hostname);
- return;
+ src);
+bad_nolog:
+ r->status = HTTP_BAD_REQUEST;
+ return is_v6literal;
}
-
/* return 1 if host matches ServerName or ServerAliases */
static int matches_aliases(server_rec *s, const char *host)
{
@@ -1040,23 +1108,79 @@ static void check_serverpath(request_rec *r)
}
}
+static APR_INLINE const char *construct_host_header(request_rec *r,
+ int is_v6literal)
+{
+ struct iovec iov[5];
+ apr_size_t nvec = 0;
+ /*
+ * We cannot use ap_get_server_name/port here, because we must
+ * ignore UseCanonicalName/Port.
+ */
+ if (is_v6literal) {
+ iov[nvec].iov_base = "[";
+ iov[nvec].iov_len = 1;
+ nvec++;
+ }
+ iov[nvec].iov_base = (void *)r->hostname;
+ iov[nvec].iov_len = strlen(r->hostname);
+ nvec++;
+ if (is_v6literal) {
+ iov[nvec].iov_base = "]";
+ iov[nvec].iov_len = 1;
+ nvec++;
+ }
+ if (r->parsed_uri.port_str) {
+ iov[nvec].iov_base = ":";
+ iov[nvec].iov_len = 1;
+ nvec++;
+ iov[nvec].iov_base = r->parsed_uri.port_str;
+ iov[nvec].iov_len = strlen(r->parsed_uri.port_str);
+ nvec++;
+ }
+ return apr_pstrcatv(r->pool, iov, nvec, NULL);
+}
AP_DECLARE(void) ap_update_vhost_from_headers(request_rec *r)
{
- const char *host_header;
+ core_server_config *conf = ap_get_core_module_config(r->server->module_config);
+ const char *host_header = apr_table_get(r->headers_in, "Host");
+ int is_v6literal, have_hostname_from_url = 0;
if (r->hostname) {
/*
* If there was a host part in the Request-URI, ignore the 'Host'
* header.
*/
- fix_hostname(r, NULL);
+ have_hostname_from_url = 1;
+ is_v6literal = fix_hostname(r, NULL, conf->http_conformance);
}
- else if ((host_header = apr_table_get(r->headers_in, "Host")) != NULL ) {
- fix_hostname(r, host_header);
+ else if (host_header != NULL) {
+ is_v6literal = fix_hostname(r, host_header, conf->http_conformance);
}
if (r->status != HTTP_OK)
return;
+
+ if (conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT) {
+ /*
+ * If we have both hostname from an absoluteURI and a Host header,
+ * we must ignore the Host header (RFC 2616 5.2).
+ * To enforce this, we reset the Host header to the value from the
+ * request line.
+ */
+ if (have_hostname_from_url && host_header != NULL) {
+ const char *info = "Would replace";
+ const char *new = construct_host_header(r, is_v6literal);
+ if (!(conf->http_conformance & AP_HTTP_CONFORMANCE_LOGONLY)) {
+ apr_table_set(r->headers_in, "Host", r->hostname);
+ info = "Replacing";
+ }
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02417)
+ "%s Host header '%s' with host from request uri: "
+ "'%s'", info, host_header, new);
+ }
+ }
+
/* check if we tucked away a name_chain */
if (r->connection->vhost_lookup_data) {
if (r->hostname)