| /* 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. |
| */ |
| |
| /* |
| * Originally written @ BBC by Graham Leggett |
| * (C) 2011 British Broadcasting Corporation |
| */ |
| |
| /* |
| * mod_policy.c --- Enforce specific policies on outgoing requests, logging |
| * or rejecting requests as appropriate. |
| * |
| * To enable, add the corresponding filters like so: |
| * |
| * SetOutputFilter POLICY_TYPE,POLICY_LENGTH |
| * |
| */ |
| |
| #include "util_filter.h" |
| #include "httpd.h" |
| #include "http_config.h" |
| #include "http_core.h" |
| #include "http_log.h" |
| #include "http_protocol.h" |
| |
| #include "apr_tables.h" |
| #include "apr_strings.h" |
| #include "apr_date.h" |
| |
| module AP_MODULE_DECLARE_DATA policy_module; |
| |
| #define POLICY_DEFAULT_TYPE "*/*" |
| |
| typedef enum policy_result |
| { |
| policy_ignore = 0, /* ignore this policy */ |
| policy_log, /* log the violation as a warning, but let it through */ |
| policy_enforce /* log the violation as an error, and decline */ |
| } policy_result; |
| |
| typedef struct policy_conf |
| { |
| int policy; /* whether the filters should do anything at all */ |
| int policy_set; |
| const char *environment; /* optional name of the subprocess environment variable that |
| * controls whether the policies are enforced. |
| */ |
| const char *environment_log; /* value to trigger logging only */ |
| const char *environment_ignore; /* value to suspend policy enforcement */ |
| int environment_set; |
| policy_result type_action; |
| apr_array_header_t *type_matches; /* content type default patterns to match */ |
| int type_set; |
| const char *type_url; |
| int type_url_set; |
| policy_result length_action; |
| int length_set; |
| const char *length_url; |
| int length_url_set; |
| policy_result keepalive_action; |
| int keepalive_set; |
| const char *keepalive_url; |
| int keepalive_url_set; |
| policy_result vary_action; |
| apr_array_header_t *vary_matches; /* Vary default patterns to match */ |
| int vary_set; |
| const char *vary_url; |
| int vary_url_set; |
| policy_result validation_action; |
| int validation_set; |
| const char *validation_url; |
| int validation_url_set; |
| policy_result conditional_action; |
| int conditional_set; |
| const char *conditional_url; |
| int conditional_url_set; |
| policy_result nocache_action; |
| int nocache_set; |
| const char *nocache_url; |
| int nocache_url_set; |
| policy_result maxage_action; |
| apr_int64_t maxage; |
| int maxage_set; |
| const char *maxage_url; |
| int maxage_url_set; |
| policy_result version_action; |
| const char *version; |
| int version_num; |
| int version_set; |
| const char *version_url; |
| int version_url_set; |
| } policy_conf; |
| |
| /** |
| * Does the value of a flagpole override the original value? |
| */ |
| static int check_enabled(request_rec *r, policy_conf *conf, |
| policy_result result) |
| { |
| if (conf && !conf->policy) { |
| return policy_ignore; |
| } |
| if (conf && result != policy_ignore && conf->environment) { |
| const char *value = apr_table_get(r->subprocess_env, conf->environment); |
| if (value) { |
| /* downgrade enforce to log? */ |
| if (conf->environment_log && !strcmp(value, conf->environment_log)) { |
| if (result == policy_enforce) { |
| return policy_log; |
| } |
| } |
| /* downgrade enforce and log to ignore? */ |
| else if (conf->environment_ignore && !strcmp(value, |
| conf->environment_ignore)) { |
| return policy_ignore; |
| } |
| } |
| } |
| return result; |
| } |
| |
| static void handle_policy(request_rec *r, policy_result result, |
| const char *message, const char *url, apr_bucket_brigade *bb, |
| int status) |
| { |
| apr_bucket *e; |
| |
| switch (result) { |
| case policy_log: { |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(03041) |
| "mod_policy: violation: %s, uri: %s", |
| message, r->uri); |
| apr_table_addn(r->headers_out, "Warning", apr_psprintf(r->pool, |
| "299 %s \"%s\"", ap_get_server_name(r), message)); |
| break; |
| } |
| case policy_enforce: { |
| |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(03042) |
| "mod_policy: violation, rejecting request: %s, uri: %s", |
| message, r->uri); |
| apr_table_addn(r->err_headers_out, "Warning", apr_psprintf(r->pool, |
| "299 %s \"Rejected: %s\"", ap_get_server_name(r), message)); |
| apr_table_setn(r->notes, "error-notes", |
| url ? apr_pstrcat(r->pool, "<a href=\"", url, "\">", |
| message, "</a>", NULL) |
| : message); |
| |
| apr_brigade_cleanup(bb); |
| e = ap_bucket_error_create(status, NULL, r->pool, |
| r->connection->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(bb, e); |
| e = apr_bucket_eos_create(r->connection->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(bb, e); |
| |
| } |
| case policy_ignore: { |
| } |
| } |
| |
| } |
| |
| /** |
| * Policy for Content-Type. |
| * |
| * - It must be present. |
| * - It must match the optional regex (default .* / .*) |
| */ |
| static apr_status_t policy_type_out_filter(ap_filter_t *f, |
| apr_bucket_brigade *bb) |
| { |
| |
| policy_conf *conf = ap_get_module_config(f->r->per_dir_config, |
| &policy_module); |
| policy_result result = check_enabled(f->r, conf, conf->type_action); |
| |
| if (result != policy_ignore) { |
| int fail = 1; |
| |
| /* content type present and valid? */ |
| if (f->r->content_type) { |
| const char *type = f->r->content_type; |
| const char *end = ap_strchr_c(type, ';'); |
| if (end) { |
| type = apr_pstrmemdup(f->r->pool, type, end - type); |
| } |
| if (!conf->type_matches) { |
| if (!ap_strcmp_match(type, POLICY_DEFAULT_TYPE)) { |
| fail = 0; |
| } |
| } |
| else { |
| int i; |
| for (i = 0; i < conf->type_matches->nelts; i++) { |
| if (!ap_strcmp_match(type, |
| ((char **) conf->type_matches->elts)[i])) { |
| fail = 0; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (fail) { |
| const char *types = NULL; |
| if (conf->type_matches) { |
| int i; |
| for (i = 0; i < conf->type_matches->nelts; i++) { |
| types = apr_pstrcat(f->r->pool, types ? ", " : "", |
| ((char **) conf->type_matches->elts)[i], NULL); |
| } |
| } |
| else { |
| types = POLICY_DEFAULT_TYPE; |
| } |
| |
| handle_policy( |
| f->r, |
| result, |
| apr_psprintf( |
| f->r->pool, |
| "Content-Type of '%s' should be RFC compliant and match one of: %s", |
| f->r->content_type, types), conf->type_url, bb, HTTP_BAD_GATEWAY); |
| |
| } |
| |
| } |
| |
| ap_remove_output_filter(f); |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| /** |
| * Policy for Content-Length. |
| * |
| * - It must be present (missing, or Transfer-Encoding: chunked would be rejected) |
| * - Only applies to 2xx result codes |
| */ |
| static apr_status_t policy_length_out_filter(ap_filter_t *f, |
| apr_bucket_brigade *bb) |
| { |
| request_rec *r = f->r; |
| |
| policy_conf *conf = ap_get_module_config(r->per_dir_config, |
| &policy_module); |
| policy_result result = check_enabled(r, conf, conf->length_action); |
| |
| if (result != policy_ignore && r->status >= 200 && r->status < 300 |
| && r->status != HTTP_NO_CONTENT) { |
| |
| if (!apr_table_get(r->headers_out, "Content-Length")) { |
| |
| handle_policy(r, result, "Content-Length should be present", |
| conf->length_url, bb, HTTP_BAD_GATEWAY); |
| |
| } |
| |
| } |
| |
| ap_remove_output_filter(f); |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| /** |
| * Policy for Content-Length / Chunked Encoding. |
| * |
| * We follow a subset of the algorithm httpd uses, which is: |
| * |
| * IF we have not marked this connection as errored; |
| * and the client isn't expecting 100-continue (PR47087 - more |
| * input here could be the client continuing when we're |
| * closing the request). |
| * and the response status does not require a close; |
| * and the response body has a defined length due to the status code |
| * being 304 or 204, the request method being HEAD, already |
| * having defined Content-Length or Transfer-Encoding: chunked, or |
| * the request version being HTTP/1.1 and thus capable of being set |
| * as chunked |
| * THEN we support keepalive. |
| * |
| * Note: The server may choose to turn off keepalive for various reasons, |
| * such as an imminent shutdown, or a Connection: close from the client, |
| * but for our purposes we only care that keepalive was possible from |
| * the application, not that keepalive actually took place. |
| */ |
| static apr_status_t policy_keepalive_out_filter(ap_filter_t *f, |
| apr_bucket_brigade *bb) |
| { |
| request_rec *r = f->r; |
| |
| policy_conf *conf = ap_get_module_config(r->per_dir_config, |
| &policy_module); |
| policy_result result = check_enabled(r, conf, conf->keepalive_action); |
| |
| if (result != policy_ignore && r->connection->keepalive != AP_CONN_CLOSE |
| && !r->expecting_100 && !ap_status_drops_connection(r->status)) { |
| |
| if (!((r->status == HTTP_NOT_MODIFIED) |
| || (r->status == HTTP_NO_CONTENT) |
| || r->header_only |
| || apr_table_get(r->headers_out, "Content-Length") |
| || ap_find_last_token(r->pool, apr_table_get(r->headers_out, |
| "Transfer-Encoding"), "chunked") |
| || r->proto_num >= HTTP_VERSION(1, 1))) { |
| |
| handle_policy(r, result, "Keepalive should be possible (supply Content-Length or HTTP/1.1 Transfer-Encoding)", |
| conf->keepalive_url, bb, HTTP_BAD_GATEWAY); |
| |
| } |
| |
| } |
| |
| ap_remove_output_filter(f); |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| static int vary_test(void *rec, const char *key, const char *value) |
| { |
| request_rec *r = (request_rec *)rec; |
| char *token = apr_pstrdup(r->pool, value); |
| char *last; |
| |
| policy_conf *conf = ap_get_module_config(r->per_dir_config, |
| &policy_module); |
| |
| token = apr_strtok(token, ", \t", &last); |
| while (token) { |
| int i; |
| for (i = 0; i < conf->vary_matches->nelts; i++) { |
| if (!ap_strcasecmp_match(token, |
| ((char **) conf->vary_matches->elts)[i])) { |
| return 0; |
| } |
| } |
| |
| token = apr_strtok(NULL, ", \t", &last); |
| } |
| return 1; |
| } |
| |
| /** |
| * Policy for Vary. |
| * |
| * - If an element matches the optional regex (no default), the request is rejected. |
| * Typically used to reject Varying on User-Agent. |
| */ |
| static apr_status_t policy_vary_out_filter(ap_filter_t *f, |
| apr_bucket_brigade *bb) |
| { |
| |
| policy_conf *conf = ap_get_module_config(f->r->per_dir_config, |
| &policy_module); |
| policy_result result = check_enabled(f->r, conf, conf->vary_action); |
| |
| if (result != policy_ignore) { |
| |
| /* Vary present and valid? */ |
| if (!apr_table_do(vary_test, f->r, f->r->headers_out, "Vary", NULL)) { |
| const char *varys = NULL; |
| if (conf->vary_matches) { |
| int i; |
| for (i = 0; i < conf->vary_matches->nelts; i++) { |
| varys = apr_pstrcat(f->r->pool, varys ? ", " : "", |
| ((char **) conf->vary_matches->elts)[i], NULL); |
| } |
| } |
| |
| handle_policy(f->r, result, apr_psprintf(f->r->pool, |
| "Vary header(s) should NOT match any of: %s", varys), |
| conf->vary_url, bb, HTTP_BAD_GATEWAY); |
| |
| } |
| |
| } |
| |
| ap_remove_output_filter(f); |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| /** |
| * Policy for Validation. |
| * |
| * Validation is possible through either the ETag or Last-Modified header, as described |
| * in http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3. |
| * |
| * - Either must be present |
| * - Last-Modified, if present, must parse to a valid date |
| * - ETag, if present, must parse to a valid ETag. |
| */ |
| static apr_status_t policy_validation_out_filter(ap_filter_t *f, |
| apr_bucket_brigade *bb) |
| { |
| |
| policy_conf *conf = ap_get_module_config(f->r->per_dir_config, |
| &policy_module); |
| policy_result result = check_enabled(f->r, conf, conf->validation_action); |
| |
| if (result != policy_ignore) { |
| int fail = 1, etagfail = 0, lmfail = 0; |
| const char *etag = apr_table_get(f->r->headers_out, "ETag"); |
| const char *lastmodified = apr_table_get(f->r->headers_out, |
| "Last-Modified"); |
| |
| if (etag) { |
| int len = strlen(etag); |
| if (len > 1) { |
| if (etag[0] == '\"' && etag[len - 1] == '\"') { |
| fail = 0; |
| } |
| else if (etag[0] == 'W' && etag[1] == '/' && etag[2] == '\"' |
| && etag[len - 1] == '\"') { |
| fail = 0; |
| } |
| } |
| if (fail) { |
| etagfail = 1; |
| } |
| } |
| |
| if (lastmodified) { |
| apr_time_t lastmod = apr_date_parse_http(lastmodified); |
| if (lastmod != APR_DATE_BAD) { |
| fail = 0; |
| } |
| if (fail) { |
| lmfail = 1; |
| } |
| } |
| |
| if (fail) { |
| const char *error = NULL; |
| if (!etag && !lastmodified) { |
| error = apr_psprintf(f->r->pool, |
| "Etag and Last Modified missing"); |
| } |
| else { |
| error = apr_pstrcat(f->r->pool, |
| (etagfail ? "ETag syntax error (check quotes)" : ""), |
| (etagfail && lmfail ? " / " : ""), |
| (lmfail ? "Last-Modified could not be parsed" : ""), |
| NULL); |
| } |
| |
| handle_policy(f->r, result, error, conf->validation_url, bb, |
| HTTP_BAD_GATEWAY); |
| |
| } |
| |
| } |
| |
| ap_remove_output_filter(f); |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| /** |
| * Policy for Revalidation through Conditional Requests. |
| * |
| * The If-None-Match header is described in |
| * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26. Over and above the |
| * checks done in the validation filter, the conditional filter detects when an |
| * If-None-Match header is present in the request, an ETag is present in the response, |
| * and the response code is unexpected given the match. A result code is unexpected |
| * where a 304 Not Modified or 412 Precondition Failed was expected, but a 200 response |
| * was seen instead. |
| */ |
| static apr_status_t policy_conditional_out_filter(ap_filter_t *f, |
| apr_bucket_brigade *bb) |
| { |
| |
| policy_conf *conf = ap_get_module_config(f->r->per_dir_config, |
| &policy_module); |
| policy_result result = check_enabled(f->r, conf, conf->conditional_action); |
| |
| if (result != policy_ignore) { |
| int code = ap_meets_conditions(f->r); |
| |
| if (OK != code && code != f->r->status) { |
| |
| handle_policy( |
| f->r, |
| result, |
| apr_psprintf( |
| f->r->pool, |
| "Conditional request should have returned %d, instead returned %d", |
| code, f->r->status), conf->conditional_url, bb, |
| HTTP_BAD_GATEWAY); |
| |
| } |
| |
| } |
| |
| ap_remove_output_filter(f); |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| /** |
| * Policy for No-Cache Requests. |
| * |
| * If the Cache-Control and/or Pragma header specifies that the content is not |
| * cacheable, the request will be rejected. |
| * |
| * - If Cache-Control: no-cache |
| * - If Pragma: no-cache |
| * - If Cache-Control: no-store |
| * - If Cache-Control: private |
| */ |
| static apr_status_t policy_nocache_out_filter(ap_filter_t *f, |
| apr_bucket_brigade *bb) |
| { |
| |
| policy_conf *conf = ap_get_module_config(f->r->per_dir_config, |
| &policy_module); |
| policy_result result = check_enabled(f->r, conf, conf->nocache_action); |
| |
| if (result != policy_ignore) { |
| request_rec *r = f->r; |
| const char *cc_header = apr_table_get(r->headers_out, "Cache-Control"); |
| const char *pragma_header = apr_table_get(r->headers_out, "Pragma"); |
| int fail = 0; |
| char *last; |
| |
| if (pragma_header) { |
| char *header = apr_pstrdup(r->pool, pragma_header); |
| const char *token = apr_strtok(header, ", ", &last); |
| while (token) { |
| if (!ap_cstr_casecmp(token, "no-cache")) { |
| fail = 1; |
| } |
| token = apr_strtok(NULL, ", ", &last); |
| } |
| } |
| |
| if (cc_header) { |
| char *header = apr_pstrdup(r->pool, cc_header); |
| const char *token = apr_strtok(header, ", ", &last); |
| while (token) { |
| switch (token[0]) { |
| case 'n': |
| case 'N': { |
| if (!ap_cstr_casecmpn(token, "no-cache", 8)) { |
| if (token[8] == '=') { |
| } |
| else if (!token[8]) { |
| fail = 1; |
| } |
| break; |
| } |
| else if (!ap_cstr_casecmp(token, "no-store")) { |
| fail = 1; |
| } |
| break; |
| } |
| case 'p': |
| case 'P': { |
| if (!ap_cstr_casecmpn(token, "private", 7)) { |
| if (token[7] == '=') { |
| } |
| else if (!token[7]) { |
| fail = 1; |
| } |
| break; |
| } |
| break; |
| } |
| } |
| token = apr_strtok(NULL, ", ", &last); |
| } |
| } |
| |
| if (fail) { |
| |
| handle_policy(r, result, "Response is marked uncacheable", |
| conf->nocache_url, bb, HTTP_BAD_GATEWAY); |
| |
| } |
| |
| } |
| |
| ap_remove_output_filter(f); |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| /** |
| * Policy for Maxage. |
| * |
| * If the effective maxage of the request is less than the parameter provided, |
| * the request will be rejected. |
| * |
| * - If Cache-Control: s-maxage is less than the limit |
| * - If Cache-Control: maxage is less than the limit |
| * - If Expires - Date is less than the limit |
| * - If none of the above, reject the request, as maxage is heuristic |
| * |
| * As soon as a test passes, we stop, as HTTP maxage handling follows a given |
| * set of priorities (s-maxage beats maxage, maxage beats Expires). |
| */ |
| static apr_status_t policy_maxage_out_filter(ap_filter_t *f, |
| apr_bucket_brigade *bb) |
| { |
| |
| policy_conf *conf = ap_get_module_config(f->r->per_dir_config, |
| &policy_module); |
| policy_result result = check_enabled(f->r, conf, conf->maxage_action); |
| |
| if (result != policy_ignore) { |
| request_rec *r = f->r; |
| const char *cc_header; |
| const char *expires_header; |
| const char *date_header; |
| char *last; |
| |
| int max_age = 0; |
| apr_int64_t max_age_value = 0; |
| int s_maxage = 0; |
| apr_int64_t s_maxage_value = 0; |
| |
| /* parse Cache-Control */ |
| cc_header = apr_table_get(r->headers_out, "Cache-Control"); |
| if (cc_header) { |
| char *header = apr_pstrdup(r->pool, cc_header); |
| const char *token = apr_strtok(header, ", ", &last); |
| while (token) { |
| switch (token[0]) { |
| case 'm': |
| case 'M': { |
| if (!ap_cstr_casecmpn(token, "max-age", 7)) { |
| if (token[7] == '=') { |
| max_age = 1; |
| max_age_value = apr_atoi64(token + 8); |
| } |
| break; |
| } |
| break; |
| } |
| case 's': |
| case 'S': { |
| if (!ap_cstr_casecmpn(token, "s-maxage", 8)) { |
| if (token[8] == '=') { |
| s_maxage = 1; |
| s_maxage_value = apr_atoi64(token + 9); |
| } |
| break; |
| } |
| break; |
| } |
| } |
| token = apr_strtok(NULL, ", ", &last); |
| } |
| } |
| |
| /* test s-maxage, if present */ |
| if (s_maxage) { |
| if (s_maxage_value < conf->maxage) { |
| |
| handle_policy( |
| f->r, |
| result, |
| apr_psprintf( |
| f->r->pool, |
| "Response s-maxage of %" APR_INT64_T_FMT " must be at least %" APR_INT64_T_FMT, |
| s_maxage_value, conf->maxage), conf->maxage_url, |
| bb, HTTP_BAD_GATEWAY); |
| |
| } |
| |
| /* decision is made, leave */ |
| ap_remove_output_filter(f); |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| /* test max-age, if present */ |
| if (max_age) { |
| if (max_age_value < conf->maxage) { |
| |
| handle_policy( |
| f->r, |
| result, |
| apr_psprintf( |
| f->r->pool, |
| "Response max-age of %" APR_INT64_T_FMT " must be at least %" APR_INT64_T_FMT, |
| max_age_value, conf->maxage), conf->maxage_url, |
| bb, HTTP_BAD_GATEWAY); |
| |
| } |
| |
| /* decision is made, leave */ |
| ap_remove_output_filter(f); |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| /* test expires, if present */ |
| expires_header = apr_table_get(r->headers_out, "Expires"); |
| date_header = apr_table_get(r->headers_out, "Date"); |
| if (expires_header && date_header) { |
| apr_time_t expires = apr_date_parse_http(expires_header); |
| apr_time_t date = apr_date_parse_http(date_header); |
| apr_int64_t fresh = apr_time_sec(expires - date); |
| |
| if (expires == APR_DATE_BAD) { |
| |
| handle_policy( |
| f->r, |
| result, |
| apr_psprintf( |
| f->r->pool, |
| "Response Expires of '%s' is invalid, maxage %" APR_INT64_T_FMT " required", |
| expires_header, conf->maxage), |
| conf->maxage_url, bb, HTTP_BAD_GATEWAY); |
| |
| } |
| |
| else if (date == APR_DATE_BAD) { |
| |
| handle_policy( |
| f->r, |
| result, |
| apr_psprintf( |
| f->r->pool, |
| "Response Date of '%s' is invalid, maxage %" APR_INT64_T_FMT " required", |
| date_header, conf->maxage), |
| conf->maxage_url, bb, HTTP_BAD_GATEWAY); |
| |
| } |
| |
| else if (conf->maxage > 0 && fresh < conf->maxage) { |
| |
| handle_policy( |
| f->r, |
| result, |
| apr_psprintf( |
| f->r->pool, |
| "Response expires in %" APR_INT64_T_FMT " seconds, must be at least %" APR_INT64_T_FMT, |
| fresh, conf->maxage), |
| conf->maxage_url, bb, HTTP_BAD_GATEWAY); |
| |
| } |
| |
| /* decision is made, leave */ |
| ap_remove_output_filter(f); |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| /* no explicit maxage defined, so fail */ |
| handle_policy(r, result, "Response has no explicit freshness lifetime (s-maxage, max-age or Expires/Date)", |
| conf->maxage_url, bb, HTTP_BAD_GATEWAY); |
| |
| } |
| |
| ap_remove_output_filter(f); |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| static const char *version_string(int proto_num) |
| { |
| switch (proto_num) { |
| case HTTP_VERSION(0, 9): { |
| return "HTTP/0.9"; |
| } |
| case HTTP_VERSION(1, 0): { |
| return "HTTP/1.0"; |
| } |
| case HTTP_VERSION(1, 1): { |
| return "HTTP/1.1"; |
| } |
| default: { |
| return "(unknown)"; |
| } |
| } |
| } |
| |
| /** |
| * Policy for HTTP Version. |
| * |
| * - The HTTP version of the response must be at least the level specified. |
| */ |
| static apr_status_t policy_version_out_filter(ap_filter_t *f, |
| apr_bucket_brigade *bb) |
| { |
| request_rec *r = f->r; |
| |
| policy_conf *conf = ap_get_module_config(r->per_dir_config, |
| &policy_module); |
| policy_result result = check_enabled(r, conf, conf->version_action); |
| |
| if (result != policy_ignore) { |
| |
| if (r->proto_num > 0 && r->proto_num < conf->version_num) { |
| |
| handle_policy(f->r, result, apr_psprintf(f->r->pool, |
| "Request HTTP version '%s' should be at least '%s'", |
| version_string(r->proto_num), conf->version), |
| conf->version_url, bb, HTTP_VERSION_NOT_SUPPORTED); |
| |
| } |
| |
| } |
| |
| ap_remove_output_filter(f); |
| return ap_pass_brigade(f->next, bb); |
| } |
| |
| static void *create_policy_config(apr_pool_t *p, char *dummy) |
| { |
| policy_conf *new = (policy_conf *) apr_pcalloc(p, sizeof(policy_conf)); |
| |
| new->policy = 1; |
| new->type_action = policy_log; |
| new->length_action = policy_log; |
| new->vary_action = policy_log; |
| new->validation_action = policy_log; |
| new->conditional_action = policy_log; |
| new->nocache_action = policy_log; |
| new->maxage_action = policy_log; |
| new->version_action = policy_log; |
| new->version_num = HTTP_VERSION(0, 9); |
| new->version = "HTTP/0.9"; |
| |
| return (void *) new; |
| } |
| |
| static void *merge_policy_config(apr_pool_t *p, void *basev, void *addv) |
| { |
| policy_conf *new = (policy_conf *) apr_pcalloc(p, sizeof(policy_conf)); |
| policy_conf *add = (policy_conf *) addv; |
| policy_conf *base = (policy_conf *) basev; |
| |
| new->policy = (add->policy_set == 0) ? base->policy : add->policy; |
| new->policy_set = add->policy_set || base->policy_set; |
| new->environment = (add->environment_set == 0) ? base->environment : add->environment; |
| new->environment_log = (add->environment_set == 0) ? base->environment_log |
| : add->environment_log; |
| new->environment_ignore = (add->environment_set == 0) ? base->environment_ignore |
| : add->environment_ignore; |
| new->environment_set = add->environment_set || base->environment_set; |
| new->type_action = (add->type_set == 0) ? base->type_action |
| : add->type_action; |
| new->type_matches = (add->type_set == 0) ? base->type_matches |
| : add->type_matches; |
| new->type_set = add->type_set || base->type_set; |
| new->type_url = (add->type_url_set == 0) ? base->type_url : add->type_url; |
| new->type_url_set = add->type_url_set || base->type_url_set; |
| new->length_action = (add->length_set == 0) ? base->length_action |
| : add->length_action; |
| new->length_set = add->length_set || base->length_set; |
| new->length_url = (add->length_url_set == 0) ? base->length_url |
| : add->length_url; |
| new->length_url_set = add->length_url_set || base->length_url_set; |
| new->vary_action = (add->vary_set == 0) ? base->vary_action |
| : add->vary_action; |
| new->vary_matches = (add->vary_set == 0) ? base->vary_matches |
| : add->vary_matches; |
| new->vary_set = add->vary_set || base->vary_set; |
| new->vary_url = (add->vary_url_set == 0) ? base->vary_url : add->vary_url; |
| new->vary_url_set = add->vary_url_set || base->vary_url_set; |
| new->validation_action |
| = (add->validation_set == 0) ? base->validation_action |
| : add->validation_action; |
| new->validation_set = add->validation_set || base->validation_set; |
| new->validation_url = (add->validation_url_set == 0) ? base->validation_url |
| : add->validation_url; |
| new->validation_url_set = add->validation_url_set |
| || base->validation_url_set; |
| new->conditional_action |
| = (add->conditional_set == 0) ? base->conditional_action |
| : add->conditional_action; |
| new->conditional_set = add->conditional_set || base->conditional_set; |
| new->conditional_url |
| = (add->conditional_url_set == 0) ? base->conditional_url |
| : add->conditional_url; |
| new->conditional_url_set = add->conditional_url_set |
| || base->conditional_url_set; |
| new->nocache_action = (add->nocache_set == 0) ? base->nocache_action |
| : add->nocache_action; |
| new->nocache_set = add->nocache_set || base->nocache_set; |
| new->nocache_url = (add->nocache_url_set == 0) ? base->nocache_url |
| : add->nocache_url; |
| new->nocache_url_set = add->nocache_url_set || base->nocache_url_set; |
| new->maxage_action = (add->maxage_set == 0) ? base->maxage_action |
| : add->maxage_action; |
| new->maxage = (add->maxage_set == 0) ? base->maxage : add->maxage; |
| new->maxage_set = add->maxage_set || base->maxage_set; |
| new->maxage_url = (add->maxage_url_set == 0) ? base->maxage_url |
| : add->maxage_url; |
| new->maxage_url_set = add->maxage_url_set || base->maxage_url_set; |
| new->version_action = (add->version_set == 0) ? base->version_action |
| : add->version_action; |
| new->version = (add->version_set == 0) ? base->version : add->version; |
| new->version_num = (add->version_set == 0) ? base->version_num : add->version_num; |
| new->version_set = add->version_set || base->version_set; |
| new->version_url = (add->version_url_set == 0) ? base->version_url |
| : add->version_url; |
| new->version_url_set = add->version_url_set || base->version_url_set; |
| |
| |
| return new; |
| } |
| |
| static const char *parse_action(apr_pool_t *pool, const char *action, |
| policy_result *result) |
| { |
| if (!strcmp(action, "enforce")) { |
| *result = policy_enforce; |
| } |
| else if (!strcmp(action, "log")) { |
| *result = policy_log; |
| } |
| else if (!strcmp(action, "ignore")) { |
| *result = policy_ignore; |
| } |
| else { |
| return apr_psprintf(pool, |
| "'%s' must be one of 'enforce, 'log' or 'ignore'.", action); |
| } |
| return NULL; |
| } |
| |
| static const char *set_policy(cmd_parms *cmd, void *dconf, int flag) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->policy = flag; |
| conf->policy_set = 1; |
| |
| return NULL; |
| } |
| |
| static const char *set_environment(cmd_parms *cmd, void *dconf, |
| const char *environment, const char *log, const char *ignore) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->environment = environment; |
| conf->environment_log = log; |
| conf->environment_ignore = ignore; |
| conf->environment_set = 1; |
| |
| return NULL; |
| } |
| |
| static const char *set_type(cmd_parms *cmd, void *dconf, const char *action, |
| const char *type) |
| { |
| policy_conf *conf = dconf; |
| |
| if (type) { |
| const char **match_ptr; |
| if (!conf->type_matches) { |
| conf->type_matches = apr_array_make(cmd->pool, 2, |
| sizeof(const char *)); |
| } |
| match_ptr = apr_array_push(conf->type_matches); |
| *match_ptr = type; |
| } |
| conf->type_set = 1; |
| |
| return parse_action(cmd->pool, action, &conf->type_action); |
| } |
| |
| static const char *set_type_url(cmd_parms *cmd, void *dconf, const char *url) |
| { |
| policy_conf *conf = dconf; |
| |
| /* url is only used inside <a href="...">, escape accordingly */ |
| conf->type_url = ap_escape_html(cmd->pool, url); |
| conf->type_url_set = 1; |
| |
| return NULL; |
| } |
| |
| static const char *set_length(cmd_parms *cmd, void *dconf, const char *action) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->length_set = 1; |
| |
| return parse_action(cmd->pool, action, &conf->length_action); |
| } |
| |
| static const char *set_length_url(cmd_parms *cmd, void *dconf, const char *url) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->length_url = url; |
| conf->length_url_set = 1; |
| |
| return NULL; |
| } |
| |
| static const char *set_keepalive(cmd_parms *cmd, void *dconf, const char *action) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->keepalive_set = 1; |
| |
| return parse_action(cmd->pool, action, &conf->keepalive_action); |
| } |
| |
| static const char *set_keepalive_url(cmd_parms *cmd, void *dconf, const char *url) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->keepalive_url = url; |
| conf->keepalive_url_set = 1; |
| |
| return NULL; |
| } |
| |
| static const char *set_vary(cmd_parms *cmd, void *dconf, const char *action, |
| const char *vary) |
| { |
| policy_conf *conf = dconf; |
| |
| if (vary) { |
| const char **match_ptr; |
| if (!conf->vary_matches) { |
| conf->vary_matches = apr_array_make(cmd->pool, 2, |
| sizeof(const char *)); |
| } |
| match_ptr = apr_array_push(conf->vary_matches); |
| *match_ptr = vary; |
| } |
| conf->vary_set = 1; |
| |
| return parse_action(cmd->pool, action, &conf->vary_action); |
| } |
| |
| static const char *set_vary_url(cmd_parms *cmd, void *dconf, const char *url) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->vary_url = url; |
| conf->vary_url_set = 1; |
| |
| return NULL; |
| } |
| |
| static const char *set_validation(cmd_parms *cmd, void *dconf, |
| const char *action) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->validation_set = 1; |
| |
| return parse_action(cmd->pool, action, &conf->validation_action); |
| } |
| |
| static const char *set_validation_url(cmd_parms *cmd, void *dconf, |
| const char *url) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->validation_url = url; |
| conf->validation_url_set = 1; |
| |
| return NULL; |
| } |
| |
| static const char *set_conditional(cmd_parms *cmd, void *dconf, |
| const char *action) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->conditional_set = 1; |
| |
| return parse_action(cmd->pool, action, &conf->conditional_action); |
| } |
| |
| static const char *set_conditional_url(cmd_parms *cmd, void *dconf, |
| const char *url) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->conditional_url = url; |
| conf->conditional_url_set = 1; |
| |
| return NULL; |
| } |
| |
| static const char *set_nocache(cmd_parms *cmd, void *dconf, const char *action) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->nocache_set = 1; |
| |
| return parse_action(cmd->pool, action, &conf->nocache_action); |
| } |
| |
| static const char *set_nocache_url(cmd_parms *cmd, void *dconf, const char *url) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->nocache_url = url; |
| conf->nocache_url_set = 1; |
| |
| return NULL; |
| } |
| |
| static const char *set_maxage(cmd_parms *cmd, void *dconf, const char *action, const char *maxage) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->maxage_set = 1; |
| conf->maxage = apr_atoi64(maxage); |
| if (conf->maxage < 0) { |
| return apr_psprintf(cmd->pool, |
| "'%s' must be a positive integer.", maxage); |
| } |
| |
| return parse_action(cmd->pool, action, &conf->maxage_action); |
| } |
| |
| static const char *set_maxage_url(cmd_parms *cmd, void *dconf, const char *url) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->maxage_url = url; |
| conf->maxage_url_set = 1; |
| |
| return NULL; |
| } |
| |
| static const char *set_version(cmd_parms *cmd, void *dconf, const char *action, const char *version) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->version_set = 1; |
| |
| if (!strcmp(version, "HTTP/1.1")) { |
| conf->version = "HTTP/1.1"; |
| conf->version_num = HTTP_VERSION(1, 1); |
| } |
| else if (!strcmp(version, "HTTP/1.0")) { |
| conf->version = "HTTP/1.0"; |
| conf->version_num = HTTP_VERSION(1, 0); |
| } |
| else if (!strcmp(version, "HTTP/0.9")) { |
| conf->version = "HTTP/0.9"; |
| conf->version_num = HTTP_VERSION(0, 9); |
| } |
| else { |
| return apr_psprintf(cmd->pool, |
| "'%s' must be one of 'HTTP/1.1', 'HTTP/1.0' or 'HTTP/0.9'.", version); |
| } |
| |
| return parse_action(cmd->pool, action, &conf->version_action); |
| } |
| |
| static const char *set_version_url(cmd_parms *cmd, void *dconf, const char *url) |
| { |
| policy_conf *conf = dconf; |
| |
| conf->version_url = url; |
| conf->version_url_set = 1; |
| |
| return NULL; |
| } |
| |
| static const command_rec |
| policy_cmds[] = |
| { |
| AP_INIT_FLAG("PolicyFilter", set_policy, NULL, RSRC_CONF |
| | ACCESS_CONF, |
| "Whether policies should be applied. Defaults to 'on'."), |
| AP_INIT_TAKE1( |
| "PolicyConditional", |
| set_conditional, |
| NULL, |
| RSRC_CONF | ACCESS_CONF, |
| "Action to take (enforce, ignore, log) if a conditional request was not honoured. Defaults to 'log'."), |
| AP_INIT_TAKE1("PolicyConditionalURL", set_conditional_url, |
| NULL, RSRC_CONF | ACCESS_CONF, |
| "URL describing the conditional request policy."), |
| AP_INIT_TAKE3( |
| "PolicyEnvironment", |
| set_environment, |
| NULL, |
| RSRC_CONF | ACCESS_CONF, |
| "Environment variable to control policy enforcement, followed by the variable value for logging only, and the value for policy suspension."), |
| AP_INIT_TAKE1( |
| "PolicyLength", |
| set_length, |
| NULL, |
| RSRC_CONF | ACCESS_CONF, |
| "Action to take (enforce, ignore, log) if Content-Length missing. Defaults to 'log'."), |
| AP_INIT_TAKE1("PolicyLengthURL", set_length_url, NULL, |
| RSRC_CONF | ACCESS_CONF, |
| "URL describing the content length policy."), |
| AP_INIT_TAKE1( |
| "PolicyKeepalive", |
| set_keepalive, |
| NULL, |
| RSRC_CONF | ACCESS_CONF, |
| "Action to take (enforce, ignore, log) if keepalive is not possible. Defaults to 'log'."), |
| AP_INIT_TAKE1("PolicyKeepaliveURL", set_keepalive_url, NULL, |
| RSRC_CONF | ACCESS_CONF, |
| "URL describing the keepalive policy."), |
| AP_INIT_ITERATE2( |
| "PolicyType", |
| set_type, |
| NULL, |
| RSRC_CONF | ACCESS_CONF, |
| "Action to take (enforce, ignore, log), followed by one or more valid content types containing optional wildcards ? and *"), |
| AP_INIT_TAKE1("PolicyTypeURL", set_type_url, NULL, RSRC_CONF |
| | ACCESS_CONF, |
| "URL describing the content type policy."), |
| AP_INIT_ITERATE2( |
| "PolicyVary", |
| set_vary, |
| NULL, |
| RSRC_CONF | ACCESS_CONF, |
| "Action to take (enforce, ignore, log), followed by one or more headers containing optional wildcards ? and * that are NOT to appear in a Vary header"), |
| AP_INIT_TAKE1("PolicyVaryURL", set_vary_url, NULL, RSRC_CONF |
| | ACCESS_CONF, |
| "URL describing the vary header policy."), |
| AP_INIT_TAKE1( |
| "PolicyValidation", |
| set_validation, |
| NULL, |
| RSRC_CONF | ACCESS_CONF, |
| "Action to take (enforce, ignore, log) if Last-Modified or Etag is missing or invalid. Defaults to 'log'."), |
| AP_INIT_TAKE1("PolicyValidationURL", set_validation_url, NULL, |
| RSRC_CONF | ACCESS_CONF, |
| "URL describing the content validation policy."), |
| AP_INIT_TAKE1( |
| "PolicyNocache", |
| set_nocache, |
| NULL, |
| RSRC_CONF | ACCESS_CONF, |
| "Action to take (enforce, ignore, log) if a response is not cacheable. Defaults to 'log'."), |
| AP_INIT_TAKE1("PolicyNocacheURL", set_nocache_url, NULL, |
| RSRC_CONF | ACCESS_CONF, |
| "URL describing the no cache policy."), |
| AP_INIT_TAKE2( |
| "PolicyMaxage", |
| set_maxage, |
| NULL, |
| RSRC_CONF | ACCESS_CONF, |
| "Action to take (enforce, ignore, log) if a response has an effective maxage less than the age provided. Defaults to 'log'."), |
| AP_INIT_TAKE1("PolicyMaxageURL", set_maxage_url, NULL, |
| RSRC_CONF | ACCESS_CONF, |
| "URL describing the maxage policy."), |
| AP_INIT_TAKE2( |
| "PolicyVersion", |
| set_version, |
| NULL, |
| RSRC_CONF | ACCESS_CONF, |
| "Action to take (enforce, ignore, log) if a response has an HTTP version less than the version provided. Defaults to 'log HTTP/0.9'."), |
| AP_INIT_TAKE1("PolicyVersionURL", set_version_url, NULL, |
| RSRC_CONF | ACCESS_CONF, |
| "URL describing the version policy."), |
| { NULL } }; |
| |
| static void register_hooks(apr_pool_t *p) |
| { |
| ap_register_output_filter("POLICY_TYPE", policy_type_out_filter, NULL, |
| AP_FTYPE_CONTENT_SET + 5); |
| ap_register_output_filter("POLICY_LENGTH", policy_length_out_filter, NULL, |
| AP_FTYPE_CONTENT_SET + 5); |
| ap_register_output_filter("POLICY_KEEPALIVE", policy_keepalive_out_filter, NULL, |
| AP_FTYPE_CONTENT_SET + 5); |
| ap_register_output_filter("POLICY_VARY", policy_vary_out_filter, NULL, |
| AP_FTYPE_CONTENT_SET + 5); |
| ap_register_output_filter("POLICY_VALIDATION", |
| policy_validation_out_filter, NULL, AP_FTYPE_CONTENT_SET + 5); |
| ap_register_output_filter("POLICY_CONDITIONAL", |
| policy_conditional_out_filter, NULL, AP_FTYPE_CONTENT_SET + 5); |
| ap_register_output_filter("POLICY_NOCACHE", policy_nocache_out_filter, |
| NULL, AP_FTYPE_CONTENT_SET + 5); |
| ap_register_output_filter("POLICY_MAXAGE", policy_maxage_out_filter, |
| NULL, AP_FTYPE_CONTENT_SET + 5); |
| ap_register_output_filter("POLICY_VERSION", policy_version_out_filter, |
| NULL, AP_FTYPE_CONTENT_SET + 5); |
| } |
| |
| AP_DECLARE_MODULE(policy) = |
| { |
| STANDARD20_MODULE_STUFF, create_policy_config, /* create per-directory config structure */ |
| merge_policy_config, /* merge per-directory config structures */ |
| NULL, /* create per-server config structure */ |
| NULL, /* merge per-server config structures */ |
| policy_cmds, /* command apr_table_t */ |
| register_hooks /* register hooks */ |
| }; |