diff options
| author | Stefan Fritsch <sf@sfritsch.de> | 2011-12-27 19:42:53 +0100 |
|---|---|---|
| committer | Stefan Fritsch <sf@sfritsch.de> | 2011-12-27 19:42:53 +0100 |
| commit | db26b587c04799e75b6dd0fcd4b46aaa168f9161 (patch) | |
| tree | 127af2f77fd3eddb75604ebecedeeea163325078 /modules | |
| parent | d9f98b967bedecc0bffe82682d1ed4e06c9df687 (diff) | |
| download | apache2-db26b587c04799e75b6dd0fcd4b46aaa168f9161.tar.gz | |
Upstream tarball 2.2.15upstream/2.2.15
Diffstat (limited to 'modules')
52 files changed, 1824 insertions, 325 deletions
diff --git a/modules/aaa/mod_authnz_ldap.c b/modules/aaa/mod_authnz_ldap.c index 9b61022a..bb08d20f 100644 --- a/modules/aaa/mod_authnz_ldap.c +++ b/modules/aaa/mod_authnz_ldap.c @@ -63,6 +63,7 @@ typedef struct { deref_options deref; /* how to handle alias dereferening */ char *binddn; /* DN to bind to server (can be NULL) */ char *bindpw; /* Password to bind to server (can be NULL) */ + int bind_authoritative; /* If true, will return errors when bind fails */ int user_is_dn; /* If true, connection->user is DN instead of userid */ char *remote_user_attribute; /* If set, connection->user is this attribute instead of userid */ @@ -294,6 +295,7 @@ static void *create_authnz_ldap_dir_config(apr_pool_t *p, char *d) sec->host = NULL; sec->binddn = NULL; sec->bindpw = NULL; + sec->bind_authoritative = 1; sec->deref = always; sec->group_attrib_is_dn = 1; sec->auth_authoritative = 1; @@ -409,7 +411,15 @@ start_over: /* handle bind failure */ if (result != LDAP_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + if (!sec->bind_authoritative) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "[%" APR_PID_T_FMT "] auth_ldap authenticate: " + "user %s authentication failed; URI %s [%s][%s] (not authoritative)", + getpid(), user, r->uri, ldc->reason, ldap_err2string(result)); + return AUTH_USER_NOT_FOUND; + } + + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "[%" APR_PID_T_FMT "] auth_ldap authenticate: " "user %s authentication failed; URI %s [%s][%s]", getpid(), user, r->uri, ldc->reason, ldap_err2string(result)); @@ -1065,6 +1075,10 @@ static const command_rec authnz_ldap_cmds[] = (void *)APR_OFFSETOF(authn_ldap_config_t, bindpw), OR_AUTHCFG, "Password to use to bind to LDAP server. If not provided, will do an anonymous bind."), + AP_INIT_FLAG("AuthLDAPBindAuthoritative", ap_set_flag_slot, + (void *)APR_OFFSETOF(authn_ldap_config_t, bind_authoritative), OR_AUTHCFG, + "Set to 'on' to return failures when user-specific bind fails - defaults to on."), + AP_INIT_FLAG("AuthLDAPRemoteUserIsDN", ap_set_flag_slot, (void *)APR_OFFSETOF(authn_ldap_config_t, user_is_dn), OR_AUTHCFG, "Set to 'on' to set the REMOTE_USER environment variable to be the full " diff --git a/modules/arch/win32/mod_isapi.c b/modules/arch/win32/mod_isapi.c index bfc26aad..ec0c800d 100644 --- a/modules/arch/win32/mod_isapi.c +++ b/modules/arch/win32/mod_isapi.c @@ -1503,7 +1503,6 @@ apr_status_t isapi_handler (request_rec *r) /* Set up client input */ res = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR); if (res) { - isapi_unload(isa, 0); return res; } @@ -1534,7 +1533,6 @@ apr_status_t isapi_handler (request_rec *r) } if (res < 0) { - isapi_unload(isa, 0); return HTTP_INTERNAL_SERVER_ERROR; } diff --git a/modules/cache/NWGNUdsk_cach b/modules/cache/NWGNUdsk_cach index b91823dd..a2fb7d06 100644 --- a/modules/cache/NWGNUdsk_cach +++ b/modules/cache/NWGNUdsk_cach @@ -26,9 +26,7 @@ XINCDIRS += \ $(APR)/include \ $(APRUTIL)/include \ $(AP_WORK)/include \ - $(AP_WORK)/os/NetWare \ $(AP_WORK)/server/mpm/NetWare \ - $(AP_WORK)/srclib/pcre \ $(NWOS) \ $(EOLIST) diff --git a/modules/cache/NWGNUmem_cach b/modules/cache/NWGNUmem_cach index bcf98e7f..19e329a7 100644 --- a/modules/cache/NWGNUmem_cach +++ b/modules/cache/NWGNUmem_cach @@ -26,9 +26,7 @@ XINCDIRS += \ $(APR)/include \ $(APRUTIL)/include \ $(AP_WORK)/include \ - $(AP_WORK)/os/NetWare \ $(AP_WORK)/server/mpm/NetWare \ - $(AP_WORK)/srclib/pcre \ $(NWOS) \ $(EOLIST) diff --git a/modules/cache/NWGNUmod_cach b/modules/cache/NWGNUmod_cach index ff0a9667..424da522 100644 --- a/modules/cache/NWGNUmod_cach +++ b/modules/cache/NWGNUmod_cach @@ -26,9 +26,7 @@ XINCDIRS += \ $(APR)/include \ $(APRUTIL)/include \ $(AP_WORK)/include \ - $(AP_WORK)/os/NetWare \ $(AP_WORK)/server/mpm/NetWare \ - $(AP_WORK)/srclib/pcre \ $(NWOS) \ $(EOLIST) diff --git a/modules/cache/cache_storage.c b/modules/cache/cache_storage.c index 1af26d77..2fbadc8b 100644 --- a/modules/cache/cache_storage.c +++ b/modules/cache/cache_storage.c @@ -37,7 +37,7 @@ int cache_remove_url(cache_request_rec *cache, apr_pool_t *p) /* Remove the stale cache entry if present. If not, we're * being called from outside of a request; remove the - * non-stalle handle. + * non-stale handle. */ h = cache->stale_handle ? cache->stale_handle : cache->handle; if (!h) { diff --git a/modules/cache/cache_util.c b/modules/cache/cache_util.c index ec2d749b..cb3d5653 100644 --- a/modules/cache/cache_util.c +++ b/modules/cache/cache_util.c @@ -22,6 +22,8 @@ /* -------------------------------------------------------------- */ +extern APR_OPTIONAL_FN_TYPE(ap_cache_generate_key) *cache_generate_key; + extern module AP_MODULE_DECLARE_DATA cache_module; /* Determine if "url" matches the hostname, scheme and port and path @@ -164,9 +166,182 @@ CACHE_DECLARE(apr_int64_t) ap_cache_current_age(cache_info *info, return apr_time_sec(current_age); } +/** + * Try obtain a cache wide lock on the given cache key. + * + * If we return APR_SUCCESS, we obtained the lock, and we are clear to + * proceed to the backend. If we return APR_EEXISTS, then the lock is + * already locked, someone else has gone to refresh the backend data + * already, so we must return stale data with a warning in the mean + * time. If we return anything else, then something has gone pear + * shaped, and we allow the request through to the backend regardless. + * + * This lock is created from the request pool, meaning that should + * something go wrong and the lock isn't deleted on return of the + * request headers from the backend for whatever reason, at worst the + * lock will be cleaned up when the request dies or finishes. + * + * If something goes truly bananas and the lock isn't deleted when the + * request dies, the lock will be trashed when its max-age is reached, + * or when a request arrives containing a Cache-Control: no-cache. At + * no point is it possible for this lock to permanently deny access to + * the backend. + */ +CACHE_DECLARE(apr_status_t) ap_cache_try_lock(cache_server_conf *conf, + request_rec *r, char *key) { + apr_status_t status; + const char *lockname; + const char *path; + char dir[5]; + apr_time_t now = apr_time_now(); + apr_finfo_t finfo; + apr_file_t *lockfile; + void *dummy; + + finfo.mtime = 0; + + if (!conf || !conf->lock || !conf->lockpath) { + /* no locks configured, leave */ + return APR_SUCCESS; + } + + /* lock already obtained earlier? if so, success */ + apr_pool_userdata_get(&dummy, CACHE_LOCKFILE_KEY, r->pool); + if (dummy) { + return APR_SUCCESS; + } + + /* create the key if it doesn't exist */ + if (!key) { + cache_generate_key(r, r->pool, &key); + } + + /* create a hashed filename from the key, and save it for later */ + lockname = ap_cache_generate_name(r->pool, 0, 0, key); + + /* lock files represent discrete just-went-stale URLs "in flight", so + * we support a simple two level directory structure, more is overkill. + */ + dir[0] = '/'; + dir[1] = lockname[0]; + dir[2] = '/'; + dir[3] = lockname[1]; + dir[4] = 0; + + /* make the directories */ + path = apr_pstrcat(r->pool, conf->lockpath, dir, NULL); + if (APR_SUCCESS != (status = apr_dir_make_recursive(path, + APR_UREAD|APR_UWRITE|APR_UEXECUTE, r->pool))) { + ap_log_error(APLOG_MARK, APLOG_ERR, status, r->server, + "Could not create a cache lock directory: %s", + path); + return status; + } + lockname = apr_pstrcat(r->pool, path, "/", lockname, NULL); + apr_pool_userdata_set(lockname, CACHE_LOCKNAME_KEY, NULL, r->pool); + + /* is an existing lock file too old? */ + status = apr_stat(&finfo, lockname, + APR_FINFO_MTIME | APR_FINFO_NLINK, r->pool); + if (!(APR_STATUS_IS_ENOENT(status)) && APR_SUCCESS != status) { + ap_log_error(APLOG_MARK, APLOG_ERR, APR_EEXIST, r->server, + "Could not stat a cache lock file: %s", + lockname); + return status; + } + if ((status == APR_SUCCESS) && (((now - finfo.mtime) > conf->lockmaxage) + || (now < finfo.mtime))) { + ap_log_error(APLOG_MARK, APLOG_INFO, status, r->server, + "Cache lock file for '%s' too old, removing: %s", + r->uri, lockname); + apr_file_remove(lockname, r->pool); + } + + /* try obtain a lock on the file */ + if (APR_SUCCESS == (status = apr_file_open(&lockfile, lockname, + APR_WRITE | APR_CREATE | APR_EXCL | APR_DELONCLOSE, + APR_UREAD | APR_UWRITE, r->pool))) { + apr_pool_userdata_set(lockfile, CACHE_LOCKFILE_KEY, NULL, r->pool); + } + return status; + +} + +/** + * Remove the cache lock, if present. + * + * First, try to close the file handle, whose delete-on-close should + * kill the file. Otherwise, just delete the file by name. + * + * If no lock name has yet been calculated, do the calculation of the + * lock name first before trying to delete the file. + * + * If an optional bucket brigade is passed, the lock will only be + * removed if the bucket brigade contains an EOS bucket. + */ +CACHE_DECLARE(apr_status_t) ap_cache_remove_lock(cache_server_conf *conf, + request_rec *r, char *key, apr_bucket_brigade *bb) { + void *dummy; + const char *lockname; + + if (!conf || !conf->lock || !conf->lockpath) { + /* no locks configured, leave */ + return APR_SUCCESS; + } + if (bb) { + apr_bucket *e; + int eos_found = 0; + for (e = APR_BRIGADE_FIRST(bb); + e != APR_BRIGADE_SENTINEL(bb); + e = APR_BUCKET_NEXT(e)) + { + if (APR_BUCKET_IS_EOS(e)) { + eos_found = 1; + break; + } + } + if (!eos_found) { + /* no eos found in brigade, don't delete anything just yet, + * we are not done. + */ + return APR_SUCCESS; + } + } + apr_pool_userdata_get(&dummy, CACHE_LOCKFILE_KEY, r->pool); + if (dummy) { + return apr_file_close((apr_file_t *)dummy); + } + apr_pool_userdata_get(&dummy, CACHE_LOCKNAME_KEY, r->pool); + lockname = (const char *)dummy; + if (!lockname) { + char dir[5]; + + /* create the key if it doesn't exist */ + if (!key) { + cache_generate_key(r, r->pool, &key); + } + + /* create a hashed filename from the key, and save it for later */ + lockname = ap_cache_generate_name(r->pool, 0, 0, key); + + /* lock files represent discrete just-went-stale URLs "in flight", so + * we support a simple two level directory structure, more is overkill. + */ + dir[0] = '/'; + dir[1] = lockname[0]; + dir[2] = '/'; + dir[3] = lockname[1]; + dir[4] = 0; + + lockname = apr_pstrcat(r->pool, conf->lockpath, dir, "/", lockname, NULL); + } + return apr_file_remove(lockname, r->pool); +} + CACHE_DECLARE(int) ap_cache_check_freshness(cache_handle_t *h, request_rec *r) { + apr_status_t status; apr_int64_t age, maxage_req, maxage_cresp, maxage, smaxage, maxstale; apr_int64_t minfresh; const char *cc_cresp, *cc_req; @@ -176,6 +351,7 @@ CACHE_DECLARE(int) ap_cache_check_freshness(cache_handle_t *h, char *val; apr_time_t age_c = 0; cache_info *info = &(h->cache_obj->info); + const char *warn_head; cache_server_conf *conf = (cache_server_conf *)ap_get_module_config(r->server->module_config, &cache_module); @@ -338,7 +514,6 @@ CACHE_DECLARE(int) ap_cache_check_freshness(cache_handle_t *h, ((smaxage == -1) && (maxage == -1) && (info->expire != APR_DATE_BAD) && (age < (apr_time_sec(info->expire - info->date) + maxstale - minfresh)))) { - const char *warn_head; warn_head = apr_table_get(h->resp_hdrs, "Warning"); @@ -361,7 +536,7 @@ CACHE_DECLARE(int) ap_cache_check_freshness(cache_handle_t *h, } /* * If none of Expires, Cache-Control: max-age, or Cache-Control: - * s-maxage appears in the response, and the respose header age + * s-maxage appears in the response, and the response header age * calculated is more than 24 hours add the warning 113 */ if ((maxage_cresp == -1) && (smaxage == -1) && @@ -380,7 +555,71 @@ CACHE_DECLARE(int) ap_cache_check_freshness(cache_handle_t *h, return 1; /* Cache object is fresh (enough) */ } - return 0; /* Cache object is stale */ + /* + * At this point we are stale, but: if we are under load, we may let + * a significant number of stale requests through before the first + * stale request successfully revalidates itself, causing a sudden + * unexpected thundering herd which in turn brings angst and drama. + * + * So. + * + * We want the first stale request to go through as normal. But the + * second and subsequent request, we must pretend to be fresh until + * the first request comes back with either new content or confirmation + * that the stale content is still fresh. + * + * To achieve this, we create a very simple file based lock based on + * the key of the cached object. We attempt to open the lock file with + * exclusive write access. If we succeed, woohoo! we're first, and we + * follow the stale path to the backend server. If we fail, oh well, + * we follow the fresh path, and avoid being a thundering herd. + * + * The lock lives only as long as the stale request that went on ahead. + * If the request succeeds, the lock is deleted. If the request fails, + * the lock is deleted, and another request gets to make a new lock + * and try again. + * + * At any time, a request marked "no-cache" will force a refresh, + * ignoring the lock, ensuring an extended lockout is impossible. + * + * A lock that exceeds a maximum age will be deleted, and another + * request gets to make a new lock and try again. + */ + status = ap_cache_try_lock(conf, r, (char *)h->cache_obj->key); + if (APR_SUCCESS == status) { + /* we obtained a lock, follow the stale path */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "Cache lock obtained for stale cached URL, " + "revalidating entry: %s", + r->unparsed_uri); + return 0; + } + else if (APR_EEXIST == status) { + /* lock already exists, return stale data anyway, with a warning */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "Cache already locked for stale cached URL, " + "pretend it is fresh: %s", + r->unparsed_uri); + + /* make sure we don't stomp on a previous warning */ + warn_head = apr_table_get(h->resp_hdrs, "Warning"); + if ((warn_head == NULL) || + ((warn_head != NULL) && (ap_strstr_c(warn_head, "110") == NULL))) { + apr_table_merge(h->resp_hdrs, "Warning", + "110 Response is stale"); + } + + return 1; + } + else { + /* some other error occurred, just treat the object as stale */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, status, r->server, + "Attempt to obtain a cache lock for stale " + "cached URL failed, revalidating entry anyway: %s", + r->unparsed_uri); + return 0; + } + } /* diff --git a/modules/cache/mod_cache.c b/modules/cache/mod_cache.c index 49a37423..162773f9 100644 --- a/modules/cache/mod_cache.c +++ b/modules/cache/mod_cache.c @@ -114,40 +114,56 @@ static int cache_url_handler(request_rec *r, int lookup) if (rv == DECLINED) { if (!lookup) { - /* - * Add cache_save filter to cache this request. Choose - * the correct filter by checking if we are a subrequest - * or not. + /* try to obtain a cache lock at this point. if we succeed, + * we are the first to try and cache this url. if we fail, + * it means someone else is already trying to cache this + * url, and we should just let the request through to the + * backend without any attempt to cache. this stops + * duplicated simultaneous attempts to cache an entity. */ - if (r->main) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, - r->server, - "Adding CACHE_SAVE_SUBREQ filter for %s", - r->uri); - ap_add_output_filter_handle(cache_save_subreq_filter_handle, - NULL, r, r->connection); + rv = ap_cache_try_lock(conf, r, NULL); + if (APR_SUCCESS == rv) { + + /* + * Add cache_save filter to cache this request. Choose + * the correct filter by checking if we are a subrequest + * or not. + */ + if (r->main) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, + r->server, + "Adding CACHE_SAVE_SUBREQ filter for %s", + r->uri); + ap_add_output_filter_handle(cache_save_subreq_filter_handle, + NULL, r, r->connection); + } + else { + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, + r->server, "Adding CACHE_SAVE filter for %s", + r->uri); + ap_add_output_filter_handle(cache_save_filter_handle, + NULL, r, r->connection); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server, + "Adding CACHE_REMOVE_URL filter for %s", + r->uri); + + /* Add cache_remove_url filter to this request to remove a + * stale cache entry if needed. Also put the current cache + * request rec in the filter context, as the request that + * is available later during running the filter maybe + * different due to an internal redirect. + */ + cache->remove_url_filter = + ap_add_output_filter_handle(cache_remove_url_filter_handle, + cache, r, r->connection); } else { - ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, - r->server, "Adding CACHE_SAVE filter for %s", - r->uri); - ap_add_output_filter_handle(cache_save_filter_handle, - NULL, r, r->connection); + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, + r->server, "Cache locked for url, not caching " + "response: %s", r->uri); } - - ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server, - "Adding CACHE_REMOVE_URL filter for %s", - r->uri); - - /* Add cache_remove_url filter to this request to remove a - * stale cache entry if needed. Also put the current cache - * request rec in the filter context, as the request that - * is available later during running the filter maybe - * different due to an internal redirect. - */ - cache->remove_url_filter = - ap_add_output_filter_handle(cache_remove_url_filter_handle, - cache, r, r->connection); } else { if (cache->stale_headers) { @@ -166,7 +182,7 @@ static int cache_url_handler(request_rec *r, int lookup) /* error */ ap_log_error(APLOG_MARK, APLOG_ERR, rv, r->server, "cache: error returned while checking for cached " - "file by %s cache", cache->provider_name); + "file by '%s' cache", cache->provider_name); } return DECLINED; } @@ -313,6 +329,10 @@ static int cache_out_filter(ap_filter_t *f, apr_bucket_brigade *bb) * Check to see if we *can* save this particular response. * If we can, call cache_create_entity() and save the headers and body * Finally, pass the data to the next filter (the network or whatever) + * + * After the various failure cases, the cache lock is proactively removed, so + * that another request is given the opportunity to attempt to cache without + * waiting for a potentially slow client to acknowledge the failure. */ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) @@ -328,6 +348,7 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) cache_info *info = NULL; char *reason; apr_pool_t *p; + apr_bucket *e; conf = (cache_server_conf *) ap_get_module_config(r->server->module_config, &cache_module); @@ -370,7 +391,19 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server, "cache: Cache provider's store_body failed!"); ap_remove_output_filter(f); + + /* give someone else the chance to cache the file */ + ap_cache_remove_lock(conf, r, cache->handle ? + (char *)cache->handle->cache_obj->key : NULL, NULL); } + else { + + /* proactively remove the lock as soon as we see the eos bucket */ + ap_cache_remove_lock(conf, r, cache->handle ? + (char *)cache->handle->cache_obj->key : NULL, in); + + } + return ap_pass_brigade(f->next, in); } @@ -441,17 +474,17 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) * telling us to serve the cached copy. */ if (exps != NULL || cc_out != NULL) { - /* We are also allowed to cache any response given that it has a - * valid Expires or Cache Control header. If we find a either of - * those here, we pass request through the rest of the tests. From + /* We are also allowed to cache any response given that it has a + * valid Expires or Cache Control header. If we find a either of + * those here, we pass request through the rest of the tests. From * the RFC: * - * A response received with any other status code (e.g. status - * codes 302 and 307) MUST NOT be returned in a reply to a - * subsequent request unless there are cache-control directives or - * another header(s) that explicitly allow it. For example, these - * include the following: an Expires header (section 14.21); a - * "max-age", "s-maxage", "must-revalidate", "proxy-revalidate", + * A response received with any other status code (e.g. status + * codes 302 and 307) MUST NOT be returned in a reply to a + * subsequent request unless there are cache-control directives or + * another header(s) that explicitly allow it. For example, these + * include the following: an Expires header (section 14.21); a + * "max-age", "s-maxage", "must-revalidate", "proxy-revalidate", * "public" or "private" cache-control directive (section 14.9). */ } @@ -473,7 +506,8 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) reason = "Expires header already expired, not cacheable"; } else if (!conf->ignorequerystring && r->parsed_uri.query && exps == NULL && - !ap_cache_liststr(NULL, cc_out, "max-age", NULL)) { + !ap_cache_liststr(NULL, cc_out, "max-age", NULL) && + !ap_cache_liststr(NULL, cc_out, "s-maxage", NULL)) { /* if a query string is present but no explicit expiration time, * don't cache it (RFC 2616/13.9 & 13.2.1) */ @@ -487,14 +521,17 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) reason = "HTTP Status 304 Not Modified"; } else if (r->status == HTTP_OK && lastmods == NULL && etag == NULL - && (exps == NULL) && (conf->no_last_mod_ignore ==0)) { + && (exps == NULL) && (conf->no_last_mod_ignore ==0) && + !ap_cache_liststr(NULL, cc_out, "max-age", NULL) && + !ap_cache_liststr(NULL, cc_out, "s-maxage", NULL)) { /* 200 OK response from HTTP/1.0 and up without Last-Modified, - * Etag, or Expires headers. + * Etag, Expires, Cache-Control:max-age, or Cache-Control:s-maxage + * headers. */ /* Note: mod-include clears last_modified/expires/etags - this * is why we have an optional function for a key-gen ;-) */ - reason = "No Last-Modified, Etag, or Expires headers"; + reason = "No Last-Modified, Etag, Expires, Cache-Control:max-age or Cache-Control:s-maxage headers"; } else if (r->header_only && !cache->stale_handle) { /* Forbid HEAD requests unless we have it cached already */ @@ -540,7 +577,7 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) "*", NULL)) { reason = "Vary header contains '*'"; } - else if (apr_table_get(r->subprocess_env, "no-cache") != NULL) { + else if (apr_table_get(r->subprocess_env, "no-cache") != NULL) { reason = "environment variable 'no-cache' is set"; } else if (r->no_cache) { @@ -556,6 +593,10 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) /* remove this filter from the chain */ ap_remove_output_filter(f); + /* remove the lock file unconditionally */ + ap_cache_remove_lock(conf, r, cache->handle ? + (char *)cache->handle->cache_obj->key : NULL, NULL); + /* ship the data up the stack */ return ap_pass_brigade(f->next, in); } @@ -580,7 +621,6 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) /* if we don't get the content-length, see if we have all the * buckets and use their length to calculate the size */ - apr_bucket *e; int all_buckets_here=0; int unresolved_length = 0; size=0; @@ -657,6 +697,8 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) if (rv != OK) { /* Caching layer declined the opportunity to cache the response */ ap_remove_output_filter(f); + ap_cache_remove_lock(conf, r, cache->handle ? + (char *)cache->handle->cache_obj->key : NULL, NULL); return ap_pass_brigade(f->next, in); } @@ -845,16 +887,23 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server, "cache: attempt to remove url from cache unsuccessful."); } + } + /* let someone else attempt to cache */ + ap_cache_remove_lock(conf, r, cache->handle ? + (char *)cache->handle->cache_obj->key : NULL, NULL); + return ap_pass_brigade(f->next, bb); } - if(rv != APR_SUCCESS) { + if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server, "cache: store_headers failed"); - ap_remove_output_filter(f); + ap_remove_output_filter(f); + ap_cache_remove_lock(conf, r, cache->handle ? + (char *)cache->handle->cache_obj->key : NULL, NULL); return ap_pass_brigade(f->next, in); } @@ -863,8 +912,15 @@ static int cache_save_filter(ap_filter_t *f, apr_bucket_brigade *in) ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server, "cache: store_body failed"); ap_remove_output_filter(f); + ap_cache_remove_lock(conf, r, cache->handle ? + (char *)cache->handle->cache_obj->key : NULL, NULL); + return ap_pass_brigade(f->next, in); } + /* proactively remove the lock as soon as we see the eos bucket */ + ap_cache_remove_lock(conf, r, cache->handle ? + (char *)cache->handle->cache_obj->key : NULL, in); + return ap_pass_brigade(f->next, in); } @@ -908,6 +964,7 @@ static int cache_remove_url_filter(ap_filter_t *f, apr_bucket_brigade *in) ap_remove_output_filter(f); return ap_pass_brigade(f->next, in); } + /* Now remove this cache entry from the cache */ cache_remove_url(cache, r->pool); @@ -921,6 +978,7 @@ static int cache_remove_url_filter(ap_filter_t *f, apr_bucket_brigade *in) static void * create_cache_config(apr_pool_t *p, server_rec *s) { + const char *tmppath; cache_server_conf *ps = apr_pcalloc(p, sizeof(cache_server_conf)); /* array of URL prefixes for which caching is enabled */ @@ -953,6 +1011,13 @@ static void * create_cache_config(apr_pool_t *p, server_rec *s) /* array of identifiers that should not be used for key calculation */ ps->ignore_session_id = apr_array_make(p, 10, sizeof(char *)); ps->ignore_session_id_set = CACHE_IGNORE_SESSION_ID_UNSET; + ps->lock = 0; /* thundering herd lock defaults to off */ + ps->lock_set = 0; + apr_temp_dir_get(&tmppath, p); + if (tmppath) { + ps->lockpath = apr_pstrcat(p, tmppath, DEFAULT_CACHE_LOCKPATH, NULL); + } + ps->lockmaxage = apr_time_from_sec(DEFAULT_CACHE_MAXAGE); return ps; } @@ -1006,6 +1071,18 @@ static void * merge_cache_config(apr_pool_t *p, void *basev, void *overridesv) (overrides->ignore_session_id_set == CACHE_IGNORE_SESSION_ID_UNSET) ? base->ignore_session_id : overrides->ignore_session_id; + ps->lock = + (overrides->lock_set == 0) + ? base->lock + : overrides->lock; + ps->lockpath = + (overrides->lockpath_set == 0) + ? base->lockpath + : overrides->lockpath; + ps->lockmaxage = + (overrides->lockmaxage_set == 0) + ? base->lockmaxage + : overrides->lockmaxage; return ps; } static const char *set_cache_ignore_no_last_mod(cmd_parms *parms, void *dummy, @@ -1225,6 +1302,55 @@ static const char *set_cache_ignore_querystring(cmd_parms *parms, void *dummy, return NULL; } +static const char *set_cache_lock(cmd_parms *parms, void *dummy, + int flag) +{ + cache_server_conf *conf; + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + conf->lock = flag; + conf->lock_set = 1; + return NULL; +} + +static const char *set_cache_lock_path(cmd_parms *parms, void *dummy, + const char *arg) +{ + cache_server_conf *conf; + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + + conf->lockpath = ap_server_root_relative(parms->pool, arg); + if (!conf->lockpath) { + return apr_pstrcat(parms->pool, "Invalid CacheLockPath path ", + arg, NULL); + } + conf->lockpath_set = 1; + return NULL; +} + +static const char *set_cache_lock_maxage(cmd_parms *parms, void *dummy, + const char *arg) +{ + cache_server_conf *conf; + apr_int64_t seconds; + + conf = + (cache_server_conf *)ap_get_module_config(parms->server->module_config, + &cache_module); + seconds = apr_atoi64(arg); + if (seconds <= 0) { + return "CacheLockMaxAge value must be a non-zero positive integer"; + } + conf->lockmaxage = apr_time_from_sec(seconds); + conf->lockmaxage_set = 1; + return NULL; +} + static int cache_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { @@ -1284,6 +1410,15 @@ static const command_rec cache_cmds[] = AP_INIT_TAKE1("CacheLastModifiedFactor", set_cache_factor, NULL, RSRC_CONF, "The factor used to estimate Expires date from " "LastModified date"), + AP_INIT_FLAG("CacheLock", set_cache_lock, + NULL, RSRC_CONF, + "Enable or disable the thundering herd lock."), + AP_INIT_TAKE1("CacheLockPath", set_cache_lock_path, NULL, RSRC_CONF, + "The thundering herd lock path. Defaults to the '" + DEFAULT_CACHE_LOCKPATH "' directory in the system " + "temp directory."), + AP_INIT_TAKE1("CacheLockMaxAge", set_cache_lock_maxage, NULL, RSRC_CONF, + "Maximum age of any thundering herd lock."), {NULL} }; diff --git a/modules/cache/mod_cache.h b/modules/cache/mod_cache.h index 72653ae3..eca42c01 100644 --- a/modules/cache/mod_cache.h +++ b/modules/cache/mod_cache.h @@ -24,7 +24,7 @@ */ #ifndef MOD_CACHE_H -#define MOD_CACHE_H +#define MOD_CACHE_H #define CORE_PRIVATE @@ -86,9 +86,13 @@ #define DEFAULT_CACHE_MAXEXPIRE MSEC_ONE_DAY #define DEFAULT_CACHE_EXPIRE MSEC_ONE_HR #define DEFAULT_CACHE_LMFACTOR (0.1) +#define DEFAULT_CACHE_MAXAGE 5 +#define DEFAULT_CACHE_LOCKPATH "/mod_cache-lock" +#define CACHE_LOCKNAME_KEY "mod_cache-lockname" +#define CACHE_LOCKFILE_KEY "mod_cache-lockfile" -/* Create a set of PROXY_DECLARE(type), PROXY_DECLARE_NONSTD(type) and - * PROXY_DECLARE_DATA with appropriate export and import tags for the platform +/* Create a set of CACHE_DECLARE(type), CACHE_DECLARE_NONSTD(type) and + * CACHE_DECLARE_DATA with appropriate export and import tags for the platform */ #if !defined(WIN32) #define CACHE_DECLARE(type) type @@ -134,7 +138,7 @@ typedef struct { int factor_set; /** ignore the last-modified header when deciding to cache this request */ int no_last_mod_ignore_set; - int no_last_mod_ignore; + int no_last_mod_ignore; /** ignore client's requests for uncached responses */ int ignorecachecontrol; int ignorecachecontrol_set; @@ -159,6 +163,13 @@ typedef struct { #define CACHE_IGNORE_SESSION_ID_SET 1 #define CACHE_IGNORE_SESSION_ID_UNSET 0 int ignore_session_id_set; + /* thundering herd lock */ + int lock; + int lock_set; + const char *lockpath; + int lockpath_set; + apr_time_t lockmaxage; + int lockmaxage_set; } cache_server_conf; /* cache info information */ @@ -173,11 +184,11 @@ struct cache_info { /* cache handle information */ -/* XXX TODO On the next structure change/MMN bump, +/* XXX TODO On the next structure change/MMN bump, * count must become an apr_off_t, representing * the potential size of disk cached objects. * Then dig for - * "XXX Bad Temporary Cast - see cache_object_t notes" + * "XXX Bad Temporary Cast - see cache_object_t notes" */ typedef struct cache_object cache_object_t; struct cache_object { @@ -206,7 +217,7 @@ typedef struct { apr_status_t (*store_headers)(cache_handle_t *h, request_rec *r, cache_info *i); apr_status_t (*store_body)(cache_handle_t *h, request_rec *r, apr_bucket_brigade *b); apr_status_t (*recall_headers) (cache_handle_t *h, request_rec *r); - apr_status_t (*recall_body) (cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb); + apr_status_t (*recall_body) (cache_handle_t *h, apr_pool_t *p, apr_bucket_brigade *bb); int (*create_entity) (cache_handle_t *h, request_rec *r, const char *urlkey, apr_off_t len); int (*open_entity) (cache_handle_t *h, request_rec *r, @@ -260,6 +271,45 @@ CACHE_DECLARE(apr_time_t) ap_cache_current_age(cache_info *info, const apr_time_ CACHE_DECLARE(int) ap_cache_check_freshness(cache_handle_t *h, request_rec *r); /** + * Try obtain a cache wide lock on the given cache key. + * + * If we return APR_SUCCESS, we obtained the lock, and we are clear to + * proceed to the backend. If we return APR_EEXISTS, the the lock is + * already locked, someone else has gone to refresh the backend data + * already, so we must return stale data with a warning in the mean + * time. If we return anything else, then something has gone pear + * shaped, and we allow the request through to the backend regardless. + * + * This lock is created from the request pool, meaning that should + * something go wrong and the lock isn't deleted on return of the + * request headers from the backend for whatever reason, at worst the + * lock will be cleaned up when the request is dies or finishes. + * + * If something goes truly bananas and the lock isn't deleted when the + * request dies, the lock will be trashed when its max-age is reached, + * or when a request arrives containing a Cache-Control: no-cache. At + * no point is it possible for this lock to permanently deny access to + * the backend. + */ +CACHE_DECLARE(apr_status_t) ap_cache_try_lock(cache_server_conf *conf, + request_rec *r, char *key); + +/** + * Remove the cache lock, if present. + * + * First, try to close the file handle, whose delete-on-close should + * kill the file. Otherwise, just delete the file by name. + * + * If no lock name has yet been calculated, do the calculation of the + * lock name first before trying to delete the file. + * + * If an optional bucket brigade is passed, the lock will only be + * removed if the bucket brigade contains an EOS bucket. + */ +CACHE_DECLARE(apr_status_t) ap_cache_remove_lock(cache_server_conf *conf, + request_rec *r, char *key, apr_bucket_brigade *bb); + +/** * Merge in cached headers into the response * @param h cache_handle_t * @param r request_rec @@ -330,8 +380,8 @@ apr_status_t cache_recall_entity_body(cache_handle_t *h, apr_pool_t *p, apr_buck #define CACHE_DECLARE_DATA __declspec(dllimport) #endif -APR_DECLARE_OPTIONAL_FN(apr_status_t, - ap_cache_generate_key, +APR_DECLARE_OPTIONAL_FN(apr_status_t, + ap_cache_generate_key, (request_rec *r, apr_pool_t*p, char**key )); diff --git a/modules/cache/mod_disk_cache.c b/modules/cache/mod_disk_cache.c index 70a804b8..f13800b3 100644 --- a/modules/cache/mod_disk_cache.c +++ b/modules/cache/mod_disk_cache.c @@ -1041,6 +1041,8 @@ static apr_status_t store_body(cache_handle_t *h, request_rec *r, * sanity checks. */ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) { + const char *cl_header = apr_table_get(r->headers_out, "Content-Length"); + if (r->connection->aborted || r->no_cache) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server, "disk_cache: Discarding body for URL %s " @@ -1059,6 +1061,17 @@ static apr_status_t store_body(cache_handle_t *h, request_rec *r, file_cache_errorcleanup(dobj, r); return APR_EGENERAL; } + if (cl_header) { + apr_int64_t cl = apr_atoi64(cl_header); + if ((errno == 0) && (dobj->file_size != cl)) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "disk_cache: URL %s didn't receive complete response, not caching", + h->cache_obj->key); + /* Remove the intermediate cache file and return non-APR_SUCCESS */ + file_cache_errorcleanup(dobj, r); + return APR_EGENERAL; + } + } /* All checks were fine. Move tempfile to final destination */ /* Link to the perm file, and close the descriptor */ diff --git a/modules/cache/mod_mem_cache.c b/modules/cache/mod_mem_cache.c index 48bd785c..fb988915 100644 --- a/modules/cache/mod_mem_cache.c +++ b/modules/cache/mod_mem_cache.c @@ -745,6 +745,16 @@ static apr_status_t store_body(cache_handle_t *h, request_rec *r, apr_bucket_bri apr_size_t len; if (APR_BUCKET_IS_EOS(e)) { + const char *cl_header = apr_table_get(r->headers_out, "Content-Length"); + if (cl_header) { + apr_int64_t cl = apr_atoi64(cl_header); + if ((errno == 0) && (obj->count != cl)) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "mem_cache: URL %s didn't receive complete response, not caching", + h->cache_obj->key); + return APR_EGENERAL; + } + } if (mobj->m_len > obj->count) { /* Caching a streamed response. Reallocate a buffer of the * correct size and copy the streamed response into that diff --git a/modules/config5.m4 b/modules/config5.m4 index b8a5868b..7dd021ee 100644 --- a/modules/config5.m4 +++ b/modules/config5.m4 @@ -18,7 +18,7 @@ AC_ARG_WITH(module, fi cp $pkg $modpath_current/$modfilec fi - module=`echo $pkg | sed -e 's;\(.*/\)*mod_\(.*\).c;\2;'` + module=`echo $pkg | sed -e 's;\(.*/\).*mod_\(.*\).c;\2;'` objects="mod_$module.lo" # The filename of a convenience library must have a "lib" prefix: libname="libmod_$module.la" diff --git a/modules/database/mod_dbd.c b/modules/database/mod_dbd.c index 9914875c..cb01193f 100644 --- a/modules/database/mod_dbd.c +++ b/modules/database/mod_dbd.c @@ -641,6 +641,15 @@ static apr_status_t dbd_setup_init(apr_pool_t *pool, server_rec *s) return rv; } +static void dbd_child_init(apr_pool_t *p, server_rec *s) +{ + apr_status_t rv = dbd_setup_init(p, s); + if (rv) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, + "DBD: child init failed!"); + } +} + #if APR_HAS_THREADS static apr_status_t dbd_setup_lock(server_rec *s, dbd_group_t *group) { @@ -895,7 +904,7 @@ static void dbd_hooks(apr_pool_t *pool) { ap_hook_pre_config(dbd_pre_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_post_config(dbd_post_config, NULL, NULL, APR_HOOK_MIDDLE); - ap_hook_child_init((void*)dbd_setup_init, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_child_init(dbd_child_init, NULL, NULL, APR_HOOK_MIDDLE); APR_REGISTER_OPTIONAL_FN(ap_dbd_prepare); APR_REGISTER_OPTIONAL_FN(ap_dbd_open); diff --git a/modules/dav/fs/NWGNUmakefile b/modules/dav/fs/NWGNUmakefile index e9c27aea..f15b2dd6 100644 --- a/modules/dav/fs/NWGNUmakefile +++ b/modules/dav/fs/NWGNUmakefile @@ -27,9 +27,7 @@ XINCDIRS += \ $(APR)/include \ $(APRUTIL)/include \ $(AP_WORK)/include \ - $(AP_WORK)/os/NetWare \ $(AP_WORK)/server/mpm/NetWare \ - $(AP_WORK)/srclib/pcre \ $(AP_WORK)/modules/dav/main \ $(AP_WORK)/modules/arch/netware \ $(NWOS) \ diff --git a/modules/dav/lock/NWGNUmakefile b/modules/dav/lock/NWGNUmakefile index 66e22eae..e36a5237 100644 --- a/modules/dav/lock/NWGNUmakefile +++ b/modules/dav/lock/NWGNUmakefile @@ -27,9 +27,7 @@ XINCDIRS += \ $(APR)/include \ $(APRUTIL)/include \ $(AP_WORK)/include \ - $(AP_WORK)/os/NetWare \ $(AP_WORK)/server/mpm/NetWare \ - $(AP_WORK)/srclib/pcre \ $(AP_WORK)/modules/dav/main \ $(NWOS) \ $(EOLIST) diff --git a/modules/dav/main/NWGNUmakefile b/modules/dav/main/NWGNUmakefile index c8626342..812dc9e3 100644 --- a/modules/dav/main/NWGNUmakefile +++ b/modules/dav/main/NWGNUmakefile @@ -26,9 +26,7 @@ XINCDIRS += \ $(APR)/include \ $(APRUTIL)/include \ $(AP_WORK)/include \ - $(AP_WORK)/os/NetWare \ $(AP_WORK)/server/mpm/NetWare \ - $(AP_WORK)/srclib/pcre \ $(NWOS) \ $(EOLIST) diff --git a/modules/filters/config.m4 b/modules/filters/config.m4 index 9c1f6083..247d4f65 100644 --- a/modules/filters/config.m4 +++ b/modules/filters/config.m4 @@ -4,6 +4,7 @@ dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]]) APACHE_MODPATH_INIT(filters) +APACHE_MODULE(reqtimeout, Limit time waiting for request from client, , , most) APACHE_MODULE(ext_filter, external filter module, , , most) APACHE_MODULE(include, Server Side Includes, , , yes) APACHE_MODULE(filter, Smart Filtering, , , yes) diff --git a/modules/filters/mod_charset_lite.c b/modules/filters/mod_charset_lite.c index 2d54ff6f..76bbe551 100644 --- a/modules/filters/mod_charset_lite.c +++ b/modules/filters/mod_charset_lite.c @@ -181,7 +181,7 @@ static const char *add_charset_options(cmd_parms *cmd, void *in_dc, else if (!strcasecmp(flag, "NoImplicitAdd")) { dc->implicit_add = IA_NOIMPADD; } - if (!strcasecmp(flag, "TranslateAllMimeTypes")) { + else if (!strcasecmp(flag, "TranslateAllMimeTypes")) { dc->force_xlate = FX_FORCE; } else if (!strcasecmp(flag, "NoTranslateAllMimeTypes")) { @@ -337,6 +337,15 @@ static void xlate_insert_filter(request_rec *r) charset_dir_t *dc = ap_get_module_config(r->per_dir_config, &charset_lite_module); + if (dc && (dc->implicit_add == IA_NOIMPADD)) { + if (dc->debug >= DBGLVL_GORY) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "xlate output filter not added implicitly because " + "CharsetOptions included 'NoImplicitAdd'"); + } + return; + } + if (reqinfo) { if (reqinfo->output_ctx && !configured_on_output(r, XLATEOUT_FILTER_NAME)) { ap_add_output_filter(XLATEOUT_FILTER_NAME, reqinfo->output_ctx, r, diff --git a/modules/filters/mod_filter.c b/modules/filters/mod_filter.c index 3ba13c95..73893642 100644 --- a/modules/filters/mod_filter.c +++ b/modules/filters/mod_filter.c @@ -201,9 +201,7 @@ static int filter_lookup(ap_filter_t *f, ap_filter_rec_t *filter) * Not sure if there's anything better to do with them */ if (!str) { - if (provider->match_type == DEFINED && provider->match.string) { - match = 0; - } + match = 0; } /* we can't check for NULL in provider as that kills integer 0 * so we have to test each string/regexp case in the switch diff --git a/modules/filters/mod_include.c b/modules/filters/mod_include.c index 6ec0fa89..59ce8e3e 100644 --- a/modules/filters/mod_include.c +++ b/modules/filters/mod_include.c @@ -115,7 +115,9 @@ typedef struct { const char *default_time_fmt; const char *undefined_echo; xbithack_t xbithack; - const int accessenable; + int accessenable; + int lastmodified; + int etag; } include_dir_config; typedef struct { @@ -461,6 +463,9 @@ static const char lazy_eval_sentinel; #define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]" #define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z" #define DEFAULT_UNDEFINED_ECHO "(none)" +#define DEFAULT_ACCESSENABLE 0 +#define DEFAULT_LASTMODIFIED 0 +#define DEFAULT_ETAG 0 #ifdef XBITHACK #define DEFAULT_XBITHACK XBITHACK_FULL @@ -3529,7 +3534,9 @@ static int includes_setup(ap_filter_t *f) * We don't know if we are going to be including a file or executing * a program - in either case a strong ETag header will likely be invalid. */ - apr_table_setn(f->r->notes, "no-etag", ""); + if (!conf->etag) { + apr_table_setn(f->r->notes, "no-etag", ""); + } return OK; } @@ -3618,13 +3625,32 @@ static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b) * a program which may change the Last-Modified header or make the * content completely dynamic. Therefore, we can't support these * headers. - * Exception: XBitHack full means we *should* set the Last-Modified field. + * + * Exception: XBitHack full means we *should* set the + * Last-Modified field. + * + * SSILastModified on means we *should* set the Last-Modified field + * if not present, or respect an existing value if present. */ + /* Must we respect the last modified header? By default, no */ + if (conf->lastmodified) { + + /* update the last modified if we have a valid time, and only if + * we don't already have a valid last modified. + */ + if (r->finfo.valid & APR_FINFO_MTIME + && !apr_table_get(f->r->headers_out, "Last-Modified")) { + ap_update_mtime(r, r->finfo.mtime); + ap_set_last_modified(r); + } + + } + /* Assure the platform supports Group protections */ - if ((conf->xbithack == XBITHACK_FULL) + else if (((conf->xbithack == XBITHACK_FULL) && (r->finfo.valid & APR_FINFO_GPROT) - && (r->finfo.protection & APR_GEXECUTE)) { + && (r->finfo.protection & APR_GEXECUTE))) { ap_update_mtime(r, r->finfo.mtime); ap_set_last_modified(r); } @@ -3704,6 +3730,9 @@ static void *create_includes_dir_config(apr_pool_t *p, char *dummy) result->default_time_fmt = DEFAULT_TIME_FORMAT; result->undefined_echo = DEFAULT_UNDEFINED_ECHO; result->xbithack = DEFAULT_XBITHACK; + result->accessenable = DEFAULT_ACCESSENABLE; + result->lastmodified = DEFAULT_LASTMODIFIED; + result->etag = DEFAULT_ETAG; return result; } @@ -3856,6 +3885,14 @@ static const command_rec includes_cmds[] = AP_INIT_FLAG("SSIAccessEnable", ap_set_flag_slot, (void *)APR_OFFSETOF(include_dir_config, accessenable), OR_LIMIT, "Whether testing access is enabled. Limited to 'on' or 'off'"), + AP_INIT_FLAG("SSILastModified", ap_set_flag_slot, + (void *)APR_OFFSETOF(include_dir_config, lastmodified), + OR_LIMIT, "Whether to set the last modified header or respect " + "an existing header. Limited to 'on' or 'off'"), + AP_INIT_FLAG("SSIEtag", ap_set_flag_slot, + (void *)APR_OFFSETOF(include_dir_config, etag), + OR_LIMIT, "Whether to allow the generation of ETags within the server. " + "Existing ETags will be preserved. Limited to 'on' or 'off'"), {NULL} }; diff --git a/modules/filters/mod_reqtimeout.c b/modules/filters/mod_reqtimeout.c new file mode 100644 index 00000000..215a04c0 --- /dev/null +++ b/modules/filters/mod_reqtimeout.c @@ -0,0 +1,431 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_connection.h" +#include "http_protocol.h" +#include "http_log.h" +#include "util_filter.h" +#define APR_WANT_STRFUNC +#include "apr_strings.h" + +module AP_MODULE_DECLARE_DATA reqtimeout_module; + +typedef struct +{ + int header_timeout; /* timeout for reading the req hdrs in secs */ + int header_max_timeout; /* max timeout for req hdrs in secs */ + int header_min_rate; /* min rate for reading req hdrs in bytes/s */ + apr_time_t header_rate_factor; + int body_timeout; /* timeout for reading the req body in secs */ + int body_max_timeout; /* max timeout for req body in secs */ + int body_min_rate; /* timeout for reading the req body in secs */ + apr_time_t body_rate_factor; +} reqtimeout_srv_cfg; + +typedef struct +{ + apr_time_t timeout_at; + apr_time_t max_timeout_at; + int min_rate; + int new_timeout; + int new_max_timeout; + int in_keep_alive; + char *type; + apr_time_t rate_factor; +} reqtimeout_con_cfg; + +typedef struct +{ + apr_socket_t *socket; +} reqtimeout_ctx; + +static const char *const reqtimeout_filter_name = "reqtimeout"; + +static void extend_timeout(reqtimeout_con_cfg *ccfg, apr_bucket_brigade *bb) +{ + apr_off_t len; + apr_time_t new_timeout_at; + + if (apr_brigade_length(bb, 0, &len) != APR_SUCCESS || len <= 0) + return; + + new_timeout_at = ccfg->timeout_at + len * ccfg->rate_factor; + if (ccfg->max_timeout_at > 0 && new_timeout_at > ccfg->max_timeout_at) { + ccfg->timeout_at = ccfg->max_timeout_at; + } + else { + ccfg->timeout_at = new_timeout_at; + } +} + +static apr_status_t reqtimeout_filter(ap_filter_t *f, + apr_bucket_brigade *bb, + ap_input_mode_t mode, + apr_read_type_e block, + apr_off_t readbytes) +{ + reqtimeout_ctx *ctx; + apr_time_t time_left; + apr_time_t now; + apr_status_t rv; + apr_interval_time_t saved_sock_timeout = -1; + reqtimeout_con_cfg *ccfg; + + ctx = f->ctx; + AP_DEBUG_ASSERT(ctx != NULL); + + ccfg = ap_get_module_config(f->c->conn_config, &reqtimeout_module); + AP_DEBUG_ASSERT(ccfg != NULL); + + if (ccfg->in_keep_alive) { + /* For this read, the normal keep-alive timeout must be used */ + ccfg->in_keep_alive = 0; + return ap_get_brigade(f->next, bb, mode, block, readbytes); + } + + now = apr_time_now(); + if (ccfg->new_timeout > 0) { + /* set new timeout */ + ccfg->timeout_at = now + apr_time_from_sec(ccfg->new_timeout); + ccfg->new_timeout = 0; + if (ccfg->new_max_timeout > 0) { + ccfg->max_timeout_at = now + apr_time_from_sec(ccfg->new_max_timeout); + ccfg->new_max_timeout = 0; + } + } + else if (ccfg->timeout_at == 0) { + /* no timeout set */ + return ap_get_brigade(f->next, bb, mode, block, readbytes); + } + + time_left = ccfg->timeout_at - now; + if (time_left <= 0) { + ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c, + "Request %s read timeout", ccfg->type); + return APR_TIMEUP; + } + + if (block == APR_NONBLOCK_READ || mode == AP_MODE_INIT + || mode == AP_MODE_EATCRLF) { + rv = ap_get_brigade(f->next, bb, mode, block, readbytes); + if (ccfg->min_rate > 0 && rv == APR_SUCCESS) { + extend_timeout(ccfg, bb); + } + return rv; + } + + if (time_left < apr_time_from_sec(1)) { + time_left = apr_time_from_sec(1); + } + + rv = apr_socket_timeout_get(ctx->socket, &saved_sock_timeout); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + + if (saved_sock_timeout >= time_left) { + rv = apr_socket_timeout_set(ctx->socket, time_left); + AP_DEBUG_ASSERT(rv == APR_SUCCESS); + } + else { + saved_sock_timeout = -1; + } + + rv = ap_get_brigade(f->next, bb, mode, block, readbytes); + + if (saved_sock_timeout != -1) { + apr_socket_timeout_set(ctx->socket, saved_sock_timeout); + } + + if (ccfg->min_rate > 0 && rv == APR_SUCCESS) { + extend_timeout(ccfg, bb); + } + + if (rv == APR_TIMEUP) { + ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c, + "Request %s read timeout", ccfg->type); + } + return rv; +} + +static int reqtimeout_pre_conn(conn_rec *c, void *csd) +{ + reqtimeout_ctx *ctx; + reqtimeout_con_cfg *ccfg; + reqtimeout_srv_cfg *cfg; + + cfg = ap_get_module_config(c->base_server->module_config, + &reqtimeout_module); + AP_DEBUG_ASSERT(cfg != NULL); + if (cfg->header_timeout <= 0 && cfg->body_timeout <= 0) { + /* not configured for this vhost */ + return OK; + } + + ctx = apr_pcalloc(c->pool, sizeof(reqtimeout_ctx)); + ctx->socket = csd; + + ccfg = apr_pcalloc(c->pool, sizeof(reqtimeout_con_cfg)); + ccfg->new_timeout = cfg->header_timeout; + ccfg->new_max_timeout = cfg->header_max_timeout; + ccfg->type = "header"; + ccfg->min_rate = cfg->header_min_rate; + ccfg->rate_factor = cfg->header_rate_factor; + ap_set_module_config(c->conn_config, &reqtimeout_module, ccfg); + + ap_add_input_filter("reqtimeout", ctx, NULL, c); + return OK; +} + +static int reqtimeout_after_headers(request_rec *r) +{ + reqtimeout_srv_cfg *cfg; + reqtimeout_con_cfg *ccfg = + ap_get_module_config(r->connection->conn_config, &reqtimeout_module); + + if (ccfg == NULL) { + /* not configured for this vhost */ + return OK; + } + + cfg = ap_get_module_config(r->connection->base_server->module_config, + &reqtimeout_module); + AP_DEBUG_ASSERT(cfg != NULL); + + ccfg->timeout_at = 0; + ccfg->max_timeout_at = 0; + ccfg->new_timeout = cfg->body_timeout; + ccfg->new_max_timeout = cfg->body_max_timeout; + ccfg->min_rate = cfg->body_min_rate; + ccfg->rate_factor = cfg->body_rate_factor; + ccfg->type = "body"; + + return OK; +} + +static int reqtimeout_after_body(request_rec *r) +{ + reqtimeout_srv_cfg *cfg; + reqtimeout_con_cfg *ccfg = + ap_get_module_config(r->connection->conn_config, &reqtimeout_module); + + if (ccfg == NULL) { + /* not configured for this vhost */ + return OK; + } + + cfg = ap_get_module_config(r->connection->base_server->module_config, + &reqtimeout_module); + AP_DEBUG_ASSERT(cfg != NULL); + + ccfg->timeout_at = 0; + ccfg->max_timeout_at = 0; + ccfg->in_keep_alive = 1; + ccfg->new_timeout = cfg->header_timeout; + ccfg->new_max_timeout = cfg->header_max_timeout; + ccfg->min_rate = cfg->header_min_rate; + ccfg->rate_factor = cfg->header_rate_factor; + + ccfg->type = "header"; + + return OK; +} + +static void *reqtimeout_create_srv_config(apr_pool_t *p, server_rec *s) +{ + reqtimeout_srv_cfg *cfg = apr_pcalloc(p, sizeof(reqtimeout_srv_cfg)); + + cfg->header_timeout = -1; + cfg->header_max_timeout = -1; + cfg->header_min_rate = -1; + cfg->body_timeout = -1; + cfg->body_max_timeout = -1; + cfg->body_min_rate = -1; + + return cfg; +} + +#define MERGE_INT(cfg, b, a, val) cfg->val = (a->val == -1) ? b->val : a->val; +static void *reqtimeout_merge_srv_config(apr_pool_t *p, void *base_, void *add_) +{ + reqtimeout_srv_cfg *base = base_; + reqtimeout_srv_cfg *add = add_; + reqtimeout_srv_cfg *cfg = apr_pcalloc(p, sizeof(reqtimeout_srv_cfg)); + + MERGE_INT(cfg, base, add, header_timeout); + MERGE_INT(cfg, base, add, header_max_timeout); + MERGE_INT(cfg, base, add, header_min_rate); + MERGE_INT(cfg, base, add, body_timeout); + MERGE_INT(cfg, base, add, body_max_timeout); + MERGE_INT(cfg, base, add, body_min_rate); + + cfg->header_rate_factor = (cfg->header_min_rate == -1) ? base->header_rate_factor : + add->header_rate_factor; + cfg->body_rate_factor = (cfg->body_min_rate == -1) ? base->body_rate_factor : + add->body_rate_factor; + + return cfg; +} + +static const char *parse_int(apr_pool_t *p, const char *arg, int *val) { + char *endptr; + *val = strtol(arg, &endptr, 10); + + if (arg == endptr) { + return apr_psprintf(p, "Value '%s' not numerical", endptr); + } + if (*endptr != '\0') { + return apr_psprintf(p, "Cannot parse '%s'", endptr); + } + if (*val < 0) { + return "Value must be non-negative"; + } + return NULL; +} + +static const char *set_reqtimeout_param(reqtimeout_srv_cfg *conf, + apr_pool_t *p, + const char *key, + const char *val) +{ + const char *ret = NULL; + char *rate_str = NULL, *initial_str, *max_str = NULL; + int rate = 0, initial = 0, max = 0; + enum { PARAM_HEADER, PARAM_BODY } type; + + if (!strcasecmp(key, "header")) { + type = PARAM_HEADER; + } + else if (!strcasecmp(key, "body")) { + type = PARAM_BODY; + } + else { + return "Unknown RequestReadTimeout parameter"; + } + + if ((rate_str = ap_strcasestr(val, ",minrate="))) { + initial_str = apr_pstrndup(p, val, rate_str - val); + rate_str += strlen(",minrate="); + ret = parse_int(p, rate_str, &rate); + if (ret) + return ret; + + if (rate == 0) + return "Minimum data rate must be larger than 0"; + + if ((max_str = strchr(initial_str, '-'))) { + *max_str++ = '\0'; + ret = parse_int(p, max_str, &max); + if (ret) + return ret; + } + + ret = parse_int(p, initial_str, &initial); + } + else { + if (ap_strchr_c(val, '-')) + return "Must set MinRate option if using timeout range"; + ret = parse_int(p, val, &initial); + } + + if (ret) + return ret; + + if (max && initial >= max) { + return "Maximum timeout must be larger than initial timeout"; + } + + if (type == PARAM_HEADER) { + conf->header_timeout = initial; + conf->header_max_timeout = max; + conf->header_min_rate = rate; + if (rate) + conf->header_rate_factor = apr_time_from_sec(1) / rate; + } + else { + conf->body_timeout = initial; + conf->body_max_timeout = max; + conf->body_min_rate = rate; + if (rate) + conf->body_rate_factor = apr_time_from_sec(1) / rate; + } + return ret; +} + +static const char *set_reqtimeouts(cmd_parms *cmd, void *mconfig, + const char *arg) +{ + reqtimeout_srv_cfg *conf = + ap_get_module_config(cmd->server->module_config, + &reqtimeout_module); + + while (*arg) { + char *word, *val; + const char *err; + + word = ap_getword_conf(cmd->pool, &arg); + val = strchr(word, '='); + if (!val) { + return "Invalid RequestReadTimeout parameter. Parameter must be " + "in the form 'key=value'"; + } + else + *val++ = '\0'; + + err = set_reqtimeout_param(conf, cmd->pool, word, val); + + if (err) + return apr_psprintf(cmd->temp_pool, "RequestReadTimeout: %s=%s: %s", + word, val, err); + } + + return NULL; + +} + +static void reqtimeout_hooks(apr_pool_t *pool) +{ + /* + * mod_ssl is AP_FTYPE_CONNECTION + 5 and mod_reqtimeout needs to + * be called before mod_ssl. Otherwise repeated reads during the ssl + * handshake can prevent the timeout from triggering. + */ + ap_register_input_filter(reqtimeout_filter_name, reqtimeout_filter, NULL, + AP_FTYPE_CONNECTION + 8); + ap_hook_pre_connection(reqtimeout_pre_conn, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_read_request(reqtimeout_after_headers, NULL, NULL, + APR_HOOK_MIDDLE); + ap_hook_log_transaction(reqtimeout_after_body, NULL, NULL, + APR_HOOK_MIDDLE); +} + +static const command_rec reqtimeout_cmds[] = { + AP_INIT_RAW_ARGS("RequestReadTimeout", set_reqtimeouts, NULL, RSRC_CONF, + "Set various timeout parameters for reading request " + "headers and body"), + {NULL} +}; + +module AP_MODULE_DECLARE_DATA reqtimeout_module = { + STANDARD20_MODULE_STUFF, + NULL, /* create per-dir config structures */ + NULL, /* merge per-dir config structures */ + reqtimeout_create_srv_config, /* create per-server config structures */ + reqtimeout_merge_srv_config, /* merge per-server config structures */ + reqtimeout_cmds, /* table of config file commands */ + reqtimeout_hooks +}; diff --git a/modules/filters/mod_reqtimeout.dsp b/modules/filters/mod_reqtimeout.dsp new file mode 100644 index 00000000..0afa4683 --- /dev/null +++ b/modules/filters/mod_reqtimeout.dsp @@ -0,0 +1,111 @@ +# Microsoft Developer Studio Project File - Name="mod_reqtimeout" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=mod_reqtimeout - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "mod_reqtimeout.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "mod_reqtimeout.mak" CFG="mod_reqtimeout - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "mod_reqtimeout - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "mod_reqtimeout - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "mod_reqtimeout - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MD /W3 /O2 /Oy- /Zi /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_RL_DECLARE_EXPORT" /Fd"Release\mod_reqtimeout_src" /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /fo"Release/mod_reqtimeout.res" /i "../../include" /i "../../srclib/apr/include" /d "NDEBUG" /d BIN_NAME="mod_reqtimeout.so" /d LONG_NAME="reqtimeout_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /out:".\Release\mod_reqtimeout.so" /base:@..\..\os\win32\BaseAddr.ref,mod_reqtimeout.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Release\mod_reqtimeout.so" /base:@..\..\os\win32\BaseAddr.ref,mod_reqtimeout.so /opt:ref
+# Begin Special Build Tool
+TargetPath=.\Release\mod_reqtimeout.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ELSEIF "$(CFG)" == "mod_reqtimeout - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c
+# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /I "../../include" /I "../../srclib/apr/include" /I "../../srclib/apr-util/include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "AP_RL_DECLARE_EXPORT" /Fd"Debug\mod_reqtimeout_src" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /fo"Debug/mod_reqtimeout.res" /i "../../include" /i "../../srclib/apr/include" /d "_DEBUG" /d BIN_NAME="mod_reqtimeout.so" /d LONG_NAME="reqtimeout_module for Apache"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_reqtimeout.so" /base:@..\..\os\win32\BaseAddr.ref,mod_reqtimeout.so
+# ADD LINK32 kernel32.lib /nologo /subsystem:windows /dll /incremental:no /debug /out:".\Debug\mod_reqtimeout.so" /base:@..\..\os\win32\BaseAddr.ref,mod_reqtimeout.so
+# Begin Special Build Tool
+TargetPath=.\Debug\mod_reqtimeout.so
+SOURCE="$(InputPath)"
+PostBuild_Desc=Embed .manifest
+PostBuild_Cmds=if exist $(TargetPath).manifest mt.exe -manifest $(TargetPath).manifest -outputresource:$(TargetPath);2
+# End Special Build Tool
+
+!ENDIF
+
+# Begin Target
+
+# Name "mod_reqtimeout - Win32 Release"
+# Name "mod_reqtimeout - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\mod_reqtimeout.c
+# End Source File
+# Begin Source File
+
+SOURCE=..\..\build\win32\httpd.rc
+# End Source File
+# End Target
+# End Project
diff --git a/modules/http/byterange_filter.c b/modules/http/byterange_filter.c index a25d1e59..e38d366e 100644 --- a/modules/http/byterange_filter.c +++ b/modules/http/byterange_filter.c @@ -308,7 +308,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_byterange_filter(ap_filter_t *f, APR_BRIGADE_INSERT_TAIL(bsend, e); /* we're done with the original content - all of our data is in bsend. */ - apr_brigade_destroy(bb); + apr_brigade_cleanup(bb); /* send our multipart output */ return ap_pass_brigade(f->next, bsend); diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c index f7f86df4..7fbc3692 100644 --- a/modules/http/http_filters.c +++ b/modules/http/http_filters.c @@ -524,6 +524,11 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, if (ctx->state != BODY_NONE) { ctx->remaining -= totalread; + if (ctx->remaining > 0) { + e = APR_BRIGADE_LAST(b); + if (APR_BUCKET_IS_EOS(e)) + return APR_EOF; + } } /* If we have no more bytes remaining on a C-L request, @@ -1115,7 +1120,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, ctx = f->ctx = apr_pcalloc(r->pool, sizeof(header_filter_ctx)); } else if (ctx->headers_sent) { - apr_brigade_destroy(b); + apr_brigade_cleanup(b); return OK; } } @@ -1286,7 +1291,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, ap_pass_brigade(f->next, b2); if (r->header_only) { - apr_brigade_destroy(b); + apr_brigade_cleanup(b); ctx->headers_sent = 1; return OK; } diff --git a/modules/http/http_request.c b/modules/http/http_request.c index fe10a196..3a7e5ef9 100644 --- a/modules/http/http_request.c +++ b/modules/http/http_request.c @@ -353,6 +353,8 @@ static request_rec *internal_internal_redirect(const char *new_uri, new->method_number = r->method_number; new->allowed_methods = ap_make_method_list(new->pool, 2); ap_parse_uri(new, new_uri); + new->parsed_uri.port_str = r->parsed_uri.port_str; + new->parsed_uri.port = r->parsed_uri.port; new->request_config = ap_create_request_config(r->pool); @@ -386,7 +388,6 @@ static request_rec *internal_internal_redirect(const char *new_uri, new->err_headers_out = r->err_headers_out; new->subprocess_env = rename_original_env(r->pool, r->subprocess_env); new->notes = apr_table_make(r->pool, 5); - new->allowed_methods = ap_make_method_list(new->pool, 2); new->htaccess = r->htaccess; new->no_cache = r->no_cache; diff --git a/modules/http/mod_mime.c b/modules/http/mod_mime.c index d9d8b101..eed6ebd9 100644 --- a/modules/http/mod_mime.c +++ b/modules/http/mod_mime.c @@ -274,6 +274,16 @@ static const char *add_extension_info(cmd_parms *cmd, void *m_, } /* + * As RemoveType should also override the info from TypesConfig, we add an + * empty string as type instead of actually removing the type. + */ +static const char *remove_extension_type(cmd_parms *cmd, void *m_, + const char *ext) +{ + return add_extension_info(cmd, m_, "", ext); +} + +/* * Note handler names are un-added with each per_dir_config merge. * This keeps the association from being inherited, but not * from being re-added at a subordinate level. @@ -312,6 +322,12 @@ static const char *multiviews_match(cmd_parms *cmd, void *m_, const char *include) { mime_dir_config *m = (mime_dir_config *) m_; + const char *errmsg; + + errmsg = ap_check_cmd_context(cmd, NOT_IN_LOCATION); + if (errmsg != NULL) { + return errmsg; + } if (strcasecmp(include, "Any") == 0) { if (m->multimatch && (m->multimatch & ~MULTIMATCH_ANY)) { @@ -397,7 +413,7 @@ static const command_rec mime_cmds[] = AP_INIT_ITERATE("RemoveOutputFilter", remove_extension_info, (void *)APR_OFFSETOF(extension_info, output_filters), OR_FILEINFO, "one or more file extensions"), - AP_INIT_ITERATE("RemoveType", remove_extension_info, + AP_INIT_ITERATE("RemoveType", remove_extension_type, (void *)APR_OFFSETOF(extension_info, forced_type), OR_FILEINFO, "one or more file extensions"), AP_INIT_TAKE1("TypesConfig", set_types_config, NULL, RSRC_CONF, @@ -809,7 +825,8 @@ static int find_ct(request_rec *r) if (exinfo != NULL) { - if (exinfo->forced_type) { + /* empty string is treated as special case for RemoveType */ + if (exinfo->forced_type && *exinfo->forced_type) { ap_set_content_type(r, exinfo->forced_type); found = 1; } diff --git a/modules/ldap/util_ldap.c b/modules/ldap/util_ldap.c index 5b432696..a894b6ea 100644 --- a/modules/ldap/util_ldap.c +++ b/modules/ldap/util_ldap.c @@ -1820,7 +1820,7 @@ static void *util_ldap_create_config(apr_pool_t *p, server_rec *s) apr_thread_mutex_create(&st->mutex, APR_THREAD_MUTEX_DEFAULT, st->pool); #endif - st->cache_bytes = 100000; + st->cache_bytes = 500000; st->search_cache_ttl = 600000000; st->search_cache_size = 1024; st->compare_cache_ttl = 600000000; diff --git a/modules/ldap/util_ldap_cache_mgr.c b/modules/ldap/util_ldap_cache_mgr.c index 56b8cbec..6bb066ab 100644 --- a/modules/ldap/util_ldap_cache_mgr.c +++ b/modules/ldap/util_ldap_cache_mgr.c @@ -385,6 +385,7 @@ void *util_ald_cache_fetch(util_ald_cache_t *cache, void *payload) void *util_ald_cache_insert(util_ald_cache_t *cache, void *payload) { unsigned long hashval; + void *tmp_payload; util_cache_node_t *node; /* sanity check */ @@ -397,21 +398,68 @@ void *util_ald_cache_insert(util_ald_cache_t *cache, void *payload) util_ald_cache_purge(cache); if (cache->numentries >= cache->maxentries) { /* if the purge was not effective, we leave now to avoid an overflow */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, + "Purge of LDAP cache failed"); return NULL; } } - /* should be safe to add an entry */ - if ((node = (util_cache_node_t *)util_ald_alloc(cache, sizeof(util_cache_node_t))) == NULL) { - return NULL; + node = (util_cache_node_t *)util_ald_alloc(cache, + sizeof(util_cache_node_t)); + if (node == NULL) { + /* + * XXX: The cache management should be rewritten to work + * properly when LDAPSharedCacheSize is too small. + */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, + "LDAPSharedCacheSize is too small. Increase it or " + "reduce LDAPCacheEntries/LDAPOpCacheEntries!"); + if (cache->numentries < cache->fullmark) { + /* + * We have not even reached fullmark, trigger a complete purge. + * This is still better than not being able to add new entries + * at all. + */ + cache->marktime = apr_time_now(); + } + util_ald_cache_purge(cache); + node = (util_cache_node_t *)util_ald_alloc(cache, + sizeof(util_cache_node_t)); + if (node == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, + "Could not allocate memory for LDAP cache entry"); + return NULL; + } } /* Take a copy of the payload before proceeeding. */ - payload = (*cache->copy)(cache, payload); - if (!payload) { - util_ald_free(cache, node); - return NULL; + tmp_payload = (*cache->copy)(cache, payload); + if (tmp_payload == NULL) { + /* + * XXX: The cache management should be rewritten to work + * properly when LDAPSharedCacheSize is too small. + */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, + "LDAPSharedCacheSize is too small. Increase it or " + "reduce LDAPCacheEntries/LDAPOpCacheEntries!"); + if (cache->numentries < cache->fullmark) { + /* + * We have not even reached fullmark, trigger a complete purge. + * This is still better than not being able to add new entries + * at all. + */ + cache->marktime = apr_time_now(); + } + util_ald_cache_purge(cache); + tmp_payload = (*cache->copy)(cache, payload); + if (tmp_payload == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, + "Could not allocate memory for LDAP cache value"); + util_ald_free(cache, node); + return NULL; + } } + payload = tmp_payload; /* populate the entry */ cache->inserts++; diff --git a/modules/loggers/mod_log_config.c b/modules/loggers/mod_log_config.c index 60485dd3..8ceadf7c 100644 --- a/modules/loggers/mod_log_config.c +++ b/modules/loggers/mod_log_config.c @@ -381,6 +381,11 @@ static const char *log_status(request_rec *r, char *a) return pfmt(r->pool, r->status); } +static const char *log_handler(request_rec *r, char *a) +{ + return ap_escape_logitem(r->pool, r->handler); +} + static const char *clf_log_bytes_sent(request_rec *r, char *a) { if (!r->sent_bodyct || !r->bytes_sent) { @@ -1516,6 +1521,7 @@ static int log_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp) log_pfn_register(p, "T", log_request_duration, 1); log_pfn_register(p, "U", log_request_uri, 1); log_pfn_register(p, "s", log_status, 1); + log_pfn_register(p, "R", log_handler, 1); } return OK; diff --git a/modules/mappers/mod_negotiation.c b/modules/mappers/mod_negotiation.c index a9c6bc9e..d3ae87c6 100644 --- a/modules/mappers/mod_negotiation.c +++ b/modules/mappers/mod_negotiation.c @@ -3130,6 +3130,9 @@ static int handle_multi(request_rec *r) goto return_from_multi; } } + if (sub_req->args == NULL) { + sub_req->args = r->args; + } /* now do a "fast redirect" ... promotes the sub_req into the main req */ ap_internal_fast_redirect(sub_req, r); diff --git a/modules/mappers/mod_rewrite.c b/modules/mappers/mod_rewrite.c index b9c8a51f..d5bd5679 100644 --- a/modules/mappers/mod_rewrite.c +++ b/modules/mappers/mod_rewrite.c @@ -606,6 +606,13 @@ static unsigned is_absolute_uri(char *uri) return 7; } break; + + case 's': + case 'S': + if (!strncasecmp(uri, "cgi://", 6)) { /* scgi:// */ + return 7; + } + break; } return 0; @@ -838,7 +845,10 @@ static void reduce_uri(request_rec *r) */ static void fully_qualify_uri(request_rec *r) { - if (!is_absolute_uri(r->filename)) { + if (r->method_number == M_CONNECT) { + return; + } + else if (!is_absolute_uri(r->filename)) { const char *thisserver; char *thisport; int port; diff --git a/modules/proxy/NWGNUmakefile b/modules/proxy/NWGNUmakefile index 212a5be7..311282e2 100644 --- a/modules/proxy/NWGNUmakefile +++ b/modules/proxy/NWGNUmakefile @@ -158,6 +158,7 @@ TARGET_nlm = \ $(OBJDIR)/proxyhtp.nlm \ $(OBJDIR)/proxybalancer.nlm \ $(OBJDIR)/proxyajp.nlm \ + $(OBJDIR)/proxyscgi.nlm \ $(EOLIST) # diff --git a/modules/proxy/NWGNUproxy b/modules/proxy/NWGNUproxy index eede5a5c..4d3beee4 100644 --- a/modules/proxy/NWGNUproxy +++ b/modules/proxy/NWGNUproxy @@ -223,16 +223,7 @@ endif # Any symbols exported to here # FILES_nlm_exports = \ - proxy_module \ - proxy_hook_scheme_handler \ - proxy_hook_canon_handler \ - proxy_hook_pre_request \ - proxy_hook_post_request \ - ap_proxy_ssl_enable \ - ap_proxy_ssl_disable \ - ap_proxy_conn_is_https \ - ap_proxy_ssl_val \ - proxy_run_fixups \ + @$(OBJDIR)/mod_proxy.imp \ $(EOLIST) # @@ -248,7 +239,7 @@ FILES_lib_objs = \ libs :: $(OBJDIR) $(TARGET_lib) -nlms :: libs $(TARGET_nlm) +nlms :: libs $(OBJDIR)/mod_proxy.imp $(TARGET_nlm) # # Updated this target to create necessary directories and copy files to the @@ -262,6 +253,20 @@ install :: nlms FORCE vpath %.c ../arch/netware +$(OBJDIR)/mod_proxy.imp: + @echo Creating $@ + @echo # Exports of mod_proxy > $@ + @echo proxy_module, >> $@ + @echo proxy_hook_canon_handler, >> $@ + @echo proxy_hook_post_request, >> $@ + @echo proxy_hook_pre_request, >> $@ + @echo proxy_hook_scheme_handler, >> $@ + @echo proxy_run_fixups, >> $@ + @echo ap_proxy_conn_is_https, >> $@ + @echo ap_proxy_ssl_enable, >> $@ + @echo ap_proxy_ssl_disable, >> $@ + @echo ap_proxy_ssl_val >> $@ + # # Include the 'tail' makefile that has targets that depend on variables defined # in this makefile diff --git a/modules/proxy/NWGNUproxyajp b/modules/proxy/NWGNUproxyajp index 77942ffc..446549f8 100644 --- a/modules/proxy/NWGNUproxyajp +++ b/modules/proxy/NWGNUproxyajp @@ -213,15 +213,8 @@ FILE_nlm_copyright = FILES_nlm_Ximports = \ @$(APR)/aprlib.imp \ @$(NWOS)/httpd.imp \ + @$(OBJDIR)/mod_proxy.imp \ @libc.imp \ - proxy_module \ - proxy_hook_scheme_handler \ - proxy_hook_canon_handler \ - proxy_run_fixups \ - ap_proxy_ssl_enable \ - ap_proxy_ssl_disable \ - ap_proxy_conn_is_https \ - ap_proxy_ssl_val \ $(EOLIST) # Don't link with Winsock if standard sockets are being used diff --git a/modules/proxy/NWGNUproxybalancer b/modules/proxy/NWGNUproxybalancer index c50c48f6..7dc04b8e 100644 --- a/modules/proxy/NWGNUproxybalancer +++ b/modules/proxy/NWGNUproxybalancer @@ -209,16 +209,8 @@ FILE_nlm_copyright = FILES_nlm_Ximports = \ @$(APR)/aprlib.imp \ @$(NWOS)/httpd.imp \ + @$(OBJDIR)/mod_proxy.imp \ @libc.imp \ - proxy_module \ - proxy_hook_scheme_handler \ - proxy_hook_canon_handler \ - proxy_hook_pre_request \ - proxy_hook_post_request \ - proxy_hook_load_lbmethods \ - proxy_run_fixups \ - ap_proxy_ssl_enable \ - ap_proxy_ssl_disable \ $(EOLIST) # Don't link with Winsock if standard sockets are being used diff --git a/modules/proxy/NWGNUproxycon b/modules/proxy/NWGNUproxycon index 6650197b..4ce871cf 100644 --- a/modules/proxy/NWGNUproxycon +++ b/modules/proxy/NWGNUproxycon @@ -208,10 +208,8 @@ FILE_nlm_copyright = FILES_nlm_Ximports = \ @$(APR)/aprlib.imp \ @$(NWOS)/httpd.imp \ + @$(OBJDIR)/mod_proxy.imp \ @libc.imp \ - proxy_module \ - proxy_hook_scheme_handler \ - proxy_hook_canon_handler \ $(EOLIST) # diff --git a/modules/proxy/NWGNUproxyftp b/modules/proxy/NWGNUproxyftp index 89fef5ac..653d9d7b 100644 --- a/modules/proxy/NWGNUproxyftp +++ b/modules/proxy/NWGNUproxyftp @@ -209,12 +209,8 @@ FILE_nlm_copyright = FILES_nlm_Ximports = \ @$(APR)/aprlib.imp \ @$(NWOS)/httpd.imp \ + @$(OBJDIR)/mod_proxy.imp \ @libc.imp \ - proxy_module \ - proxy_hook_scheme_handler \ - proxy_hook_canon_handler \ - ap_proxy_ssl_enable \ - ap_proxy_ssl_disable \ $(EOLIST) # Don't link with Winsock if standard sockets are being used diff --git a/modules/proxy/NWGNUproxyhtp b/modules/proxy/NWGNUproxyhtp index 5f5c2ada..21debea1 100644 --- a/modules/proxy/NWGNUproxyhtp +++ b/modules/proxy/NWGNUproxyhtp @@ -209,13 +209,8 @@ FILE_nlm_copyright = FILES_nlm_Ximports = \ @$(APR)/aprlib.imp \ @$(NWOS)/httpd.imp \ + @$(OBJDIR)/mod_proxy.imp \ @libc.imp \ - proxy_module \ - proxy_hook_scheme_handler \ - proxy_hook_canon_handler \ - proxy_run_fixups \ - ap_proxy_ssl_enable \ - ap_proxy_ssl_disable \ $(EOLIST) # Don't link with Winsock if standard sockets are being used diff --git a/modules/proxy/NWGNUproxyscgi b/modules/proxy/NWGNUproxyscgi index 40d8bb24..4c51621f 100644 --- a/modules/proxy/NWGNUproxyscgi +++ b/modules/proxy/NWGNUproxyscgi @@ -94,7 +94,7 @@ endif # This is used by the link 'name' directive to name the nlm. If left blank # TARGET_nlm (see below) will be used. # -NLM_NAME = proxyscg +NLM_NAME = proxyscgi # # This is used by the link '-desc ' directive. diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h index 04b71066..358248f1 100644 --- a/modules/proxy/mod_proxy.h +++ b/modules/proxy/mod_proxy.h @@ -247,6 +247,7 @@ typedef struct { * which the backend currently answers. */ int need_flush;/* Flag to decide whether we need to flush the * filter chain or not */ + void *forward; /* opaque forward proxy data */ } proxy_conn_rec; typedef struct { diff --git a/modules/proxy/mod_proxy_ajp.c b/modules/proxy/mod_proxy_ajp.c index 66693aa9..fa8c41f8 100644 --- a/modules/proxy/mod_proxy_ajp.c +++ b/modules/proxy/mod_proxy_ajp.c @@ -257,7 +257,7 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r, ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "proxy: ap_get_brigade failed"); apr_brigade_destroy(input_brigade); - return HTTP_INTERNAL_SERVER_ERROR; + return HTTP_BAD_REQUEST; } /* have something */ @@ -469,7 +469,9 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r, if (ap_pass_brigade(r->output_filters, output_brigade) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "proxy: error processing body"); + "proxy: error processing body.%s", + r->connection->aborted ? + " Client aborted connection." : ""); output_failed = 1; } data_sent = 1; @@ -507,6 +509,7 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r, conn->close++; output_failed = 0; result = CMD_AJP13_END_RESPONSE; + request_ended = 1; } /* diff --git a/modules/proxy/mod_proxy_connect.c b/modules/proxy/mod_proxy_connect.c index 8804359c..d66a50d8 100644 --- a/modules/proxy/mod_proxy_connect.c +++ b/modules/proxy/mod_proxy_connect.c @@ -201,7 +201,7 @@ static int proxy_connect_handler(request_rec *r, proxy_worker *worker, return DECLINED; } else { - return HTTP_BAD_GATEWAY; + return HTTP_SERVICE_UNAVAILABLE; } } diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c index 34015263..7e4767f9 100644 --- a/modules/proxy/mod_proxy_http.c +++ b/modules/proxy/mod_proxy_http.c @@ -1369,6 +1369,10 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, {"Keep-Alive", "Proxy-Authenticate", "TE", "Trailer", "Upgrade", NULL}; int i; const char *te = NULL; + int original_status = r->status; + int proxy_status = OK; + const char *original_status_line = r->status_line; + const char *proxy_status_line = NULL; bb = apr_brigade_create(p, c->bucket_alloc); pass_bb = apr_brigade_create(p, c->bucket_alloc); @@ -1482,7 +1486,7 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, keepchar = buffer[12]; buffer[12] = '\0'; - r->status = atoi(&buffer[9]); + proxy_status = atoi(&buffer[9]); if (keepchar != '\0') { buffer[12] = keepchar; @@ -1493,8 +1497,13 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, buffer[12] = ' '; buffer[13] = '\0'; } - r->status_line = apr_pstrdup(p, &buffer[9]); + proxy_status_line = apr_pstrdup(p, &buffer[9]); + /* The status out of the front is the same as the status coming in + * from the back, until further notice. + */ + r->status = proxy_status; + r->status_line = proxy_status_line; /* read the headers. */ /* N.B. for HTTP/1.0 clients, we have to fold line-wrapped headers*/ @@ -1570,7 +1579,7 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, if ((buf = apr_table_get(r->headers_out, "Content-Type"))) { ap_set_content_type(r, apr_pstrdup(p, buf)); } - if (!ap_is_HTTP_INFO(r->status)) { + if (!ap_is_HTTP_INFO(proxy_status)) { ap_proxy_pre_http_request(origin, rp); } @@ -1621,7 +1630,7 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, backend->close += 1; } - if (ap_is_HTTP_INFO(r->status)) { + if (ap_is_HTTP_INFO(proxy_status)) { interim_response++; } else { @@ -1660,7 +1669,7 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, * ProxyPassReverse/etc from here to ap_proxy_read_headers */ - if ((r->status == 401) && (conf->error_override)) { + if ((proxy_status == 401) && (conf->error_override)) { const char *buf; const char *wa = "WWW-Authenticate"; if ((buf = apr_table_get(r->headers_out, wa))) { @@ -1700,8 +1709,8 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, /* send body - but only if a body is expected */ if ((!r->header_only) && /* not HEAD request */ !interim_response && /* not any 1xx response */ - (r->status != HTTP_NO_CONTENT) && /* not 204 */ - (r->status != HTTP_NOT_MODIFIED)) { /* not 304 */ + (proxy_status != HTTP_NO_CONTENT) && /* not 204 */ + (proxy_status != HTTP_NOT_MODIFIED)) { /* not 304 */ /* We need to copy the output headers and treat them as input * headers as well. BUT, we need to do this before we remove @@ -1727,11 +1736,22 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, * if we are overriding the errors, we can't put the content * of the page into the brigade */ - if (!conf->error_override || !ap_is_HTTP_ERROR(r->status)) { + if (!conf->error_override || !ap_is_HTTP_ERROR(proxy_status)) { /* read the body, pass it to the output filters */ apr_read_type_e mode = APR_NONBLOCK_READ; int finish = FALSE; + /* Handle the case where the error document is itself reverse + * proxied and was successful. We must maintain any previous + * error status so that an underlying error (eg HTTP_NOT_FOUND) + * doesn't become an HTTP_OK. + */ + if (conf->error_override && !ap_is_HTTP_ERROR(proxy_status) + && ap_is_HTTP_ERROR(original_status)) { + r->status = original_status; + r->status_line = original_status_line; + } + do { apr_off_t readbytes; apr_status_t rv; @@ -1848,25 +1868,27 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, if (conf->error_override) { /* the code above this checks for 'OK' which is what the hook expects */ - if (!ap_is_HTTP_ERROR(r->status)) + if (!ap_is_HTTP_ERROR(proxy_status)) { return OK; + } else { /* clear r->status for override error, otherwise ErrorDocument * thinks that this is a recursive error, and doesn't find the * custom error page */ - int status = r->status; r->status = HTTP_OK; /* Discard body, if one is expected */ if (!r->header_only && /* not HEAD request */ - (status != HTTP_NO_CONTENT) && /* not 204 */ - (status != HTTP_NOT_MODIFIED)) { /* not 304 */ - ap_discard_request_body(rp); - } - return status; + (proxy_status != HTTP_NO_CONTENT) && /* not 204 */ + (proxy_status != HTTP_NOT_MODIFIED)) { /* not 304 */ + ap_discard_request_body(rp); + } + return proxy_status; } - } else + } + else { return OK; + } } static @@ -1974,10 +1996,7 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker, /* Step Two: Make the Connection */ if (ap_proxy_connect_backend(proxy_function, backend, worker, r->server)) { - if (r->proxyreq == PROXYREQ_PROXY) - status = HTTP_NOT_FOUND; - else - status = HTTP_SERVICE_UNAVAILABLE; + status = HTTP_SERVICE_UNAVAILABLE; goto cleanup; } diff --git a/modules/proxy/proxy_util.c b/modules/proxy/proxy_util.c index b835832d..a70a8758 100644 --- a/modules/proxy/proxy_util.c +++ b/modules/proxy/proxy_util.c @@ -29,6 +29,18 @@ #define apr_socket_create apr_socket_create_ex #endif +/* + * Opaque structure containing target server info when + * using a forward proxy. + * Up to now only used in combination with HTTP CONNECT. + */ +typedef struct { + int use_http_connect; /* Use SSL Tunneling via HTTP CONNECT */ + const char *target_host; /* Target hostname */ + apr_port_t target_port; /* Target port */ + const char *proxy_auth; /* Proxy authorization */ +} forward_info; + /* Global balancer counter */ int PROXY_DECLARE_DATA proxy_lb_workers = 0; static int lb_workers_limit = 0; @@ -2085,6 +2097,34 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r, if (proxyname) { conn->hostname = apr_pstrdup(conn->pool, proxyname); conn->port = proxyport; + /* + * If we have a forward proxy and the protocol is HTTPS, + * then we need to prepend a HTTP CONNECT request before + * sending our actual HTTPS requests. + * Save our real backend data for using it later during HTTP CONNECT. + */ + if (conn->is_ssl) { + const char *proxy_auth; + + forward_info *forward = apr_pcalloc(conn->pool, sizeof(forward_info)); + conn->forward = forward; + forward->use_http_connect = 1; + forward->target_host = apr_pstrdup(conn->pool, uri->hostname); + forward->target_port = uri->port; + /* Do we want to pass Proxy-Authorization along? + * If we haven't used it, then YES + * If we have used it then MAYBE: RFC2616 says we MAY propagate it. + * So let's make it configurable by env. + * The logic here is the same used in mod_proxy_http. + */ + proxy_auth = apr_table_get(r->headers_in, "Proxy-Authorization"); + if (proxy_auth != NULL && + proxy_auth[0] != '\0' && + r->user == NULL && /* we haven't yet authenticated */ + apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) { + forward->proxy_auth = apr_pstrdup(conn->pool, proxy_auth); + } + } } else { conn->hostname = apr_pstrdup(conn->pool, uri->hostname); @@ -2224,6 +2264,102 @@ static int is_socket_connected(apr_socket_t *sock) } #endif /* USE_ALTERNATE_IS_CONNECTED */ + +/* + * Send a HTTP CONNECT request to a forward proxy. + * The proxy is given by "backend", the target server + * is contained in the "forward" member of "backend". + */ +static apr_status_t send_http_connect(proxy_conn_rec *backend, + server_rec *s) +{ + int status; + apr_size_t nbytes; + apr_size_t left; + int complete = 0; + char buffer[HUGE_STRING_LEN]; + char drain_buffer[HUGE_STRING_LEN]; + forward_info *forward = (forward_info *)backend->forward; + int len = 0; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "proxy: CONNECT: sending the CONNECT request for %s:%d " + "to the remote proxy %pI (%s)", + forward->target_host, forward->target_port, + backend->addr, backend->hostname); + /* Create the CONNECT request */ + nbytes = apr_snprintf(buffer, sizeof(buffer), + "CONNECT %s:%d HTTP/1.0" CRLF, + forward->target_host, forward->target_port); + /* Add proxy authorization from the initial request if necessary */ + if (forward->proxy_auth != NULL) { + nbytes += apr_snprintf(buffer + nbytes, sizeof(buffer) - nbytes, + "Proxy-Authorization: %s" CRLF, + forward->proxy_auth); + } + /* Set a reasonable agent and send everything */ + nbytes += apr_snprintf(buffer + nbytes, sizeof(buffer) - nbytes, + "Proxy-agent: %s" CRLF CRLF, + ap_get_server_banner()); + apr_socket_send(backend->sock, buffer, &nbytes); + + /* Receive the whole CONNECT response */ + left = sizeof(buffer) - 1; + /* Read until we find the end of the headers or run out of buffer */ + do { + nbytes = left; + status = apr_socket_recv(backend->sock, buffer + len, &nbytes); + len += nbytes; + left -= nbytes; + buffer[len] = '\0'; + if (strstr(buffer + len - nbytes, "\r\n\r\n") != NULL) { + complete = 1; + break; + } + } while (status == APR_SUCCESS && left > 0); + /* Drain what's left */ + if (!complete) { + nbytes = sizeof(drain_buffer) - 1; + while (status == APR_SUCCESS && nbytes) { + status = apr_socket_recv(backend->sock, drain_buffer, &nbytes); + buffer[nbytes] = '\0'; + nbytes = sizeof(drain_buffer) - 1; + if (strstr(drain_buffer, "\r\n\r\n") != NULL) { + complete = 1; + break; + } + } + } + + /* Check for HTTP_OK response status */ + if (status == APR_SUCCESS) { + int major, minor; + /* Only scan for three character status code */ + char code_str[4]; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "send_http_connect: response from the forward proxy: %s", + buffer); + + /* Extract the returned code */ + if (sscanf(buffer, "HTTP/%u.%u %3s", &major, &minor, code_str) == 3) { + status = atoi(code_str); + if (status == HTTP_OK) { + status = APR_SUCCESS; + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, + "send_http_connect: the forward proxy returned code is '%s'", + code_str); + status = APR_INCOMPLETE; + } + } + } + + return(status); +} + + PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function, proxy_conn_rec *conn, proxy_worker *worker, @@ -2336,7 +2472,33 @@ PROXY_DECLARE(int) ap_proxy_connect_backend(const char *proxy_function, apr_socket_timeout_set(newsock, s->timeout); } - conn->sock = newsock; + conn->sock = newsock; + + if (conn->forward) { + forward_info *forward = (forward_info *)conn->forward; + /* + * For HTTP CONNECT we need to prepend CONNECT request before + * sending our actual HTTPS requests. + */ + if (forward->use_http_connect) { + rv = send_http_connect(conn, s); + /* If an error occurred, loop round and try again */ + if (rv != APR_SUCCESS) { + conn->sock = NULL; + apr_socket_close(newsock); + loglevel = backend_addr->next ? APLOG_DEBUG : APLOG_ERR; + ap_log_error(APLOG_MARK, loglevel, rv, s, + "proxy: %s: attempt to connect to %s:%d " + "via http CONNECT through %pI (%s) failed", + proxy_function, + forward->target_host, forward->target_port, + backend_addr, worker->hostname); + backend_addr = backend_addr->next; + continue; + } + } + } + connected = 1; } /* @@ -2516,4 +2678,3 @@ ap_proxy_buckets_lifetime_transform(request_rec *r, apr_bucket_brigade *from, } return rv; } - diff --git a/modules/ssl/mod_ssl.c b/modules/ssl/mod_ssl.c index c8600e9d..37c13731 100644 --- a/modules/ssl/mod_ssl.c +++ b/modules/ssl/mod_ssl.c @@ -143,6 +143,8 @@ static const command_rec ssl_config_cmds[] = { "(`[+-][SSLv2|SSLv3|TLSv1] ...' - see manual)") SSL_CMD_SRV(HonorCipherOrder, FLAG, "Use the server's cipher ordering preference") + SSL_CMD_SRV(InsecureRenegotiation, FLAG, + "Enable support for insecure renegotiation") SSL_CMD_ALL(UserName, TAKE1, "Set user name to SSL variable value") SSL_CMD_SRV(StrictSNIVHostCheck, FLAG, diff --git a/modules/ssl/ssl_engine_config.c b/modules/ssl/ssl_engine_config.c index bda6fe56..e983f1e4 100644 --- a/modules/ssl/ssl_engine_config.c +++ b/modules/ssl/ssl_engine_config.c @@ -169,6 +169,7 @@ static SSLSrvConfigRec *ssl_config_server_new(apr_pool_t *p) sc->vhost_id_len = 0; /* set during module init */ sc->session_cache_timeout = UNSET; sc->cipher_server_pref = UNSET; + sc->insecure_reneg = UNSET; sc->proxy_ssl_check_peer_expire = SSL_ENABLED_UNSET; sc->proxy_ssl_check_peer_cn = SSL_ENABLED_UNSET; #ifndef OPENSSL_NO_TLSEXT @@ -262,6 +263,7 @@ void *ssl_config_server_merge(apr_pool_t *p, void *basev, void *addv) cfgMergeBool(proxy_enabled); cfgMergeInt(session_cache_timeout); cfgMergeBool(cipher_server_pref); + cfgMergeBool(insecure_reneg); cfgMerge(proxy_ssl_check_peer_expire, SSL_ENABLED_UNSET); cfgMerge(proxy_ssl_check_peer_cn, SSL_ENABLED_UNSET); #ifndef OPENSSL_NO_TLSEXT @@ -688,6 +690,19 @@ const char *ssl_cmd_SSLHonorCipherOrder(cmd_parms *cmd, void *dcfg, int flag) #endif } +const char *ssl_cmd_SSLInsecureRenegotiation(cmd_parms *cmd, void *dcfg, int flag) +{ +#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION + SSLSrvConfigRec *sc = mySrvConfig(cmd->server); + sc->insecure_reneg = flag?TRUE:FALSE; + return NULL; +#else + return "The SSLInsecureRenegotiation directive is not available " + "with this SSL library"; +#endif +} + + static const char *ssl_cmd_check_dir(cmd_parms *parms, const char **dir) { diff --git a/modules/ssl/ssl_engine_init.c b/modules/ssl/ssl_engine_init.c index 259254a9..d4f9171d 100644 --- a/modules/ssl/ssl_engine_init.c +++ b/modules/ssl/ssl_engine_init.c @@ -394,6 +394,7 @@ static void ssl_init_ctx_protocol(server_rec *s, MODSSL_SSL_METHOD_CONST SSL_METHOD *method = NULL; char *cp; int protocol = mctx->protocol; + SSLSrvConfigRec *sc = mySrvConfig(s); /* * Create the new per-server SSL context @@ -444,11 +445,14 @@ static void ssl_init_ctx_protocol(server_rec *s, } #ifdef SSL_OP_CIPHER_SERVER_PREFERENCE - { - SSLSrvConfigRec *sc = mySrvConfig(s); - if (sc->cipher_server_pref == TRUE) { - SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); - } + if (sc->cipher_server_pref == TRUE) { + SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + } +#endif + +#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION + if (sc->insecure_reneg == TRUE) { + SSL_CTX_set_options(ctx, SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION); } #endif @@ -501,10 +505,7 @@ static void ssl_init_ctx_callbacks(server_rec *s, SSL_CTX_set_tmp_rsa_callback(ctx, ssl_callback_TmpRSA); SSL_CTX_set_tmp_dh_callback(ctx, ssl_callback_TmpDH); - if (s->loglevel >= APLOG_DEBUG) { - /* this callback only logs if LogLevel >= info */ - SSL_CTX_set_info_callback(ctx, ssl_callback_LogTracingState); - } + SSL_CTX_set_info_callback(ctx, ssl_callback_Info); } static void ssl_init_ctx_verify(server_rec *s, diff --git a/modules/ssl/ssl_engine_io.c b/modules/ssl/ssl_engine_io.c index 6e58c6b3..d26a0c2b 100644 --- a/modules/ssl/ssl_engine_io.c +++ b/modules/ssl/ssl_engine_io.c @@ -103,6 +103,7 @@ typedef struct { ap_filter_t *pInputFilter; ap_filter_t *pOutputFilter; int nobuffer; /* non-zero to prevent buffering */ + SSLConnRec *config; } ssl_filter_ctx_t; typedef struct { @@ -193,7 +194,13 @@ static int bio_filter_out_read(BIO *bio, char *out, int outl) static int bio_filter_out_write(BIO *bio, const char *in, int inl) { bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)(bio->ptr); - + + /* Abort early if the client has initiated a renegotiation. */ + if (outctx->filter_ctx->config->reneg_state == RENEG_ABORT) { + outctx->rc = APR_ECONNABORTED; + return -1; + } + /* when handshaking we'll have a small number of bytes. * max size SSL will pass us here is about 16k. * (16413 bytes to be exact) @@ -458,7 +465,6 @@ static int bio_filter_in_read(BIO *bio, char *in, int inlen) apr_size_t inl = inlen; bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)(bio->ptr); apr_read_type_e block = inctx->block; - SSLConnRec *sslconn = myConnConfig(inctx->f->c); inctx->rc = APR_SUCCESS; @@ -466,17 +472,25 @@ static int bio_filter_in_read(BIO *bio, char *in, int inlen) if (!in) return 0; - /* XXX: flush here only required for SSLv2; - * OpenSSL calls BIO_flush() at the appropriate times for - * the other protocols. + /* Abort early if the client has initiated a renegotiation. */ + if (inctx->filter_ctx->config->reneg_state == RENEG_ABORT) { + inctx->rc = APR_ECONNABORTED; + return -1; + } + + /* In theory, OpenSSL should flush as necessary, but it is known + * not to do so correctly in some cases; see PR 46952. + * + * Historically, this flush call was performed only for an SSLv2 + * connection or for a proxy connection. Calling _out_flush + * should be very cheap in cases where it is unnecessary (and no + * output is buffered) so the performance impact of doing it + * unconditionally should be minimal. */ - if ((SSL_version(inctx->ssl) == SSL2_VERSION) || sslconn->is_proxy) { - if (bio_filter_out_flush(inctx->bio_out) < 0) { - bio_filter_out_ctx_t *outctx = - (bio_filter_out_ctx_t *)(inctx->bio_out->ptr); - inctx->rc = outctx->rc; - return -1; - } + if (bio_filter_out_flush(inctx->bio_out) < 0) { + bio_filter_out_ctx_t *outctx = inctx->bio_out->ptr; + inctx->rc = outctx->rc; + return -1; } BIO_clear_retry_flags(bio); @@ -1358,9 +1372,17 @@ static apr_status_t ssl_io_filter_input(ap_filter_t *f, } else { /* We have no idea what you are talking about, so return an error. */ - return APR_ENOTIMPL; + status = APR_ENOTIMPL; } + /* It is possible for mod_ssl's BIO to be used outside of the + * direct control of mod_ssl's input or output filter -- notably, + * when mod_ssl initiates a renegotiation. Switching the BIO mode + * back to "blocking" here ensures such operations don't fail with + * SSL_ERROR_WANT_READ. */ + inctx->block = APR_BLOCK_READ; + + /* Handle custom errors. */ if (status != APR_SUCCESS) { return ssl_io_filter_error(f, bb, status); } @@ -1665,7 +1687,7 @@ static apr_status_t ssl_io_filter_buffer(ap_filter_t *f, } else { /* Split a line into the passed-in brigade. */ - rv = apr_brigade_split_line(bb, ctx->bb, mode, bytes); + rv = apr_brigade_split_line(bb, ctx->bb, block, bytes); if (rv) { ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, @@ -1724,6 +1746,8 @@ void ssl_io_filter_init(conn_rec *c, SSL *ssl) filter_ctx = apr_palloc(c->pool, sizeof(ssl_filter_ctx_t)); + filter_ctx->config = myConnConfig(c); + filter_ctx->nobuffer = 0; filter_ctx->pOutputFilter = ap_add_output_filter(ssl_io_filter, filter_ctx, NULL, c); diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c index 15186209..33f97bd9 100644 --- a/modules/ssl/ssl_engine_kernel.c +++ b/modules/ssl/ssl_engine_kernel.c @@ -35,6 +35,29 @@ static void ssl_configure_env(request_rec *r, SSLConnRec *sslconn); static int ssl_find_vhost(void *servername, conn_rec *c, server_rec *s); #endif +/* Perform a speculative (and non-blocking) read from the connection + * filters for the given request, to determine whether there is any + * pending data to read. Return non-zero if there is, else zero. */ +static int has_buffered_data(request_rec *r) +{ + apr_bucket_brigade *bb; + apr_off_t len; + apr_status_t rv; + int result; + + bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + + rv = ap_get_brigade(r->connection->input_filters, bb, AP_MODE_SPECULATIVE, + APR_NONBLOCK_READ, 1); + result = rv == APR_SUCCESS + && apr_brigade_length(bb, 1, &len) == APR_SUCCESS + && len > 0; + + apr_brigade_destroy(bb); + + return result; +} + /* * Post Read Request Handler */ @@ -720,21 +743,50 @@ int ssl_hook_Access(request_rec *r) else { request_rec *id = r->main ? r->main : r; - /* do a full renegotiation */ + /* Additional mitigation for CVE-2009-3555: At this point, + * before renegotiating, an (entire) request has been read + * from the connection. An attacker may have sent further + * data to "prefix" any subsequent request by the victim's + * client after the renegotiation; this data may already + * have been read and buffered. Forcing a connection + * closure after the response ensures such data will be + * discarded. Legimately pipelined HTTP requests will be + * retried anyway with this approach. */ + if (has_buffered_data(r)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "insecure SSL re-negotiation required, but " + "a pipelined request is present; keepalive " + "disabled"); + r->connection->keepalive = AP_CONN_CLOSE; + } + + /* Perform a full renegotiation. */ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, - "Performing full renegotiation: " - "complete handshake protocol"); + "Performing full renegotiation: complete handshake " + "protocol (%s support secure renegotiation)", +#if defined(SSL_get_secure_renegotiation_support) + SSL_get_secure_renegotiation_support(ssl) ? + "client does" : "client does not" +#else + "server does not" +#endif + ); SSL_set_session_id_context(ssl, (unsigned char *)&id, sizeof(id)); + /* Toggle the renegotiation state to allow the new + * handshake to proceed. */ + sslconn->reneg_state = RENEG_ALLOW; + SSL_renegotiate(ssl); SSL_do_handshake(ssl); if (SSL_get_state(ssl) != SSL_ST_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Re-negotiation request failed"); + ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, r->server); r->connection->aborted = 1; return HTTP_FORBIDDEN; @@ -750,6 +802,8 @@ int ssl_hook_Access(request_rec *r) SSL_set_state(ssl, SSL_ST_ACCEPT); SSL_do_handshake(ssl); + sslconn->reneg_state = RENEG_REJECT; + if (SSL_get_state(ssl) != SSL_ST_OK) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Re-negotiation handshake failed: " @@ -1021,6 +1075,7 @@ static const char *ssl_hook_Fixup_vars[] = { "SSL_VERSION_INTERFACE", "SSL_VERSION_LIBRARY", "SSL_PROTOCOL", + "SSL_SECURE_RENEG", "SSL_COMPRESS_METHOD", "SSL_CIPHER", "SSL_CIPHER_EXPORT", @@ -1033,33 +1088,7 @@ static const char *ssl_hook_Fixup_vars[] = { "SSL_CLIENT_V_END", "SSL_CLIENT_V_REMAIN", "SSL_CLIENT_S_DN", - "SSL_CLIENT_S_DN_C", - "SSL_CLIENT_S_DN_ST", - "SSL_CLIENT_S_DN_L", - "SSL_CLIENT_S_DN_O", - "SSL_CLIENT_S_DN_OU", - "SSL_CLIENT_S_DN_CN", - "SSL_CLIENT_S_DN_T", - "SSL_CLIENT_S_DN_I", - "SSL_CLIENT_S_DN_G", - "SSL_CLIENT_S_DN_S", - "SSL_CLIENT_S_DN_D", - "SSL_CLIENT_S_DN_UID", - "SSL_CLIENT_S_DN_Email", "SSL_CLIENT_I_DN", - "SSL_CLIENT_I_DN_C", - "SSL_CLIENT_I_DN_ST", - "SSL_CLIENT_I_DN_L", - "SSL_CLIENT_I_DN_O", - "SSL_CLIENT_I_DN_OU", - "SSL_CLIENT_I_DN_CN", - "SSL_CLIENT_I_DN_T", - "SSL_CLIENT_I_DN_I", - "SSL_CLIENT_I_DN_G", - "SSL_CLIENT_I_DN_S", - "SSL_CLIENT_I_DN_D", - "SSL_CLIENT_I_DN_UID", - "SSL_CLIENT_I_DN_Email", "SSL_CLIENT_A_KEY", "SSL_CLIENT_A_SIG", "SSL_SERVER_M_VERSION", @@ -1067,33 +1096,7 @@ static const char *ssl_hook_Fixup_vars[] = { "SSL_SERVER_V_START", "SSL_SERVER_V_END", "SSL_SERVER_S_DN", - "SSL_SERVER_S_DN_C", - "SSL_SERVER_S_DN_ST", - "SSL_SERVER_S_DN_L", - "SSL_SERVER_S_DN_O", - "SSL_SERVER_S_DN_OU", - "SSL_SERVER_S_DN_CN", - "SSL_SERVER_S_DN_T", - "SSL_SERVER_S_DN_I", - "SSL_SERVER_S_DN_G", - "SSL_SERVER_S_DN_S", - "SSL_SERVER_S_DN_D", - "SSL_SERVER_S_DN_UID", - "SSL_SERVER_S_DN_Email", "SSL_SERVER_I_DN", - "SSL_SERVER_I_DN_C", - "SSL_SERVER_I_DN_ST", - "SSL_SERVER_I_DN_L", - "SSL_SERVER_I_DN_O", - "SSL_SERVER_I_DN_OU", - "SSL_SERVER_I_DN_CN", - "SSL_SERVER_I_DN_T", - "SSL_SERVER_I_DN_I", - "SSL_SERVER_I_DN_G", - "SSL_SERVER_I_DN_S", - "SSL_SERVER_I_DN_D", - "SSL_SERVER_I_DN_UID", - "SSL_SERVER_I_DN_Email", "SSL_SERVER_A_KEY", "SSL_SERVER_A_SIG", "SSL_SESSION_ID", @@ -1140,6 +1143,8 @@ int ssl_hook_Fixup(request_rec *r) /* standard SSL environment variables */ if (dc->nOptions & SSL_OPT_STDENVVARS) { + modssl_var_extract_dns(env, sslconn->ssl, r->pool); + for (i = 0; ssl_hook_Fixup_vars[i]; i++) { var = (char *)ssl_hook_Fixup_vars[i]; val = ssl_var_lookup(r->pool, r->server, r->connection, r, var); @@ -1175,6 +1180,12 @@ int ssl_hook_Fixup(request_rec *r) } } + +#ifdef SSL_get_secure_renegotiation_support + apr_table_setn(r->notes, "ssl-secure-reneg", + SSL_get_secure_renegotiation_support(ssl) ? "1" : "0"); +#endif + return DECLINED; } @@ -1844,76 +1855,55 @@ void ssl_callback_DelSessionCacheEntry(SSL_CTX *ctx, return; } -/* - * This callback function is executed while OpenSSL processes the - * SSL handshake and does SSL record layer stuff. We use it to - * trace OpenSSL's processing in out SSL logfile. - */ -void ssl_callback_LogTracingState(MODSSL_INFO_CB_ARG_TYPE ssl, int where, int rc) +/* Dump debugginfo trace to the log file. */ +static void log_tracing_state(MODSSL_INFO_CB_ARG_TYPE ssl, conn_rec *c, + server_rec *s, int where, int rc) { - conn_rec *c; - server_rec *s; - SSLSrvConfigRec *sc; - /* - * find corresponding server + * create the various trace messages */ - if (!(c = (conn_rec *)SSL_get_app_data((SSL *)ssl))) { - return; + if (where & SSL_CB_HANDSHAKE_START) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "%s: Handshake: start", SSL_LIBRARY_NAME); } - - s = mySrvFromConn(c); - if (!(sc = mySrvConfig(s))) { - return; + else if (where & SSL_CB_HANDSHAKE_DONE) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "%s: Handshake: done", SSL_LIBRARY_NAME); } - - /* - * create the various trace messages - */ - if (s->loglevel >= APLOG_DEBUG) { - if (where & SSL_CB_HANDSHAKE_START) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, - "%s: Handshake: start", SSL_LIBRARY_NAME); - } - else if (where & SSL_CB_HANDSHAKE_DONE) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, - "%s: Handshake: done", SSL_LIBRARY_NAME); - } - else if (where & SSL_CB_LOOP) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, - "%s: Loop: %s", - SSL_LIBRARY_NAME, SSL_state_string_long(ssl)); - } - else if (where & SSL_CB_READ) { + else if (where & SSL_CB_LOOP) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "%s: Loop: %s", + SSL_LIBRARY_NAME, SSL_state_string_long(ssl)); + } + else if (where & SSL_CB_READ) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "%s: Read: %s", + SSL_LIBRARY_NAME, SSL_state_string_long(ssl)); + } + else if (where & SSL_CB_WRITE) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "%s: Write: %s", + SSL_LIBRARY_NAME, SSL_state_string_long(ssl)); + } + else if (where & SSL_CB_ALERT) { + char *str = (where & SSL_CB_READ) ? "read" : "write"; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "%s: Alert: %s:%s:%s", + SSL_LIBRARY_NAME, str, + SSL_alert_type_string_long(rc), + SSL_alert_desc_string_long(rc)); + } + else if (where & SSL_CB_EXIT) { + if (rc == 0) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, - "%s: Read: %s", + "%s: Exit: failed in %s", SSL_LIBRARY_NAME, SSL_state_string_long(ssl)); } - else if (where & SSL_CB_WRITE) { + else if (rc < 0) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, - "%s: Write: %s", + "%s: Exit: error in %s", SSL_LIBRARY_NAME, SSL_state_string_long(ssl)); } - else if (where & SSL_CB_ALERT) { - char *str = (where & SSL_CB_READ) ? "read" : "write"; - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, - "%s: Alert: %s:%s:%s", - SSL_LIBRARY_NAME, str, - SSL_alert_type_string_long(rc), - SSL_alert_desc_string_long(rc)); - } - else if (where & SSL_CB_EXIT) { - if (rc == 0) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, - "%s: Exit: failed in %s", - SSL_LIBRARY_NAME, SSL_state_string_long(ssl)); - } - else if (rc < 0) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, - "%s: Exit: error in %s", - SSL_LIBRARY_NAME, SSL_state_string_long(ssl)); - } - } } /* @@ -1933,6 +1923,52 @@ void ssl_callback_LogTracingState(MODSSL_INFO_CB_ARG_TYPE ssl, int where, int rc } } +/* + * This callback function is executed while OpenSSL processes the SSL + * handshake and does SSL record layer stuff. It's used to trap + * client-initiated renegotiations, and for dumping everything to the + * log. + */ +void ssl_callback_Info(MODSSL_INFO_CB_ARG_TYPE ssl, int where, int rc) +{ + conn_rec *c; + server_rec *s; + SSLConnRec *scr; + + /* Retrieve the conn_rec and the associated SSLConnRec. */ + if ((c = (conn_rec *)SSL_get_app_data((SSL *)ssl)) == NULL) { + return; + } + + if ((scr = myConnConfig(c)) == NULL) { + return; + } + + /* If the reneg state is to reject renegotiations, check the SSL + * state machine and move to ABORT if a Client Hello is being + * read. */ + if ((where & SSL_CB_ACCEPT_LOOP) && scr->reneg_state == RENEG_REJECT) { + int state = SSL_get_state(ssl); + + if (state == SSL3_ST_SR_CLNT_HELLO_A + || state == SSL23_ST_SR_CLNT_HELLO_A) { + scr->reneg_state = RENEG_ABORT; + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, + "rejecting client initiated renegotiation"); + } + } + /* If the first handshake is complete, change state to reject any + * subsequent client-initated renegotiation. */ + else if ((where & SSL_CB_HANDSHAKE_DONE) && scr->reneg_state == RENEG_INIT) { + scr->reneg_state = RENEG_REJECT; + } + + s = mySrvFromConn(c); + if (s && s->loglevel >= APLOG_DEBUG) { + log_tracing_state(ssl, c, s, where, rc); + } +} + #ifndef OPENSSL_NO_TLSEXT /* * This callback function is executed when OpenSSL encounters an extended diff --git a/modules/ssl/ssl_engine_vars.c b/modules/ssl/ssl_engine_vars.c index ab99af9e..83cff2c1 100644 --- a/modules/ssl/ssl_engine_vars.c +++ b/modules/ssl/ssl_engine_vars.c @@ -326,6 +326,14 @@ static char *ssl_var_lookup_ssl(apr_pool_t *p, conn_rec *c, char *var) TLSEXT_NAMETYPE_host_name)); } #endif + else if (ssl != NULL && strcEQ(var, "SECURE_RENEG")) { + int flag = 0; +#ifdef SSL_get_secure_renegotiation_support + flag = SSL_get_secure_renegotiation_support(ssl); +#endif + result = apr_pstrdup(p, flag ? "true" : "false"); + } + return result; } @@ -402,27 +410,31 @@ static char *ssl_var_lookup_ssl_cert(apr_pool_t *p, X509 *xs, char *var) return result; } +/* In this table, .extract is non-zero if RDNs using the NID should be + * extracted to for the SSL_{CLIENT,SERVER}_{I,S}_DN_* environment + * variables. */ static const struct { char *name; int nid; + int extract; } ssl_var_lookup_ssl_cert_dn_rec[] = { - { "C", NID_countryName }, - { "ST", NID_stateOrProvinceName }, /* officially (RFC2156) */ - { "SP", NID_stateOrProvinceName }, /* compatibility (SSLeay) */ - { "L", NID_localityName }, - { "O", NID_organizationName }, - { "OU", NID_organizationalUnitName }, - { "CN", NID_commonName }, - { "T", NID_title }, - { "I", NID_initials }, - { "G", NID_givenName }, - { "S", NID_surname }, - { "D", NID_description }, + { "C", NID_countryName, 1 }, + { "ST", NID_stateOrProvinceName, 1 }, /* officially (RFC2156) */ + { "SP", NID_stateOrProvinceName, 0 }, /* compatibility (SSLeay) */ + { "L", NID_localityName, 1 }, + { "O", NID_organizationName, 1 }, + { "OU", NID_organizationalUnitName, 1 }, + { "CN", NID_commonName, 1 }, + { "T", NID_title, 1 }, + { "I", NID_initials, 1 }, + { "G", NID_givenName, 1 }, + { "S", NID_surname, 1 }, + { "D", NID_description, 1 }, #ifdef NID_userId - { "UID", NID_userId }, + { "UID", NID_x500UniqueIdentifier, 1 }, #endif - { "Email", NID_pkcs9_emailAddress }, - { NULL, 0 } + { "Email", NID_pkcs9_emailAddress, 1 }, + { NULL, 0, 0 } }; static char *ssl_var_lookup_ssl_cert_dn(apr_pool_t *p, X509_NAME *xsname, char *var) @@ -626,7 +638,7 @@ static char *ssl_var_lookup_ssl_cipher(apr_pool_t *p, conn_rec *c, char *var) ssl_var_lookup_ssl_cipher_bits(ssl, &usekeysize, &algkeysize); if (ssl && strEQ(var, "")) { - const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); + MODSSL_SSL_CIPHER_CONST SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); result = (cipher != NULL ? (char *)SSL_CIPHER_get_name(cipher) : NULL); } else if (strcEQ(var, "_EXPORT")) @@ -647,7 +659,7 @@ static char *ssl_var_lookup_ssl_cipher(apr_pool_t *p, conn_rec *c, char *var) static void ssl_var_lookup_ssl_cipher_bits(SSL *ssl, int *usekeysize, int *algkeysize) { - const SSL_CIPHER *cipher; + MODSSL_SSL_CIPHER_CONST SSL_CIPHER *cipher; *usekeysize = 0; *algkeysize = 0; @@ -671,6 +683,96 @@ static char *ssl_var_lookup_ssl_version(apr_pool_t *p, char *var) return NULL; } +/* Add each RDN in 'xn' to the table 't' where the NID is present in + * 'nids', using key prefix 'pfx'. */ +static void extract_dn(apr_table_t *t, apr_hash_t *nids, const char *pfx, + X509_NAME *xn, apr_pool_t *p) +{ + STACK_OF(X509_NAME_ENTRY) *ents = X509_NAME_get_entries(xn); + X509_NAME_ENTRY *xsne; + apr_hash_t *count; + int i, nid; + + /* Hash of (int) NID -> (int *) counter to count each time an RDN + * with the given NID has been seen. */ + count = apr_hash_make(p); + + /* For each RDN... */ + for (i = 0; i < sk_X509_NAME_ENTRY_num(ents); i++) { + const char *tag; + + xsne = sk_X509_NAME_ENTRY_value(ents, i); + + /* Retrieve the nid, and check whether this is one of the nids + * which are to be extracted. */ + nid = OBJ_obj2nid((ASN1_OBJECT *)X509_NAME_ENTRY_get_object(xsne)); + + tag = apr_hash_get(nids, &nid, sizeof nid); + if (tag) { + unsigned char *data = X509_NAME_ENTRY_get_data_ptr(xsne); + const char *key; + int *dup; + char *value; + + /* Check whether a variable with this nid was already + * been used; if so, use the foo_N=bar syntax. */ + dup = apr_hash_get(count, &nid, sizeof nid); + if (dup) { + key = apr_psprintf(p, "%s%s_%d", pfx, tag, ++(*dup)); + } + else { + /* Otherwise, use the plain foo=bar syntax. */ + dup = apr_pcalloc(p, sizeof *dup); + apr_hash_set(count, &nid, sizeof nid, dup); + key = apr_pstrcat(p, pfx, tag, NULL); + } + + /* cast needed from 'unsigned char *' to 'char *' */ + value = apr_pstrmemdup(p, (char *)data, + X509_NAME_ENTRY_get_data_len(xsne)); +#if APR_CHARSET_EBCDIC + ap_xlate_proto_from_ascii(value, X509_NAME_ENTRY_get_data_len(xsne)); +#endif /* APR_CHARSET_EBCDIC */ + apr_table_setn(t, key, value); + } + } +} + +void modssl_var_extract_dns(apr_table_t *t, SSL *ssl, apr_pool_t *p) +{ + apr_hash_t *nids; + unsigned n; + X509 *xs; + + /* Build up a hash table of (int *)NID->(char *)short-name for all + * the tags which are to be extracted: */ + nids = apr_hash_make(p); + for (n = 0; ssl_var_lookup_ssl_cert_dn_rec[n].name; n++) { + if (ssl_var_lookup_ssl_cert_dn_rec[n].extract) { + apr_hash_set(nids, &ssl_var_lookup_ssl_cert_dn_rec[n].nid, + sizeof(ssl_var_lookup_ssl_cert_dn_rec[0].nid), + ssl_var_lookup_ssl_cert_dn_rec[n].name); + } + } + + /* Extract the server cert DNS -- note that the refcount does NOT + * increase: */ + xs = SSL_get_certificate(ssl); + if (xs) { + extract_dn(t, nids, "SSL_SERVER_S_DN_", X509_get_subject_name(xs), p); + extract_dn(t, nids, "SSL_SERVER_I_DN_", X509_get_issuer_name(xs), p); + } + + /* Extract the client cert DNs -- note that the refcount DOES + * increase: */ + xs = SSL_get_peer_certificate(ssl); + if (xs) { + extract_dn(t, nids, "SSL_CLIENT_S_DN_", X509_get_subject_name(xs), p); + extract_dn(t, nids, "SSL_CLIENT_I_DN_", X509_get_issuer_name(xs), p); + X509_free(xs); + } +} + const char *ssl_ext_lookup(apr_pool_t *p, conn_rec *c, int peer, const char *oidnum) { diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h index 818ed983..0613f0d2 100644 --- a/modules/ssl/ssl_private.h +++ b/modules/ssl/ssl_private.h @@ -356,6 +356,20 @@ typedef struct { int is_proxy; int disabled; int non_ssl_request; + + /* Track the handshake/renegotiation state for the connection so + * that all client-initiated renegotiations can be rejected, as a + * partial fix for CVE-2009-3555. */ + enum { + RENEG_INIT = 0, /* Before initial handshake */ + RENEG_REJECT, /* After initial handshake; any client-initiated + * renegotiation should be rejected */ + RENEG_ALLOW, /* A server-initated renegotiation is taking + * place (as dictated by configuration) */ + RENEG_ABORT /* Renegotiation initiated by client, abort the + * connection */ + } reneg_state; + server_rec *server; } SSLConnRec; @@ -457,6 +471,7 @@ struct SSLSrvConfigRec { int vhost_id_len; int session_cache_timeout; BOOL cipher_server_pref; + BOOL insecure_reneg; modssl_ctx_t *server; modssl_ctx_t *proxy; ssl_enabled_t proxy_ssl_check_peer_expire; @@ -531,6 +546,7 @@ const char *ssl_cmd_SSLRequire(cmd_parms *, void *, const char *); const char *ssl_cmd_SSLUserName(cmd_parms *, void *, const char *); const char *ssl_cmd_SSLRenegBufferSize(cmd_parms *cmd, void *dcfg, const char *arg); const char *ssl_cmd_SSLStrictSNIVHostCheck(cmd_parms *cmd, void *dcfg, int flag); +const char *ssl_cmd_SSLInsecureRenegotiation(cmd_parms *cmd, void *dcfg, int flag); const char *ssl_cmd_SSLProxyEngine(cmd_parms *cmd, void *dcfg, int flag); const char *ssl_cmd_SSLProxyProtocol(cmd_parms *, void *, const char *); @@ -574,7 +590,7 @@ int ssl_callback_proxy_cert(SSL *ssl, MODSSL_CLIENT_CERT_CB_ARG_TYPE ** int ssl_callback_NewSessionCacheEntry(SSL *, SSL_SESSION *); SSL_SESSION *ssl_callback_GetSessionCacheEntry(SSL *, unsigned char *, int, int *); void ssl_callback_DelSessionCacheEntry(SSL_CTX *, SSL_SESSION *); -void ssl_callback_LogTracingState(MODSSL_INFO_CB_ARG_TYPE, int, int); +void ssl_callback_Info(MODSSL_INFO_CB_ARG_TYPE, int, int); #ifndef OPENSSL_NO_TLSEXT int ssl_callback_ServerNameIndication(SSL *, int *, modssl_ctx_t *); #endif @@ -681,6 +697,10 @@ extern apr_array_header_t *ssl_extlist_by_oid(request_rec *r, const char *oidstr void ssl_var_log_config_register(apr_pool_t *p); +/* Extract SSL_*_DN_* variables into table 't' from SSL object 'ssl', + * allocating from 'p': */ +void modssl_var_extract_dns(apr_table_t *t, SSL *ssl, apr_pool_t *p); + #define APR_SHM_MAXSIZE (64 * 1024 * 1024) #endif /* SSL_PRIVATE_H */ diff --git a/modules/ssl/ssl_toolkit_compat.h b/modules/ssl/ssl_toolkit_compat.h index 06a22669..a841eccd 100644 --- a/modules/ssl/ssl_toolkit_compat.h +++ b/modules/ssl/ssl_toolkit_compat.h @@ -93,8 +93,10 @@ /** ...shifting sands of openssl... */ #if (OPENSSL_VERSION_NUMBER >= 0x0090707f) # define MODSSL_D2I_SSL_SESSION_CONST const +# define MODSSL_SSL_CIPHER_CONST const #else # define MODSSL_D2I_SSL_SESSION_CONST +# define MODSSL_SSL_CIPHER_CONST #endif #if (OPENSSL_VERSION_NUMBER >= 0x00908000) |
