diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-25 04:41:26 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-25 04:41:26 +0000 |
commit | 7b31d4f4901cdb89a79f2f7de4a6b8bb637b523b (patch) | |
tree | fdeb0b5ff80273f95ce61607fc3613dff0b9a235 /server | |
parent | Adding upstream version 2.4.38. (diff) | |
download | apache2-upstream.tar.xz apache2-upstream.zip |
Adding upstream version 2.4.59.upstream/2.4.59upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'server')
42 files changed, 3487 insertions, 1782 deletions
diff --git a/server/Makefile.in b/server/Makefile.in index 1fa3344..8111877 100644 --- a/server/Makefile.in +++ b/server/Makefile.in @@ -13,7 +13,7 @@ LTLIBRARY_SOURCES = \ mpm_common.c mpm_unix.c mpm_fdqueue.c \ util_charset.c util_cookies.c util_debug.c util_xml.c \ util_filter.c util_pcre.c util_regex.c exports.c \ - scoreboard.c error_bucket.c protocol.c core.c request.c provider.c \ + scoreboard.c error_bucket.c protocol.c core.c request.c ssl.c provider.c \ eoc_bucket.c eor_bucket.c core_filters.c \ util_expr_parse.c util_expr_scan.c util_expr_eval.c diff --git a/server/NWGNUmakefile b/server/NWGNUmakefile index 7f96e81..4917811 100644 --- a/server/NWGNUmakefile +++ b/server/NWGNUmakefile @@ -225,7 +225,7 @@ FILES_lib_objs = \ $(EOLIST) # -# implement targets and dependancies (leave this section alone) +# implement targets and dependencies (leave this section alone) # libs :: $(OBJDIR) $(TARGET_lib) diff --git a/server/buildmark.c b/server/buildmark.c index a9cd684..36bd713 100644 --- a/server/buildmark.c +++ b/server/buildmark.c @@ -23,7 +23,7 @@ static const char server_built[] = __DATE__ " " __TIME__; static const char server_built[] = "unknown"; #endif -AP_DECLARE(const char *) ap_get_server_built() +AP_DECLARE(const char *) ap_get_server_built(void) { return server_built; } diff --git a/server/config.c b/server/config.c index f815b22..3d11ff5 100644 --- a/server/config.c +++ b/server/config.c @@ -59,7 +59,6 @@ AP_DECLARE_DATA const char *ap_server_argv0 = NULL; AP_DECLARE_DATA const char *ap_server_root = NULL; -AP_DECLARE_DATA const char *ap_runtime_dir = NULL; AP_DECLARE_DATA server_rec *ap_server_conf = NULL; AP_DECLARE_DATA apr_pool_t *ap_pglobal = NULL; @@ -1038,11 +1037,11 @@ static const char *invoke_cmd(const command_rec *cmd, cmd_parms *parms, */ w = ap_getword_conf(parms->temp_pool, &args); - if (*w == '\0' || (strcasecmp(w, "on") && strcasecmp(w, "off"))) + if (*w == '\0' || (ap_cstr_casecmp(w, "on") && ap_cstr_casecmp(w, "off"))) return apr_pstrcat(parms->pool, cmd->name, " must be On or Off", NULL); - return cmd->AP_FLAG(parms, mconfig, strcasecmp(w, "off") != 0); + return cmd->AP_FLAG(parms, mconfig, ap_cstr_casecmp(w, "off") != 0); default: return apr_pstrcat(parms->pool, cmd->name, @@ -1055,7 +1054,7 @@ AP_CORE_DECLARE(const command_rec *) ap_find_command(const char *name, const command_rec *cmds) { while (cmds->name) { - if (!strcasecmp(name, cmds->name)) + if (!ap_cstr_casecmp(name, cmds->name)) return cmds; ++cmds; @@ -1118,8 +1117,9 @@ static const char *ap_build_config_sub(apr_pool_t *p, apr_pool_t *temp_pool, const char *args; char *cmd_name; ap_directive_t *newdir; - module *mod = ap_top_module; const command_rec *cmd; + ap_mod_list *ml; + char *lname; if (*l == '#' || *l == '\0') return NULL; @@ -1157,9 +1157,12 @@ static const char *ap_build_config_sub(apr_pool_t *p, apr_pool_t *temp_pool, newdir->line_num = parms->config_file->line_number; newdir->args = apr_pstrdup(p, args); - if ((cmd = ap_find_command_in_modules(cmd_name, &mod)) != NULL) { + lname = apr_pstrdup(temp_pool, cmd_name); + ap_str_tolower(lname); + ml = apr_hash_get(ap_config_hash, lname, APR_HASH_KEY_STRING); + + if (ml && (cmd = ml->cmd) != NULL) { newdir->directive = cmd->name; - if (cmd->req_override & EXEC_ON_READ) { ap_directive_t *sub_tree = NULL; @@ -1220,7 +1223,7 @@ static const char *ap_build_config_sub(apr_pool_t *p, apr_pool_t *temp_pool, *bracket = '\0'; - if (strcasecmp(cmd_name + 2, + if (ap_cstr_casecmp(cmd_name + 2, (*curr_parent)->directive + 1) != 0) { parms->err_directive = newdir; return apr_pstrcat(p, "Expected </", @@ -1267,7 +1270,7 @@ AP_DECLARE(const char *) ap_build_cont_config(apr_pool_t *p, while ((rc = ap_varbuf_cfg_getline(&vb, parms->config_file, max_len)) == APR_SUCCESS) { if (!memcmp(vb.buf, "</", 2) - && (strcasecmp(vb.buf + 2, bracket) == 0) + && (ap_cstr_casecmp(vb.buf + 2, bracket) == 0) && (*curr_parent == NULL)) { break; } @@ -1542,8 +1545,8 @@ AP_DECLARE_NONSTD(const char *) ap_set_file_slot(cmd_parms *cmd, void *struct_pt path = ap_server_root_relative(cmd->pool, arg); if (!path) { - return apr_pstrcat(cmd->pool, "Invalid file path ", - arg, NULL); + return apr_pstrcat(cmd->pool, cmd->cmd->name, ": Invalid file path '", + arg, "'", NULL); } *(const char **) ((char*)struct_ptr + offset) = path; @@ -1644,7 +1647,7 @@ AP_DECLARE(const char *) ap_soak_end_container(cmd_parms *cmd, char *directive) if (cmd_name[1] == '/') { cmd_name[strlen(cmd_name) - 1] = '\0'; - if (strcasecmp(cmd_name + 2, directive + 1) != 0) { + if (ap_cstr_casecmp(cmd_name + 2, directive + 1) != 0) { return apr_pstrcat(cmd->pool, "Expected </", directive + 1, "> but saw ", cmd_name, ">", NULL); @@ -1793,18 +1796,6 @@ static const char *process_command_config(server_rec *s, return NULL; } -typedef struct { - const char *fname; -} fnames; - -static int fname_alphasort(const void *fn1, const void *fn2) -{ - const fnames *f1 = fn1; - const fnames *f2 = fn2; - - return strcmp(f1->fname,f2->fname); -} - /** * Used by -D DUMP_INCLUDES to output the config file "tree". */ @@ -1897,200 +1888,15 @@ AP_DECLARE(const char *) ap_process_resource_config(server_rec *s, return NULL; } -static const char *process_resource_config_nofnmatch(server_rec *s, - const char *fname, - ap_directive_t **conftree, - apr_pool_t *p, - apr_pool_t *ptemp, - unsigned depth, - int optional) -{ - const char *error; - apr_status_t rv; - - if (ap_is_directory(ptemp, fname)) { - apr_dir_t *dirp; - apr_finfo_t dirent; - int current; - apr_array_header_t *candidates = NULL; - fnames *fnew; - char *path = apr_pstrdup(ptemp, fname); - - if (++depth > AP_MAX_INCLUDE_DIR_DEPTH) { - return apr_psprintf(p, "Directory %s exceeds the maximum include " - "directory nesting level of %u. You have " - "probably a recursion somewhere.", path, - AP_MAX_INCLUDE_DIR_DEPTH); - } - - /* - * first course of business is to grok all the directory - * entries here and store 'em away. Recall we need full pathnames - * for this. - */ - rv = apr_dir_open(&dirp, path, ptemp); - if (rv != APR_SUCCESS) { - return apr_psprintf(p, "Could not open config directory %s: %pm", - path, &rv); - } - - candidates = apr_array_make(ptemp, 1, sizeof(fnames)); - while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) { - /* strip out '.' and '..' */ - if (strcmp(dirent.name, ".") - && strcmp(dirent.name, "..")) { - fnew = (fnames *) apr_array_push(candidates); - fnew->fname = ap_make_full_path(ptemp, path, dirent.name); - } - } - - apr_dir_close(dirp); - if (candidates->nelts != 0) { - qsort((void *) candidates->elts, candidates->nelts, - sizeof(fnames), fname_alphasort); - - /* - * Now recurse these... we handle errors and subdirectories - * via the recursion, which is nice - */ - for (current = 0; current < candidates->nelts; ++current) { - fnew = &((fnames *) candidates->elts)[current]; - error = process_resource_config_nofnmatch(s, fnew->fname, - conftree, p, ptemp, - depth, optional); - if (error) { - return error; - } - } - } - - return NULL; - } - else if (optional) { - /* If the optinal flag is set (like for IncludeOptional) we can - * tolerate that no file or directory is present and bail out. - */ - apr_finfo_t finfo; - if (apr_stat(&finfo, fname, APR_FINFO_TYPE, ptemp) != APR_SUCCESS - || finfo.filetype == APR_NOFILE) - return NULL; - } - - return ap_process_resource_config(s, fname, conftree, p, ptemp); -} +typedef struct { + server_rec *s; + ap_directive_t **conftree; +} configs; -static const char *process_resource_config_fnmatch(server_rec *s, - const char *path, - const char *fname, - ap_directive_t **conftree, - apr_pool_t *p, - apr_pool_t *ptemp, - unsigned depth, - int optional) +static const char *process_resource_config_cb(ap_dir_match_t *w, const char *fname) { - const char *rest; - apr_status_t rv; - apr_dir_t *dirp; - apr_finfo_t dirent; - apr_array_header_t *candidates = NULL; - fnames *fnew; - int current; - - /* find the first part of the filename */ - rest = ap_strchr_c(fname, '/'); - if (rest) { - fname = apr_pstrmemdup(ptemp, fname, rest - fname); - rest++; - } - - /* optimisation - if the filename isn't a wildcard, process it directly */ - if (!apr_fnmatch_test(fname)) { - path = ap_make_full_path(ptemp, path, fname); - if (!rest) { - return process_resource_config_nofnmatch(s, path, - conftree, p, - ptemp, 0, optional); - } - else { - return process_resource_config_fnmatch(s, path, rest, - conftree, p, - ptemp, 0, optional); - } - } - - /* - * first course of business is to grok all the directory - * entries here and store 'em away. Recall we need full pathnames - * for this. - */ - rv = apr_dir_open(&dirp, path, ptemp); - if (rv != APR_SUCCESS) { - /* If the directory doesn't exist and the optional flag is set - * there is no need to return an error. - */ - if (rv == APR_ENOENT && optional) { - return NULL; - } - return apr_psprintf(p, "Could not open config directory %s: %pm", - path, &rv); - } - - candidates = apr_array_make(ptemp, 1, sizeof(fnames)); - while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp) == APR_SUCCESS) { - /* strip out '.' and '..' */ - if (strcmp(dirent.name, ".") - && strcmp(dirent.name, "..") - && (apr_fnmatch(fname, dirent.name, - APR_FNM_PERIOD) == APR_SUCCESS)) { - const char *full_path = ap_make_full_path(ptemp, path, dirent.name); - /* If matching internal to path, and we happen to match something - * other than a directory, skip it - */ - if (rest && (dirent.filetype != APR_DIR)) { - continue; - } - fnew = (fnames *) apr_array_push(candidates); - fnew->fname = full_path; - } - } - - apr_dir_close(dirp); - if (candidates->nelts != 0) { - const char *error; - - qsort((void *) candidates->elts, candidates->nelts, - sizeof(fnames), fname_alphasort); - - /* - * Now recurse these... we handle errors and subdirectories - * via the recursion, which is nice - */ - for (current = 0; current < candidates->nelts; ++current) { - fnew = &((fnames *) candidates->elts)[current]; - if (!rest) { - error = process_resource_config_nofnmatch(s, fnew->fname, - conftree, p, - ptemp, 0, optional); - } - else { - error = process_resource_config_fnmatch(s, fnew->fname, rest, - conftree, p, - ptemp, 0, optional); - } - if (error) { - return error; - } - } - } - else { - - if (!optional) { - return apr_psprintf(p, "No matches for the wildcard '%s' in '%s', failing " - "(use IncludeOptional if required)", fname, path); - } - } - - return NULL; + configs *cfgs = w->ctx; + return ap_process_resource_config(cfgs->s, fname, cfgs->conftree, w->p, w->ptemp); } AP_DECLARE(const char *) ap_process_fnmatch_configs(server_rec *s, @@ -2100,8 +1906,19 @@ AP_DECLARE(const char *) ap_process_fnmatch_configs(server_rec *s, apr_pool_t *ptemp, int optional) { - /* XXX: lstat() won't work on the wildcard pattern... - */ + configs cfgs; + ap_dir_match_t w; + + cfgs.s = s; + cfgs.conftree = conftree; + + w.prefix = "Include/IncludeOptional: "; + w.p = p; + w.ptemp = ptemp; + w.flags = (optional ? AP_DIR_FLAG_OPTIONAL : AP_DIR_FLAG_NONE) | AP_DIR_FLAG_RECURSIVE; + w.cb = process_resource_config_cb; + w.ctx = &cfgs; + w.depth = 0; /* don't require conf/httpd.conf if we have a -C or -c switch */ if ((ap_server_pre_read_config->nelts @@ -2114,7 +1931,7 @@ AP_DECLARE(const char *) ap_process_fnmatch_configs(server_rec *s, } if (!apr_fnmatch_test(fname)) { - return process_resource_config_nofnmatch(s, fname, conftree, p, ptemp, 0, optional); + return ap_dir_nofnmatch(&w, fname); } else { apr_status_t status; @@ -2132,8 +1949,7 @@ AP_DECLARE(const char *) ap_process_fnmatch_configs(server_rec *s, } /* walk the filepath */ - return process_resource_config_fnmatch(s, rootpath, filepath, conftree, p, ptemp, - 0, optional); + return ap_dir_fnmatch(&w, rootpath, filepath); } } @@ -2485,6 +2301,9 @@ AP_DECLARE(server_rec*) ap_read_config(process_rec *process, apr_pool_t *ptemp, if (s == NULL) { return s; } + if (ap_server_conf == NULL) { + ap_server_conf = s; + } init_config_globals(p); @@ -2720,7 +2539,7 @@ static int count_directives_sub(const char *directive, ap_directive_t *current) while (current != NULL) { if (current->first_child != NULL) count += count_directives_sub(directive, current->first_child); - if (strcasecmp(current->directive, directive) == 0) + if (ap_cstr_casecmp(current->directive, directive) == 0) count++; current = current->next; } diff --git a/server/connection.c b/server/connection.c index 1974537..bbc94c4 100644 --- a/server/connection.c +++ b/server/connection.c @@ -122,9 +122,7 @@ AP_DECLARE(int) ap_start_lingering_close(conn_rec *c) { apr_socket_t *csd = ap_get_conn_socket(c); - if (!csd) { - return 1; - } + ap_assert(csd != NULL); if (ap_prep_lingering_close(c)) { return 1; @@ -139,18 +137,12 @@ AP_DECLARE(int) ap_start_lingering_close(conn_rec *c) ap_flush_conn(c); #ifdef NO_LINGCLOSE - apr_socket_close(csd); return 1; #else /* Shut down the socket for write, which will send a FIN * to the peer. */ - if (c->aborted - || apr_socket_shutdown(csd, APR_SHUTDOWN_WRITE) != APR_SUCCESS) { - apr_socket_close(csd); - return 1; - } - return 0; + return (c->aborted || apr_socket_shutdown(csd, APR_SHUTDOWN_WRITE)); #endif } @@ -161,7 +153,17 @@ AP_DECLARE(void) ap_lingering_close(conn_rec *c) apr_time_t now, timeup = 0; apr_socket_t *csd = ap_get_conn_socket(c); + if (!csd) { + /* Be safe with third-party modules that: + * ap_set_core_module_config(c->conn_config, NULL) + * to no-op ap_lingering_close(). + */ + c->aborted = 1; + return; + } + if (ap_start_lingering_close(c)) { + apr_socket_close(csd); return; } @@ -207,13 +209,9 @@ AP_DECLARE(void) ap_lingering_close(conn_rec *c) AP_CORE_DECLARE(void) ap_process_connection(conn_rec *c, void *csd) { - int rc; ap_update_vhost_given_ip(c); - rc = ap_run_pre_connection(c, csd); - if (rc != OK && rc != DONE) { - c->aborted = 1; - } + ap_pre_connection(c, csd); if (!c->aborted) { ap_run_process_connection(c); diff --git a/server/core.c b/server/core.c index e2a91c7..e5e059e 100644 --- a/server/core.c +++ b/server/core.c @@ -22,6 +22,11 @@ #include "apr_thread_proc.h" /* for RLIMIT stuff */ #include "apr_random.h" +#include "apr_version.h" +#if APR_MAJOR_VERSION < 2 +#include "apu_version.h" +#endif + #define APR_WANT_IOVEC #define APR_WANT_STRFUNC #define APR_WANT_MEMFUNC @@ -33,6 +38,7 @@ #include "http_core.h" #include "http_protocol.h" /* For index_of_response(). Grump. */ #include "http_request.h" +#include "http_ssl.h" #include "http_vhost.h" #include "http_main.h" /* For the default_handler below... */ #include "http_log.h" @@ -61,11 +67,13 @@ /* LimitRequestBody handling */ #define AP_LIMIT_REQ_BODY_UNSET ((apr_off_t) -1) -#define AP_DEFAULT_LIMIT_REQ_BODY ((apr_off_t) 0) +#define AP_DEFAULT_LIMIT_REQ_BODY ((apr_off_t) 1<<30) /* 1GB */ /* LimitXMLRequestBody handling */ #define AP_LIMIT_UNSET ((long) -1) #define AP_DEFAULT_LIMIT_XML_BODY ((apr_size_t)1000000) +/* Hard limit for ap_escape_html2() */ +#define AP_MAX_LIMIT_XML_BODY ((apr_size_t)(APR_SIZE_MAX / 6 - 1)) #define AP_MIN_SENDFILE_BYTES (256) @@ -81,9 +89,13 @@ #define AP_CONTENT_MD5_ON 1 #define AP_CONTENT_MD5_UNSET 2 +#define AP_FLUSH_MAX_THRESHOLD 65535 +#define AP_FLUSH_MAX_PIPELINED 4 + APR_HOOK_STRUCT( APR_HOOK_LINK(get_mgmt_items) APR_HOOK_LINK(insert_network_bucket) + APR_HOOK_LINK(get_pollfd_from_conn) ) AP_IMPLEMENT_HOOK_RUN_ALL(int, get_mgmt_items, @@ -95,6 +107,11 @@ AP_IMPLEMENT_HOOK_RUN_FIRST(apr_status_t, insert_network_bucket, apr_socket_t *socket), (c, bb, socket), AP_DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(apr_status_t, get_pollfd_from_conn, + (conn_rec *c, struct apr_pollfd_t *pfd, + apr_interval_time_t *ptimeout), + (c, pfd, ptimeout), APR_ENOTIMPL) + /* Server core module... This module provides support for really basic * server operations, including options and commands which control the * operation of other modules. Consider this the bureaucracy module. @@ -122,8 +139,12 @@ AP_DECLARE_DATA int ap_document_root_check = 1; /* magic pointer for ErrorDocument xxx "default" */ static char errordocument_default; +/* Global state allocated out of pconf: variables here MUST be + * cleared/reset in reset_config(), a pconf cleanup, to avoid the + * variable getting reused after the pool is cleared. */ static apr_array_header_t *saved_server_config_defines = NULL; static apr_table_t *server_config_defined_vars = NULL; +AP_DECLARE_DATA const char *ap_runtime_dir = NULL; AP_DECLARE_DATA int ap_main_state = AP_SQ_MS_INITIAL_STARTUP; AP_DECLARE_DATA int ap_run_mode = AP_SQ_RM_UNKNOWN; @@ -390,6 +411,13 @@ static void *merge_core_dir_configs(apr_pool_t *a, void *basev, void *newv) if (new->enable_sendfile != ENABLE_SENDFILE_UNSET) { conf->enable_sendfile = new->enable_sendfile; } + + if (new->read_buf_size) { + conf->read_buf_size = new->read_buf_size; + } + else { + conf->read_buf_size = base->read_buf_size; + } conf->allow_encoded_slashes = new->allow_encoded_slashes; conf->decode_encoded_slashes = new->decode_encoded_slashes; @@ -462,14 +490,18 @@ static void *create_core_server_config(apr_pool_t *a, server_rec *s) apr_table_setn(conf->accf_map, "http", "data"); apr_table_setn(conf->accf_map, "https", "data"); #endif + + conf->flush_max_threshold = AP_FLUSH_MAX_THRESHOLD; + conf->flush_max_pipelined = AP_FLUSH_MAX_PIPELINED; } - /* pcalloc'ed - we have NULL's/0's - else ** is_virtual ** { - conf->ap_document_root = NULL; - conf->access_name = NULL; - conf->accf_map = NULL; + else { + /* Use main ErrorLogFormat while the vhost is loading */ + core_server_config *main_conf = + ap_get_core_module_config(ap_server_conf->module_config); + conf->error_log_format = main_conf->error_log_format; + + conf->flush_max_pipelined = -1; } - */ /* initialization, no special case for global context */ @@ -490,7 +522,10 @@ static void *create_core_server_config(apr_pool_t *a, server_rec *s) conf->protocols = apr_array_make(a, 5, sizeof(const char *)); conf->protocols_honor_order = -1; + conf->merge_slashes = AP_CORE_CONFIG_UNSET; + conf->strict_host_check= AP_CORE_CONFIG_UNSET; + return (void *)conf; } @@ -555,7 +590,22 @@ static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv) conf->protocols_honor_order = ((virt->protocols_honor_order < 0)? base->protocols_honor_order : virt->protocols_honor_order); + AP_CORE_MERGE_FLAG(merge_slashes, conf, base, virt); + + conf->flush_max_threshold = (virt->flush_max_threshold) + ? virt->flush_max_threshold + : base->flush_max_threshold; + conf->flush_max_pipelined = (virt->flush_max_pipelined >= 0) + ? virt->flush_max_pipelined + : base->flush_max_pipelined; + + conf->strict_host_check = (virt->strict_host_check != AP_CORE_CONFIG_UNSET) + ? virt->strict_host_check + : base->strict_host_check; + + AP_CORE_MERGE_FLAG(strict_host_check, conf, base, virt); + return conf; } @@ -693,6 +743,7 @@ void ap_core_reorder_directories(apr_pool_t *p, server_rec *s) /* we have to allocate tmp space to do a stable sort */ apr_pool_create(&tmp, p); + apr_pool_tag(tmp, "core_reorder_directories"); sortbin = apr_palloc(tmp, sec_dir->nelts * sizeof(*sortbin)); for (i = 0; i < nelts; ++i) { sortbin[i].orig_index = i; @@ -735,7 +786,7 @@ AP_DECLARE(int) ap_allow_overrides(request_rec *r) /* * Optional function coming from mod_authn_core, used for - * retrieving the type of autorization + * retrieving the type of authorization */ static APR_OPTIONAL_FN_TYPE(authn_ap_auth_type) *authn_ap_auth_type; @@ -1217,6 +1268,13 @@ AP_DECLARE(apr_off_t) ap_get_limit_req_body(const request_rec *r) return d->limit_req_body; } +AP_DECLARE(apr_size_t) ap_get_read_buf_size(const request_rec *r) +{ + core_dir_config *d = ap_get_core_module_config(r->per_dir_config); + + return d->read_buf_size ? d->read_buf_size : AP_IOBUFSIZE; +} + /***************************************************************** * @@ -1233,7 +1291,7 @@ static const ap_directive_t * find_parent(const ap_directive_t *dirp, dirp = dirp->parent; /* ### it would be nice to have atom-ized directives */ - if (strcasecmp(dirp->directive, what) == 0) + if (ap_cstr_casecmp(dirp->directive, what) == 0) return dirp; } @@ -1412,6 +1470,7 @@ static int reset_config_defines(void *dummy) ap_server_config_defines = saved_server_config_defines; saved_server_config_defines = NULL; server_config_defined_vars = NULL; + ap_runtime_dir = NULL; return OK; } @@ -1518,7 +1577,7 @@ static const char *set_gprof_dir(cmd_parms *cmd, void *dummy, const char *arg) return err; } - conf->gprof_dir = arg; + conf->gprof_dir = apr_pstrdup(cmd->pool, arg); return NULL; } #endif /*GPROF*/ @@ -1528,10 +1587,10 @@ static const char *set_add_default_charset(cmd_parms *cmd, { core_dir_config *d = d_; - if (!strcasecmp(arg, "Off")) { + if (!ap_cstr_casecmp(arg, "Off")) { d->add_default_charset = ADD_DEFAULT_CHARSET_OFF; } - else if (!strcasecmp(arg, "On")) { + else if (!ap_cstr_casecmp(arg, "On")) { d->add_default_charset = ADD_DEFAULT_CHARSET_ON; d->add_default_charset_name = DEFAULT_ADD_DEFAULT_CHARSET_NAME; } @@ -1648,7 +1707,7 @@ static const char *set_error_document(cmd_parms *cmd, void *conf_, conf->response_code_exprs = apr_hash_make(cmd->pool); } - if (strcasecmp(msg, "default") == 0) { + if (ap_cstr_casecmp(msg, "default") == 0) { /* special case: ErrorDocument 404 default restores the * canned server error response */ @@ -1704,36 +1763,36 @@ static const char *set_allow_opts(cmd_parms *cmd, allow_options_t *opts, first = 0; } - if (!strcasecmp(w, "Indexes")) { + if (!ap_cstr_casecmp(w, "Indexes")) { opt = OPT_INDEXES; } - else if (!strcasecmp(w, "Includes")) { + else if (!ap_cstr_casecmp(w, "Includes")) { /* If Includes is permitted, both Includes and * IncludesNOEXEC may be changed. */ opt = (OPT_INCLUDES | OPT_INC_WITH_EXEC); } - else if (!strcasecmp(w, "IncludesNOEXEC")) { + else if (!ap_cstr_casecmp(w, "IncludesNOEXEC")) { opt = OPT_INCLUDES; } - else if (!strcasecmp(w, "FollowSymLinks")) { + else if (!ap_cstr_casecmp(w, "FollowSymLinks")) { opt = OPT_SYM_LINKS; } - else if (!strcasecmp(w, "SymLinksIfOwnerMatch")) { + else if (!ap_cstr_casecmp(w, "SymLinksIfOwnerMatch")) { opt = OPT_SYM_OWNER; } - else if (!strcasecmp(w, "ExecCGI")) { + else if (!ap_cstr_casecmp(w, "ExecCGI")) { opt = OPT_EXECCGI; } - else if (!strcasecmp(w, "MultiViews")) { + else if (!ap_cstr_casecmp(w, "MultiViews")) { opt = OPT_MULTI; } - else if (!strcasecmp(w, "RunScripts")) { /* AI backcompat. Yuck */ + else if (!ap_cstr_casecmp(w, "RunScripts")) { /* AI backcompat. Yuck */ opt = OPT_MULTI|OPT_EXECCGI; } - else if (!strcasecmp(w, "None")) { + else if (!ap_cstr_casecmp(w, "None")) { opt = OPT_NONE; } - else if (!strcasecmp(w, "All")) { + else if (!ap_cstr_casecmp(w, "All")) { opt = OPT_ALL; } else { @@ -1774,40 +1833,43 @@ static const char *set_override(cmd_parms *cmd, void *d_, const char *l) *v++ = '\0'; } - if (!strcasecmp(w, "Limit")) { + if (!ap_cstr_casecmp(w, "Limit")) { d->override |= OR_LIMIT; } - else if (!strcasecmp(k, "Options")) { + else if (!ap_cstr_casecmp(k, "Options")) { d->override |= OR_OPTIONS; if (v) set_allow_opts(cmd, &(d->override_opts), v); else d->override_opts = OPT_ALL; } - else if (!strcasecmp(w, "FileInfo")) { + else if (!ap_cstr_casecmp(w, "FileInfo")) { d->override |= OR_FILEINFO; } - else if (!strcasecmp(w, "AuthConfig")) { + else if (!ap_cstr_casecmp(w, "AuthConfig")) { d->override |= OR_AUTHCFG; } - else if (!strcasecmp(w, "Indexes")) { + else if (!ap_cstr_casecmp(w, "Indexes")) { d->override |= OR_INDEXES; } - else if (!strcasecmp(w, "Nonfatal")) { - if (!strcasecmp(v, "Override")) { + else if (!ap_cstr_casecmp(w, "Nonfatal")) { + if (!v) { + return apr_pstrcat(cmd->pool, "=Override, =Unknown or =All expected after ", w, NULL); + } + else if (!ap_cstr_casecmp(v, "Override")) { d->override |= NONFATAL_OVERRIDE; } - else if (!strcasecmp(v, "Unknown")) { + else if (!ap_cstr_casecmp(v, "Unknown")) { d->override |= NONFATAL_UNKNOWN; } - else if (!strcasecmp(v, "All")) { + else if (!ap_cstr_casecmp(v, "All")) { d->override |= NONFATAL_ALL; } } - else if (!strcasecmp(w, "None")) { + else if (!ap_cstr_casecmp(w, "None")) { d->override = OR_NONE; } - else if (!strcasecmp(w, "All")) { + else if (!ap_cstr_casecmp(w, "All")) { d->override = OR_ALL; } else { @@ -1863,6 +1925,13 @@ static const char *set_qualify_redirect_url(cmd_parms *cmd, void *d_, int flag) return NULL; } +static const char *set_core_server_flag(cmd_parms *cmd, void *s_, int flag) +{ + core_server_config *conf = + ap_get_core_module_config(cmd->server->module_config); + return ap_set_flag_slot(cmd, conf, flag); +} + static const char *set_override_list(cmd_parms *cmd, void *d_, int argc, char *const argv[]) { core_dir_config *d = d_; @@ -1881,7 +1950,7 @@ static const char *set_override_list(cmd_parms *cmd, void *d_, int argc, char *c d->override_list = apr_table_make(cmd->pool, argc); for (i = 0; i < argc; i++) { - if (!strcasecmp(argv[i], "None")) { + if (!ap_cstr_casecmp(argv[i], "None")) { if (argc != 1) { return "'None' not allowed with other directives in " "AllowOverrideList"; @@ -1945,31 +2014,31 @@ static const char *set_options(cmd_parms *cmd, void *d_, const char *l) return "Either all Options must start with + or -, or no Option may."; } - if (!strcasecmp(w, "Indexes")) { + if (!ap_cstr_casecmp(w, "Indexes")) { opt = OPT_INDEXES; } - else if (!strcasecmp(w, "Includes")) { + else if (!ap_cstr_casecmp(w, "Includes")) { opt = (OPT_INCLUDES | OPT_INC_WITH_EXEC); } - else if (!strcasecmp(w, "IncludesNOEXEC")) { + else if (!ap_cstr_casecmp(w, "IncludesNOEXEC")) { opt = OPT_INCLUDES; } - else if (!strcasecmp(w, "FollowSymLinks")) { + else if (!ap_cstr_casecmp(w, "FollowSymLinks")) { opt = OPT_SYM_LINKS; } - else if (!strcasecmp(w, "SymLinksIfOwnerMatch")) { + else if (!ap_cstr_casecmp(w, "SymLinksIfOwnerMatch")) { opt = OPT_SYM_OWNER; } - else if (!strcasecmp(w, "ExecCGI")) { + else if (!ap_cstr_casecmp(w, "ExecCGI")) { opt = OPT_EXECCGI; } - else if (!strcasecmp(w, "MultiViews")) { + else if (!ap_cstr_casecmp(w, "MultiViews")) { opt = OPT_MULTI; } - else if (!strcasecmp(w, "RunScripts")) { /* AI backcompat. Yuck */ + else if (!ap_cstr_casecmp(w, "RunScripts")) { /* AI backcompat. Yuck */ opt = OPT_MULTI|OPT_EXECCGI; } - else if (!strcasecmp(w, "None")) { + else if (!ap_cstr_casecmp(w, "None")) { if (!first) { return "'Options None' must be the first Option given."; } @@ -1979,7 +2048,7 @@ static const char *set_options(cmd_parms *cmd, void *d_, const char *l) opt = OPT_NONE; all_none = 1; } - else if (!strcasecmp(w, "All")) { + else if (!ap_cstr_casecmp(w, "All")) { if (!first) { return "'Options All' must be the first option given."; } @@ -2020,7 +2089,7 @@ static const char *set_options(cmd_parms *cmd, void *d_, const char *l) static const char *set_default_type(cmd_parms *cmd, void *d_, const char *arg) { - if ((strcasecmp(arg, "off") != 0) && (strcasecmp(arg, "none") != 0)) { + if ((ap_cstr_casecmp(arg, "off") != 0) && (ap_cstr_casecmp(arg, "none") != 0)) { ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00117) "Ignoring deprecated use of DefaultType in line %d of %s.", cmd->directive->line_num, cmd->directive->filename); @@ -2090,7 +2159,7 @@ static const char *set_etag_bits(cmd_parms *cmd, void *mconfig, } } - if (strcasecmp(token, "None") == 0) { + if (ap_cstr_casecmp(token, "None") == 0) { if (action != '*') { valid = 0; } @@ -2099,7 +2168,7 @@ static const char *set_etag_bits(cmd_parms *cmd, void *mconfig, explicit = 1; } } - else if (strcasecmp(token, "All") == 0) { + else if (ap_cstr_casecmp(token, "All") == 0) { if (action != '*') { valid = 0; } @@ -2108,17 +2177,20 @@ static const char *set_etag_bits(cmd_parms *cmd, void *mconfig, cfg->etag_bits = bit = ETAG_ALL; } } - else if (strcasecmp(token, "Size") == 0) { + else if (ap_cstr_casecmp(token, "Size") == 0) { bit = ETAG_SIZE; } - else if ((strcasecmp(token, "LMTime") == 0) - || (strcasecmp(token, "MTime") == 0) - || (strcasecmp(token, "LastModified") == 0)) { + else if ((ap_cstr_casecmp(token, "LMTime") == 0) + || (ap_cstr_casecmp(token, "MTime") == 0) + || (ap_cstr_casecmp(token, "LastModified") == 0)) { bit = ETAG_MTIME; } - else if (strcasecmp(token, "INode") == 0) { + else if (ap_cstr_casecmp(token, "INode") == 0) { bit = ETAG_INODE; } + else if (ap_cstr_casecmp(token, "Digest") == 0) { + bit = ETAG_DIGEST; + } else { return apr_pstrcat(cmd->pool, "Unknown keyword '", token, "' for ", cmd->cmd->name, @@ -2183,10 +2255,10 @@ static const char *set_enable_mmap(cmd_parms *cmd, void *d_, { core_dir_config *d = d_; - if (strcasecmp(arg, "on") == 0) { + if (ap_cstr_casecmp(arg, "on") == 0) { d->enable_mmap = ENABLE_MMAP_ON; } - else if (strcasecmp(arg, "off") == 0) { + else if (ap_cstr_casecmp(arg, "off") == 0) { d->enable_mmap = ENABLE_MMAP_OFF; } else { @@ -2201,10 +2273,10 @@ static const char *set_enable_sendfile(cmd_parms *cmd, void *d_, { core_dir_config *d = d_; - if (strcasecmp(arg, "on") == 0) { + if (ap_cstr_casecmp(arg, "on") == 0) { d->enable_sendfile = ENABLE_SENDFILE_ON; } - else if (strcasecmp(arg, "off") == 0) { + else if (ap_cstr_casecmp(arg, "off") == 0) { d->enable_sendfile = ENABLE_SENDFILE_OFF; } else { @@ -2214,6 +2286,64 @@ static const char *set_enable_sendfile(cmd_parms *cmd, void *d_, return NULL; } +static const char *set_read_buf_size(cmd_parms *cmd, void *d_, + const char *arg) +{ + core_dir_config *d = d_; + apr_off_t size; + char *end; + + if (apr_strtoff(&size, arg, &end, 10) + || *end || size < 0 || size > APR_UINT32_MAX) + return apr_pstrcat(cmd->pool, + "parameter must be a number between 0 and " + APR_STRINGIFY(APR_UINT32_MAX) "): ", + arg, NULL); + + d->read_buf_size = (apr_size_t)size; + + return NULL; +} + +static const char *set_flush_max_threshold(cmd_parms *cmd, void *d_, + const char *arg) +{ + core_server_config *conf = + ap_get_core_module_config(cmd->server->module_config); + apr_off_t size; + char *end; + + if (apr_strtoff(&size, arg, &end, 10) + || *end || size < 0 || size > APR_UINT32_MAX) + return apr_pstrcat(cmd->pool, + "parameter must be a number between 0 and " + APR_STRINGIFY(APR_UINT32_MAX) "): ", + arg, NULL); + + conf->flush_max_threshold = (apr_size_t)size; + + return NULL; +} + +static const char *set_flush_max_pipelined(cmd_parms *cmd, void *d_, + const char *arg) +{ + core_server_config *conf = + ap_get_core_module_config(cmd->server->module_config); + apr_off_t num; + char *end; + + if (apr_strtoff(&num, arg, &end, 10) + || *end || num < -1 || num > APR_INT32_MAX) + return apr_pstrcat(cmd->pool, + "parameter must be a number between -1 and " + APR_STRINGIFY(APR_INT32_MAX) ": ", + arg, NULL); + + conf->flush_max_pipelined = (apr_int32_t)num; + + return NULL; +} /* * Report a missing-'>' syntax error. @@ -2778,8 +2908,15 @@ static int test_iffile_section(cmd_parms *cmd, const char *arg) const char *relative; apr_finfo_t sb; + /* + * At least on Windows, if the path we are testing is not valid (for example + * a path on a USB key that is not plugged), 'ap_server_root_relative()' will + * return NULL. In such a case, consider that the file is not there and that + * the section should be skipped. + */ relative = ap_server_root_relative(cmd->temp_pool, arg); - return (apr_stat(&sb, relative, 0, cmd->pool) == APR_SUCCESS); + return (relative && + (apr_stat(&sb, relative, APR_FINFO_TYPE, cmd->temp_pool) == APR_SUCCESS)); } static int test_ifdirective_section(cmd_parms *cmd, const char *arg) @@ -3052,13 +3189,13 @@ static const char *set_signature_flag(cmd_parms *cmd, void *d_, { core_dir_config *d = d_; - if (strcasecmp(arg, "On") == 0) { + if (ap_cstr_casecmp(arg, "On") == 0) { d->server_signature = srv_sig_on; } - else if (strcasecmp(arg, "Off") == 0) { + else if (ap_cstr_casecmp(arg, "Off") == 0) { d->server_signature = srv_sig_off; } - else if (strcasecmp(arg, "EMail") == 0) { + else if (ap_cstr_casecmp(arg, "EMail") == 0) { d->server_signature = srv_sig_withmail; } else { @@ -3120,13 +3257,13 @@ static const char *set_allow2f(cmd_parms *cmd, void *d_, const char *arg) { core_dir_config *d = d_; - if (0 == strcasecmp(arg, "on")) { + if (0 == ap_cstr_casecmp(arg, "on")) { d->allow_encoded_slashes = 1; d->decode_encoded_slashes = 1; /* for compatibility with 2.0 & 2.2 */ - } else if (0 == strcasecmp(arg, "off")) { + } else if (0 == ap_cstr_casecmp(arg, "off")) { d->allow_encoded_slashes = 0; d->decode_encoded_slashes = 0; - } else if (0 == strcasecmp(arg, "nodecode")) { + } else if (0 == ap_cstr_casecmp(arg, "nodecode")) { d->allow_encoded_slashes = 1; d->decode_encoded_slashes = 0; } else { @@ -3142,13 +3279,13 @@ static const char *set_hostname_lookups(cmd_parms *cmd, void *d_, { core_dir_config *d = d_; - if (!strcasecmp(arg, "on")) { + if (!ap_cstr_casecmp(arg, "on")) { d->hostname_lookups = HOSTNAME_LOOKUP_ON; } - else if (!strcasecmp(arg, "off")) { + else if (!ap_cstr_casecmp(arg, "off")) { d->hostname_lookups = HOSTNAME_LOOKUP_OFF; } - else if (!strcasecmp(arg, "double")) { + else if (!ap_cstr_casecmp(arg, "double")) { d->hostname_lookups = HOSTNAME_LOOKUP_DOUBLE; } else { @@ -3184,13 +3321,13 @@ static const char *set_accept_path_info(cmd_parms *cmd, void *d_, const char *ar { core_dir_config *d = d_; - if (strcasecmp(arg, "on") == 0) { + if (ap_cstr_casecmp(arg, "on") == 0) { d->accept_path_info = AP_REQ_ACCEPT_PATH_INFO; } - else if (strcasecmp(arg, "off") == 0) { + else if (ap_cstr_casecmp(arg, "off") == 0) { d->accept_path_info = AP_REQ_REJECT_PATH_INFO; } - else if (strcasecmp(arg, "default") == 0) { + else if (ap_cstr_casecmp(arg, "default") == 0) { d->accept_path_info = AP_REQ_DEFAULT_PATH_INFO; } else { @@ -3205,13 +3342,13 @@ static const char *set_use_canonical_name(cmd_parms *cmd, void *d_, { core_dir_config *d = d_; - if (strcasecmp(arg, "on") == 0) { + if (ap_cstr_casecmp(arg, "on") == 0) { d->use_canonical_name = USE_CANONICAL_NAME_ON; } - else if (strcasecmp(arg, "off") == 0) { + else if (ap_cstr_casecmp(arg, "off") == 0) { d->use_canonical_name = USE_CANONICAL_NAME_OFF; } - else if (strcasecmp(arg, "dns") == 0) { + else if (ap_cstr_casecmp(arg, "dns") == 0) { d->use_canonical_name = USE_CANONICAL_NAME_DNS; } else { @@ -3226,10 +3363,10 @@ static const char *set_use_canonical_phys_port(cmd_parms *cmd, void *d_, { core_dir_config *d = d_; - if (strcasecmp(arg, "on") == 0) { + if (ap_cstr_casecmp(arg, "on") == 0) { d->use_canonical_phys_port = USE_CANONICAL_PHYS_PORT_ON; } - else if (strcasecmp(arg, "off") == 0) { + else if (ap_cstr_casecmp(arg, "off") == 0) { d->use_canonical_phys_port = USE_CANONICAL_PHYS_PORT_OFF; } else { @@ -3525,22 +3662,22 @@ static const char *set_serv_tokens(cmd_parms *cmd, void *dummy, return err; } - if (!strcasecmp(arg, "OS")) { + if (!ap_cstr_casecmp(arg, "OS")) { ap_server_tokens = SrvTk_OS; } - else if (!strcasecmp(arg, "Min") || !strcasecmp(arg, "Minimal")) { + else if (!ap_cstr_casecmp(arg, "Min") || !ap_cstr_casecmp(arg, "Minimal")) { ap_server_tokens = SrvTk_MINIMAL; } - else if (!strcasecmp(arg, "Major")) { + else if (!ap_cstr_casecmp(arg, "Major")) { ap_server_tokens = SrvTk_MAJOR; } - else if (!strcasecmp(arg, "Minor") ) { + else if (!ap_cstr_casecmp(arg, "Minor") ) { ap_server_tokens = SrvTk_MINOR; } - else if (!strcasecmp(arg, "Prod") || !strcasecmp(arg, "ProductOnly")) { + else if (!ap_cstr_casecmp(arg, "Prod") || !ap_cstr_casecmp(arg, "ProductOnly")) { ap_server_tokens = SrvTk_PRODUCT_ONLY; } - else if (!strcasecmp(arg, "Full")) { + else if (!ap_cstr_casecmp(arg, "Full")) { ap_server_tokens = SrvTk_FULL; } else { @@ -3637,6 +3774,11 @@ static const char *set_limit_xml_req_body(cmd_parms *cmd, void *conf_, if (conf->limit_xml_body < 0) return "LimitXMLRequestBody requires a non-negative integer."; + /* zero is AP_MAX_LIMIT_XML_BODY (implicitly) */ + if ((apr_size_t)conf->limit_xml_body > AP_MAX_LIMIT_XML_BODY) + return apr_psprintf(cmd->pool, "LimitXMLRequestBody must not exceed " + "%" APR_SIZE_T_FMT, AP_MAX_LIMIT_XML_BODY); + return NULL; } @@ -3645,13 +3787,13 @@ static const char *set_max_ranges(cmd_parms *cmd, void *conf_, const char *arg) core_dir_config *conf = conf_; int val = 0; - if (!strcasecmp(arg, "none")) { + if (!ap_cstr_casecmp(arg, "none")) { val = AP_MAXRANGES_NORANGES; } - else if (!strcasecmp(arg, "default")) { + else if (!ap_cstr_casecmp(arg, "default")) { val = AP_MAXRANGES_DEFAULT; } - else if (!strcasecmp(arg, "unlimited")) { + else if (!ap_cstr_casecmp(arg, "unlimited")) { val = AP_MAXRANGES_UNLIMITED; } else { @@ -3671,13 +3813,13 @@ static const char *set_max_overlaps(cmd_parms *cmd, void *conf_, const char *arg core_dir_config *conf = conf_; int val = 0; - if (!strcasecmp(arg, "none")) { + if (!ap_cstr_casecmp(arg, "none")) { val = AP_MAXRANGES_NORANGES; } - else if (!strcasecmp(arg, "default")) { + else if (!ap_cstr_casecmp(arg, "default")) { val = AP_MAXRANGES_DEFAULT; } - else if (!strcasecmp(arg, "unlimited")) { + else if (!ap_cstr_casecmp(arg, "unlimited")) { val = AP_MAXRANGES_UNLIMITED; } else { @@ -3697,13 +3839,13 @@ static const char *set_max_reversals(cmd_parms *cmd, void *conf_, const char *ar core_dir_config *conf = conf_; int val = 0; - if (!strcasecmp(arg, "none")) { + if (!ap_cstr_casecmp(arg, "none")) { val = AP_MAXRANGES_NORANGES; } - else if (!strcasecmp(arg, "default")) { + else if (!ap_cstr_casecmp(arg, "default")) { val = AP_MAXRANGES_DEFAULT; } - else if (!strcasecmp(arg, "unlimited")) { + else if (!ap_cstr_casecmp(arg, "unlimited")) { val = AP_MAXRANGES_UNLIMITED; } else { @@ -3725,6 +3867,8 @@ AP_DECLARE(apr_size_t) ap_get_limit_xml_body(const request_rec *r) conf = ap_get_core_module_config(r->per_dir_config); if (conf->limit_xml_body == AP_LIMIT_UNSET) return AP_DEFAULT_LIMIT_XML_BODY; + if (conf->limit_xml_body == 0) + return AP_MAX_LIMIT_XML_BODY; return (apr_size_t)conf->limit_xml_body; } @@ -3818,24 +3962,26 @@ static const char *set_recursion_limit(cmd_parms *cmd, void *dummy, static void log_backtrace(const request_rec *r) { - const request_rec *top = r; + if (APLOGrdebug(r)) { + const request_rec *top = r; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00121) - "r->uri = %s", r->uri ? r->uri : "(unexpectedly NULL)"); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00121) + "r->uri = %s", r->uri ? r->uri : "(unexpectedly NULL)"); - while (top && (top->prev || top->main)) { - if (top->prev) { - top = top->prev; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00122) - "redirected from r->uri = %s", - top->uri ? top->uri : "(unexpectedly NULL)"); - } + while (top && (top->prev || top->main)) { + if (top->prev) { + top = top->prev; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00122) + "redirected from r->uri = %s", + top->uri ? top->uri : "(unexpectedly NULL)"); + } - if (!top->prev && top->main) { - top = top->main; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00123) - "subrequested from r->uri = %s", - top->uri ? top->uri : "(unexpectedly NULL)"); + if (!top->prev && top->main) { + top = top->main; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00123) + "subrequested from r->uri = %s", + top->uri ? top->uri : "(unexpectedly NULL)"); + } } } } @@ -3909,13 +4055,13 @@ static const char *set_trace_enable(cmd_parms *cmd, void *dummy, core_server_config *conf = ap_get_core_module_config(cmd->server->module_config); - if (strcasecmp(arg1, "on") == 0) { + if (ap_cstr_casecmp(arg1, "on") == 0) { conf->trace_enable = AP_TRACE_ENABLE; } - else if (strcasecmp(arg1, "off") == 0) { + else if (ap_cstr_casecmp(arg1, "off") == 0) { conf->trace_enable = AP_TRACE_DISABLE; } - else if (strcasecmp(arg1, "extended") == 0) { + else if (ap_cstr_casecmp(arg1, "extended") == 0) { conf->trace_enable = AP_TRACE_EXTENDED; } else { @@ -3954,10 +4100,10 @@ static const char *set_protocols_honor_order(cmd_parms *cmd, void *dummy, return err; } - if (strcasecmp(arg, "on") == 0) { + if (ap_cstr_casecmp(arg, "on") == 0) { conf->protocols_honor_order = 1; } - else if (strcasecmp(arg, "off") == 0) { + else if (ap_cstr_casecmp(arg, "off") == 0) { conf->protocols_honor_order = 0; } else { @@ -4227,7 +4373,7 @@ static const char *set_errorlog_format(cmd_parms *cmd, void *dummy, conf->error_log_format = parse_errorlog_string(cmd->pool, arg1, &err_string, 1); } - else if (!strcasecmp(arg1, "connection")) { + else if (!ap_cstr_casecmp(arg1, "connection")) { if (!conf->error_log_conn) { conf->error_log_conn = apr_array_make(cmd->pool, 5, sizeof(apr_array_header_t *)); @@ -4239,7 +4385,7 @@ static const char *set_errorlog_format(cmd_parms *cmd, void *dummy, *e = parse_errorlog_string(cmd->pool, arg2, &err_string, 0); } } - else if (!strcasecmp(arg1, "request")) { + else if (!ap_cstr_casecmp(arg1, "request")) { if (!conf->error_log_req) { conf->error_log_req = apr_array_make(cmd->pool, 5, sizeof(apr_array_header_t *)); @@ -4374,6 +4520,13 @@ AP_INIT_TAKE1("EnableMMAP", set_enable_mmap, NULL, OR_FILEINFO, "Controls whether memory-mapping may be used to read files"), AP_INIT_TAKE1("EnableSendfile", set_enable_sendfile, NULL, OR_FILEINFO, "Controls whether sendfile may be used to transmit files"), +AP_INIT_TAKE1("ReadBufferSize", set_read_buf_size, NULL, ACCESS_CONF|RSRC_CONF, + "Size (in bytes) of the memory buffers used to read data"), +AP_INIT_TAKE1("FlushMaxThreshold", set_flush_max_threshold, NULL, RSRC_CONF, + "Maximum threshold above which pending data are flushed to the network"), +AP_INIT_TAKE1("FlushMaxPipelined", set_flush_max_pipelined, NULL, RSRC_CONF, + "Maximum number of pipelined responses (pending) above which they are " + "flushed to the network"), /* Old server config file commands */ @@ -4498,9 +4651,12 @@ AP_INIT_FLAG("CGIPassAuth", set_cgi_pass_auth, NULL, OR_AUTHCFG, AP_INIT_TAKE2("CGIVar", set_cgi_var, NULL, OR_FILEINFO, "Controls how some CGI variables are set"), AP_INIT_FLAG("QualifyRedirectURL", set_qualify_redirect_url, NULL, OR_FILEINFO, - "Controls whether HTTP authorization headers, normally hidden, will " - "be passed to scripts"), - + "Controls whether the REDIRECT_URL environment variable is fully " + "qualified"), +AP_INIT_FLAG("StrictHostCheck", set_core_server_flag, + (void *)APR_OFFSETOF(core_server_config, strict_host_check), + RSRC_CONF, + "Controls whether a hostname match is required"), AP_INIT_TAKE1("ForceType", ap_set_string_slot_lower, (void *)APR_OFFSETOF(core_dir_config, mime_type), OR_FILEINFO, "a mime type that overrides other configured type"), @@ -4562,6 +4718,10 @@ AP_INIT_ITERATE("HttpProtocolOptions", set_http_protocol_options, NULL, RSRC_CON "'Unsafe' or 'Strict' (default). Sets HTTP acceptance rules"), AP_INIT_ITERATE("RegisterHttpMethod", set_http_method, NULL, RSRC_CONF, "Registers non-standard HTTP methods"), +AP_INIT_FLAG("MergeSlashes", set_core_server_flag, + (void *)APR_OFFSETOF(core_server_config, merge_slashes), + RSRC_CONF, + "Controls whether consecutive slashes in the URI path are merged"), { NULL } }; @@ -4583,7 +4743,7 @@ AP_DECLARE_NONSTD(int) ap_core_translate(request_rec *r) } if (!r->uri || ((r->uri[0] != '/') && strcmp(r->uri, "*"))) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00126) - "Invalid URI in request %s", r->the_request); + "Invalid URI in request '%s' '%s'", r->uri, r->the_request); return HTTP_BAD_REQUEST; } @@ -4793,7 +4953,7 @@ static int default_handler(request_rec *r) ap_update_mtime(r, r->finfo.mtime); ap_set_last_modified(r); - ap_set_etag(r); + ap_set_etag_fd(r, fd); ap_set_accept_ranges(r); ap_set_content_length(r, r->finfo.size); if (bld_content_md5) { @@ -4815,12 +4975,19 @@ static int default_handler(request_rec *r) (void)apr_bucket_file_enable_mmap(e, 0); } #endif +#if APR_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 6) + if (d->read_buf_size) { + apr_bucket_file_set_buf_size(e, d->read_buf_size); + } +#endif } e = apr_bucket_eos_create(c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, e); status = ap_pass_brigade(r->output_filters, bb); + apr_brigade_cleanup(bb); + if (status == APR_SUCCESS || r->status != HTTP_OK || c->aborted) { @@ -4924,7 +5091,7 @@ static int core_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptem apr_pool_cleanup_register(pconf, NULL, reset_config_defines, apr_pool_cleanup_null); - ap_regcomp_set_default_cflags(AP_REG_DOLLAR_ENDONLY); + ap_regcomp_set_default_cflags(AP_REG_DEFAULT); mpm_common_pre_config(pconf); @@ -4943,6 +5110,7 @@ static int core_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *pte set_banner(pconf); ap_setup_make_content_type(pconf); ap_setup_auth_internal(ptemp); + ap_setup_ssl_optional_fns(pconf); if (!sys_privileges) { ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, APLOGNO(00136) "Server MUST relinquish startup privileges before " @@ -5059,10 +5227,10 @@ static conn_rec *core_create_conn(apr_pool_t *ptrans, server_rec *server, /* Got a connection structure, so initialize what fields we can * (the rest are zeroed out by pcalloc). */ + c->pool = ptrans; c->conn_config = ap_create_conn_config(ptrans); c->notes = apr_table_make(ptrans, 5); - c->pool = ptrans; if ((rv = apr_socket_addr_get(&c->local_addr, APR_LOCAL, csd)) != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_INFO, rv, server, APLOGNO(00137) @@ -5070,8 +5238,17 @@ static conn_rec *core_create_conn(apr_pool_t *ptrans, server_rec *server, apr_socket_close(csd); return NULL; } + if (apr_sockaddr_ip_get(&c->local_ip, c->local_addr)) { +#if APR_HAVE_SOCKADDR_UN + if (c->local_addr->family == APR_UNIX) { + c->local_ip = apr_pstrndup(c->pool, c->local_addr->ipaddr_ptr, + c->local_addr->ipaddr_len); + } + else +#endif + c->local_ip = apr_pstrdup(c->pool, "unknown"); + } - apr_sockaddr_ip_get(&c->local_ip, c->local_addr); if ((rv = apr_socket_addr_get(&c->client_addr, APR_REMOTE, csd)) != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_INFO, rv, server, APLOGNO(00138) @@ -5079,8 +5256,17 @@ static conn_rec *core_create_conn(apr_pool_t *ptrans, server_rec *server, apr_socket_close(csd); return NULL; } + if (apr_sockaddr_ip_get(&c->client_ip, c->client_addr)) { +#if APR_HAVE_SOCKADDR_UN + if (c->client_addr->family == APR_UNIX) { + c->client_ip = apr_pstrndup(c->pool, c->client_addr->ipaddr_ptr, + c->client_addr->ipaddr_len); + } + else +#endif + c->client_ip = apr_pstrdup(c->pool, "unknown"); + } - apr_sockaddr_ip_get(&c->client_ip, c->client_addr); c->base_server = server; c->id = id; @@ -5093,9 +5279,14 @@ static conn_rec *core_create_conn(apr_pool_t *ptrans, server_rec *server, static int core_pre_connection(conn_rec *c, void *csd) { - core_net_rec *net = apr_palloc(c->pool, sizeof(*net)); + core_net_rec *net; apr_status_t rv; + if (c->master) { + return DONE; + } + + net = apr_palloc(c->pool, sizeof(*net)); /* The Nagle algorithm says that we should delay sending partial * packets in hopes of getting more data. We don't want to do * this; we are not telnet. There are bad interactions between @@ -5136,6 +5327,31 @@ static int core_pre_connection(conn_rec *c, void *csd) return DONE; } +AP_DECLARE(int) ap_pre_connection(conn_rec *c, void *csd) +{ + int rc = OK; + + rc = ap_run_pre_connection(c, csd); + if (rc != OK && rc != DONE) { + c->aborted = 1; + /* + * In case we errored, the pre_connection hook of the core + * module maybe did not run (it is APR_HOOK_REALLY_LAST) and + * hence we missed to + * + * - Put the socket in c->conn_config + * - Setup core output and input filters + * - Set socket options and timeouts + * + * Hence call it in this case. + */ + if (!ap_get_conn_socket(c)) { + core_pre_connection(c, csd); + } + } + return rc; +} + AP_DECLARE(int) ap_state_query(int query) { switch (query) { @@ -5394,6 +5610,28 @@ static int core_upgrade_storage(request_rec *r) return DECLINED; } +static apr_status_t core_get_pollfd_from_conn(conn_rec *c, + struct apr_pollfd_t *pfd, + apr_interval_time_t *ptimeout) +{ + if (c && !c->master) { + pfd->desc_type = APR_POLL_SOCKET; + pfd->desc.s = ap_get_conn_socket(c); + if (ptimeout) { + apr_socket_timeout_get(pfd->desc.s, ptimeout); + } + return APR_SUCCESS; + } + return APR_ENOTIMPL; +} + +AP_CORE_DECLARE(apr_status_t) ap_get_pollfd_from_conn(conn_rec *c, + struct apr_pollfd_t *pfd, + apr_interval_time_t *ptimeout) +{ + return ap_run_get_pollfd_from_conn(c, pfd, ptimeout); +} + static void register_hooks(apr_pool_t *p) { errorlog_hash = apr_hash_make(p); @@ -5437,7 +5675,9 @@ static void register_hooks(apr_pool_t *p) ap_hook_open_htaccess(ap_open_htaccess, NULL, NULL, APR_HOOK_REALLY_LAST); ap_hook_optional_fn_retrieve(core_optional_fn_retrieve, NULL, NULL, APR_HOOK_MIDDLE); - + ap_hook_get_pollfd_from_conn(core_get_pollfd_from_conn, NULL, NULL, + APR_HOOK_REALLY_LAST); + /* register the core's insert_filter hook and register core-provided * filters */ @@ -5470,4 +5710,3 @@ AP_DECLARE_MODULE(core) = { core_cmds, /* command apr_table_t */ register_hooks /* register hooks */ }; - diff --git a/server/core_filters.c b/server/core_filters.c index a6c2bd6..c4ab603 100644 --- a/server/core_filters.c +++ b/server/core_filters.c @@ -25,6 +25,7 @@ #include "apr_fnmatch.h" #include "apr_hash.h" #include "apr_thread_proc.h" /* for RLIMIT stuff */ +#include "apr_version.h" #define APR_WANT_IOVEC #define APR_WANT_STRFUNC @@ -79,14 +80,10 @@ do { \ struct core_output_filter_ctx { apr_bucket_brigade *buffered_bb; - apr_bucket_brigade *tmp_flush_bb; apr_pool_t *deferred_write_pool; apr_size_t bytes_written; -}; - -struct core_filter_ctx { - apr_bucket_brigade *b; - apr_bucket_brigade *tmpbb; + struct iovec *vec; + apr_size_t nvec; }; @@ -270,7 +267,7 @@ apr_status_t ap_core_input_filter(ap_filter_t *f, apr_bucket_brigade *b, while ((len < readbytes) && (rv == APR_SUCCESS) && (e != APR_BRIGADE_SENTINEL(ctx->b))) { /* Check for the availability of buckets with known length */ - if (e->length != -1) { + if (e->length != (apr_size_t)-1) { len += e->length; e = APR_BUCKET_NEXT(e); } @@ -335,50 +332,131 @@ static void setaside_remaining_output(ap_filter_t *f, static apr_status_t send_brigade_nonblocking(apr_socket_t *s, apr_bucket_brigade *bb, - apr_size_t *bytes_written, + core_output_filter_ctx_t *ctx, conn_rec *c); -static void remove_empty_buckets(apr_bucket_brigade *bb); - -static apr_status_t send_brigade_blocking(apr_socket_t *s, - apr_bucket_brigade *bb, - apr_size_t *bytes_written, - conn_rec *c); - static apr_status_t writev_nonblocking(apr_socket_t *s, - struct iovec *vec, apr_size_t nvec, apr_bucket_brigade *bb, - apr_size_t *cumulative_bytes_written, + core_output_filter_ctx_t *ctx, + apr_size_t bytes_to_write, + apr_size_t nvec, conn_rec *c); #if APR_HAS_SENDFILE static apr_status_t sendfile_nonblocking(apr_socket_t *s, apr_bucket *bucket, - apr_size_t *cumulative_bytes_written, + core_output_filter_ctx_t *ctx, conn_rec *c); #endif /* XXX: Should these be configurable parameters? */ #define THRESHOLD_MIN_WRITE 4096 -#define THRESHOLD_MAX_BUFFER 65536 -#define MAX_REQUESTS_IN_PIPELINE 5 /* Optional function coming from mod_logio, used for logging of output * traffic */ extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *ap__logio_add_bytes_out; +static int should_send_brigade(apr_bucket_brigade *bb, conn_rec *c, int *flush) +{ + core_server_config *conf = + ap_get_core_module_config(c->base_server->module_config); + apr_size_t total_bytes = 0, non_file_bytes = 0; + apr_uint32_t eor_buckets = 0; + apr_bucket *bucket; + int need_flush = 0; + + /* Scan through the brigade and decide whether we need to flush it, + * based on the following rules: + * + * a) The brigade contains a flush bucket: Do a blocking write + * of everything up that point. + * + * b) The request is in CONN_STATE_HANDLER state, and the brigade + * contains at least flush_max_threshold bytes in non-file + * buckets: Do blocking writes until the amount of data in the + * buffer is less than flush_max_threshold. (The point of this + * rule is to provide flow control, in case a handler is + * streaming out lots of data faster than the data can be + * sent to the client.) + * + * c) The request is in CONN_STATE_HANDLER state, and the brigade + * contains at least flush_max_pipelined EOR buckets: + * Do blocking writes until less than flush_max_pipelined EOR + * buckets are left. (The point of this rule is to prevent too many + * FDs being kept open by pipelined requests, possibly allowing a + * DoS). + * + * d) The brigade contains a morphing bucket: otherwise ap_save_brigade() + * could read the whole bucket into memory. + */ + for (bucket = APR_BRIGADE_FIRST(bb); + bucket != APR_BRIGADE_SENTINEL(bb); + bucket = APR_BUCKET_NEXT(bucket)) { + + if (!APR_BUCKET_IS_METADATA(bucket)) { + if (bucket->length == (apr_size_t)-1) { + if (flush) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, c, + "core_output_filter: flushing because " + "of morphing bucket"); + } + need_flush = 1; + break; + } + + total_bytes += bucket->length; + if (!APR_BUCKET_IS_FILE(bucket)) { + non_file_bytes += bucket->length; + if (non_file_bytes > conf->flush_max_threshold) { + if (flush) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, c, + "core_output_filter: flushing because " + "of max threshold"); + } + need_flush = 1; + break; + } + } + } + else if (APR_BUCKET_IS_FLUSH(bucket)) { + if (flush) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, c, + "core_output_filter: flushing because " + "of FLUSH bucket"); + } + need_flush = 1; + break; + } + else if (AP_BUCKET_IS_EOR(bucket) + && conf->flush_max_pipelined >= 0 + && ++eor_buckets > conf->flush_max_pipelined) { + if (flush) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, c, + "core_output_filter: flushing because " + "of max pipelined"); + } + need_flush = 1; + break; + } + } + if (flush) { + *flush = need_flush; + } + + /* Also send if above flush_min_threshold, or if there are FILE buckets */ + return (need_flush + || total_bytes >= THRESHOLD_MIN_WRITE + || total_bytes > non_file_bytes); +} + apr_status_t ap_core_output_filter(ap_filter_t *f, apr_bucket_brigade *new_bb) { conn_rec *c = f->c; core_net_rec *net = f->ctx; core_output_filter_ctx_t *ctx = net->out_ctx; apr_bucket_brigade *bb = NULL; - apr_bucket *bucket, *next, *flush_upto = NULL; - apr_size_t bytes_in_brigade, non_file_bytes_in_brigade; - int eor_buckets_in_brigade, morphing_bucket_in_brigade; - apr_status_t rv; - int loglevel = ap_get_conn_module_loglevel(c, APLOG_MODULE_INDEX); + apr_status_t rv = APR_SUCCESS; /* Fail quickly if the connection has already been aborted. */ if (c->aborted) { @@ -392,12 +470,10 @@ apr_status_t ap_core_output_filter(ap_filter_t *f, apr_bucket_brigade *new_bb) ctx = apr_pcalloc(c->pool, sizeof(*ctx)); net->out_ctx = (core_output_filter_ctx_t *)ctx; /* - * Need to create tmp brigade with correct lifetime. Passing - * NULL to apr_brigade_split_ex would result in a brigade + * Need to create buffered_bb brigade with correct lifetime. Passing + * NULL to ap_save_brigade() would result in a brigade * allocated from bb->pool which might be wrong. */ - ctx->tmp_flush_bb = apr_brigade_create(c->pool, c->bucket_alloc); - /* same for buffered_bb and ap_save_brigade */ ctx->buffered_bb = apr_brigade_create(c->pool, c->bucket_alloc); } @@ -412,191 +488,62 @@ apr_status_t ap_core_output_filter(ap_filter_t *f, apr_bucket_brigade *new_bb) else { bb = ctx->buffered_bb; } - c->data_in_output_filters = 0; } else if (new_bb == NULL) { + c->data_in_output_filters = 0; return APR_SUCCESS; } - /* Scan through the brigade and decide whether to attempt a write, - * and how much to write, based on the following rules: - * - * 1) The new_bb is null: Do a nonblocking write of as much as - * possible: do a nonblocking write of as much data as possible, - * then save the rest in ctx->buffered_bb. (If new_bb == NULL, - * it probably means that the MPM is doing asynchronous write - * completion and has just determined that this connection - * is writable.) - * - * 2) Determine if and up to which bucket we need to do a blocking - * write: - * - * a) The brigade contains a flush bucket: Do a blocking write - * of everything up that point. - * - * b) The request is in CONN_STATE_HANDLER state, and the brigade - * contains at least THRESHOLD_MAX_BUFFER bytes in non-file - * buckets: Do blocking writes until the amount of data in the - * buffer is less than THRESHOLD_MAX_BUFFER. (The point of this - * rule is to provide flow control, in case a handler is - * streaming out lots of data faster than the data can be - * sent to the client.) - * - * c) The request is in CONN_STATE_HANDLER state, and the brigade - * contains at least MAX_REQUESTS_IN_PIPELINE EOR buckets: - * Do blocking writes until less than MAX_REQUESTS_IN_PIPELINE EOR - * buckets are left. (The point of this rule is to prevent too many - * FDs being kept open by pipelined requests, possibly allowing a - * DoS). - * - * d) The brigade contains a morphing bucket: If there was no other - * reason to do a blocking write yet, try reading the bucket. If its - * contents fit into memory before THRESHOLD_MAX_BUFFER is reached, - * everything is fine. Otherwise we need to do a blocking write the - * up to and including the morphing bucket, because ap_save_brigade() - * would read the whole bucket into memory later on. - * - * 3) Actually do the blocking write up to the last bucket determined - * by rules 2a-d. The point of doing only one flush is to make as - * few calls to writev() as possible. - * - * 4) If the brigade contains at least THRESHOLD_MIN_WRITE - * bytes: Do a nonblocking write of as much data as possible, - * then save the rest in ctx->buffered_bb. - */ - - if (new_bb == NULL) { - rv = send_brigade_nonblocking(net->client_socket, bb, - &(ctx->bytes_written), c); - if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) { - /* The client has aborted the connection */ - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c, - "core_output_filter: writing data to the network"); - apr_brigade_cleanup(bb); - c->aborted = 1; - return rv; - } - setaside_remaining_output(f, ctx, bb, c); - return APR_SUCCESS; - } - - bytes_in_brigade = 0; - non_file_bytes_in_brigade = 0; - eor_buckets_in_brigade = 0; - morphing_bucket_in_brigade = 0; + if (!new_bb || should_send_brigade(bb, c, NULL)) { + apr_socket_t *sock = net->client_socket; + apr_interval_time_t sock_timeout = 0; - for (bucket = APR_BRIGADE_FIRST(bb); bucket != APR_BRIGADE_SENTINEL(bb); - bucket = next) { - next = APR_BUCKET_NEXT(bucket); + /* Non-blocking writes on the socket in any case. */ + apr_socket_timeout_get(sock, &sock_timeout); + apr_socket_timeout_set(sock, 0); - if (!APR_BUCKET_IS_METADATA(bucket)) { - if (bucket->length == (apr_size_t)-1) { - /* - * A setaside of morphing buckets would read everything into - * memory. Instead, we will flush everything up to and - * including this bucket. + do { + rv = send_brigade_nonblocking(sock, bb, ctx, c); + if (new_bb && APR_STATUS_IS_EAGAIN(rv)) { + /* Scan through the brigade and decide whether we must absolutely + * flush the remaining data, based on should_send_brigade() &flush + * rules. If so, wait for writability and retry, otherwise we did + * our best already and can wait for the next call. */ - morphing_bucket_in_brigade = 1; - } - else { - bytes_in_brigade += bucket->length; - if (!APR_BUCKET_IS_FILE(bucket)) - non_file_bytes_in_brigade += bucket->length; - } - } - else if (AP_BUCKET_IS_EOR(bucket)) { - eor_buckets_in_brigade++; - } - - if (APR_BUCKET_IS_FLUSH(bucket) - || non_file_bytes_in_brigade >= THRESHOLD_MAX_BUFFER - || morphing_bucket_in_brigade - || eor_buckets_in_brigade > MAX_REQUESTS_IN_PIPELINE) { - /* this segment of the brigade MUST be sent before returning. */ - - if (loglevel >= APLOG_TRACE6) { - char *reason = APR_BUCKET_IS_FLUSH(bucket) ? - "FLUSH bucket" : - (non_file_bytes_in_brigade >= THRESHOLD_MAX_BUFFER) ? - "THRESHOLD_MAX_BUFFER" : - morphing_bucket_in_brigade ? "morphing bucket" : - "MAX_REQUESTS_IN_PIPELINE"; - ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, c, - "will flush because of %s", reason); - ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, c, - "seen in brigade%s: bytes: %" APR_SIZE_T_FMT - ", non-file bytes: %" APR_SIZE_T_FMT ", eor " - "buckets: %d, morphing buckets: %d", - flush_upto == NULL ? " so far" - : " since last flush point", - bytes_in_brigade, - non_file_bytes_in_brigade, - eor_buckets_in_brigade, - morphing_bucket_in_brigade); + int flush; + (void)should_send_brigade(bb, c, &flush); + if (flush) { + apr_int32_t nfd; + apr_pollfd_t pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.reqevents = APR_POLLOUT; + pfd.desc_type = APR_POLL_SOCKET; + pfd.desc.s = sock; + pfd.p = c->pool; + do { + rv = apr_poll(&pfd, 1, &nfd, sock_timeout); + } while (APR_STATUS_IS_EINTR(rv)); + } } - /* - * Defer the actual blocking write to avoid doing many writes. - */ - flush_upto = next; - - bytes_in_brigade = 0; - non_file_bytes_in_brigade = 0; - eor_buckets_in_brigade = 0; - morphing_bucket_in_brigade = 0; - } - } - - if (flush_upto != NULL) { - ctx->tmp_flush_bb = apr_brigade_split_ex(bb, flush_upto, - ctx->tmp_flush_bb); - if (loglevel >= APLOG_TRACE8) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, c, - "flushing now"); - } - rv = send_brigade_blocking(net->client_socket, bb, - &(ctx->bytes_written), c); - if (rv != APR_SUCCESS) { - /* The client has aborted the connection */ - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c, - "core_output_filter: writing data to the network"); - apr_brigade_cleanup(bb); - c->aborted = 1; - return rv; - } - if (loglevel >= APLOG_TRACE8) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, c, - "total bytes written: %" APR_SIZE_T_FMT, - ctx->bytes_written); - } - APR_BRIGADE_CONCAT(bb, ctx->tmp_flush_bb); - } + } while (rv == APR_SUCCESS && !APR_BRIGADE_EMPTY(bb)); - if (loglevel >= APLOG_TRACE8) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, c, - "brigade contains: bytes: %" APR_SIZE_T_FMT - ", non-file bytes: %" APR_SIZE_T_FMT - ", eor buckets: %d, morphing buckets: %d", - bytes_in_brigade, non_file_bytes_in_brigade, - eor_buckets_in_brigade, morphing_bucket_in_brigade); + /* Restore original socket timeout before leaving. */ + apr_socket_timeout_set(sock, sock_timeout); } - if (bytes_in_brigade >= THRESHOLD_MIN_WRITE) { - rv = send_brigade_nonblocking(net->client_socket, bb, - &(ctx->bytes_written), c); - if ((rv != APR_SUCCESS) && (!APR_STATUS_IS_EAGAIN(rv))) { - /* The client has aborted the connection */ - ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c, - "core_output_filter: writing data to the network"); - apr_brigade_cleanup(bb); - c->aborted = 1; - return rv; - } - if (loglevel >= APLOG_TRACE8) { - ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, c, - "tried nonblocking write, total bytes " - "written: %" APR_SIZE_T_FMT, - ctx->bytes_written); - } + if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) { + /* The client has aborted the connection */ + ap_log_cerror( + APLOG_MARK, APLOG_TRACE1, rv, c, + "core_output_filter: writing data to the network"); + /* + * Set c->aborted before apr_brigade_cleanup to have the correct status + * when logging the request as apr_brigade_cleanup triggers the logging + * of the request if it contains an EOR bucket. + */ + c->aborted = 1; + apr_brigade_cleanup(bb); + return rv; } setaside_remaining_output(f, ctx, bb, c); @@ -612,10 +559,17 @@ static void setaside_remaining_output(ap_filter_t *f, apr_bucket_brigade *bb, conn_rec *c) { - if (bb == NULL) { - return; + apr_bucket *bucket; + + /* Don't set aside leading empty buckets, all previous data have been + * consumed so it's safe to delete them now. + */ + while (((bucket = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) && + (APR_BUCKET_IS_METADATA(bucket) || (bucket->length == 0))) { + apr_bucket_delete(bucket); } - remove_empty_buckets(bb); + + c->data_in_output_filters = 0; if (!APR_BRIGADE_EMPTY(bb)) { c->data_in_output_filters = 1; if (bb != ctx->buffered_bb) { @@ -637,285 +591,276 @@ static void setaside_remaining_output(ap_filter_t *f, } #ifndef APR_MAX_IOVEC_SIZE -#define MAX_IOVEC_TO_WRITE 16 +#define NVEC_MIN 16 +#define NVEC_MAX NVEC_MIN #else #if APR_MAX_IOVEC_SIZE > 16 -#define MAX_IOVEC_TO_WRITE 16 +#define NVEC_MIN 16 #else -#define MAX_IOVEC_TO_WRITE APR_MAX_IOVEC_SIZE +#define NVEC_MIN APR_MAX_IOVEC_SIZE +#endif +#define NVEC_MAX APR_MAX_IOVEC_SIZE +#endif + +static APR_INLINE int is_in_memory_bucket(apr_bucket *b) +{ + /* These buckets' data are already in memory. */ + return APR_BUCKET_IS_HEAP(b) + || APR_BUCKET_IS_POOL(b) + || APR_BUCKET_IS_TRANSIENT(b) + || APR_BUCKET_IS_IMMORTAL(b); +} + +#if APR_HAS_SENDFILE +static APR_INLINE int can_sendfile_bucket(apr_bucket *b) +{ + /* Use sendfile to send the bucket unless: + * - the bucket is not a file bucket, or + * - the file is too small for sendfile to be useful, or + * - sendfile is disabled in the httpd config via "EnableSendfile off". + */ + if (APR_BUCKET_IS_FILE(b) && b->length >= AP_MIN_SENDFILE_BYTES) { + apr_file_t *file = ((apr_bucket_file *)b->data)->fd; + return apr_file_flags_get(file) & APR_SENDFILE_ENABLED; + } + else { + return 0; + } +} #endif + +#if defined(WIN32) && (APR_MAJOR_VERSION == 1 && APR_MINOR_VERSION <= 7) +#undef APR_TCP_NOPUSH_FLAG +#define APR_TCP_NOPUSH_FLAG 0 +#endif + +static APR_INLINE void sock_nopush(apr_socket_t *s, int to) +{ + /* Disable TCP_NOPUSH handling on OSX since unsetting it won't push + * retained data, which might introduce delays if further data don't + * come soon enough or cause the last chunk to be sent only when the + * connection is shutdown (e.g. after KeepAliveTimeout). + */ +#if APR_TCP_NOPUSH_FLAG && !defined(__APPLE__) + (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, to); #endif +} static apr_status_t send_brigade_nonblocking(apr_socket_t *s, apr_bucket_brigade *bb, - apr_size_t *bytes_written, + core_output_filter_ctx_t *ctx, conn_rec *c) { + apr_status_t rv = APR_SUCCESS; + core_server_config *conf = + ap_get_core_module_config(c->base_server->module_config); + apr_size_t nvec = 0, nbytes = 0; apr_bucket *bucket, *next; - apr_status_t rv; - struct iovec vec[MAX_IOVEC_TO_WRITE]; - apr_size_t nvec = 0; - - remove_empty_buckets(bb); + const char *data; + apr_size_t length; for (bucket = APR_BRIGADE_FIRST(bb); bucket != APR_BRIGADE_SENTINEL(bb); bucket = next) { next = APR_BUCKET_NEXT(bucket); -#if APR_HAS_SENDFILE - if (APR_BUCKET_IS_FILE(bucket)) { - apr_bucket_file *file_bucket = (apr_bucket_file *)(bucket->data); - apr_file_t *fd = file_bucket->fd; - /* Use sendfile to send this file unless: - * - the platform doesn't support sendfile, - * - the file is too small for sendfile to be useful, or - * - sendfile is disabled in the httpd config via "EnableSendfile off" - */ - if ((apr_file_flags_get(fd) & APR_SENDFILE_ENABLED) && - (bucket->length >= AP_MIN_SENDFILE_BYTES)) { - if (nvec > 0) { - (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, 1); - rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c); - if (rv != APR_SUCCESS) { - (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, 0); - return rv; - } - } - rv = sendfile_nonblocking(s, bucket, bytes_written, c); - if (nvec > 0) { - (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, 0); - nvec = 0; - } +#if APR_HAS_SENDFILE + if (can_sendfile_bucket(bucket)) { + if (nvec > 0) { + sock_nopush(s, 1); + rv = writev_nonblocking(s, bb, ctx, nbytes, nvec, c); if (rv != APR_SUCCESS) { - return rv; + goto cleanup; } - break; + nbytes = 0; + nvec = 0; } + rv = sendfile_nonblocking(s, bucket, ctx, c); + if (rv != APR_SUCCESS) { + goto cleanup; + } + continue; } #endif /* APR_HAS_SENDFILE */ - /* didn't sendfile */ - if (!APR_BUCKET_IS_METADATA(bucket)) { - const char *data; - apr_size_t length; - + + if (bucket->length) { /* Non-blocking read first, in case this is a morphing * bucket type. */ rv = apr_bucket_read(bucket, &data, &length, APR_NONBLOCK_READ); if (APR_STATUS_IS_EAGAIN(rv)) { /* Read would block; flush any pending data and retry. */ if (nvec) { - rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c); - if (rv) { - return rv; + rv = writev_nonblocking(s, bb, ctx, nbytes, nvec, c); + if (rv != APR_SUCCESS) { + goto cleanup; } + nbytes = 0; nvec = 0; } - + sock_nopush(s, 0); + rv = apr_bucket_read(bucket, &data, &length, APR_BLOCK_READ); } if (rv != APR_SUCCESS) { - return rv; + goto cleanup; } /* reading may have split the bucket, so recompute next: */ next = APR_BUCKET_NEXT(bucket); - vec[nvec].iov_base = (char *)data; - vec[nvec].iov_len = length; - nvec++; - if (nvec == MAX_IOVEC_TO_WRITE) { - rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c); - nvec = 0; - if (rv != APR_SUCCESS) { - return rv; - } - break; - } } - } - if (nvec > 0) { - rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c); - if (rv != APR_SUCCESS) { - return rv; + if (!bucket->length) { + /* Don't delete empty buckets until all the previous ones have been + * sent (nvec == 0); this must happen in sequence since metabuckets + * like EOR could free the data still pointed to by the iovec. So + * unless the latter is empty, let writev_nonblocking() cleanup the + * brigade in order. + */ + if (!nvec) { + apr_bucket_delete(bucket); + } } - } - - remove_empty_buckets(bb); - - return APR_SUCCESS; -} - -static void remove_empty_buckets(apr_bucket_brigade *bb) -{ - apr_bucket *bucket; - while (((bucket = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) && - (APR_BUCKET_IS_METADATA(bucket) || (bucket->length == 0))) { - apr_bucket_delete(bucket); - } -} - -static apr_status_t send_brigade_blocking(apr_socket_t *s, - apr_bucket_brigade *bb, - apr_size_t *bytes_written, - conn_rec *c) -{ - apr_status_t rv; - - rv = APR_SUCCESS; - while (!APR_BRIGADE_EMPTY(bb)) { - rv = send_brigade_nonblocking(s, bb, bytes_written, c); - if (rv != APR_SUCCESS) { - if (APR_STATUS_IS_EAGAIN(rv)) { - /* Wait until we can send more data */ - apr_int32_t nsds; - apr_interval_time_t timeout; - apr_pollfd_t pollset; - - pollset.p = c->pool; - pollset.desc_type = APR_POLL_SOCKET; - pollset.reqevents = APR_POLLOUT; - pollset.desc.s = s; - apr_socket_timeout_get(s, &timeout); - do { - rv = apr_poll(&pollset, 1, &nsds, timeout); - } while (APR_STATUS_IS_EINTR(rv)); - if (rv != APR_SUCCESS) { - break; + else { + /* Make sure that these new data fit in our iovec. */ + if (nvec == ctx->nvec) { + if (nvec == NVEC_MAX) { + sock_nopush(s, 1); + rv = writev_nonblocking(s, bb, ctx, nbytes, nvec, c); + if (rv != APR_SUCCESS) { + goto cleanup; + } + nbytes = 0; + nvec = 0; + } + else { + struct iovec *newvec; + apr_size_t newn = nvec * 2; + if (newn < NVEC_MIN) { + newn = NVEC_MIN; + } + else if (newn > NVEC_MAX) { + newn = NVEC_MAX; + } + newvec = apr_palloc(c->pool, newn * sizeof(struct iovec)); + if (nvec) { + memcpy(newvec, ctx->vec, nvec * sizeof(struct iovec)); + } + ctx->vec = newvec; + ctx->nvec = newn; } } - else { - break; + nbytes += length; + ctx->vec[nvec].iov_base = (void *)data; + ctx->vec[nvec].iov_len = length; + nvec++; + } + + /* Flush above max threshold, unless the brigade still contains in + * memory buckets which we want to try writing in the same pass (if + * we are at the end of the brigade, the write will happen outside + * the loop anyway). + */ + if (nbytes > conf->flush_max_threshold + && next != APR_BRIGADE_SENTINEL(bb) + && next->length && !is_in_memory_bucket(next)) { + sock_nopush(s, 1); + rv = writev_nonblocking(s, bb, ctx, nbytes, nvec, c); + if (rv != APR_SUCCESS) { + goto cleanup; } + nbytes = 0; + nvec = 0; } } + if (nvec > 0) { + rv = writev_nonblocking(s, bb, ctx, nbytes, nvec, c); + } + +cleanup: + sock_nopush(s, 0); return rv; } static apr_status_t writev_nonblocking(apr_socket_t *s, - struct iovec *vec, apr_size_t nvec, apr_bucket_brigade *bb, - apr_size_t *cumulative_bytes_written, + core_output_filter_ctx_t *ctx, + apr_size_t bytes_to_write, + apr_size_t nvec, conn_rec *c) { - apr_status_t rv = APR_SUCCESS, arv; - apr_size_t bytes_written = 0, bytes_to_write = 0; - apr_size_t i, offset; - apr_interval_time_t old_timeout; - - arv = apr_socket_timeout_get(s, &old_timeout); - if (arv != APR_SUCCESS) { - return arv; - } - arv = apr_socket_timeout_set(s, 0); - if (arv != APR_SUCCESS) { - return arv; - } + apr_status_t rv; + struct iovec *vec = ctx->vec; + apr_size_t bytes_written = 0; + apr_size_t i, offset = 0; - for (i = 0; i < nvec; i++) { - bytes_to_write += vec[i].iov_len; - } - offset = 0; - while (bytes_written < bytes_to_write) { + do { apr_size_t n = 0; rv = apr_socket_sendv(s, vec + offset, nvec - offset, &n); - if (n > 0) { - bytes_written += n; - for (i = offset; i < nvec; ) { - apr_bucket *bucket = APR_BRIGADE_FIRST(bb); - if (APR_BUCKET_IS_METADATA(bucket)) { - apr_bucket_delete(bucket); - } - else if (n >= vec[i].iov_len) { - apr_bucket_delete(bucket); - offset++; - n -= vec[i++].iov_len; - } - else { + bytes_written += n; + + for (i = offset; i < nvec; ) { + apr_bucket *bucket = APR_BRIGADE_FIRST(bb); + if (!bucket->length) { + apr_bucket_delete(bucket); + } + else if (n >= vec[i].iov_len) { + apr_bucket_delete(bucket); + n -= vec[i++].iov_len; + offset++; + } + else { + if (n) { apr_bucket_split(bucket, n); apr_bucket_delete(bucket); vec[i].iov_len -= n; vec[i].iov_base = (char *) vec[i].iov_base + n; - break; } + break; } } - if (rv != APR_SUCCESS) { - break; - } - } + } while (rv == APR_SUCCESS && bytes_written < bytes_to_write); + if ((ap__logio_add_bytes_out != NULL) && (bytes_written > 0)) { ap__logio_add_bytes_out(c, bytes_written); } - *cumulative_bytes_written += bytes_written; + ctx->bytes_written += bytes_written; - arv = apr_socket_timeout_set(s, old_timeout); - if ((arv != APR_SUCCESS) && (rv == APR_SUCCESS)) { - return arv; - } - else { - return rv; - } + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, rv, c, + "writev_nonblocking: %"APR_SIZE_T_FMT"/%"APR_SIZE_T_FMT, + bytes_written, bytes_to_write); + return rv; } #if APR_HAS_SENDFILE static apr_status_t sendfile_nonblocking(apr_socket_t *s, apr_bucket *bucket, - apr_size_t *cumulative_bytes_written, + core_output_filter_ctx_t *ctx, conn_rec *c) { - apr_status_t rv = APR_SUCCESS; - apr_bucket_file *file_bucket; - apr_file_t *fd; - apr_size_t file_length; - apr_off_t file_offset; - apr_size_t bytes_written = 0; + apr_status_t rv; + apr_file_t *file = ((apr_bucket_file *)bucket->data)->fd; + apr_size_t bytes_written = bucket->length; /* bytes_to_write for now */ + apr_off_t file_offset = bucket->start; - if (!APR_BUCKET_IS_FILE(bucket)) { - ap_log_error(APLOG_MARK, APLOG_ERR, rv, c->base_server, APLOGNO(00006) - "core_filter: sendfile_nonblocking: " - "this should never happen"); - return APR_EGENERAL; - } - file_bucket = (apr_bucket_file *)(bucket->data); - fd = file_bucket->fd; - file_length = bucket->length; - file_offset = bucket->start; - - if (bytes_written < file_length) { - apr_size_t n = file_length - bytes_written; - apr_status_t arv; - apr_interval_time_t old_timeout; - - arv = apr_socket_timeout_get(s, &old_timeout); - if (arv != APR_SUCCESS) { - return arv; - } - arv = apr_socket_timeout_set(s, 0); - if (arv != APR_SUCCESS) { - return arv; - } - rv = apr_socket_sendfile(s, fd, NULL, &file_offset, &n, 0); - if (rv == APR_SUCCESS) { - bytes_written += n; - file_offset += n; - } - arv = apr_socket_timeout_set(s, old_timeout); - if ((arv != APR_SUCCESS) && (rv == APR_SUCCESS)) { - rv = arv; - } - } + rv = apr_socket_sendfile(s, file, NULL, &file_offset, &bytes_written, 0); if ((ap__logio_add_bytes_out != NULL) && (bytes_written > 0)) { ap__logio_add_bytes_out(c, bytes_written); } - *cumulative_bytes_written += bytes_written; - if ((bytes_written < file_length) && (bytes_written > 0)) { - apr_bucket_split(bucket, bytes_written); + ctx->bytes_written += bytes_written; + + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, rv, c, + "sendfile_nonblocking: %" APR_SIZE_T_FMT "/%" APR_SIZE_T_FMT, + bytes_written, bucket->length); + if (bytes_written >= bucket->length) { apr_bucket_delete(bucket); } - else if (bytes_written == file_length) { + else if (bytes_written > 0) { + apr_bucket_split(bucket, bytes_written); apr_bucket_delete(bucket); + if (rv == APR_SUCCESS) { + rv = APR_EAGAIN; + } } return rv; } diff --git a/server/eor_bucket.c b/server/eor_bucket.c index 4d3e1ec..ecb809c 100644 --- a/server/eor_bucket.c +++ b/server/eor_bucket.c @@ -19,17 +19,22 @@ #include "http_protocol.h" #include "scoreboard.h" +typedef struct { + apr_bucket_refcount refcount; + request_rec *data; +} ap_bucket_eor; + static apr_status_t eor_bucket_cleanup(void *data) { - apr_bucket *b = (apr_bucket *)data; - request_rec *r = (request_rec *)b->data; + request_rec **rp = data; - if (r != NULL) { + if (*rp) { + request_rec *r = *rp; /* * If eor_bucket_destroy is called after us, this prevents * eor_bucket_destroy from trying to destroy the pool again. */ - b->data = NULL; + *rp = NULL; /* Update child status and log the transaction */ ap_update_child_status(r->connection->sbh, SERVER_BUSY_LOG, r); ap_run_log_transaction(r); @@ -50,11 +55,13 @@ static apr_status_t eor_bucket_read(apr_bucket *b, const char **str, AP_DECLARE(apr_bucket *) ap_bucket_eor_make(apr_bucket *b, request_rec *r) { - b->length = 0; - b->start = 0; - b->data = r; - b->type = &ap_bucket_type_eor; + ap_bucket_eor *h; + + h = apr_bucket_alloc(sizeof(*h), b->list); + h->data = r; + b = apr_bucket_shared_make(b, h, 0, 0); + b->type = &ap_bucket_type_eor; return b; } @@ -66,7 +73,9 @@ AP_DECLARE(apr_bucket *) ap_bucket_eor_create(apr_bucket_alloc_t *list, APR_BUCKET_INIT(b); b->free = apr_bucket_free; b->list = list; + b = ap_bucket_eor_make(b, r); if (r) { + ap_bucket_eor *h = b->data; /* * Register a cleanup for the request pool as the eor bucket could * have been allocated from a different pool then the request pool @@ -76,18 +85,22 @@ AP_DECLARE(apr_bucket *) ap_bucket_eor_create(apr_bucket_alloc_t *list, * We need to use a pre-cleanup here because a module may create a * sub-pool which is still needed during the log_transaction hook. */ - apr_pool_pre_cleanup_register(r->pool, (void *)b, eor_bucket_cleanup); + apr_pool_pre_cleanup_register(r->pool, &h->data, eor_bucket_cleanup); } - return ap_bucket_eor_make(b, r); + return b; } static void eor_bucket_destroy(void *data) { - request_rec *r = (request_rec *)data; + ap_bucket_eor *h = data; - if (r) { - /* eor_bucket_cleanup will be called when the pool gets destroyed */ - apr_pool_destroy(r->pool); + if (apr_bucket_shared_destroy(h)) { + request_rec *r = h->data; + if (r) { + /* eor_bucket_cleanup will be called when the pool gets destroyed */ + apr_pool_destroy(r->pool); + } + apr_bucket_free(h); } } @@ -97,6 +110,6 @@ AP_DECLARE_DATA const apr_bucket_type_t ap_bucket_type_eor = { eor_bucket_read, apr_bucket_setaside_noop, apr_bucket_split_notimpl, - apr_bucket_simple_copy + apr_bucket_shared_copy }; diff --git a/server/gen_test_char.c b/server/gen_test_char.c index 48ae6f4..248216b 100644 --- a/server/gen_test_char.c +++ b/server/gen_test_char.c @@ -54,6 +54,7 @@ #define T_ESCAPE_URLENCODED (0x40) #define T_HTTP_CTRLS (0x80) #define T_VCHAR_OBSTEXT (0x100) +#define T_URI_UNRESERVED (0x200) int main(int argc, char *argv[]) { @@ -71,6 +72,7 @@ int main(int argc, char *argv[]) "#define T_ESCAPE_URLENCODED (%u)\n" "#define T_HTTP_CTRLS (%u)\n" "#define T_VCHAR_OBSTEXT (%u)\n" + "#define T_URI_UNRESERVED (%u)\n" "\n" "static const unsigned short test_char_table[256] = {", T_ESCAPE_SHELL_CMD, @@ -81,7 +83,9 @@ int main(int argc, char *argv[]) T_ESCAPE_FORENSIC, T_ESCAPE_URLENCODED, T_HTTP_CTRLS, - T_VCHAR_OBSTEXT); + T_VCHAR_OBSTEXT, + T_URI_UNRESERVED + ); for (c = 0; c < 256; ++c) { flags = 0; @@ -123,7 +127,7 @@ int main(int argc, char *argv[]) /* Stop for any non-'token' character, including ctrls, obs-text, * and "tspecials" (RFC2068) a.k.a. "separators" (RFC2616), which - * is easer to express as characters remaining in the ASCII token set + * is easier to express as characters remaining in the ASCII token set */ if (!c || !(apr_isalnum(c) || strchr("!#$%&'*+-.^_`|~", c))) { flags |= T_HTTP_TOKEN_STOP; @@ -164,10 +168,25 @@ int main(int argc, char *argv[]) flags |= T_ESCAPE_FORENSIC; } + /* Characters in the RFC 3986 "unreserved" set. + * https://datatracker.ietf.org/doc/html/rfc3986#section-2.3 */ + if (c && (apr_isalnum(c) || strchr("-._~", c))) { + flags |= T_URI_UNRESERVED; + } + printf("0x%03x%c", flags, (c < 255) ? ',' : ' '); } - printf("\n};\n"); + printf("\n};\n\n"); + + printf( + "/* we assume the folks using this ensure 0 <= c < 256... which means\n" + " * you need a cast to (unsigned char) first, you can't just plug a\n" + " * char in here and get it to work, because if char is signed then it\n" + " * will first be sign extended.\n" + " */\n" + "#define TEST_CHAR(c, f) (test_char_table[(unsigned char)(c)] & (f))\n" + ); return 0; } diff --git a/server/listen.c b/server/listen.c index a8e9e6f..5242c2a 100644 --- a/server/listen.c +++ b/server/listen.c @@ -784,7 +784,7 @@ AP_DECLARE(void) ap_listen_pre_config(void) * * *BSDs have SO_REUSEPORT too but with a different semantic: the first * wildcard address bound socket or the last non-wildcard address bound - * socket will receive connections (no evenness garantee); the rest of + * socket will receive connections (no evenness guarantee); the rest of * the sockets bound to the same port will not. * This can't (always) work for httpd. * diff --git a/server/log.c b/server/log.c index 42d0b8f..22d2f8d 100644 --- a/server/log.c +++ b/server/log.c @@ -55,7 +55,7 @@ #include "ap_mpm.h" #include "ap_listen.h" -#if HAVE_GETTID +#ifdef HAVE_SYS_GETTID #include <sys/syscall.h> #include <sys/types.h> #endif @@ -276,7 +276,7 @@ AP_DECLARE(apr_status_t) ap_replace_stderr_log(apr_pool_t *p, /* * You might ponder why stderr_pool should survive? * The trouble is, stderr_pool may have s_main->error_log, - * so we aren't in a position to destory stderr_pool until + * so we aren't in a position to destroy stderr_pool until * the next recycle. There's also an apparent bug which * is not; if some folk decided to call this function before * the core open error logs hook, this pool won't survive. @@ -303,7 +303,7 @@ static void log_child_errfn(apr_pool_t *pool, apr_status_t err, } /* Create a child process running PROGNAME with a pipe connected to - * the childs stdin. The write-end of the pipe will be placed in + * the child's stdin. The write-end of the pipe will be placed in * *FPIN on successful return. If dummy_stderr is non-zero, the * stderr for the child will be the same as the stdout of the parent. * Otherwise the child will inherit the stderr from the parent. */ @@ -341,8 +341,10 @@ static int log_child(apr_pool_t *p, const char *progname, rc = apr_procattr_child_err_set(procattr, errfile, NULL); } - rc = apr_proc_create(procnew, args[0], (const char * const *)args, - NULL, procattr, p); + if (rc == APR_SUCCESS) { + rc = apr_proc_create(procnew, args[0], (const char * const *)args, + NULL, procattr, p); + } if (rc == APR_SUCCESS) { apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT); @@ -625,14 +627,18 @@ static int log_tid(const ap_errorlog_info *info, const char *arg, #if APR_HAS_THREADS int result; #endif -#if HAVE_GETTID +#if defined(HAVE_GETTID) || defined(HAVE_SYS_GETTID) if (arg && *arg == 'g') { +#ifdef HAVE_GETTID + pid_t tid = gettid(); +#else pid_t tid = syscall(SYS_gettid); +#endif if (tid == -1) return 0; return apr_snprintf(buf, buflen, "%"APR_PID_T_FMT, tid); } -#endif +#endif /* HAVE_GETTID || HAVE_SYS_GETTID */ #if APR_HAS_THREADS if (ap_mpm_query(AP_MPMQ_IS_THREADED, &result) == APR_SUCCESS && result != AP_MPMQ_NOT_SUPPORTED) @@ -650,14 +656,32 @@ static int log_ctime(const ap_errorlog_info *info, const char *arg, int time_len = buflen; int option = AP_CTIME_OPTION_NONE; - while (arg && *arg) { - switch (*arg) { - case 'u': option |= AP_CTIME_OPTION_USEC; - break; - case 'c': option |= AP_CTIME_OPTION_COMPACT; - break; + if (arg) { + if (arg[0] == 'u' && !arg[1]) { /* no ErrorLogFormat (fast path) */ + option |= AP_CTIME_OPTION_USEC; + } + else if (!ap_strchr_c(arg, '%')) { /* special "%{cuz}t" formats */ + while (*arg) { + switch (*arg++) { + case 'u': + option |= AP_CTIME_OPTION_USEC; + break; + case 'c': + option |= AP_CTIME_OPTION_COMPACT; + break; + case 'z': + option |= AP_CTIME_OPTION_GMTOFF; + break; + } + } + } + else { /* "%{strftime %-format}t" */ + apr_size_t len = 0; + apr_time_exp_t expt; + ap_explode_recent_localtime(&expt, apr_time_now()); + apr_strftime(buf, &len, buflen, arg, &expt); + return (int)len; } - arg++; } ap_recent_ctime_ex(buf, apr_time_now(), option, &time_len); @@ -1164,6 +1188,11 @@ static void log_error_core(const char *file, int line, int module_index, #endif logf = stderr_log; + + /* Use the main ErrorLogFormat if any */ + if (ap_server_conf) { + sconf = ap_get_core_module_config(ap_server_conf->module_config); + } } else { int configured_level = r ? ap_get_request_module_loglevel(r, module_index) : @@ -1217,6 +1246,10 @@ static void log_error_core(const char *file, int line, int module_index, } } } + else if (ap_server_conf) { + /* Use the main ErrorLogFormat if any */ + sconf = ap_get_core_module_config(ap_server_conf->module_config); + } } info.s = s; @@ -1598,6 +1631,9 @@ AP_DECLARE(void) ap_log_pid(apr_pool_t *p, const char *filename) pid_t mypid; apr_status_t rv; const char *fname; + char *temp_fname; + apr_fileperms_t perms; + char pidstr[64]; if (!filename) { return; @@ -1626,19 +1662,31 @@ AP_DECLARE(void) ap_log_pid(apr_pool_t *p, const char *filename) fname); } - if ((rv = apr_file_open(&pid_file, fname, - APR_WRITE | APR_CREATE | APR_TRUNCATE, - APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD, p)) - != APR_SUCCESS) { + temp_fname = apr_pstrcat(p, fname, ".XXXXXX", NULL); + rv = apr_file_mktemp(&pid_file, temp_fname, + APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE, p); + if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(00099) - "could not create %s", fname); + "could not create %s", temp_fname); ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00100) "%s: could not log pid to file %s", ap_server_argv0, fname); exit(1); } - apr_file_printf(pid_file, "%" APR_PID_T_FMT APR_EOL_STR, mypid); - apr_file_close(pid_file); + + apr_snprintf(pidstr, sizeof pidstr, "%" APR_PID_T_FMT APR_EOL_STR, mypid); + + perms = APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD; + if (((rv = apr_file_perms_set(temp_fname, perms)) != APR_SUCCESS && rv != APR_ENOTIMPL) + || (rv = apr_file_write_full(pid_file, pidstr, strlen(pidstr), NULL)) != APR_SUCCESS + || (rv = apr_file_close(pid_file)) != APR_SUCCESS + || (rv = apr_file_rename(temp_fname, fname, p)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, NULL, APLOGNO(10231) + "%s: Failed creating pid file %s", + ap_server_argv0, temp_fname); + exit(1); + } + saved_pid = mypid; } @@ -1930,8 +1978,8 @@ AP_DECLARE(void) ap_close_piped_log(piped_log *pl) AP_DECLARE(const char *) ap_parse_log_level(const char *str, int *val) { - char *err = "Log level keyword must be one of emerg/alert/crit/error/warn/" - "notice/info/debug/trace1/.../trace8"; + const char *err = "Log level keyword must be one of emerg/alert/crit/error/" + "warn/notice/info/debug/trace1/.../trace8"; int i = 0; if (str == NULL) diff --git a/server/main.c b/server/main.c index f1bebeb..fb23f01 100644 --- a/server/main.c +++ b/server/main.c @@ -21,6 +21,7 @@ #include "apr_lib.h" #include "apr_md5.h" #include "apr_time.h" +#include "apr_thread_proc.h" #include "apr_version.h" #include "apu_version.h" @@ -98,13 +99,17 @@ static void show_compile_settings(void) printf("Server's Module Magic Number: %u:%u\n", MODULE_MAGIC_NUMBER_MAJOR, MODULE_MAGIC_NUMBER_MINOR); #if APR_MAJOR_VERSION >= 2 - printf("Server loaded: APR %s\n", apr_version_string()); - printf("Compiled using: APR %s\n", APR_VERSION_STRING); + printf("Server loaded: APR %s, PCRE %s\n", + apr_version_string(), ap_pcre_version_string(AP_REG_PCRE_LOADED)); + printf("Compiled using: APR %s, PCRE %s\n", + APR_VERSION_STRING, ap_pcre_version_string(AP_REG_PCRE_COMPILED)); #else - printf("Server loaded: APR %s, APR-UTIL %s\n", - apr_version_string(), apu_version_string()); - printf("Compiled using: APR %s, APR-UTIL %s\n", - APR_VERSION_STRING, APU_VERSION_STRING); + printf("Server loaded: APR %s, APR-UTIL %s, PCRE %s\n", + apr_version_string(), apu_version_string(), + ap_pcre_version_string(AP_REG_PCRE_LOADED)); + printf("Compiled using: APR %s, APR-UTIL %s, PCRE %s\n", + APR_VERSION_STRING, APU_VERSION_STRING, + ap_pcre_version_string(AP_REG_PCRE_COMPILED)); #endif /* sizeof(foo) is long on some platforms so we might as well * make it long everywhere to keep the printf format @@ -256,7 +261,7 @@ static void destroy_and_exit_process(process_rec *process, * Sleep for TASK_SWITCH_SLEEP micro seconds to cause a task switch on * OS layer and thus give possibly started piped loggers a chance to * process their input. Otherwise it is possible that they get killed - * by us before they can do so. In this case maybe valueable log messages + * by us before they can do so. In this case maybe valuable log messages * might get lost. */ apr_sleep(TASK_SWITCH_SLEEP); @@ -348,6 +353,23 @@ static process_rec *init_process(int *argc, const char * const * *argv) process->argc = *argc; process->argv = *argv; process->short_name = apr_filepath_name_get((*argv)[0]); + +#if AP_HAS_THREAD_LOCAL + { + apr_status_t rv; + apr_thread_t *thd = NULL; + if ((rv = ap_thread_main_create(&thd, process->pool))) { + char ctimebuff[APR_CTIME_LEN]; + apr_ctime(ctimebuff, apr_time_now()); + fprintf(stderr, "[%s] [crit] (%d) %s: failed " + "to initialize thread context, exiting\n", + ctimebuff, rv, (*argv)[0]); + apr_terminate(); + exit(1); + } + } +#endif + return process; } @@ -656,8 +678,8 @@ int main(int argc, const char * const argv[]) if (temp_error_log) { ap_replace_stderr_log(process->pool, temp_error_log); } - ap_server_conf = ap_read_config(process, ptemp, confname, &ap_conftree); - if (!ap_server_conf) { + ap_server_conf = NULL; /* set early by ap_read_config() for logging */ + if (!ap_read_config(process, ptemp, confname, &ap_conftree)) { if (showcompile) { /* Well, we tried. Show as much as we can, but exit nonzero to * indicate that something's not right. The cause should have @@ -666,6 +688,7 @@ int main(int argc, const char * const argv[]) } destroy_and_exit_process(process, 1); } + ap_assert(ap_server_conf != NULL); apr_pool_cleanup_register(pconf, &ap_server_conf, ap_pool_cleanup_set_null, apr_pool_cleanup_null); @@ -763,10 +786,11 @@ int main(int argc, const char * const argv[]) apr_pool_create(&ptemp, pconf); apr_pool_tag(ptemp, "ptemp"); ap_server_root = def_server_root; - ap_server_conf = ap_read_config(process, ptemp, confname, &ap_conftree); - if (!ap_server_conf) { + ap_server_conf = NULL; /* set early by ap_read_config() for logging */ + if (!ap_read_config(process, ptemp, confname, &ap_conftree)) { destroy_and_exit_process(process, 1); } + ap_assert(ap_server_conf != NULL); apr_pool_cleanup_register(pconf, &ap_server_conf, ap_pool_cleanup_set_null, apr_pool_cleanup_null); /* sort hooks here to make sure pre_config hooks are sorted properly */ diff --git a/server/mpm/event/event.c b/server/mpm/event/event.c index 4cfb09c..3672f44 100644 --- a/server/mpm/event/event.c +++ b/server/mpm/event/event.c @@ -167,8 +167,6 @@ static int ap_daemons_to_start = 0; /* StartServers */ static int min_spare_threads = 0; /* MinSpareThreads */ static int max_spare_threads = 0; /* MaxSpareThreads */ static int active_daemons_limit = 0; /* MaxRequestWorkers / ThreadsPerChild */ -static int active_daemons = 0; /* workers that still active, i.e. are - not shutting down gracefully */ static int max_workers = 0; /* MaxRequestWorkers */ static int server_limit = 0; /* ServerLimit */ static int thread_limit = 0; /* ThreadLimit */ @@ -248,6 +246,8 @@ struct event_conn_state_t { conn_state_t pub; /** chaining in defer_linger_chain */ struct event_conn_state_t *chain; + /** Is lingering close from defer_lingering_close()? */ + int deferred_linger; }; APR_RING_HEAD(timeout_head_t, event_conn_state_t); @@ -285,21 +285,21 @@ static volatile apr_time_t queues_next_expiry; */ static void TO_QUEUE_APPEND(struct timeout_queue *q, event_conn_state_t *el) { - apr_time_t q_expiry; + apr_time_t elem_expiry; apr_time_t next_expiry; APR_RING_INSERT_TAIL(&q->head, el, event_conn_state_t, timeout_list); ++*q->total; ++q->count; - /* Cheaply update the overall queues' next expiry according to the - * first entry of this queue (oldest), if necessary. + /* Cheaply update the global queues_next_expiry with the one of the + * first entry of this queue (oldest) if it expires before. */ el = APR_RING_FIRST(&q->head); - q_expiry = el->queue_timestamp + q->timeout; + elem_expiry = el->queue_timestamp + q->timeout; next_expiry = queues_next_expiry; - if (!next_expiry || next_expiry > q_expiry + TIMEOUT_FUDGE_FACTOR) { - queues_next_expiry = q_expiry; + if (!next_expiry || next_expiry > elem_expiry + TIMEOUT_FUDGE_FACTOR) { + queues_next_expiry = elem_expiry; /* Unblock the poll()ing listener for it to update its timeout. */ if (listener_is_wakeable) { apr_pollset_wakeup(event_pollset); @@ -379,7 +379,7 @@ typedef struct event_retained_data { * We use this value to optimize routines that have to scan the entire * scoreboard. */ - int max_daemons_limit; + int max_daemon_used; /* * All running workers, active and shutting down, including those that @@ -387,7 +387,10 @@ typedef struct event_retained_data { * Not kept up-to-date when shutdown is pending. */ int total_daemons; - + /* + * Workers that still active, i.e. are not shutting down gracefully. + */ + int active_daemons; /* * idle_spawn_rate is the number of children that will be spawned on the * next maintenance cycle if there aren't enough idle servers. It is @@ -505,7 +508,7 @@ static APR_INLINE apr_uint32_t listeners_disabled(void) return apr_atomic_read32(&listensocks_disabled); } -static APR_INLINE int connections_above_limit(void) +static APR_INLINE int connections_above_limit(int *busy) { apr_uint32_t i_count = ap_queue_info_num_idlers(worker_queue_info); if (i_count > 0) { @@ -519,28 +522,32 @@ static APR_INLINE int connections_above_limit(void) return 0; } } + else if (busy) { + *busy = 1; + } return 1; } -static void abort_socket_nonblocking(apr_socket_t *csd) +static APR_INLINE int should_enable_listensocks(void) +{ + return !dying && listeners_disabled() && !connections_above_limit(NULL); +} + +static void close_socket_nonblocking_(apr_socket_t *csd, + const char *from, int line) { apr_status_t rv; - apr_socket_timeout_set(csd, 0); -#if defined(SOL_SOCKET) && defined(SO_LINGER) - /* This socket is over now, and we don't want to block nor linger - * anymore, so reset it. A normal close could still linger in the - * system, while RST is fast, nonblocking, and what the peer will - * get if it sends us further data anyway. - */ - { - apr_os_sock_t osd = -1; - struct linger opt; - opt.l_onoff = 1; - opt.l_linger = 0; /* zero timeout is RST */ - apr_os_sock_get(&osd, csd); - setsockopt(osd, SOL_SOCKET, SO_LINGER, (void *)&opt, sizeof opt); + apr_os_sock_t fd = -1; + + /* close_worker_sockets() may have closed it already */ + rv = apr_os_sock_get(&fd, csd); + ap_log_error(APLOG_MARK, APLOG_TRACE8, 0, ap_server_conf, + "closing socket %i/%pp from %s:%i", (int)fd, csd, from, line); + if (rv == APR_SUCCESS && fd == -1) { + return; } -#endif + + apr_socket_timeout_set(csd, 0); rv = apr_socket_close(csd); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00468) @@ -548,6 +555,8 @@ static void abort_socket_nonblocking(apr_socket_t *csd) AP_DEBUG_ASSERT(0); } } +#define close_socket_nonblocking(csd) \ + close_socket_nonblocking_(csd, __FUNCTION__, __LINE__) static void close_worker_sockets(void) { @@ -556,26 +565,16 @@ static void close_worker_sockets(void) apr_socket_t *csd = worker_sockets[i]; if (csd) { worker_sockets[i] = NULL; - abort_socket_nonblocking(csd); + close_socket_nonblocking(csd); } } - for (;;) { - event_conn_state_t *cs = defer_linger_chain; - if (!cs) { - break; - } - if (apr_atomic_casptr((void *)&defer_linger_chain, cs->chain, - cs) != cs) { - /* Race lost, try again */ - continue; - } - cs->chain = NULL; - abort_socket_nonblocking(cs->pfd.desc.s); - } } static void wakeup_listener(void) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "wake up listener%s", listener_may_exit ? " again" : ""); + listener_may_exit = 1; disable_listensocks(); @@ -637,6 +636,8 @@ static void signal_threads(int mode) ap_queue_interrupt_all(worker_queue); close_worker_sockets(); /* forcefully kill all current connections */ } + + ap_run_child_stopping(pchild, mode == ST_GRACEFUL); } static int event_query(int query_code, int *result, apr_status_t *rv) @@ -644,7 +645,7 @@ static int event_query(int query_code, int *result, apr_status_t *rv) *rv = APR_SUCCESS; switch (query_code) { case AP_MPMQ_MAX_DAEMON_USED: - *result = retained->max_daemons_limit; + *result = retained->max_daemon_used; break; case AP_MPMQ_IS_THREADED: *result = AP_MPMQ_STATIC; @@ -695,14 +696,32 @@ static int event_query(int query_code, int *result, apr_status_t *rv) return OK; } -static void event_note_child_killed(int childnum, pid_t pid, ap_generation_t gen) +static void event_note_child_stopped(int slot, pid_t pid, ap_generation_t gen) { - if (childnum != -1) { /* child had a scoreboard slot? */ - ap_run_child_status(ap_server_conf, - ap_scoreboard_image->parent[childnum].pid, - ap_scoreboard_image->parent[childnum].generation, - childnum, MPM_CHILD_EXITED); - ap_scoreboard_image->parent[childnum].pid = 0; + if (slot != -1) { /* child had a scoreboard slot? */ + process_score *ps = &ap_scoreboard_image->parent[slot]; + int i; + + pid = ps->pid; + gen = ps->generation; + for (i = 0; i < threads_per_child; i++) { + ap_update_child_status_from_indexes(slot, i, SERVER_DEAD, NULL); + } + ap_run_child_status(ap_server_conf, pid, gen, slot, MPM_CHILD_EXITED); + if (ps->quiescing != 2) { /* vs perform_idle_server_maintenance() */ + retained->active_daemons--; + } + retained->total_daemons--; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "Child %d stopped: pid %d, gen %d, " + "active %d/%d, total %d/%d/%d, quiescing %d", + slot, (int)pid, (int)gen, + retained->active_daemons, active_daemons_limit, + retained->total_daemons, retained->max_daemon_used, + server_limit, ps->quiescing); + ps->not_accepting = 0; + ps->quiescing = 0; + ps->pid = 0; } else { ap_run_child_status(ap_server_conf, pid, gen, -1, MPM_CHILD_EXITED); @@ -712,9 +731,19 @@ static void event_note_child_killed(int childnum, pid_t pid, ap_generation_t gen static void event_note_child_started(int slot, pid_t pid) { ap_generation_t gen = retained->mpm->my_generation; + + retained->total_daemons++; + retained->active_daemons++; ap_scoreboard_image->parent[slot].pid = pid; ap_scoreboard_image->parent[slot].generation = gen; ap_run_child_status(ap_server_conf, pid, gen, slot, MPM_CHILD_STARTED); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "Child %d started: pid %d, gen %d, " + "active %d/%d, total %d/%d/%d", + slot, (int)pid, (int)gen, + retained->active_daemons, active_daemons_limit, + retained->total_daemons, retained->max_daemon_used, + server_limit); } static const char *event_get_name(void) @@ -727,12 +756,16 @@ static void clean_child_exit(int code) __attribute__ ((noreturn)); static void clean_child_exit(int code) { retained->mpm->mpm_state = AP_MPMQ_STOPPING; + if (terminate_mode == ST_INIT) { + ap_run_child_stopping(pchild, 0); + } + if (pchild) { apr_pool_destroy(pchild); } if (one_process) { - event_note_child_killed(/* slot */ 0, 0, 0); + event_note_child_stopped(/* slot */ 0, 0, 0); } exit(code); @@ -753,7 +786,10 @@ static apr_status_t decrement_connection_count(void *cs_) { int is_last_connection; event_conn_state_t *cs = cs_; + ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, cs->c, + "cleanup connection from state %i", (int)cs->pub.state); switch (cs->pub.state) { + case CONN_STATE_LINGER: case CONN_STATE_LINGER_NORMAL: case CONN_STATE_LINGER_SHORT: apr_atomic_dec32(&lingering_count); @@ -771,9 +807,13 @@ static apr_status_t decrement_connection_count(void *cs_) is_last_connection = !apr_atomic_dec32(&connection_count); if (listener_is_wakeable && ((is_last_connection && listener_may_exit) - || (listeners_disabled() && !connections_above_limit()))) { + || should_enable_listensocks())) { apr_pollset_wakeup(event_pollset); } + if (dying) { + /* Help worker_thread_should_exit_early() */ + ap_queue_interrupt_one(worker_queue); + } return APR_SUCCESS; } @@ -792,65 +832,26 @@ static void notify_resume(event_conn_state_t *cs, int cleanup) } /* - * Close our side of the connection, flushing data to the client first. - * Pre-condition: cs is not in any timeout queue and not in the pollset, - * timeout_mutex is not locked - * return: 0 if connection is fully closed, - * 1 if connection is lingering - * May only be called by worker thread. + * Defer flush and close of the connection by adding it to defer_linger_chain, + * for a worker to grab it and do the job (should that be blocking). + * Pre-condition: nonblocking, can be called from anywhere provided cs is not + * in any timeout queue or in the pollset. */ -static int start_lingering_close_blocking(event_conn_state_t *cs) +static int defer_lingering_close(event_conn_state_t *cs) { - apr_socket_t *csd = cs->pfd.desc.s; - - if (ap_start_lingering_close(cs->c)) { - notify_suspend(cs); - apr_socket_close(csd); - ap_queue_info_push_pool(worker_queue_info, cs->p); - return DONE; - } - -#ifdef AP_DEBUG - { - apr_status_t rv; - rv = apr_socket_timeout_set(csd, 0); - AP_DEBUG_ASSERT(rv == APR_SUCCESS); - } -#else - apr_socket_timeout_set(csd, 0); -#endif + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, cs->c, + "deferring close from state %i", (int)cs->pub.state); - cs->queue_timestamp = apr_time_now(); - /* - * If some module requested a shortened waiting period, only wait for - * 2s (SECONDS_TO_LINGER). This is useful for mitigating certain - * DoS attacks. + /* The connection is not shutdown() yet strictly speaking, but it's not + * in any queue nor handled by a worker either (will be very soon), so + * to account for it somewhere we bump lingering_count now (and set + * deferred_linger for process_lingering_close() to know). */ - if (apr_table_get(cs->c->notes, "short-lingering-close")) { - cs->pub.state = CONN_STATE_LINGER_SHORT; - } - else { - cs->pub.state = CONN_STATE_LINGER_NORMAL; - } + cs->pub.state = CONN_STATE_LINGER; apr_atomic_inc32(&lingering_count); - notify_suspend(cs); - - return OK; -} - -/* - * Defer flush and close of the connection by adding it to defer_linger_chain, - * for a worker to grab it and do the job (should that be blocking). - * Pre-condition: cs is not in any timeout queue and not in the pollset, - * timeout_mutex is not locked - * return: 1 connection is alive (but aside and about to linger) - * May be called by listener thread. - */ -static int start_lingering_close_nonblocking(event_conn_state_t *cs) -{ - event_conn_state_t *chain; + cs->deferred_linger = 1; for (;;) { - cs->chain = chain = defer_linger_chain; + event_conn_state_t *chain = cs->chain = defer_linger_chain; if (apr_atomic_casptr((void *)&defer_linger_chain, cs, chain) != chain) { /* Race lost, try again */ @@ -860,22 +861,37 @@ static int start_lingering_close_nonblocking(event_conn_state_t *cs) } } -/* - * forcibly close a lingering connection after the lingering period has - * expired - * Pre-condition: cs is not in any timeout queue and not in the pollset - * return: irrelevant (need same prototype as start_lingering_close) +/* Close the connection and release its resources (ptrans), either because an + * unrecoverable error occured (queues or pollset add/remove) or more usually + * if lingering close timed out. + * Pre-condition: nonblocking, can be called from anywhere provided cs is not + * in any timeout queue or in the pollset. */ -static int stop_lingering_close(event_conn_state_t *cs) +static void close_connection(event_conn_state_t *cs) { - apr_socket_t *csd = ap_get_conn_socket(cs->c); - ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, ap_server_conf, - "socket abort in state %i", (int)cs->pub.state); - abort_socket_nonblocking(csd); + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, cs->c, + "closing connection from state %i", (int)cs->pub.state); + + close_socket_nonblocking(cs->pfd.desc.s); ap_queue_info_push_pool(worker_queue_info, cs->p); - if (dying) - ap_queue_interrupt_one(worker_queue); - return 0; +} + +/* Shutdown the connection in case of timeout, error or resources shortage. + * This starts short lingering close if not already there, or directly closes + * the connection otherwise. + * Pre-condition: nonblocking, can be called from anywhere provided cs is not + * in any timeout queue or in the pollset. + */ +static int shutdown_connection(event_conn_state_t *cs) +{ + if (cs->pub.state < CONN_STATE_LINGER) { + apr_table_setn(cs->c->notes, "short-lingering-close", "1"); + defer_lingering_close(cs); + } + else { + close_connection(cs); + } + return 1; } /* @@ -947,6 +963,27 @@ static int event_post_read_request(request_rec *r) /* Forward declare */ static void process_lingering_close(event_conn_state_t *cs); +static void update_reqevents_from_sense(event_conn_state_t *cs, int sense) +{ + if (sense < 0) { + sense = cs->pub.sense; + } + if (sense == CONN_SENSE_WANT_READ) { + cs->pfd.reqevents = APR_POLLIN | APR_POLLHUP; + } + else { + cs->pfd.reqevents = APR_POLLOUT; + } + /* POLLERR is usually returned event only, but some pollset + * backends may require it in reqevents to do the right thing, + * so it shouldn't hurt (ignored otherwise). + */ + cs->pfd.reqevents |= APR_POLLERR; + + /* Reset to default for the next round */ + cs->pub.sense = CONN_SENSE_DEFAULT; +} + /* * process one connection in the worker */ @@ -976,14 +1013,14 @@ static void process_socket(apr_thread_t *thd, apr_pool_t * p, apr_socket_t * soc apr_pool_cleanup_null); ap_set_module_config(c->conn_config, &mpm_event_module, cs); c->current_thread = thd; + c->cs = &cs->pub; cs->c = c; - c->cs = &(cs->pub); cs->p = p; cs->sc = ap_get_module_config(ap_server_conf->module_config, &mpm_event_module); cs->pfd.desc_type = APR_POLL_SOCKET; - cs->pfd.reqevents = APR_POLLIN; cs->pfd.desc.s = sock; + update_reqevents_from_sense(cs, CONN_SENSE_WANT_READ); pt->type = PT_CSD; pt->baton = cs; cs->pfd.client_data = pt; @@ -992,11 +1029,10 @@ static void process_socket(apr_thread_t *thd, apr_pool_t * p, apr_socket_t * soc ap_update_vhost_given_ip(c); - rc = ap_run_pre_connection(c, sock); + rc = ap_pre_connection(c, sock); if (rc != OK && rc != DONE) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(00469) "process_socket: connection aborted"); - c->aborted = 1; } /** @@ -1074,7 +1110,7 @@ read_request: * completion at some point may require reads (e.g. SSL_ERROR_WANT_READ), * an output filter can also set the sense to CONN_SENSE_WANT_READ at any * time for event MPM to do the right thing, - * - suspend the connection (SUSPENDED) such that it now interracts with + * - suspend the connection (SUSPENDED) such that it now interacts with * the MPM through suspend/resume_connection() hooks, and/or registered * poll callbacks (PT_USER), and/or registered timed callbacks triggered * by timer events. @@ -1113,27 +1149,16 @@ read_request: "network write failure in core output filter"); cs->pub.state = CONN_STATE_LINGER; } - else if (c->data_in_output_filters) { + else if (c->data_in_output_filters || + cs->pub.sense == CONN_SENSE_WANT_READ) { /* Still in WRITE_COMPLETION_STATE: - * Set a write timeout for this connection, and let the - * event thread poll for writeability. + * Set a read/write timeout for this connection, and let the + * event thread poll for read/writeability. */ cs->queue_timestamp = apr_time_now(); notify_suspend(cs); - if (cs->pub.sense == CONN_SENSE_WANT_READ) { - cs->pfd.reqevents = APR_POLLIN; - } - else { - cs->pfd.reqevents = APR_POLLOUT; - } - /* POLLHUP/ERR are usually returned event only (ignored here), but - * some pollset backends may require them in reqevents to do the - * right thing, so it shouldn't hurt. - */ - cs->pfd.reqevents |= APR_POLLHUP | APR_POLLERR; - cs->pub.sense = CONN_SENSE_DEFAULT; - + update_reqevents_from_sense(cs, -1); apr_thread_mutex_lock(timeout_mutex); TO_QUEUE_APPEND(cs->sc->wc_q, cs); rv = apr_pollset_add(event_pollset, &cs->pfd); @@ -1144,25 +1169,27 @@ read_request: ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(03465) "process_socket: apr_pollset_add failure for " "write completion"); - apr_socket_close(cs->pfd.desc.s); - ap_queue_info_push_pool(worker_queue_info, cs->p); + close_connection(cs); + signal_threads(ST_GRACEFUL); } else { apr_thread_mutex_unlock(timeout_mutex); } return; } - else if (c->keepalive != AP_CONN_KEEPALIVE || c->aborted || - listener_may_exit) { + else if (c->keepalive != AP_CONN_KEEPALIVE || c->aborted) { cs->pub.state = CONN_STATE_LINGER; } else if (c->data_in_input_filters) { cs->pub.state = CONN_STATE_READ_REQUEST_LINE; goto read_request; } - else { + else if (!listener_may_exit) { cs->pub.state = CONN_STATE_CHECK_REQUEST_LINE_READABLE; } + else { + cs->pub.state = CONN_STATE_LINGER; + } } if (cs->pub.state == CONN_STATE_CHECK_REQUEST_LINE_READABLE) { @@ -1180,7 +1207,7 @@ read_request: notify_suspend(cs); /* Add work to pollset. */ - cs->pfd.reqevents = APR_POLLIN; + update_reqevents_from_sense(cs, CONN_SENSE_WANT_READ); apr_thread_mutex_lock(timeout_mutex); TO_QUEUE_APPEND(cs->sc->ka_q, cs); rv = apr_pollset_add(event_pollset, &cs->pfd); @@ -1191,8 +1218,8 @@ read_request: ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(03093) "process_socket: apr_pollset_add failure for " "keep alive"); - apr_socket_close(cs->pfd.desc.s); - ap_queue_info_push_pool(worker_queue_info, cs->p); + close_connection(cs); + signal_threads(ST_GRACEFUL); } else { apr_thread_mutex_unlock(timeout_mutex); @@ -1206,12 +1233,10 @@ read_request: return; } - if (cs->pub.state == CONN_STATE_LINGER) { - rc = start_lingering_close_blocking(cs); - } - if (rc == OK && (cs->pub.state == CONN_STATE_LINGER_NORMAL || - cs->pub.state == CONN_STATE_LINGER_SHORT)) { + /* CONN_STATE_LINGER[_*] fall through process_lingering_close() */ + if (cs->pub.state >= CONN_STATE_LINGER) { process_lingering_close(cs); + return; } } @@ -1231,12 +1256,17 @@ static void check_infinite_requests(void) } } -static void close_listeners(int *closed) +static int close_listeners(int *closed) { + ap_log_error(APLOG_MARK, APLOG_TRACE6, 0, ap_server_conf, + "clos%s listeners (connection_count=%u)", + *closed ? "ed" : "ing", apr_atomic_read32(&connection_count)); if (!*closed) { int i; + ap_close_listeners_ex(my_bucket->listeners); - *closed = 1; + *closed = 1; /* once */ + dying = 1; ap_scoreboard_image->parent[ap_child_slot].quiescing = 1; for (i = 0; i < threads_per_child; ++i) { @@ -1248,7 +1278,10 @@ static void close_listeners(int *closed) ap_queue_info_free_idle_pools(worker_queue_info); ap_queue_interrupt_all(worker_queue); + + return 1; } + return 0; } static void unblock_signal(int sig) @@ -1297,11 +1330,16 @@ static apr_status_t push2worker(event_conn_state_t *cs, apr_socket_t *csd, /* trash the connection; we couldn't queue the connected * socket to a worker */ - if (csd) { - abort_socket_nonblocking(csd); + if (cs) { + shutdown_connection(cs); } - if (ptrans) { - ap_queue_info_push_pool(worker_queue_info, ptrans); + else { + if (csd) { + close_socket_nonblocking(csd); + } + if (ptrans) { + ap_queue_info_push_pool(worker_queue_info, ptrans); + } } signal_threads(ST_GRACEFUL); } @@ -1349,7 +1387,7 @@ static void get_worker(int *have_idle_worker_p, int blocking, int *all_busy) } /* Structures to reuse */ -static APR_RING_HEAD(timer_free_ring_t, timer_event_t) timer_free_ring; +static timer_event_t timer_free_ring; static apr_skiplist *timer_skiplist; static volatile apr_time_t timers_next_expiry; @@ -1391,8 +1429,8 @@ static apr_status_t event_register_timed_callback(apr_time_t t, /* oh yeah, and make locking smarter/fine grained. */ apr_thread_mutex_lock(g_timer_skiplist_mtx); - if (!APR_RING_EMPTY(&timer_free_ring, timer_event_t, link)) { - te = APR_RING_FIRST(&timer_free_ring); + if (!APR_RING_EMPTY(&timer_free_ring.link, timer_event_t, link)) { + te = APR_RING_FIRST(&timer_free_ring.link); APR_RING_REMOVE(te, link); } else { @@ -1411,8 +1449,8 @@ static apr_status_t event_register_timed_callback(apr_time_t t, /* Okay, add sorted by when.. */ apr_skiplist_insert(timer_skiplist, te); - /* Cheaply update the overall timers' next expiry according to - * this event, if necessary. + /* Cheaply update the global timers_next_expiry with this event's + * if it expires before. */ next_expiry = timers_next_expiry; if (!next_expiry || next_expiry > te->when + EVENT_FUDGE_FACTOR) { @@ -1431,10 +1469,13 @@ static apr_status_t event_register_timed_callback(apr_time_t t, /* - * Close socket and clean up if remote closed its end while we were in - * lingering close. Only to be called in the worker thread, and since it's - * in immediate call stack, we can afford a comfortable buffer size to - * consume data quickly. + * Flush data and close our side of the connection, then drain incoming data. + * If the latter would block put the connection in one of the linger timeout + * queues to be called back when ready, and repeat until it's closed by peer. + * Only to be called in the worker thread, and since it's in immediate call + * stack, we can afford a comfortable buffer size to consume data quickly. + * Pre-condition: cs is not in any timeout queue and not in the pollset, + * timeout_mutex is not locked */ #define LINGERING_BUF_SIZE (32 * 1024) static void process_lingering_close(event_conn_state_t *cs) @@ -1445,22 +1486,55 @@ static void process_lingering_close(event_conn_state_t *cs) apr_status_t rv; struct timeout_queue *q; - /* socket is already in non-blocking state */ + ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, cs->c, + "lingering close from state %i", (int)cs->pub.state); + AP_DEBUG_ASSERT(cs->pub.state >= CONN_STATE_LINGER); + + if (cs->pub.state == CONN_STATE_LINGER) { + /* defer_lingering_close() may have bumped lingering_count already */ + if (!cs->deferred_linger) { + apr_atomic_inc32(&lingering_count); + } + + apr_socket_timeout_set(csd, apr_time_from_sec(SECONDS_TO_LINGER)); + if (ap_start_lingering_close(cs->c)) { + notify_suspend(cs); + close_connection(cs); + return; + } + + cs->queue_timestamp = apr_time_now(); + /* Clear APR_INCOMPLETE_READ if it was ever set, we'll do the poll() + * at the listener only from now, if needed. + */ + apr_socket_opt_set(csd, APR_INCOMPLETE_READ, 0); + /* + * If some module requested a shortened waiting period, only wait for + * 2s (SECONDS_TO_LINGER). This is useful for mitigating certain + * DoS attacks. + */ + if (apr_table_get(cs->c->notes, "short-lingering-close")) { + cs->pub.state = CONN_STATE_LINGER_SHORT; + } + else { + cs->pub.state = CONN_STATE_LINGER_NORMAL; + } + notify_suspend(cs); + } + + apr_socket_timeout_set(csd, 0); do { nbytes = sizeof(dummybuf); rv = apr_socket_recv(csd, dummybuf, &nbytes); } while (rv == APR_SUCCESS); if (!APR_STATUS_IS_EAGAIN(rv)) { - rv = apr_socket_close(csd); - AP_DEBUG_ASSERT(rv == APR_SUCCESS); - ap_queue_info_push_pool(worker_queue_info, cs->p); + close_connection(cs); return; } - /* Re-queue the connection to come back when readable */ - cs->pfd.reqevents = APR_POLLIN; - cs->pub.sense = CONN_SENSE_DEFAULT; + /* (Re)queue the connection to come back when readable */ + update_reqevents_from_sense(cs, CONN_SENSE_WANT_READ); q = (cs->pub.state == CONN_STATE_LINGER_SHORT) ? short_linger_q : linger_q; apr_thread_mutex_lock(timeout_mutex); TO_QUEUE_APPEND(q, cs); @@ -1471,25 +1545,23 @@ static void process_lingering_close(event_conn_state_t *cs) apr_thread_mutex_unlock(timeout_mutex); ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(03092) "process_lingering_close: apr_pollset_add failure"); - rv = apr_socket_close(cs->pfd.desc.s); - AP_DEBUG_ASSERT(rv == APR_SUCCESS); - ap_queue_info_push_pool(worker_queue_info, cs->p); + close_connection(cs); + signal_threads(ST_GRACEFUL); return; } apr_thread_mutex_unlock(timeout_mutex); } -/* call 'func' for all elements of 'q' with timeout less than 'timeout_time'. +/* call 'func' for all elements of 'q' above 'expiry'. * Pre-condition: timeout_mutex must already be locked * Post-condition: timeout_mutex will be locked again */ -static void process_timeout_queue(struct timeout_queue *q, - apr_time_t timeout_time, +static void process_timeout_queue(struct timeout_queue *q, apr_time_t expiry, int (*func)(event_conn_state_t *)) { apr_uint32_t total = 0, count; event_conn_state_t *first, *cs, *last; - struct timeout_head_t trash; + struct event_conn_state_t trash; struct timeout_queue *qp; apr_status_t rv; @@ -1497,33 +1569,33 @@ static void process_timeout_queue(struct timeout_queue *q, return; } - APR_RING_INIT(&trash, event_conn_state_t, timeout_list); + APR_RING_INIT(&trash.timeout_list, event_conn_state_t, timeout_list); for (qp = q; qp; qp = qp->next) { count = 0; cs = first = last = APR_RING_FIRST(&qp->head); while (cs != APR_RING_SENTINEL(&qp->head, event_conn_state_t, timeout_list)) { /* Trash the entry if: - * - no timeout_time was given (asked for all), or + * - no expiry was given (zero means all), or * - it expired (according to the queue timeout), or * - the system clock skewed in the past: no entry should be - * registered above the given timeout_time (~now) + the queue + * registered above the given expiry (~now) + the queue * timeout, we won't keep any here (eg. for centuries). * * Otherwise stop, no following entry will match thanks to the * single timeout per queue (entries are added to the end!). * This allows maintenance in O(1). */ - if (timeout_time - && cs->queue_timestamp + qp->timeout > timeout_time - && cs->queue_timestamp < timeout_time + qp->timeout) { - /* Since this is the next expiring of this queue, update the - * overall queues' next expiry if it's later than this one. + if (expiry && cs->queue_timestamp + qp->timeout > expiry + && cs->queue_timestamp < expiry + qp->timeout) { + /* Since this is the next expiring entry of this queue, update + * the global queues_next_expiry if it's later than this one. */ - apr_time_t q_expiry = cs->queue_timestamp + qp->timeout; + apr_time_t elem_expiry = cs->queue_timestamp + qp->timeout; apr_time_t next_expiry = queues_next_expiry; - if (!next_expiry || next_expiry > q_expiry) { - queues_next_expiry = q_expiry; + if (!next_expiry + || next_expiry > elem_expiry + TIMEOUT_FUDGE_FACTOR) { + queues_next_expiry = elem_expiry; } break; } @@ -1542,7 +1614,7 @@ static void process_timeout_queue(struct timeout_queue *q, continue; APR_RING_UNSPLICE(first, last, timeout_list); - APR_RING_SPLICE_TAIL(&trash, first, last, event_conn_state_t, + APR_RING_SPLICE_TAIL(&trash.timeout_list, first, last, event_conn_state_t, timeout_list); AP_DEBUG_ASSERT(*q->total >= count && qp->count >= count); *q->total -= count; @@ -1553,7 +1625,7 @@ static void process_timeout_queue(struct timeout_queue *q, return; apr_thread_mutex_unlock(timeout_mutex); - first = APR_RING_FIRST(&trash); + first = APR_RING_FIRST(&trash.timeout_list); do { cs = APR_RING_NEXT(first, timeout_list); TO_QUEUE_ELEM_INIT(first); @@ -1563,18 +1635,17 @@ static void process_timeout_queue(struct timeout_queue *q, apr_thread_mutex_lock(timeout_mutex); } -static void process_keepalive_queue(apr_time_t timeout_time) +static void process_keepalive_queue(apr_time_t expiry) { /* If all workers are busy, we kill older keep-alive connections so * that they may connect to another process. */ - if (!timeout_time) { + if (!expiry && *keepalive_q->total) { ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, - "All workers are busy or dying, will close %u " + "All workers are busy or dying, will shutdown %u " "keep-alive connections", *keepalive_q->total); } - process_timeout_queue(keepalive_q, timeout_time, - start_lingering_close_nonblocking); + process_timeout_queue(keepalive_q, expiry, shutdown_connection); } static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) @@ -1593,25 +1664,35 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) /* Unblock the signal used to wake this thread up, and set a handler for * it. */ - unblock_signal(LISTENER_SIGNAL); apr_signal(LISTENER_SIGNAL, dummy_signal_handler); + unblock_signal(LISTENER_SIGNAL); for (;;) { timer_event_t *te; const apr_pollfd_t *out_pfd; apr_int32_t num = 0; - apr_interval_time_t timeout_interval; - apr_time_t now, timeout_time; + apr_interval_time_t timeout; + apr_time_t now, expiry = -1; int workers_were_busy = 0; if (conns_this_child <= 0) check_infinite_requests(); if (listener_may_exit) { - close_listeners(&closed); + int first_close = close_listeners(&closed); + if (terminate_mode == ST_UNGRACEFUL || apr_atomic_read32(&connection_count) == 0) break; + + /* Don't wait in poll() for the first close (i.e. dying now), we + * want to maintain the queues and schedule defer_linger_chain ASAP + * to kill kept-alive connection and shutdown the workers and child + * faster. + */ + if (first_close) { + goto do_maintenance; /* with expiry == -1 */ + } } now = apr_time_now(); @@ -1625,8 +1706,8 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) "keep-alive: %d lingering: %d suspended: %u)", apr_atomic_read32(&connection_count), apr_atomic_read32(&clogged_count), - *(volatile apr_uint32_t*)write_completion_q->total, - *(volatile apr_uint32_t*)keepalive_q->total, + apr_atomic_read32(write_completion_q->total), + apr_atomic_read32(keepalive_q->total), apr_atomic_read32(&lingering_count), apr_atomic_read32(&suspended_count)); if (dying) { @@ -1645,18 +1726,19 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) * up occurs, otherwise periodic checks (maintenance, shutdown, ...) * must be performed. */ - timeout_interval = -1; + now = apr_time_now(); + timeout = -1; /* Push expired timers to a worker, the first remaining one determines * the maximum time to poll() below, if any. */ - timeout_time = timers_next_expiry; - if (timeout_time && timeout_time < now + EVENT_FUDGE_FACTOR) { + expiry = timers_next_expiry; + if (expiry && expiry < now) { apr_thread_mutex_lock(g_timer_skiplist_mtx); while ((te = apr_skiplist_peek(timer_skiplist))) { - if (te->when > now + EVENT_FUDGE_FACTOR) { + if (te->when > now) { timers_next_expiry = te->when; - timeout_interval = te->when - now; + timeout = te->when - now; break; } apr_skiplist_pop(timer_skiplist, NULL); @@ -1669,37 +1751,40 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) } /* Same for queues, use their next expiry, if any. */ - timeout_time = queues_next_expiry; - if (timeout_time - && (timeout_interval < 0 - || timeout_time <= now - || timeout_interval > timeout_time - now)) { - timeout_interval = timeout_time > now ? timeout_time - now : 1; + expiry = queues_next_expiry; + if (expiry + && (timeout < 0 + || expiry <= now + || timeout > expiry - now)) { + timeout = expiry > now ? expiry - now : 0; } /* When non-wakeable, don't wait more than 100 ms, in any case. */ #define NON_WAKEABLE_POLL_TIMEOUT apr_time_from_msec(100) if (!listener_is_wakeable - && (timeout_interval < 0 - || timeout_interval > NON_WAKEABLE_POLL_TIMEOUT)) { - timeout_interval = NON_WAKEABLE_POLL_TIMEOUT; + && (timeout < 0 + || timeout > NON_WAKEABLE_POLL_TIMEOUT)) { + timeout = NON_WAKEABLE_POLL_TIMEOUT; } + else if (timeout > 0) { + /* apr_pollset_poll() might round down the timeout to milliseconds, + * let's forcibly round up here to never return before the timeout. + */ + timeout = apr_time_from_msec( + apr_time_as_msec(timeout + apr_time_from_msec(1) - 1) + ); + } + + ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf, + "polling with timeout=%" APR_TIME_T_FMT + " queues_timeout=%" APR_TIME_T_FMT + " timers_timeout=%" APR_TIME_T_FMT, + timeout, queues_next_expiry - now, + timers_next_expiry - now); - rc = apr_pollset_poll(event_pollset, timeout_interval, &num, &out_pfd); + rc = apr_pollset_poll(event_pollset, timeout, &num, &out_pfd); if (rc != APR_SUCCESS) { - if (APR_STATUS_IS_EINTR(rc)) { - /* Woken up, if we are exiting or listeners are disabled we - * must fall through to kill kept-alive connections or test - * whether listeners should be re-enabled. Otherwise we only - * need to update timeouts (logic is above, so simply restart - * the loop). - */ - if (!listener_may_exit && !listeners_disabled()) { - continue; - } - timeout_time = 0; - } - else if (!APR_STATUS_IS_TIMEUP(rc)) { + if (!APR_STATUS_IS_EINTR(rc) && !APR_STATUS_IS_TIMEUP(rc)) { ap_log_error(APLOG_MARK, APLOG_CRIT, rc, ap_server_conf, "apr_pollset_poll failed. Attempting to " "shutdown process gracefully"); @@ -1708,13 +1793,21 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) num = 0; } - if (listener_may_exit) { - close_listeners(&closed); - if (terminate_mode == ST_UNGRACEFUL - || apr_atomic_read32(&connection_count) == 0) - break; + if (APLOGtrace7(ap_server_conf)) { + now = apr_time_now(); + ap_log_error(APLOG_MARK, APLOG_TRACE7, rc, ap_server_conf, + "polled with num=%u exit=%d/%d conns=%d" + " queues_timeout=%" APR_TIME_T_FMT + " timers_timeout=%" APR_TIME_T_FMT, + num, listener_may_exit, dying, + apr_atomic_read32(&connection_count), + queues_next_expiry - now, timers_next_expiry - now); } + /* XXX possible optimization: stash the current time for use as + * r->request_time for new requests or queues maintenance + */ + for (; num; --num, ++out_pfd) { listener_poll_type *pt = (listener_poll_type *) out_pfd->client_data; if (pt->type == PT_CSD) { @@ -1767,25 +1860,21 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) AP_DEBUG_ASSERT(0); ap_log_error(APLOG_MARK, APLOG_ERR, rc, ap_server_conf, APLOGNO(03094) "pollset remove failed"); - start_lingering_close_nonblocking(cs); + close_connection(cs); + signal_threads(ST_GRACEFUL); break; } /* If we don't get a worker immediately (nonblocking), we * close the connection; the client can re-connect to a * different process for keepalive, and for lingering close - * the connection will be reset so the choice is to favor + * the connection will be shutdown so the choice is to favor * incoming/alive connections. */ get_worker(&have_idle_worker, blocking, &workers_were_busy); if (!have_idle_worker) { - if (remove_from_q == cs->sc->ka_q) { - start_lingering_close_nonblocking(cs); - } - else { - stop_lingering_close(cs); - } + shutdown_connection(cs); } else if (push2worker(cs, NULL, NULL) == APR_SUCCESS) { have_idle_worker = 0; @@ -1800,7 +1889,7 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) "All workers busy, not accepting new conns " "in this process"); } - else if (connections_above_limit()) { + else if (connections_above_limit(&workers_were_busy)) { disable_listensocks(); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "Too many open connections (%u), " @@ -1809,7 +1898,6 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, "Idle workers: %u", ap_queue_info_num_idlers(worker_queue_info)); - workers_were_busy = 1; } else if (!listener_may_exit) { void *csd = NULL; @@ -1872,23 +1960,22 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) } /* if:else on pt->type */ } /* for processing poll */ - /* XXX possible optimization: stash the current time for use as - * r->request_time for new requests - */ - /* We process the timeout queues here only when their overall next - * expiry (read once above) is over. This happens accurately since + /* We process the timeout queues here only when the global + * queues_next_expiry is passed. This happens accurately since * adding to the queues (in workers) can only decrease this expiry, * while latest ones are only taken into account here (in listener) * during queues' processing, with the lock held. This works both * with and without wake-ability. */ - if (timeout_time && timeout_time < (now = apr_time_now())) { - timeout_time = now + TIMEOUT_FUDGE_FACTOR; - - /* handle timed out sockets */ + expiry = queues_next_expiry; +do_maintenance: + if (expiry && expiry < (now = apr_time_now())) { + ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf, + "queues maintenance with timeout=%" APR_TIME_T_FMT, + expiry > 0 ? expiry - now : -1); apr_thread_mutex_lock(timeout_mutex); - /* Processing all the queues below will recompute this. */ + /* Steps below will recompute this. */ queues_next_expiry = 0; /* Step 1: keepalive timeouts */ @@ -1896,28 +1983,34 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) process_keepalive_queue(0); /* kill'em all \m/ */ } else { - process_keepalive_queue(timeout_time); + process_keepalive_queue(now); } /* Step 2: write completion timeouts */ - process_timeout_queue(write_completion_q, timeout_time, - start_lingering_close_nonblocking); + process_timeout_queue(write_completion_q, now, + defer_lingering_close); /* Step 3: (normal) lingering close completion timeouts */ - process_timeout_queue(linger_q, timeout_time, - stop_lingering_close); + if (dying && linger_q->timeout > short_linger_q->timeout) { + /* Dying, force short timeout for normal lingering close */ + linger_q->timeout = short_linger_q->timeout; + } + process_timeout_queue(linger_q, now, shutdown_connection); /* Step 4: (short) lingering close completion timeouts */ - process_timeout_queue(short_linger_q, timeout_time, - stop_lingering_close); + process_timeout_queue(short_linger_q, now, shutdown_connection); apr_thread_mutex_unlock(timeout_mutex); + ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, ap_server_conf, + "queues maintained with timeout=%" APR_TIME_T_FMT, + queues_next_expiry > now ? queues_next_expiry - now + : -1); - ps->keep_alive = *(volatile apr_uint32_t*)keepalive_q->total; - ps->write_completion = *(volatile apr_uint32_t*)write_completion_q->total; + ps->keep_alive = apr_atomic_read32(keepalive_q->total); + ps->write_completion = apr_atomic_read32(write_completion_q->total); ps->connections = apr_atomic_read32(&connection_count); ps->suspended = apr_atomic_read32(&suspended_count); ps->lingering_close = apr_atomic_read32(&lingering_count); } else if ((workers_were_busy || dying) - && *(volatile apr_uint32_t*)keepalive_q->total) { + && apr_atomic_read32(keepalive_q->total)) { apr_thread_mutex_lock(timeout_mutex); process_keepalive_queue(0); /* kill'em all \m/ */ apr_thread_mutex_unlock(timeout_mutex); @@ -1942,14 +2035,11 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t * thd, void *dummy) } } - if (listeners_disabled() - && !workers_were_busy - && !connections_above_limit()) { + if (!workers_were_busy && should_enable_listensocks()) { enable_listensocks(); } } /* listener main loop */ - close_listeners(&closed); ap_queue_term(worker_queue); apr_thread_exit(thd, APR_SUCCESS); @@ -2007,7 +2097,7 @@ static void *APR_THREAD_FUNC worker_thread(apr_thread_t * thd, void *dummy) ap_update_child_status_from_indexes(process_slot, thread_slot, SERVER_STARTING, NULL); - while (!workers_may_exit) { + for (;;) { apr_socket_t *csd = NULL; event_conn_state_t *cs; timer_event_t *te = NULL; @@ -2022,6 +2112,12 @@ static void *APR_THREAD_FUNC worker_thread(apr_thread_t * thd, void *dummy) signal_threads(ST_GRACEFUL); break; } + /* A new idler may have changed connections_above_limit(), + * let the listener know and decide. + */ + if (listener_is_wakeable && should_enable_listensocks()) { + apr_pollset_wakeup(event_pollset); + } is_idle = 1; } @@ -2072,7 +2168,7 @@ static void *APR_THREAD_FUNC worker_thread(apr_thread_t * thd, void *dummy) { apr_thread_mutex_lock(g_timer_skiplist_mtx); - APR_RING_INSERT_TAIL(&timer_free_ring, te, timer_event_t, link); + APR_RING_INSERT_TAIL(&timer_free_ring.link, te, timer_event_t, link); apr_thread_mutex_unlock(g_timer_skiplist_mtx); } } @@ -2097,15 +2193,9 @@ static void *APR_THREAD_FUNC worker_thread(apr_thread_t * thd, void *dummy) continue; } cs->chain = NULL; + AP_DEBUG_ASSERT(cs->pub.state == CONN_STATE_LINGER); worker_sockets[thread_slot] = csd = cs->pfd.desc.s; -#ifdef AP_DEBUG - rv = apr_socket_timeout_set(csd, SECONDS_TO_LINGER); - AP_DEBUG_ASSERT(rv == APR_SUCCESS); -#else - apr_socket_timeout_set(csd, SECONDS_TO_LINGER); -#endif - cs->pub.state = CONN_STATE_LINGER; process_socket(thd, cs->p, csd, cs, process_slot, thread_slot); worker_sockets[thread_slot] = NULL; } @@ -2139,11 +2229,11 @@ static void create_listener_thread(thread_starter * ts) my_info = (proc_info *) ap_malloc(sizeof(proc_info)); my_info->pslot = my_child_num; my_info->tslot = -1; /* listener thread doesn't have a thread slot */ - rv = apr_thread_create(&ts->listener, thread_attr, listener_thread, - my_info, pruntime); + rv = ap_thread_create(&ts->listener, thread_attr, listener_thread, + my_info, pruntime); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(00474) - "apr_thread_create: unable to create listener thread"); + "ap_thread_create: unable to create listener thread"); /* let the parent decide how bad this really is */ clean_child_exit(APEXIT_CHILDSICK); } @@ -2177,7 +2267,7 @@ static void setup_threads_runtime(void) apr_pool_create(&pskip, pconf); apr_pool_tag(pskip, "mpm_skiplist"); apr_thread_mutex_create(&g_timer_skiplist_mtx, APR_THREAD_MUTEX_DEFAULT, pskip); - APR_RING_INIT(&timer_free_ring, timer_event_t, link); + APR_RING_INIT(&timer_free_ring.link, timer_event_t, link); apr_skiplist_init(&timer_skiplist, pskip); apr_skiplist_set_compare(timer_skiplist, timer_comp, timer_comp); @@ -2186,7 +2276,7 @@ static void setup_threads_runtime(void) * the connections they handle (i.e. ptrans). We can't use this thread's * self pool because all these objects survive it, nor use pchild or pconf * directly because this starter thread races with other modules' runtime, - * nor finally pchild (or subpool thereof) because it is killed explicitely + * nor finally pchild (or subpool thereof) because it is killed explicitly * before pconf (thus connections/ptrans can live longer, which matters in * ONE_PROCESS mode). So this leaves us with a subpool of pconf, created * before any ptrans hence destroyed after. @@ -2271,7 +2361,7 @@ static void setup_threads_runtime(void) AP_DEBUG_ASSERT(i < num_listensocks); pfd = &listener_pollfd[i]; - pfd->reqevents = APR_POLLIN; + pfd->reqevents = APR_POLLIN | APR_POLLHUP | APR_POLLERR; pfd->desc_type = APR_POLL_SOCKET; pfd->desc.s = lr->sd; @@ -2334,12 +2424,12 @@ static void *APR_THREAD_FUNC start_threads(apr_thread_t * thd, void *dummy) /* We let each thread update its own scoreboard entry. This is * done because it lets us deal with tid better. */ - rv = apr_thread_create(&threads[i], thread_attr, - worker_thread, my_info, pruntime); + rv = ap_thread_create(&threads[i], thread_attr, + worker_thread, my_info, pruntime); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(03104) - "apr_thread_create: unable to create worker thread"); + "ap_thread_create: unable to create worker thread"); /* let the parent decide how bad this really is */ clean_child_exit(APEXIT_CHILDSICK); } @@ -2402,13 +2492,17 @@ static void join_workers(apr_thread_t * listener, apr_thread_t ** threads) */ iter = 0; - while (iter < 10 && !dying) { + while (!dying) { + apr_sleep(apr_time_from_msec(500)); + if (dying || ++iter > 10) { + break; + } /* listener has not stopped accepting yet */ - apr_sleep(apr_time_make(0, 500000)); + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "listener has not stopped accepting yet (%d iter)", iter); wakeup_listener(); - ++iter; } - if (iter >= 10) { + if (iter > 10) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, APLOGNO(00475) "the listener thread didn't stop accepting"); } @@ -2470,6 +2564,17 @@ static void child_main(int child_num_arg, int child_bucket) apr_pool_create(&pchild, pconf); apr_pool_tag(pchild, "pchild"); +#if AP_HAS_THREAD_LOCAL + if (!one_process) { + apr_thread_t *thd = NULL; + if ((rv = ap_thread_main_create(&thd, pchild))) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(10377) + "Couldn't initialize child main thread"); + clean_child_exit(APEXIT_CHILDFATAL); + } + } +#endif + /* close unused listeners and pods */ for (i = 0; i < retained->mpm->num_buckets; i++) { if (i != child_bucket) { @@ -2490,7 +2595,7 @@ static void child_main(int child_num_arg, int child_bucket) * from being received. The child processes no longer use signals for * any communication with the parent process. Let's also do this before * child_init() hooks are called and possibly create threads that - * otherwise could "steal" (implicitely) MPM's signals. + * otherwise could "steal" (implicitly) MPM's signals. */ rv = apr_setup_signal_thread(); if (rv != APR_SUCCESS) { @@ -2539,11 +2644,11 @@ static void child_main(int child_num_arg, int child_bucket) ts->child_num_arg = child_num_arg; ts->threadattr = thread_attr; - rv = apr_thread_create(&start_thread_id, thread_attr, start_threads, - ts, pchild); + rv = ap_thread_create(&start_thread_id, thread_attr, start_threads, + ts, pchild); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(00480) - "apr_thread_create: unable to create worker thread"); + "ap_thread_create: unable to create worker thread"); /* let the parent decide how bad this really is */ clean_child_exit(APEXIT_CHILDSICK); } @@ -2583,8 +2688,8 @@ static void child_main(int child_num_arg, int child_bucket) * the other threads in the process needs to take us down * (e.g., for MaxConnectionsPerChild) it will send us SIGTERM */ - unblock_signal(SIGTERM); apr_signal(SIGTERM, dummy_signal_handler); + unblock_signal(SIGTERM); /* Watch for any messages from the parent over the POD */ while (1) { rv = ap_mpm_podx_check(my_bucket->pod); @@ -2617,7 +2722,13 @@ static void child_main(int child_num_arg, int child_bucket) * If the worker hasn't exited, then this blocks until * they have (then cleans up). */ + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "%s termination received, joining workers", + rv == AP_MPM_PODX_GRACEFUL ? "graceful" : "ungraceful"); join_workers(ts->listener, threads); + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "%s termination, workers joined, exiting", + rv == AP_MPM_PODX_GRACEFUL ? "graceful" : "ungraceful"); } free(threads); @@ -2629,8 +2740,8 @@ static int make_child(server_rec * s, int slot, int bucket) { int pid; - if (slot + 1 > retained->max_daemons_limit) { - retained->max_daemons_limit = slot + 1; + if (slot + 1 > retained->max_daemon_used) { + retained->max_daemon_used = slot + 1; } if (ap_scoreboard_image->parent[slot].pid != 0) { @@ -2672,6 +2783,10 @@ static int make_child(server_rec * s, int slot, int bucket) } if (!pid) { +#if AP_HAS_THREAD_LOCAL + ap_thread_current_after_fork(); +#endif + my_bucket = &all_buckets[bucket]; #ifdef HAVE_BINDPROCESSOR @@ -2694,12 +2809,7 @@ static int make_child(server_rec * s, int slot, int bucket) return -1; } - ap_scoreboard_image->parent[slot].quiescing = 0; - ap_scoreboard_image->parent[slot].not_accepting = 0; - ap_scoreboard_image->parent[slot].bucket = bucket; event_note_child_started(slot, pid); - active_daemons++; - retained->total_daemons++; return 0; } @@ -2719,36 +2829,47 @@ static void startup_children(int number_to_start) } } -static void perform_idle_server_maintenance(int child_bucket, int num_buckets) +static void perform_idle_server_maintenance(int child_bucket, + int *max_daemon_used) { - int i, j; + int num_buckets = retained->mpm->num_buckets; int idle_thread_count = 0; - worker_score *ws; process_score *ps; int free_length = 0; int free_slots[MAX_SPAWN_RATE]; int last_non_dead = -1; int active_thread_count = 0; + int i, j; for (i = 0; i < server_limit; ++i) { - /* Initialization to satisfy the compiler. It doesn't know - * that threads_per_child is always > 0 */ - int status = SERVER_DEAD; - int child_threads_active = 0; - - if (i >= retained->max_daemons_limit && + if (num_buckets > 1 && (i % num_buckets) != child_bucket) { + /* We only care about child_bucket in this call */ + continue; + } + if (i >= retained->max_daemon_used && free_length == retained->idle_spawn_rate[child_bucket]) { /* short cut if all active processes have been examined and * enough empty scoreboard slots have been found */ - break; } + ps = &ap_scoreboard_image->parent[i]; if (ps->pid != 0) { + int child_threads_active = 0; + if (ps->quiescing == 1) { + ps->quiescing = 2; + retained->active_daemons--; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "Child %d quiescing: pid %d, gen %d, " + "active %d/%d, total %d/%d/%d", + i, (int)ps->pid, (int)ps->generation, + retained->active_daemons, active_daemons_limit, + retained->total_daemons, retained->max_daemon_used, + server_limit); + } for (j = 0; j < threads_per_child; j++) { - ws = &ap_scoreboard_image->servers[i][j]; - status = ws->status; + int status = ap_scoreboard_image->servers[i][j].status; /* We consider a starting server as idle because we started it * at least a cycle ago, and if it still hasn't finished starting @@ -2757,22 +2878,25 @@ static void perform_idle_server_maintenance(int child_bucket, int num_buckets) * This depends on the ordering of SERVER_READY and SERVER_STARTING. */ if (status <= SERVER_READY && !ps->quiescing && !ps->not_accepting - && ps->generation == retained->mpm->my_generation - && ps->bucket == child_bucket) - { + && ps->generation == retained->mpm->my_generation) { ++idle_thread_count; } if (status >= SERVER_READY && status < SERVER_GRACEFUL) { ++child_threads_active; } } + active_thread_count += child_threads_active; + if (child_threads_active == threads_per_child) { + had_healthy_child = 1; + } last_non_dead = i; } - active_thread_count += child_threads_active; - if (!ps->pid && free_length < retained->idle_spawn_rate[child_bucket]) + else if (free_length < retained->idle_spawn_rate[child_bucket]) { free_slots[free_length++] = i; - else if (child_threads_active == threads_per_child) - had_healthy_child = 1; + } + } + if (*max_daemon_used < last_non_dead + 1) { + *max_daemon_used = last_non_dead + 1; } if (retained->sick_child_detected) { @@ -2783,6 +2907,10 @@ static void perform_idle_server_maintenance(int child_bucket, int num_buckets) */ retained->sick_child_detected = 0; } + else if (child_bucket < num_buckets - 1) { + /* check for had_healthy_child up to the last child bucket */ + return; + } else { /* looks like a basket case, as no child ever fully initialized; give up. */ @@ -2798,18 +2926,20 @@ static void perform_idle_server_maintenance(int child_bucket, int num_buckets) } } - retained->max_daemons_limit = last_non_dead + 1; + AP_DEBUG_ASSERT(retained->active_daemons <= retained->total_daemons + && retained->total_daemons <= retained->max_daemon_used + && retained->max_daemon_used <= server_limit); - if (idle_thread_count > max_spare_threads / num_buckets) - { + if (idle_thread_count > max_spare_threads / num_buckets) { /* * Child processes that we ask to shut down won't die immediately * but may stay around for a long time when they finish their * requests. If the server load changes many times, many such * gracefully finishing processes may accumulate, filling up the * scoreboard. To avoid running out of scoreboard entries, we - * don't shut down more processes when the total number of processes - * is high. + * don't shut down more processes if there are stopping ones + * already (i.e. active_daemons != total_daemons) and not enough + * slack space in the scoreboard for a graceful restart. * * XXX It would be nice if we could * XXX - kill processes without keepalive connections first @@ -2817,23 +2947,28 @@ static void perform_idle_server_maintenance(int child_bucket, int num_buckets) * XXX depending on server load, later be able to resurrect them * or kill them */ - if (retained->total_daemons <= active_daemons_limit && - retained->total_daemons < server_limit) { - /* Kill off one child */ + int do_kill = (retained->active_daemons == retained->total_daemons + || (server_limit - retained->total_daemons > + active_daemons_limit)); + ap_log_error(APLOG_MARK, APLOG_TRACE5, 0, ap_server_conf, + "%shutting down one child: " + "active %d/%d, total %d/%d/%d, " + "idle threads %d, max workers %d", + (do_kill) ? "S" : "Not s", + retained->active_daemons, active_daemons_limit, + retained->total_daemons, retained->max_daemon_used, + server_limit, idle_thread_count, max_workers); + if (do_kill) { ap_mpm_podx_signal(all_buckets[child_bucket].pod, AP_MPM_PODX_GRACEFUL); - retained->idle_spawn_rate[child_bucket] = 1; - active_daemons--; - } else { - ap_log_error(APLOG_MARK, APLOG_TRACE5, 0, ap_server_conf, - "Not shutting down child: total daemons %d / " - "active limit %d / ServerLimit %d", - retained->total_daemons, active_daemons_limit, - server_limit); } + else { + /* Wait for dying daemon(s) to exit */ + } + retained->idle_spawn_rate[child_bucket] = 1; } else if (idle_thread_count < min_spare_threads / num_buckets) { - if (active_thread_count >= max_workers) { + if (active_thread_count >= max_workers / num_buckets) { if (0 == idle_thread_count) { if (!retained->maxclients_reported) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(00484) @@ -2864,6 +2999,24 @@ static void perform_idle_server_maintenance(int child_bucket, int num_buckets) if (free_length > retained->idle_spawn_rate[child_bucket]) { free_length = retained->idle_spawn_rate[child_bucket]; } + if (free_length + retained->active_daemons > active_daemons_limit) { + if (retained->active_daemons < active_daemons_limit) { + free_length = active_daemons_limit - retained->active_daemons; + } + else { + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "server is at active daemons limit, spawning " + "of %d children cancelled: active %d/%d, " + "total %d/%d/%d, rate %d", free_length, + retained->active_daemons, active_daemons_limit, + retained->total_daemons, retained->max_daemon_used, + server_limit, retained->idle_spawn_rate[child_bucket]); + /* reset the spawning rate and prevent its growth below */ + retained->idle_spawn_rate[child_bucket] = 1; + ++retained->hold_off_on_exponential_spawning; + free_length = 0; + } + } if (retained->idle_spawn_rate[child_bucket] >= 8) { ap_log_error(APLOG_MARK, APLOG_INFO, 0, ap_server_conf, APLOGNO(00486) "server seems busy, (you may need " @@ -2872,16 +3025,17 @@ static void perform_idle_server_maintenance(int child_bucket, int num_buckets) "spawning %d children, there are around %d idle " "threads, %d active children, and %d children " "that are shutting down", free_length, - idle_thread_count, active_daemons, + idle_thread_count, retained->active_daemons, retained->total_daemons); } for (i = 0; i < free_length; ++i) { - ap_log_error(APLOG_MARK, APLOG_TRACE5, 0, ap_server_conf, - "Spawning new child: slot %d active / " - "total daemons: %d/%d", - free_slots[i], active_daemons, - retained->total_daemons); - make_child(ap_server_conf, free_slots[i], child_bucket); + int slot = free_slots[i]; + if (make_child(ap_server_conf, slot, child_bucket) < 0) { + continue; + } + if (*max_daemon_used < slot + 1) { + *max_daemon_used = slot + 1; + } } /* the next time around we want to spawn twice as many if this * wasn't good enough, but not if we've just done a graceful @@ -2900,8 +3054,11 @@ static void perform_idle_server_maintenance(int child_bucket, int num_buckets) } } -static void server_main_loop(int remaining_children_to_start, int num_buckets) +static void server_main_loop(int remaining_children_to_start) { + int num_buckets = retained->mpm->num_buckets; + int max_daemon_used = 0; + int successive_kills = 0; int child_slot; apr_exit_why_e exitwhy; int status, processed_status; @@ -2947,28 +3104,18 @@ static void server_main_loop(int remaining_children_to_start, int num_buckets) } /* non-fatal death... note that it's gone in the scoreboard. */ if (child_slot >= 0) { - process_score *ps; - - for (i = 0; i < threads_per_child; i++) - ap_update_child_status_from_indexes(child_slot, i, - SERVER_DEAD, NULL); - - event_note_child_killed(child_slot, 0, 0); - ps = &ap_scoreboard_image->parent[child_slot]; - if (!ps->quiescing) - active_daemons--; - ps->quiescing = 0; - /* NOTE: We don't dec in the (child_slot < 0) case! */ - retained->total_daemons--; + event_note_child_stopped(child_slot, 0, 0); + if (processed_status == APEXIT_CHILDSICK) { /* resource shortage, minimize the fork rate */ - retained->idle_spawn_rate[ps->bucket] = 1; + retained->idle_spawn_rate[child_slot % num_buckets] = 1; } else if (remaining_children_to_start) { /* we're still doing a 1-for-1 replacement of dead * children with new children */ - make_child(ap_server_conf, child_slot, ps->bucket); + make_child(ap_server_conf, child_slot, + child_slot % num_buckets); --remaining_children_to_start; } } @@ -2990,11 +3137,30 @@ static void server_main_loop(int remaining_children_to_start, int num_buckets) /* Don't perform idle maintenance when a child dies, * only do it when there's a timeout. Remember only a * finite number of children can die, and it's pretty - * pathological for a lot to die suddenly. + * pathological for a lot to die suddenly. If a child is + * killed by a signal (faulting) we want to restart it ASAP + * though, up to 3 successive faults or we stop this until + * a timeout happens again (to avoid the flood of fork()ed + * processes that keep being killed early). */ - continue; + if (child_slot < 0 || !APR_PROC_CHECK_SIGNALED(exitwhy)) { + continue; + } + if (++successive_kills >= 3) { + if (successive_kills % 10 == 3) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, + ap_server_conf, APLOGNO(10392) + "children are killed successively!"); + } + continue; + } + ++remaining_children_to_start; + } + else { + successive_kills = 0; } - else if (remaining_children_to_start) { + + if (remaining_children_to_start) { /* we hit a 1 second timeout in which none of the previous * generation of children needed to be reaped... so assume * they're all done, and pick up the slack if any is left. @@ -3008,9 +3174,11 @@ static void server_main_loop(int remaining_children_to_start, int num_buckets) continue; } + max_daemon_used = 0; for (i = 0; i < num_buckets; i++) { - perform_idle_server_maintenance(i, num_buckets); + perform_idle_server_maintenance(i, &max_daemon_used); } + retained->max_daemon_used = max_daemon_used; } } @@ -3086,7 +3254,7 @@ static int event_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s) retained->mpm->mpm_state = AP_MPMQ_RUNNING; - server_main_loop(remaining_children_to_start, num_buckets); + server_main_loop(remaining_children_to_start); retained->mpm->mpm_state = AP_MPMQ_STOPPING; if (retained->mpm->shutdown_pending && retained->mpm->is_ungraceful) { @@ -3098,7 +3266,7 @@ static int event_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s) AP_MPM_PODX_RESTART); } ap_reclaim_child_processes(1, /* Start with SIGTERM */ - event_note_child_killed); + event_note_child_stopped); if (!child_fatal) { /* cleanup pid file on normal shutdown */ @@ -3124,7 +3292,7 @@ static int event_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s) ap_mpm_podx_killpg(all_buckets[i].pod, active_daemons_limit, AP_MPM_PODX_GRACEFUL); } - ap_relieve_child_processes(event_note_child_killed); + ap_relieve_child_processes(event_note_child_stopped); if (!child_fatal) { /* cleanup pid file on normal shutdown */ @@ -3146,10 +3314,10 @@ static int event_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s) apr_sleep(apr_time_from_sec(1)); /* Relieve any children which have now exited */ - ap_relieve_child_processes(event_note_child_killed); + ap_relieve_child_processes(event_note_child_stopped); active_children = 0; - for (index = 0; index < retained->max_daemons_limit; ++index) { + for (index = 0; index < retained->max_daemon_used; ++index) { if (ap_mpm_safe_kill(MPM_CHILD_PID(index), 0) == APR_SUCCESS) { active_children = 1; /* Having just one child is enough to stay around */ @@ -3167,7 +3335,7 @@ static int event_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s) ap_mpm_podx_killpg(all_buckets[i].pod, active_daemons_limit, AP_MPM_PODX_RESTART); } - ap_reclaim_child_processes(1, event_note_child_killed); + ap_reclaim_child_processes(1, event_note_child_stopped); return DONE; } @@ -3187,8 +3355,7 @@ static int event_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s) if (!retained->mpm->is_ungraceful) { ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00493) - AP_SIG_GRACEFUL_STRING - " received. Doing graceful restart"); + AP_SIG_GRACEFUL_STRING " received. Doing graceful restart"); /* wake up the children...time to die. But we'll have more soon */ for (i = 0; i < num_buckets; i++) { ap_mpm_podx_killpg(all_buckets[i].pod, active_daemons_limit, @@ -3201,6 +3368,8 @@ static int event_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s) } else { + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00494) + "SIGHUP received. Attempting to restart"); /* Kill 'em all. Since the child acts the same on the parents SIGTERM * and a SIGHUP, we may as well use the same signal, because some user * pthreads are stealing signals from us left and right. @@ -3211,13 +3380,9 @@ static int event_run(apr_pool_t * _pconf, apr_pool_t * plog, server_rec * s) } ap_reclaim_child_processes(1, /* Start with SIGTERM */ - event_note_child_killed); - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, APLOGNO(00494) - "SIGHUP received. Attempting to restart"); + event_note_child_stopped); } - active_daemons = 0; - return OK; } @@ -3332,8 +3497,9 @@ static int event_open_logs(apr_pool_t * p, apr_pool_t * plog, new_max = num_buckets; } new_ptr = (int *)apr_palloc(ap_pglobal, new_max * sizeof(int)); - memcpy(new_ptr, retained->idle_spawn_rate, - retained->mpm->num_buckets * sizeof(int)); + if (retained->idle_spawn_rate) /* NULL at startup */ + memcpy(new_ptr, retained->idle_spawn_rate, + retained->mpm->num_buckets * sizeof(int)); retained->idle_spawn_rate = new_ptr; retained->mpm->max_buckets = new_max; } @@ -3383,7 +3549,6 @@ static int event_pre_config(apr_pool_t * pconf, apr_pool_t * plog, if (!retained) { retained = ap_retained_data_create(userdata_key, sizeof(*retained)); retained->mpm = ap_unixd_mpm_get_retained_data(); - retained->max_daemons_limit = -1; if (retained->mpm->module_loads) { test_atomics = 1; } @@ -3453,6 +3618,7 @@ static int event_pre_config(apr_pool_t * pconf, apr_pool_t * plog, worker_queue_info = NULL; listener_os_thread = NULL; listensocks_disabled = 0; + listener_is_wakeable = 0; return OK; } diff --git a/server/mpm/mpmt_os2/mpmt_os2.c b/server/mpm/mpmt_os2/mpmt_os2.c index 22bf5e7..b3adb03 100644 --- a/server/mpm/mpmt_os2/mpmt_os2.c +++ b/server/mpm/mpmt_os2/mpmt_os2.c @@ -26,7 +26,7 @@ * * Each child process consists of a pool of worker threads and a * main thread that accepts connections & passes them to the workers via - * a work queue. The worker thread pool is dynamic, managed by a maintanence + * a work queue. The worker thread pool is dynamic, managed by a maintenance * thread so that the number of idle threads is kept between * min_spare_threads & max_spare_threads. * @@ -79,7 +79,7 @@ int ap_min_spare_threads = 0; int ap_max_spare_threads = 0; /* Keep track of a few interesting statistics */ -int ap_max_daemons_limit = -1; +int ap_max_daemons_limit = 0; /* volatile just in case */ static int volatile shutdown_pending; @@ -344,8 +344,8 @@ static void spawn_child(int slot) "error spawning child, slot %d", slot); } - if (ap_max_daemons_limit < slot) { - ap_max_daemons_limit = slot; + if (slot + 1 > ap_max_daemons_limit) { + ap_max_daemons_limit = slot + 1; } ap_scoreboard_image->parent[slot].pid = proc_rc.codeTerminate; diff --git a/server/mpm/mpmt_os2/mpmt_os2_child.c b/server/mpm/mpmt_os2/mpmt_os2_child.c index bb7e136..f405cd2 100644 --- a/server/mpm/mpmt_os2/mpmt_os2_child.c +++ b/server/mpm/mpmt_os2/mpmt_os2_child.c @@ -200,6 +200,7 @@ void ap_mpm_child_main(apr_pool_t *pconf) int last_poll_idx = 0; apr_pool_create(&pconn, pchild); + apr_pool_tag(pconn, "transaction"); worker_args = apr_palloc(pconn, sizeof(worker_args_t)); worker_args->pconn = pconn; diff --git a/server/mpm/netware/mpm_netware.c b/server/mpm/netware/mpm_netware.c index 8248033..e89fdef 100644 --- a/server/mpm/netware/mpm_netware.c +++ b/server/mpm/netware/mpm_netware.c @@ -896,6 +896,7 @@ static int netware_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) set_signals(); apr_pool_create(&pmain, pconf); + apr_pool_tag(pmain, "pmain"); ap_run_child_init(pmain, ap_server_conf); if (ap_threads_max_free < ap_threads_min_free + 1) /* Don't thrash... */ diff --git a/server/mpm/prefork/prefork.c b/server/mpm/prefork/prefork.c index 8efda72..b5adb57 100644 --- a/server/mpm/prefork/prefork.c +++ b/server/mpm/prefork/prefork.c @@ -223,6 +223,10 @@ static void clean_child_exit(int code) apr_signal(SIGHUP, SIG_IGN); apr_signal(SIGTERM, SIG_IGN); + if (code == 0) { + ap_run_child_stopping(pchild, 0); + } + if (pchild) { apr_pool_destroy(pchild); } @@ -376,11 +380,22 @@ static void stop_listening(int sig) static int requests_this_child; static int num_listensocks = 0; +#if APR_HAS_THREADS +static void child_sigmask(sigset_t *new_mask, sigset_t *old_mask) +{ +#if defined(SIGPROCMASK_SETS_THREAD_MASK) + sigprocmask(SIG_SETMASK, new_mask, old_mask); +#else + pthread_sigmask(SIG_SETMASK, new_mask, old_mask); +#endif +} +#endif + static void child_main(int child_num_arg, int child_bucket) { #if APR_HAS_THREADS apr_thread_t *thd = NULL; - apr_os_thread_t osthd; + sigset_t sig_mask; #endif apr_pool_t *ptrans; apr_allocator_t *allocator; @@ -411,9 +426,23 @@ static void child_main(int child_num_arg, int child_bucket) apr_allocator_owner_set(allocator, pchild); apr_pool_tag(pchild, "pchild"); +#if AP_HAS_THREAD_LOCAL + if (one_process) { + thd = ap_thread_current(); + } + else if ((status = ap_thread_main_create(&thd, pchild))) { + ap_log_error(APLOG_MARK, APLOG_EMERG, status, ap_server_conf, APLOGNO(10378) + "Couldn't initialize child main thread"); + clean_child_exit(APEXIT_CHILDFATAL); + } +#elif APR_HAS_THREADS + { + apr_os_thread_t osthd = apr_os_thread_current(); + apr_os_thread_put(&thd, &osthd, pchild); + } +#endif #if APR_HAS_THREADS - osthd = apr_os_thread_current(); - apr_os_thread_put(&thd, &osthd, pchild); + ap_assert(thd != NULL); #endif apr_pool_create(&ptrans, pchild); @@ -446,8 +475,31 @@ static void child_main(int child_num_arg, int child_bucket) clean_child_exit(APEXIT_CHILDFATAL); } +#if APR_HAS_THREADS + /* Save the signal mask and block all the signals from being received by + * threads potentially created in child_init() hooks (e.g. mod_watchdog). + */ + child_sigmask(NULL, &sig_mask); + { + apr_status_t rv; + rv = apr_setup_signal_thread(); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(10271) + "Couldn't initialize signal thread"); + clean_child_exit(APEXIT_CHILDFATAL); + } + } +#endif /* APR_HAS_THREADS */ + ap_run_child_init(pchild, ap_server_conf); +#if APR_HAS_THREADS + /* Restore the original signal mask for this main thread, the only one + * that should possibly get interrupted by signals. + */ + child_sigmask(&sig_mask, NULL); +#endif + ap_create_sb_handle(&sbh, pchild, my_child_num, 0); (void) ap_update_child_status(sbh, SERVER_READY, (request_rec *) NULL); @@ -637,8 +689,9 @@ static void child_main(int child_num_arg, int child_bucket) } -static int make_child(server_rec *s, int slot, int bucket) +static int make_child(server_rec *s, int slot) { + int bucket = slot % retained->mpm->num_buckets; int pid; if (slot + 1 > retained->max_daemons_limit) { @@ -682,6 +735,10 @@ static int make_child(server_rec *s, int slot, int bucket) } if (!pid) { +#if AP_HAS_THREAD_LOCAL + ap_thread_current_after_fork(); +#endif + my_bucket = &all_buckets[bucket]; #ifdef HAVE_BINDPROCESSOR @@ -702,8 +759,8 @@ static int make_child(server_rec *s, int slot, int bucket) */ apr_signal(SIGHUP, just_die); apr_signal(SIGTERM, just_die); - /* Ignore SIGINT in child. This fixes race-condition in signals - * handling when httpd is runnning on foreground and user hits ctrl+c. + /* Ignore SIGINT in child. This fixes race-conditions in signals + * handling when httpd is running on foreground and user hits ctrl+c. * In this case, SIGINT is sent to all children followed by SIGTERM * from the main process, which interrupts the SIGINT handler and * leads to inconsistency. @@ -716,7 +773,6 @@ static int make_child(server_rec *s, int slot, int bucket) child_main(slot, bucket); } - ap_scoreboard_image->parent[slot].bucket = bucket; prefork_note_child_started(slot, pid); return 0; @@ -732,7 +788,7 @@ static void startup_children(int number_to_start) if (ap_scoreboard_image->servers[i][0].status != SERVER_DEAD) { continue; } - if (make_child(ap_server_conf, i, i % retained->mpm->num_buckets) < 0) { + if (make_child(ap_server_conf, i) < 0) { break; } --number_to_start; @@ -741,8 +797,6 @@ static void startup_children(int number_to_start) static void perform_idle_server_maintenance(apr_pool_t *p) { - static int bucket_make_child_record = -1; - static int bucket_kill_child_record = -1; int i; int idle_count; worker_score *ws; @@ -789,6 +843,7 @@ static void perform_idle_server_maintenance(apr_pool_t *p) } retained->max_daemons_limit = last_non_dead + 1; if (idle_count > ap_daemons_max_free) { + static int bucket_kill_child_record = -1; /* kill off one child... we use the pod because that'll cause it to * shut down gracefully, in case it happened to pick up a request * while we were counting @@ -819,10 +874,7 @@ static void perform_idle_server_maintenance(apr_pool_t *p) idle_count, total_non_dead); } for (i = 0; i < free_length; ++i) { - bucket_make_child_record++; - bucket_make_child_record %= retained->mpm->num_buckets; - make_child(ap_server_conf, free_slots[i], - bucket_make_child_record); + make_child(ap_server_conf, free_slots[i]); } /* the next time around we want to spawn twice as many if this * wasn't good enough, but not if we've just done a graceful @@ -867,7 +919,7 @@ static int prefork_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) if (one_process) { AP_MONCONTROL(1); - make_child(ap_server_conf, 0, 0); + make_child(ap_server_conf, 0); /* NOTREACHED */ ap_assert(0); return !OK; @@ -976,8 +1028,7 @@ static int prefork_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) /* we're still doing a 1-for-1 replacement of dead * children with new children */ - make_child(ap_server_conf, child_slot, - ap_get_scoreboard_process(child_slot)->bucket); + make_child(ap_server_conf, child_slot); --remaining_children_to_start; } #if APR_HAS_OTHER_CHILD @@ -1254,7 +1305,6 @@ static int prefork_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp if (!retained) { retained = ap_retained_data_create(userdata_key, sizeof(*retained)); retained->mpm = ap_unixd_mpm_get_retained_data(); - retained->max_daemons_limit = -1; retained->idle_spawn_rate = 1; } retained->mpm->mpm_state = AP_MPMQ_STARTING; @@ -1268,7 +1318,7 @@ static int prefork_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp if (retained->mpm->module_loads == 2) { if (!one_process && !foreground) { /* before we detach, setup crash handlers to log to errorlog */ - ap_fatal_signal_setup(ap_server_conf, pconf); + ap_fatal_signal_setup(ap_server_conf, p /* == pconf */); rv = apr_proc_detach(no_detach ? APR_PROC_DETACH_FOREGROUND : APR_PROC_DETACH_DAEMONIZE); if (rv != APR_SUCCESS) { diff --git a/server/mpm/winnt/child.c b/server/mpm/winnt/child.c index 21755f3..05151a8 100644 --- a/server/mpm/winnt/child.c +++ b/server/mpm/winnt/child.c @@ -352,7 +352,7 @@ static unsigned int __stdcall winnt_accept(void *lr_) ap_listen_rec *lr = (ap_listen_rec *)lr_; apr_os_sock_info_t sockinfo; winnt_conn_ctx_t *context = NULL; - DWORD BytesRead; + DWORD BytesRead = 0; SOCKET nlsd; LPFN_ACCEPTEX lpfnAcceptEx = NULL; LPFN_GETACCEPTEXSOCKADDRS lpfnGetAcceptExSockaddrs = NULL; @@ -784,8 +784,8 @@ static winnt_conn_ctx_t *winnt_get_connection(winnt_conn_ctx_t *context) */ static DWORD __stdcall worker_main(void *thread_num_val) { - apr_thread_t *thd; - apr_os_thread_t osthd; + apr_thread_t *thd = NULL; + apr_os_thread_t osthd = NULL; static int requests_this_child = 0; winnt_conn_ctx_t *context = NULL; int thread_num = (int)thread_num_val; @@ -793,7 +793,16 @@ static DWORD __stdcall worker_main(void *thread_num_val) conn_rec *c; apr_int32_t disconnected; +#if AP_HAS_THREAD_LOCAL + if (ap_thread_current_create(&thd, NULL, pchild) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, APLOGNO(10376) + "Couldn't initialize worker thread, thread locals won't " + "be available"); + osthd = apr_os_thread_current(); + } +#else osthd = apr_os_thread_current(); +#endif while (1) { @@ -826,8 +835,10 @@ static DWORD __stdcall worker_main(void *thread_num_val) continue; } - thd = NULL; - apr_os_thread_put(&thd, &osthd, context->ptrans); + if (osthd) { + thd = NULL; + apr_os_thread_put(&thd, &osthd, context->ptrans); + } c->current_thread = thd; ap_process_connection(c, context->sock); @@ -842,6 +853,12 @@ static DWORD __stdcall worker_main(void *thread_num_val) ap_update_child_status_from_indexes(0, thread_num, SERVER_DEAD, NULL); +#if AP_HAS_THREAD_LOCAL + if (!osthd) { + apr_pool_destroy(apr_thread_pool_get(thd)); + } +#endif + return 0; } diff --git a/server/mpm/winnt/mpm_winnt.c b/server/mpm/winnt/mpm_winnt.c index 3d71f80..1b8962e 100644 --- a/server/mpm/winnt/mpm_winnt.c +++ b/server/mpm/winnt/mpm_winnt.c @@ -437,7 +437,7 @@ static int send_handles_to_child(apr_pool_t *p, * get_listeners_from_parent() * The listen sockets are opened in the parent. This function, which runs * exclusively in the child process, receives them from the parent and - * makes them availeble in the child. + * makes them available in the child. */ static void get_listeners_from_parent(server_rec *s) { @@ -559,6 +559,7 @@ static int create_process(apr_pool_t *p, HANDLE *child_proc, HANDLE *child_exit_ int envc; apr_pool_create_ex(&ptemp, p, NULL, NULL); + apr_pool_tag(ptemp, "create_process"); /* Build the command line. Should look something like this: * C:/apache/bin/httpd.exe -f ap_server_confname @@ -586,10 +587,10 @@ static int create_process(apr_pool_t *p, HANDLE *child_proc, HANDLE *child_exit_ return -1; } - args = malloc((ap_server_conf->process->argc + 1) * sizeof (char*)); + args = ap_malloc((ap_server_conf->process->argc + 1) * sizeof (char*)); memcpy(args + 1, ap_server_conf->process->argv + 1, (ap_server_conf->process->argc - 1) * sizeof (char*)); - args[0] = malloc(strlen(cmd) + 1); + args[0] = ap_malloc(strlen(cmd) + 1); strcpy(args[0], cmd); args[ap_server_conf->process->argc] = NULL; } @@ -743,7 +744,7 @@ static int create_process(apr_pool_t *p, HANDLE *child_proc, HANDLE *child_exit_ * of this event means that the child process has exited prematurely * due to a seg fault or other irrecoverable error. For server * robustness, master_main will restart the child process under this - * condtion. + * condition. * * master_main uses the child_exit_event to signal the child process * to exit. @@ -1130,7 +1131,7 @@ static void winnt_rewrite_args(process_rec *process) "Failed to get the full path of %s", process->argv[0]); exit(APEXIT_INIT); } - /* WARNING: There is an implict assumption here that the + /* WARNING: There is an implicit assumption here that the * executable resides in ServerRoot or ServerRoot\bin */ def_server_root = (char *) apr_filepath_name_get(binpath); @@ -1377,7 +1378,7 @@ static int winnt_pre_config(apr_pool_t *pconf_, apr_pool_t *plog, apr_pool_t *pt ap_exists_config_define("DEBUG")) one_process = -1; - /* XXX: presume proper privilages; one nice thing would be + /* XXX: presume proper privileges; one nice thing would be * a loud emit if running as "LocalSystem"/"SYSTEM" to indicate * they should change to a user with write access to logs/ alone. */ @@ -1574,7 +1575,6 @@ static int winnt_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *pt /* This code should be run once in the parent and not run * across a restart */ - PSECURITY_ATTRIBUTES sa = GetNullACL(); /* returns NULL if invalid (Win95?) */ setup_signal_names(apr_psprintf(pconf, "ap%lu", parent_pid)); ap_log_pid(pconf, ap_pid_fname); @@ -1582,29 +1582,26 @@ static int winnt_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *pt /* Create shutdown event, apPID_shutdown, where PID is the parent * Apache process ID. Shutdown is signaled by 'apache -k shutdown'. */ - shutdown_event = CreateEvent(sa, FALSE, FALSE, signal_shutdown_name); + shutdown_event = CreateEvent(NULL, FALSE, FALSE, signal_shutdown_name); if (!shutdown_event) { ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00448) "Parent: Cannot create shutdown event %s", signal_shutdown_name); - CleanNullACL((void *)sa); return HTTP_INTERNAL_SERVER_ERROR; } /* Create restart event, apPID_restart, where PID is the parent * Apache process ID. Restart is signaled by 'apache -k restart'. */ - restart_event = CreateEvent(sa, FALSE, FALSE, signal_restart_name); + restart_event = CreateEvent(NULL, FALSE, FALSE, signal_restart_name); if (!restart_event) { CloseHandle(shutdown_event); ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, APLOGNO(00449) "Parent: Cannot create restart event %s", signal_restart_name); - CleanNullACL((void *)sa); return HTTP_INTERNAL_SERVER_ERROR; } - CleanNullACL((void *)sa); /* Create the start mutex, as an unnamed object for security. - * Ths start mutex is used during a restart to prevent more than + * The start mutex is used during a restart to prevent more than * one child process from entering the accept loop at once. */ rv = apr_proc_mutex_create(&start_mutex, NULL, diff --git a/server/mpm/winnt/nt_eventlog.c b/server/mpm/winnt/nt_eventlog.c index e7e80d0..cd49ee6 100644 --- a/server/mpm/winnt/nt_eventlog.c +++ b/server/mpm/winnt/nt_eventlog.c @@ -39,6 +39,7 @@ static DWORD WINAPI service_stderr_thread(LPVOID hPipe) apr_pool_t *p; apr_pool_create_ex(&p, NULL, NULL, NULL); + apr_pool_tag(p, "service_stderr_thread"); errarg[0] = "The Apache service named"; errarg[1] = display_name; diff --git a/server/mpm/winnt/service.c b/server/mpm/winnt/service.c index 121aca1..2e473cf 100644 --- a/server/mpm/winnt/service.c +++ b/server/mpm/winnt/service.c @@ -801,7 +801,7 @@ apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc, rv = apr_get_os_error(); ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, APLOGNO(00369) "Failed to open the Windows service " - "manager, perhaps you forgot to log in as Adminstrator?"); + "manager, perhaps you forgot to log in as Administrator?"); return (rv); } @@ -957,7 +957,7 @@ apr_status_t mpm_service_uninstall(void) rv = apr_get_os_error(); ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, APLOGNO(10009) "Failed to open the Windows service " - "manager, perhaps you forgot to log in as Adminstrator?"); + "manager, perhaps you forgot to log in as Administrator?"); return (rv); } @@ -1047,7 +1047,7 @@ apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc, rv = apr_get_os_error(); ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, APLOGNO(10011) "Failed to open the Windows service " - "manager, perhaps you forgot to log in as Adminstrator?"); + "manager, perhaps you forgot to log in as Administrator?"); return (rv); } @@ -1157,7 +1157,7 @@ void mpm_signal_service(apr_pool_t *ptemp, int signal) ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL, APLOGNO(10013) "Failed to open the Windows service " - "manager, perhaps you forgot to log in as Adminstrator?"); + "manager, perhaps you forgot to log in as Administrator?"); return; } diff --git a/server/mpm/worker/worker.c b/server/mpm/worker/worker.c index 8012fe2..7b572bd 100644 --- a/server/mpm/worker/worker.c +++ b/server/mpm/worker/worker.c @@ -324,6 +324,8 @@ static void signal_threads(int mode) ap_queue_interrupt_all(worker_queue); close_worker_sockets(); /* forcefully kill all current connections */ } + + ap_run_child_stopping(pchild, mode == ST_GRACEFUL); } static int worker_query(int query_code, int *result, apr_status_t *rv) @@ -432,6 +434,10 @@ static void clean_child_exit(int code) __attribute__ ((noreturn)); static void clean_child_exit(int code) { retained->mpm->mpm_state = AP_MPMQ_STOPPING; + if (terminate_mode == ST_INIT) { + ap_run_child_stopping(pchild, 0); + } + if (pchild) { apr_pool_destroy(pchild); } @@ -553,8 +559,8 @@ static void * APR_THREAD_FUNC listener_thread(apr_thread_t *thd, void * dummy) /* Unblock the signal used to wake this thread up, and set a handler for * it. */ - unblock_signal(LISTENER_SIGNAL); apr_signal(LISTENER_SIGNAL, dummy_signal_handler); + unblock_signal(LISTENER_SIGNAL); /* TODO: Switch to a system where threads reuse the results from earlier poll calls - manoj */ @@ -748,8 +754,8 @@ static void * APR_THREAD_FUNC worker_thread(apr_thread_t *thd, void * dummy) SERVER_STARTING, NULL); #ifdef HAVE_PTHREAD_KILL - unblock_signal(WORKER_SIGNAL); apr_signal(WORKER_SIGNAL, dummy_signal_handler); + unblock_signal(WORKER_SIGNAL); #endif while (!workers_may_exit) { @@ -841,11 +847,11 @@ static void create_listener_thread(thread_starter *ts) my_info->pid = my_child_num; my_info->tid = -1; /* listener thread doesn't have a thread slot */ my_info->sd = 0; - rv = apr_thread_create(&ts->listener, thread_attr, listener_thread, - my_info, pruntime); + rv = ap_thread_create(&ts->listener, thread_attr, listener_thread, + my_info, pruntime); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(00275) - "apr_thread_create: unable to create listener thread"); + "ap_thread_create: unable to create listener thread"); /* let the parent decide how bad this really is */ clean_child_exit(APEXIT_CHILDSICK); } @@ -862,7 +868,7 @@ static void setup_threads_runtime(void) * the connections they handle (i.e. ptrans). We can't use this thread's * self pool because all these objects survive it, nor use pchild or pconf * directly because this starter thread races with other modules' runtime, - * nor finally pchild (or subpool thereof) because it is killed explicitely + * nor finally pchild (or subpool thereof) because it is killed explicitly * before pconf (thus connections/ptrans can live longer, which matters in * ONE_PROCESS mode). So this leaves us with a subpool of pconf, created * before any ptrans hence destroyed after. @@ -961,11 +967,11 @@ static void * APR_THREAD_FUNC start_threads(apr_thread_t *thd, void *dummy) /* We let each thread update its own scoreboard entry. This is * done because it lets us deal with tid better. */ - rv = apr_thread_create(&threads[i], thread_attr, - worker_thread, my_info, pruntime); + rv = ap_thread_create(&threads[i], thread_attr, + worker_thread, my_info, pruntime); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(03142) - "apr_thread_create: unable to create worker thread"); + "ap_thread_create: unable to create worker thread"); /* let the parent decide how bad this really is */ clean_child_exit(APEXIT_CHILDSICK); } @@ -1109,6 +1115,17 @@ static void child_main(int child_num_arg, int child_bucket) apr_pool_create(&pchild, pconf); apr_pool_tag(pchild, "pchild"); +#if AP_HAS_THREAD_LOCAL + if (!one_process) { + apr_thread_t *thd = NULL; + if ((rv = ap_thread_main_create(&thd, pchild))) { + ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ap_server_conf, APLOGNO(10375) + "Couldn't initialize child main thread"); + clean_child_exit(APEXIT_CHILDFATAL); + } + } +#endif + /* close unused listeners and pods */ for (i = 0; i < retained->mpm->num_buckets; i++) { if (i != child_bucket) { @@ -1138,7 +1155,7 @@ static void child_main(int child_num_arg, int child_bucket) * from being received. The child processes no longer use signals for * any communication with the parent process. Let's also do this before * child_init() hooks are called and possibly create threads that - * otherwise could "steal" (implicitely) MPM's signals. + * otherwise could "steal" (implicitly) MPM's signals. */ rv = apr_setup_signal_thread(); if (rv != APR_SUCCESS) { @@ -1188,11 +1205,11 @@ static void child_main(int child_num_arg, int child_bucket) ts->child_num_arg = child_num_arg; ts->threadattr = thread_attr; - rv = apr_thread_create(&start_thread_id, thread_attr, start_threads, - ts, pchild); + rv = ap_thread_create(&start_thread_id, thread_attr, start_threads, + ts, pchild); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_ALERT, rv, ap_server_conf, APLOGNO(00282) - "apr_thread_create: unable to create worker thread"); + "ap_thread_create: unable to create worker thread"); /* let the parent decide how bad this really is */ clean_child_exit(APEXIT_CHILDSICK); } @@ -1230,8 +1247,8 @@ static void child_main(int child_num_arg, int child_bucket) * the other threads in the process needs to take us down * (e.g., for MaxConnectionsPerChild) it will send us SIGTERM */ - unblock_signal(SIGTERM); apr_signal(SIGTERM, dummy_signal_handler); + unblock_signal(SIGTERM); /* Watch for any messages from the parent over the POD */ while (1) { rv = ap_mpm_podx_check(my_bucket->pod); @@ -1309,6 +1326,10 @@ static int make_child(server_rec *s, int slot, int bucket) } if (!pid) { +#if AP_HAS_THREAD_LOCAL + ap_thread_current_after_fork(); +#endif + my_bucket = &all_buckets[bucket]; #ifdef HAVE_BINDPROCESSOR @@ -1339,7 +1360,6 @@ static int make_child(server_rec *s, int slot, int bucket) worker_note_child_lost_slot(slot, pid); } ap_scoreboard_image->parent[slot].quiescing = 0; - ap_scoreboard_image->parent[slot].bucket = bucket; worker_note_child_started(slot, pid); return 0; } @@ -1360,11 +1380,10 @@ static void startup_children(int number_to_start) } } -static void perform_idle_server_maintenance(int child_bucket, int num_buckets) +static void perform_idle_server_maintenance(int child_bucket) { - int i, j; + int num_buckets = retained->mpm->num_buckets; int idle_thread_count; - worker_score *ws; process_score *ps; int free_length; int totally_free_length = 0; @@ -1372,6 +1391,7 @@ static void perform_idle_server_maintenance(int child_bucket, int num_buckets) int last_non_dead; int total_non_dead; int active_thread_count = 0; + int i, j; /* initialize the free_list */ free_length = 0; @@ -1383,12 +1403,15 @@ static void perform_idle_server_maintenance(int child_bucket, int num_buckets) for (i = 0; i < ap_daemons_limit; ++i) { /* Initialization to satisfy the compiler. It doesn't know * that threads_per_child is always > 0 */ - int status = SERVER_DEAD; int any_dying_threads = 0; int any_dead_threads = 0; int all_dead_threads = 1; int child_threads_active = 0; + if (num_buckets > 1 && (i % num_buckets) != child_bucket) { + /* We only care about child_bucket in this call */ + continue; + } if (i >= retained->max_daemons_limit && totally_free_length == retained->idle_spawn_rate[child_bucket]) { /* short cut if all active processes have been examined and @@ -1398,8 +1421,7 @@ static void perform_idle_server_maintenance(int child_bucket, int num_buckets) } ps = &ap_scoreboard_image->parent[i]; for (j = 0; j < threads_per_child; j++) { - ws = &ap_scoreboard_image->servers[i][j]; - status = ws->status; + int status = ap_scoreboard_image->servers[i][j].status; /* XXX any_dying_threads is probably no longer needed GLA */ any_dying_threads = any_dying_threads || @@ -1419,8 +1441,7 @@ static void perform_idle_server_maintenance(int child_bucket, int num_buckets) loop if no pid? not much else matters */ if (status <= SERVER_READY && !ps->quiescing && - ps->generation == retained->mpm->my_generation && - ps->bucket == child_bucket) { + ps->generation == retained->mpm->my_generation) { ++idle_thread_count; } if (status >= SERVER_READY && status < SERVER_GRACEFUL) { @@ -1457,11 +1478,15 @@ static void perform_idle_server_maintenance(int child_bucket, int num_buckets) } /* XXX if (!ps->quiescing) is probably more reliable GLA */ if (!any_dying_threads) { - last_non_dead = i; ++total_non_dead; } + if (ps->pid != 0) { + last_non_dead = i; + } } + retained->max_daemons_limit = last_non_dead + 1; + if (retained->sick_child_detected) { if (had_healthy_child) { /* Assume this is a transient error, even though it may not be. Leave @@ -1470,6 +1495,10 @@ static void perform_idle_server_maintenance(int child_bucket, int num_buckets) */ retained->sick_child_detected = 0; } + else if (child_bucket < num_buckets - 1) { + /* check for had_healthy_child up to the last child bucket */ + return; + } else { /* looks like a basket case, as no child ever fully initialized; give up. */ @@ -1485,8 +1514,6 @@ static void perform_idle_server_maintenance(int child_bucket, int num_buckets) } } - retained->max_daemons_limit = last_non_dead + 1; - if (idle_thread_count > max_spare_threads / num_buckets) { /* Kill off one child */ ap_mpm_podx_signal(all_buckets[child_bucket].pod, @@ -1497,7 +1524,7 @@ static void perform_idle_server_maintenance(int child_bucket, int num_buckets) /* terminate the free list */ if (free_length == 0) { /* scoreboard is full, can't fork */ - if (active_thread_count >= ap_daemons_limit * threads_per_child) { + if (active_thread_count >= max_workers / num_buckets) { /* no threads are "inactive" - starting, stopping, etc. */ /* have we reached MaxRequestWorkers, or just getting close? */ if (0 == idle_thread_count) { @@ -1560,8 +1587,10 @@ static void perform_idle_server_maintenance(int child_bucket, int num_buckets) } } -static void server_main_loop(int remaining_children_to_start, int num_buckets) +static void server_main_loop(int remaining_children_to_start) { + int num_buckets = retained->mpm->num_buckets; + int successive_kills = 0; ap_generation_t old_gen; int child_slot; apr_exit_why_e exitwhy; @@ -1615,14 +1644,15 @@ static void server_main_loop(int remaining_children_to_start, int num_buckets) ps->quiescing = 0; if (processed_status == APEXIT_CHILDSICK) { /* resource shortage, minimize the fork rate */ - retained->idle_spawn_rate[ps->bucket] = 1; + retained->idle_spawn_rate[child_slot % num_buckets] = 1; } else if (remaining_children_to_start && child_slot < ap_daemons_limit) { /* we're still doing a 1-for-1 replacement of dead * children with new children */ - make_child(ap_server_conf, child_slot, ps->bucket); + make_child(ap_server_conf, child_slot, + child_slot % num_buckets); --remaining_children_to_start; } } @@ -1655,11 +1685,30 @@ static void server_main_loop(int remaining_children_to_start, int num_buckets) /* Don't perform idle maintenance when a child dies, * only do it when there's a timeout. Remember only a * finite number of children can die, and it's pretty - * pathological for a lot to die suddenly. + * pathological for a lot to die suddenly. If a child is + * killed by a signal (faulting) we want to restart it ASAP + * though, up to 3 successive faults or we stop this until + * a timeout happens again (to avoid the flood of fork()ed + * processes that keep being killed early). */ - continue; + if (child_slot < 0 || !APR_PROC_CHECK_SIGNALED(exitwhy)) { + continue; + } + if (++successive_kills >= 3) { + if (successive_kills % 10 == 3) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, + ap_server_conf, APLOGNO(10393) + "children are killed successively!"); + } + continue; + } + ++remaining_children_to_start; + } + else { + successive_kills = 0; } - else if (remaining_children_to_start) { + + if (remaining_children_to_start) { /* we hit a 1 second timeout in which none of the previous * generation of children needed to be reaped... so assume * they're all done, and pick up the slack if any is left. @@ -1674,7 +1723,7 @@ static void server_main_loop(int remaining_children_to_start, int num_buckets) } for (i = 0; i < num_buckets; i++) { - perform_idle_server_maintenance(i, num_buckets); + perform_idle_server_maintenance(i); } } } @@ -1756,7 +1805,7 @@ static int worker_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s) apr_proc_mutex_defname()); retained->mpm->mpm_state = AP_MPMQ_RUNNING; - server_main_loop(remaining_children_to_start, num_buckets); + server_main_loop(remaining_children_to_start); retained->mpm->mpm_state = AP_MPMQ_STOPPING; if (retained->mpm->shutdown_pending && retained->mpm->is_ungraceful) { @@ -1960,8 +2009,9 @@ static int worker_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, new_max = num_buckets; } new_ptr = (int *)apr_palloc(ap_pglobal, new_max * sizeof(int)); - memcpy(new_ptr, retained->idle_spawn_rate, - retained->mpm->num_buckets * sizeof(int)); + if (retained->idle_spawn_rate) /* NULL at startup */ + memcpy(new_ptr, retained->idle_spawn_rate, + retained->mpm->num_buckets * sizeof(int)); retained->idle_spawn_rate = new_ptr; retained->mpm->max_buckets = new_max; } @@ -2010,7 +2060,6 @@ static int worker_pre_config(apr_pool_t *pconf, apr_pool_t *plog, if (!retained) { retained = ap_retained_data_create(userdata_key, sizeof(*retained)); retained->mpm = ap_unixd_mpm_get_retained_data(); - retained->max_daemons_limit = -1; } retained->mpm->mpm_state = AP_MPMQ_STARTING; if (retained->mpm->baton != retained) { diff --git a/server/mpm_common.c b/server/mpm_common.c index 04f36d9..9bcd760 100644 --- a/server/mpm_common.c +++ b/server/mpm_common.c @@ -72,7 +72,8 @@ APR_HOOK_LINK(end_generation) \ APR_HOOK_LINK(child_status) \ APR_HOOK_LINK(suspend_connection) \ - APR_HOOK_LINK(resume_connection) + APR_HOOK_LINK(resume_connection) \ + APR_HOOK_LINK(child_stopping) #if AP_ENABLE_EXCEPTION_HOOK APR_HOOK_STRUCT( @@ -112,6 +113,9 @@ AP_IMPLEMENT_HOOK_VOID(suspend_connection, AP_IMPLEMENT_HOOK_VOID(resume_connection, (conn_rec *c, request_rec *r), (c, r)) +AP_IMPLEMENT_HOOK_VOID(child_stopping, + (apr_pool_t *pchild, int graceful), + (pchild, graceful)) /* hooks with no args are implemented last, after disabling APR hook probes */ #if defined(APR_HOOK_PROBES_ENABLED) diff --git a/server/mpm_fdqueue.c b/server/mpm_fdqueue.c index c812450..3697ca7 100644 --- a/server/mpm_fdqueue.c +++ b/server/mpm_fdqueue.c @@ -493,6 +493,10 @@ static apr_status_t queue_interrupt(fd_queue_t *queue, int all, int term) { apr_status_t rv; + if (queue->terminated) { + return APR_EOF; + } + if ((rv = apr_thread_mutex_lock(queue->one_big_mutex)) != APR_SUCCESS) { return rv; } diff --git a/server/mpm_fdqueue.h b/server/mpm_fdqueue.h index 9aeedde..1047f88 100644 --- a/server/mpm_fdqueue.h +++ b/server/mpm_fdqueue.h @@ -83,7 +83,7 @@ struct fd_queue_t unsigned int out; apr_thread_mutex_t *one_big_mutex; apr_thread_cond_t *not_empty; - int terminated; + volatile int terminated; }; typedef struct fd_queue_t fd_queue_t; diff --git a/server/mpm_unix.c b/server/mpm_unix.c index 1800f5d..ed4555a 100644 --- a/server/mpm_unix.c +++ b/server/mpm_unix.c @@ -259,10 +259,12 @@ AP_DECLARE(void) ap_reclaim_child_processes(int terminate, while (cur_extra) { ap_generation_t old_gen; extra_process_t *next = cur_extra->next; + pid_t pid = cur_extra->pid; - if (reclaim_one_pid(cur_extra->pid, action_table[cur_action].action)) { - if (ap_unregister_extra_mpm_process(cur_extra->pid, &old_gen) == 1) { - mpm_callback(-1, cur_extra->pid, old_gen); + if (reclaim_one_pid(pid, action_table[cur_action].action)) { + if (ap_unregister_extra_mpm_process(pid, &old_gen) == 1) { + /* cur_extra dangling pointer from here. */ + mpm_callback(-1, pid, old_gen); } else { AP_DEBUG_ASSERT(1 == 0); @@ -307,10 +309,12 @@ AP_DECLARE(void) ap_relieve_child_processes(ap_reclaim_callback_fn_t *mpm_callba while (cur_extra) { ap_generation_t old_gen; extra_process_t *next = cur_extra->next; + pid_t pid = cur_extra->pid; - if (reclaim_one_pid(cur_extra->pid, DO_NOTHING)) { - if (ap_unregister_extra_mpm_process(cur_extra->pid, &old_gen) == 1) { - mpm_callback(-1, cur_extra->pid, old_gen); + if (reclaim_one_pid(pid, DO_NOTHING)) { + if (ap_unregister_extra_mpm_process(pid, &old_gen) == 1) { + /* cur_extra dangling pointer from here. */ + mpm_callback(-1, pid, old_gen); } else { AP_DEBUG_ASSERT(1 == 0); @@ -632,13 +636,14 @@ static apr_status_t dummy_connection(ap_pod_t *pod) if (rv != APR_SUCCESS) { return rv; } + apr_pool_tag(p, "dummy_connection"); /* If possible, find a listener which is configured for * plain-HTTP, not SSL; using an SSL port would either be * expensive to do correctly (performing a complete SSL handshake) * or cause log spam by doing incorrectly (simply sending EOF). */ lp = ap_listeners; - while (lp && lp->protocol && strcasecmp(lp->protocol, "http") != 0) { + while (lp && lp->protocol && ap_cstr_casecmp(lp->protocol, "http") != 0) { lp = lp->next; } if (!lp) { @@ -686,7 +691,7 @@ static apr_status_t dummy_connection(ap_pod_t *pod) return rv; } - if (lp->protocol && strcasecmp(lp->protocol, "https") == 0) { + if (lp->protocol && ap_cstr_casecmp(lp->protocol, "https") == 0) { /* Send a TLS 1.0 close_notify alert. This is perhaps the * "least wrong" way to open and cleanly terminate an SSL * connection. It should "work" without noisy error logs if @@ -706,7 +711,7 @@ static apr_status_t dummy_connection(ap_pod_t *pod) } else /* ... XXX other request types here? */ { /* Create an HTTP request string. We include a User-Agent so - * that adminstrators can track down the cause of the + * that administrators can track down the cause of the * odd-looking requests in their logs. A complete request is * used since kernel-level filtering may require that much * data before returning from accept(). */ diff --git a/server/protocol.c b/server/protocol.c index 8d90055..6f9540a 100644 --- a/server/protocol.c +++ b/server/protocol.c @@ -508,6 +508,8 @@ cleanup: /* PR#43039: We shouldn't accept NULL bytes within the line */ bytes_handled = strlen(*s); if (bytes_handled < *read) { + ap_log_data(APLOG_MARK, APLOG_DEBUG, ap_server_conf, + "NULL bytes in header", *s, *read, 0); *read = bytes_handled; if (rv == APR_SUCCESS) { rv = APR_EINVAL; @@ -601,7 +603,7 @@ AP_CORE_DECLARE(void) ap_parse_uri(request_rec *r, const char *uri) if (status == APR_SUCCESS) { /* if it has a scheme we may need to do absoluteURI vhost stuff */ if (r->parsed_uri.scheme - && !strcasecmp(r->parsed_uri.scheme, ap_http_scheme(r))) { + && !ap_cstr_casecmp(r->parsed_uri.scheme, ap_http_scheme(r))) { r->hostname = r->parsed_uri.hostname; } else if (r->method_number == M_CONNECT) { @@ -609,8 +611,15 @@ AP_CORE_DECLARE(void) ap_parse_uri(request_rec *r, const char *uri) } r->args = r->parsed_uri.query; - r->uri = r->parsed_uri.path ? r->parsed_uri.path - : apr_pstrdup(r->pool, "/"); + if (r->parsed_uri.path) { + r->uri = r->parsed_uri.path; + } + else if (r->method_number == M_OPTIONS) { + r->uri = apr_pstrdup(r->pool, "*"); + } + else { + r->uri = apr_pstrdup(r->pool, "/"); + } #if defined(OS2) || defined(WIN32) /* Handle path translations for OS/2 and plug security hole. @@ -645,13 +654,6 @@ static int field_name_len(const char *field) static int read_request_line(request_rec *r, apr_bucket_brigade *bb) { - enum { - rrl_none, rrl_badmethod, rrl_badwhitespace, rrl_excesswhitespace, - rrl_missinguri, rrl_baduri, rrl_badprotocol, rrl_trailingtext, - rrl_badmethod09, rrl_reject09 - } deferred_error = rrl_none; - char *ll; - char *uri; apr_size_t len; int num_blank_lines = DEFAULT_LIMIT_BLANK_LINES; core_server_config *conf = ap_get_core_module_config(r->server->module_config); @@ -704,13 +706,29 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) } } while ((len <= 0) && (--num_blank_lines >= 0)); + /* Set r->request_time before any logging, mod_unique_id needs it. */ + r->request_time = apr_time_now(); + if (APLOGrtrace5(r)) { ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "Request received from client: %s", ap_escape_logitem(r->pool, r->the_request)); } - r->request_time = apr_time_now(); + return 1; +} + +AP_DECLARE(int) ap_parse_request_line(request_rec *r) +{ + core_server_config *conf = ap_get_core_module_config(r->server->module_config); + int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); + enum { + rrl_none, rrl_badmethod, rrl_badwhitespace, rrl_excesswhitespace, + rrl_missinguri, rrl_baduri, rrl_badprotocol, rrl_trailingtext, + rrl_badmethod09, rrl_reject09 + } deferred_error = rrl_none; + apr_size_t len = 0; + char *uri, *ll; r->method = r->the_request; @@ -742,7 +760,6 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) if (deferred_error == rrl_none) deferred_error = rrl_missinguri; r->protocol = uri = ""; - len = 0; goto rrl_done; } else if (strict && ll[0] && apr_isspace(ll[1]) @@ -773,7 +790,6 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) /* Verify URI terminated with a single SP, or mark as specific error */ if (!ll) { r->protocol = ""; - len = 0; goto rrl_done; } else if (strict && ll[0] && apr_isspace(ll[1]) @@ -866,6 +882,14 @@ rrl_done: r->header_only = 1; ap_parse_uri(r, uri); + if (r->status == HTTP_OK + && (r->parsed_uri.path != NULL) + && (r->parsed_uri.path[0] != '/') + && (r->method_number != M_OPTIONS + || strcmp(r->parsed_uri.path, "*") != 0)) { + /* Invalid request-target per RFC 7230 section 5.3 */ + r->status = HTTP_BAD_REQUEST; + } /* With the request understood, we can consider HTTP/0.9 specific errors */ if (r->proto_num == HTTP_VERSION(0, 9) && deferred_error == rrl_none) { @@ -901,7 +925,7 @@ rrl_done: else if (deferred_error == rrl_excesswhitespace) ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03448) "HTTP Request Line; Excess whitespace " - "(disallowed by HttpProtocolOptions Strict"); + "(disallowed by HttpProtocolOptions Strict)"); else if (deferred_error == rrl_trailingtext) ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03449) "HTTP Request Line; Extraneous text found '%.*s' " @@ -973,6 +997,79 @@ rrl_failed: return 0; } +AP_DECLARE(int) ap_check_request_header(request_rec *r) +{ + core_server_config *conf; + int strict_host_check; + const char *expect; + int access_status; + + conf = ap_get_core_module_config(r->server->module_config); + + /* update what we think the virtual host is based on the headers we've + * now read. may update status. + */ + strict_host_check = (conf->strict_host_check == AP_CORE_CONFIG_ON); + access_status = ap_update_vhost_from_headers_ex(r, strict_host_check); + if (strict_host_check && access_status != HTTP_OK) { + if (r->server == ap_server_conf) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(10156) + "Requested hostname '%s' did not match any ServerName/ServerAlias " + "in the global server configuration ", r->hostname); + } + else { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(10157) + "Requested hostname '%s' did not match any ServerName/ServerAlias " + "in the matching virtual host (default vhost for " + "current connection is %s:%u)", + r->hostname, r->server->defn_name, r->server->defn_line_number); + } + r->status = access_status; + } + if (r->status != HTTP_OK) { + return 0; + } + + if ((!r->hostname && (r->proto_num >= HTTP_VERSION(1, 1))) + || ((r->proto_num == HTTP_VERSION(1, 1)) + && !apr_table_get(r->headers_in, "Host"))) { + /* + * Client sent us an HTTP/1.1 or later request without telling us the + * hostname, either with a full URL or a Host: header. We therefore + * need to (as per the 1.1 spec) send an error. As a special case, + * HTTP/1.1 mentions twice (S9, S14.23) that a request MUST contain + * a Host: header, and the server MUST respond with 400 if it doesn't. + */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00569) + "client sent HTTP/1.1 request without hostname " + "(see RFC2616 section 14.23): %s", r->uri); + r->status = HTTP_BAD_REQUEST; + return 0; + } + + if (((expect = apr_table_get(r->headers_in, "Expect")) != NULL) + && (expect[0] != '\0')) { + /* + * The Expect header field was added to HTTP/1.1 after RFC 2068 + * as a means to signal when a 100 response is desired and, + * unfortunately, to signal a poor man's mandatory extension that + * the server must understand or return 417 Expectation Failed. + */ + if (ap_cstr_casecmp(expect, "100-continue") == 0) { + r->expecting_100 = 1; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00570) + "client sent an unrecognized expectation value " + "of Expect: %s", expect); + r->status = HTTP_EXPECTATION_FAILED; + return 0; + } + } + + return 1; +} + static int table_do_fn_check_lengths(void *r_, const char *key, const char *value) { @@ -1256,16 +1353,10 @@ AP_DECLARE(void) ap_get_mime_headers(request_rec *r) apr_brigade_destroy(tmp_bb); } -request_rec *ap_read_request(conn_rec *conn) +AP_DECLARE(request_rec *) ap_create_request(conn_rec *conn) { request_rec *r; apr_pool_t *p; - const char *expect; - int access_status; - apr_bucket_brigade *tmp_bb; - apr_socket_t *csd; - apr_interval_time_t cur_timeout; - apr_pool_create(&p, conn->pool); apr_pool_tag(p, "request"); @@ -1304,6 +1395,7 @@ request_rec *ap_read_request(conn_rec *conn) r->read_body = REQUEST_NO_BODY; r->status = HTTP_OK; /* Until further notice */ + r->header_only = 0; r->the_request = NULL; /* Begin by presuming any module can make its own path_info assumptions, @@ -1314,12 +1406,35 @@ request_rec *ap_read_request(conn_rec *conn) r->useragent_addr = conn->client_addr; r->useragent_ip = conn->client_ip; + return r; +} + +/* Apply the server's timeout/config to the connection/request. */ +static void apply_server_config(request_rec *r) +{ + apr_socket_t *csd; + + csd = ap_get_conn_socket(r->connection); + apr_socket_timeout_set(csd, r->server->timeout); + + r->per_dir_config = r->server->lookup_defaults; +} + +request_rec *ap_read_request(conn_rec *conn) +{ + int access_status; + apr_bucket_brigade *tmp_bb; + + request_rec *r = ap_create_request(conn); + tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + conn->keepalive = AP_CONN_UNKNOWN; ap_run_pre_read_request(r, conn); /* Get the request... */ - if (!read_request_line(r, tmp_bb)) { + if (!read_request_line(r, tmp_bb) || !ap_parse_request_line(r)) { + apr_brigade_cleanup(tmp_bb); switch (r->status) { case HTTP_REQUEST_URI_TOO_LARGE: case HTTP_BAD_REQUEST: @@ -1335,116 +1450,84 @@ request_rec *ap_read_request(conn_rec *conn) "request failed: malformed request line"); } access_status = r->status; - r->status = HTTP_OK; - ap_die(access_status, r); - ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); - ap_run_log_transaction(r); - r = NULL; - apr_brigade_destroy(tmp_bb); - goto traceout; + goto die_unusable_input; + case HTTP_REQUEST_TIME_OUT: + /* Just log, no further action on this connection. */ ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, NULL); if (!r->connection->keepalives) ap_run_log_transaction(r); - apr_brigade_destroy(tmp_bb); - goto traceout; - default: - apr_brigade_destroy(tmp_bb); - r = NULL; - goto traceout; + break; } + /* Not worth dying with. */ + conn->keepalive = AP_CONN_CLOSE; + apr_pool_destroy(r->pool); + goto ignore; } + apr_brigade_cleanup(tmp_bb); /* We may have been in keep_alive_timeout mode, so toggle back * to the normal timeout mode as we fetch the header lines, * as necessary. */ - csd = ap_get_conn_socket(conn); - apr_socket_timeout_get(csd, &cur_timeout); - if (cur_timeout != conn->base_server->timeout) { - apr_socket_timeout_set(csd, conn->base_server->timeout); - cur_timeout = conn->base_server->timeout; - } + apply_server_config(r); if (!r->assbackwards) { - const char *tenc; + const char *tenc, *clen; ap_get_mime_headers_core(r, tmp_bb); + apr_brigade_cleanup(tmp_bb); if (r->status != HTTP_OK) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00567) "request failed: error reading the headers"); - ap_send_error_response(r, 0); - ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); - ap_run_log_transaction(r); - apr_brigade_destroy(tmp_bb); - goto traceout; + access_status = r->status; + goto die_unusable_input; + } + + clen = apr_table_get(r->headers_in, "Content-Length"); + if (clen) { + apr_off_t cl; + + if (!ap_parse_strict_length(&cl, clen)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10242) + "client sent invalid Content-Length " + "(%s): %s", clen, r->uri); + access_status = HTTP_BAD_REQUEST; + goto die_unusable_input; + } } tenc = apr_table_get(r->headers_in, "Transfer-Encoding"); if (tenc) { - /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-23 + /* https://tools.ietf.org/html/rfc7230 * Section 3.3.3.3: "If a Transfer-Encoding header field is * present in a request and the chunked transfer coding is not * the final encoding ...; the server MUST respond with the 400 * (Bad Request) status code and then close the connection". */ - if (!(strcasecmp(tenc, "chunked") == 0 /* fast path */ - || ap_find_last_token(r->pool, tenc, "chunked"))) { + if (!ap_is_chunked(r->pool, tenc)) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02539) "client sent unknown Transfer-Encoding " "(%s): %s", tenc, r->uri); - r->status = HTTP_BAD_REQUEST; - conn->keepalive = AP_CONN_CLOSE; - ap_send_error_response(r, 0); - ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); - ap_run_log_transaction(r); - apr_brigade_destroy(tmp_bb); - goto traceout; + access_status = HTTP_BAD_REQUEST; + goto die_unusable_input; } - /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-23 + /* https://tools.ietf.org/html/rfc7230 * Section 3.3.3.3: "If a message is received with both a * Transfer-Encoding and a Content-Length header field, the * Transfer-Encoding overrides the Content-Length. ... A sender * MUST remove the received Content-Length field". */ - apr_table_unset(r->headers_in, "Content-Length"); - } - } - - apr_brigade_destroy(tmp_bb); - - /* update what we think the virtual host is based on the headers we've - * now read. may update status. - */ - ap_update_vhost_from_headers(r); - access_status = r->status; - - /* Toggle to the Host:-based vhost's timeout mode to fetch the - * request body and send the response body, if needed. - */ - if (cur_timeout != r->server->timeout) { - apr_socket_timeout_set(csd, r->server->timeout); - cur_timeout = r->server->timeout; - } + if (clen) { + apr_table_unset(r->headers_in, "Content-Length"); - /* we may have switched to another server */ - r->per_dir_config = r->server->lookup_defaults; - - if ((!r->hostname && (r->proto_num >= HTTP_VERSION(1, 1))) - || ((r->proto_num == HTTP_VERSION(1, 1)) - && !apr_table_get(r->headers_in, "Host"))) { - /* - * Client sent us an HTTP/1.1 or later request without telling us the - * hostname, either with a full URL or a Host: header. We therefore - * need to (as per the 1.1 spec) send an error. As a special case, - * HTTP/1.1 mentions twice (S9, S14.23) that a request MUST contain - * a Host: header, and the server MUST respond with 400 if it doesn't. - */ - access_status = HTTP_BAD_REQUEST; - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00569) - "client sent HTTP/1.1 request without hostname " - "(see RFC2616 section 14.23): %s", r->uri); + /* Don't reuse this connection anyway to avoid confusion with + * intermediaries and request/reponse spltting. + */ + conn->keepalive = AP_CONN_CLOSE; + } + } } /* @@ -1453,47 +1536,94 @@ request_rec *ap_read_request(conn_rec *conn) * status codes that do not cause the connection to be dropped and * in situations where the connection should be kept alive. */ - ap_add_input_filter_handle(ap_http_input_filter_handle, NULL, r, r->connection); - if (access_status != HTTP_OK - || (access_status = ap_run_post_read_request(r))) { - ap_die(access_status, r); - ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); - ap_run_log_transaction(r); - r = NULL; - goto traceout; + /* Validate Host/Expect headers and select vhost. */ + if (!ap_check_request_header(r)) { + /* we may have switched to another server still */ + apply_server_config(r); + access_status = r->status; + goto die_before_hooks; } - if (((expect = apr_table_get(r->headers_in, "Expect")) != NULL) - && (expect[0] != '\0')) { - /* - * The Expect header field was added to HTTP/1.1 after RFC 2068 - * as a means to signal when a 100 response is desired and, - * unfortunately, to signal a poor man's mandatory extension that - * the server must understand or return 417 Expectation Failed. - */ - if (strcasecmp(expect, "100-continue") == 0) { - r->expecting_100 = 1; - } - else { - r->status = HTTP_EXPECTATION_FAILED; - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00570) - "client sent an unrecognized expectation value of " - "Expect: %s", expect); - ap_send_error_response(r, 0); - ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); - ap_run_log_transaction(r); - goto traceout; - } + /* we may have switched to another server */ + apply_server_config(r); + + if ((access_status = ap_post_read_request(r))) { + goto die; } - AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method, (char *)r->uri, (char *)r->server->defn_name, r->status); + AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method, + (char *)r->uri, (char *)r->server->defn_name, + r->status); return r; - traceout: + + /* Everything falls through on failure */ + +die_unusable_input: + /* Input filters are in an undeterminate state, cleanup (including + * CORE_IN's socket) such that any further attempt to read is EOF. + */ + { + ap_filter_t *f = conn->input_filters; + while (f) { + if (f->frec == ap_core_input_filter_handle) { + core_net_rec *net = f->ctx; + apr_brigade_cleanup(net->in_ctx->b); + break; + } + ap_remove_input_filter(f); + f = f->next; + } + conn->input_filters = r->input_filters = f; + conn->keepalive = AP_CONN_CLOSE; + } + +die_before_hooks: + /* First call to ap_die() (non recursive) */ + r->status = HTTP_OK; + +die: + ap_die(access_status, r); + + /* ap_die() sent the response through the output filters, we must now + * end the request with an EOR bucket for stream/pipeline accounting. + */ + { + apr_bucket_brigade *eor_bb; + eor_bb = apr_brigade_create(conn->pool, conn->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(eor_bb, + ap_bucket_eor_create(conn->bucket_alloc, r)); + ap_pass_brigade(conn->output_filters, eor_bb); + apr_brigade_cleanup(eor_bb); + } + +ignore: + r = NULL; AP_READ_REQUEST_FAILURE((uintptr_t)r); - return r; + return NULL; +} + +AP_DECLARE(int) ap_post_read_request(request_rec *r) +{ + int status; + + if ((status = ap_run_post_read_request(r))) { + return status; + } + + /* Enforce http(s) only scheme for non-forward-proxy requests */ + if (!r->proxyreq + && r->parsed_uri.scheme + && (ap_cstr_casecmpn(r->parsed_uri.scheme, "http", 4) != 0 + || (r->parsed_uri.scheme[4] != '\0' + && (apr_tolower(r->parsed_uri.scheme[4]) != 's' + || r->parsed_uri.scheme[5] != '\0')))) { + return HTTP_BAD_REQUEST; + } + + return OK; } /* if a request with a body creates a subrequest, remove original request's @@ -1559,23 +1689,29 @@ AP_DECLARE(void) ap_set_sub_req_protocol(request_rec *rnew, rnew->main = (request_rec *) r; } -static void end_output_stream(request_rec *r) +static void end_output_stream(request_rec *r, int status) { conn_rec *c = r->connection; apr_bucket_brigade *bb; apr_bucket *b; bb = apr_brigade_create(r->pool, c->bucket_alloc); + if (status != OK) { + b = ap_bucket_error_create(status, NULL, r->pool, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + } b = apr_bucket_eos_create(c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); + ap_pass_brigade(r->output_filters, bb); + apr_brigade_cleanup(bb); } AP_DECLARE(void) ap_finalize_sub_req_protocol(request_rec *sub) { /* tell the filter chain there is no more content coming */ if (!sub->eos_sent) { - end_output_stream(sub); + end_output_stream(sub, OK); } } @@ -1586,11 +1722,11 @@ AP_DECLARE(void) ap_finalize_sub_req_protocol(request_rec *sub) */ AP_DECLARE(void) ap_finalize_request_protocol(request_rec *r) { - (void) ap_discard_request_body(r); + int status = ap_discard_request_body(r); /* tell the filter chain there is no more content coming */ if (!r->eos_sent) { - end_output_stream(r); + end_output_stream(r, status); } } @@ -1627,7 +1763,7 @@ AP_DECLARE(int) ap_get_basic_auth_pw(request_rec *r, const char **pw) : "Authorization"); const char *t; - if (!(t = ap_auth_type(r)) || strcasecmp(t, "Basic")) + if (!(t = ap_auth_type(r)) || ap_cstr_casecmp(t, "Basic")) return DECLINED; if (!ap_auth_name(r)) { @@ -1641,7 +1777,7 @@ AP_DECLARE(int) ap_get_basic_auth_pw(request_rec *r, const char **pw) return HTTP_UNAUTHORIZED; } - if (strcasecmp(ap_getword(r->pool, &auth_line, ' '), "Basic")) { + if (ap_cstr_casecmp(ap_getword(r->pool, &auth_line, ' '), "Basic")) { /* Client tried to authenticate using wrong auth scheme */ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00573) "client used wrong authentication scheme: %s", r->uri); @@ -1994,6 +2130,9 @@ AP_DECLARE(int) ap_rputc(int c, request_rec *r) AP_DECLARE(int) ap_rwrite(const void *buf, int nbyte, request_rec *r) { + if (nbyte < 0) + return -1; + if (r->connection->aborted) return -1; @@ -2036,7 +2175,7 @@ static int r_flush(apr_vformatter_buff_t *buff) AP_DECLARE(int) ap_vrprintf(request_rec *r, const char *fmt, va_list va) { - apr_size_t written; + int written; struct ap_vrprintf_data vd; char vrprintf_buf[AP_IOBUFSIZE]; @@ -2054,7 +2193,7 @@ AP_DECLARE(int) ap_vrprintf(request_rec *r, const char *fmt, va_list va) int n = vd.vbuff.curpos - vrprintf_buf; /* last call to buffer_output, to finish clearing the buffer */ - if (buffer_output(r, vrprintf_buf,n) != APR_SUCCESS) + if (buffer_output(r, vrprintf_buf, n) != APR_SUCCESS) return -1; written += n; @@ -2100,6 +2239,7 @@ AP_DECLARE_NONSTD(int) ap_rvputs(request_rec *r, ...) len = strlen(s); if (buffer_output(r, s, len) != APR_SUCCESS) { + va_end(va); return -1; } @@ -2176,7 +2316,8 @@ static int send_header(void *data, const char *key, const char *val) AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers) { hdr_ptr x; - char *status_line = NULL; + char *response_line = NULL; + const char *status_line; request_rec *rr; if (r->proto_num < HTTP_VERSION(1,1)) { @@ -2188,30 +2329,38 @@ AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers) "Status is %d - not sending interim response", r->status); return; } - if ((r->status == HTTP_CONTINUE) && !r->expecting_100) { - /* - * Don't send 100-Continue when there was no Expect: 100-continue - * in the request headers. For origin servers this is a SHOULD NOT - * for proxies it is a MUST NOT according to RFC 2616 8.2.3 + if (r->status == HTTP_CONTINUE) { + if (!r->expecting_100) { + /* + * Don't send 100-Continue when there was no Expect: 100-continue + * in the request headers. For origin servers this is a SHOULD NOT + * for proxies it is a MUST NOT according to RFC 2616 8.2.3 + */ + return; + } + + /* if we send an interim response, we're no longer in a state of + * expecting one. Also, this could feasibly be in a subrequest, + * so we need to propagate the fact that we responded. */ - return; + for (rr = r; rr != NULL; rr = rr->main) { + rr->expecting_100 = 0; + } } - /* if we send an interim response, we're no longer in a state of - * expecting one. Also, this could feasibly be in a subrequest, - * so we need to propagate the fact that we responded. - */ - for (rr = r; rr != NULL; rr = rr->main) { - rr->expecting_100 = 0; + status_line = r->status_line; + if (status_line == NULL) { + status_line = ap_get_status_line_ex(r->pool, r->status); } - - status_line = apr_pstrcat(r->pool, AP_SERVER_PROTOCOL, " ", r->status_line, CRLF, NULL); - ap_xlate_proto_to_ascii(status_line, strlen(status_line)); + response_line = apr_pstrcat(r->pool, + AP_SERVER_PROTOCOL " ", status_line, CRLF, + NULL); + ap_xlate_proto_to_ascii(response_line, strlen(response_line)); x.f = r->connection->output_filters; x.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); - ap_fputs(x.f, x.bb, status_line); + ap_fputs(x.f, x.bb, response_line); if (send_headers) { apr_table_do(send_header, &x, r->headers_out, NULL); apr_table_clear(r->headers_out); @@ -2239,7 +2388,7 @@ static int protocol_cmp(const apr_array_header_t *preferences, return (index2 >= 0) ? -1 : 1; } } - /* both have the same index (mabye -1 or no pref configured) and we compare + /* both have the same index (maybe -1 or no pref configured) and we compare * the names so that spdy3 gets precedence over spdy2. That makes * the outcome at least deterministic. */ return strcmp(proto1, proto2); diff --git a/server/provider.c b/server/provider.c index cf307e7..f54fb5e 100644 --- a/server/provider.c +++ b/server/provider.c @@ -55,7 +55,6 @@ AP_DECLARE(apr_status_t) ap_register_provider(apr_pool_t *pool, provider_group_hash = apr_hash_make(pool); apr_hash_set(global_providers, provider_group, APR_HASH_KEY_STRING, provider_group_hash); - } provider_version_hash = apr_hash_get(provider_group_hash, provider_name, @@ -65,7 +64,6 @@ AP_DECLARE(apr_status_t) ap_register_provider(apr_pool_t *pool, provider_version_hash = apr_hash_make(pool); apr_hash_set(provider_group_hash, provider_name, APR_HASH_KEY_STRING, provider_version_hash); - } /* just set it. no biggy if it was there before. */ @@ -80,7 +78,6 @@ AP_DECLARE(apr_status_t) ap_register_provider(apr_pool_t *pool, provider_group_hash = apr_hash_make(pool); apr_hash_set(global_providers_names, provider_group, APR_HASH_KEY_STRING, provider_group_hash); - } provider_version_hash = apr_hash_get(provider_group_hash, provider_version, @@ -90,7 +87,6 @@ AP_DECLARE(apr_status_t) ap_register_provider(apr_pool_t *pool, provider_version_hash = apr_hash_make(pool); apr_hash_set(provider_group_hash, provider_version, APR_HASH_KEY_STRING, provider_version_hash); - } /* just set it. no biggy if it was there before. */ @@ -132,35 +128,40 @@ AP_DECLARE(apr_array_header_t *) ap_list_provider_names(apr_pool_t *pool, const char *provider_group, const char *provider_version) { - apr_array_header_t *ret = apr_array_make(pool, 10, sizeof(ap_list_provider_names_t)); + apr_array_header_t *ret = NULL; ap_list_provider_names_t *entry; apr_hash_t *provider_group_hash, *h; apr_hash_index_t *hi; char *val; if (global_providers_names == NULL) { - return ret; + goto out; } provider_group_hash = apr_hash_get(global_providers_names, provider_group, APR_HASH_KEY_STRING); if (provider_group_hash == NULL) { - return ret; + goto out; } - h = apr_hash_get(provider_group_hash, provider_version, - APR_HASH_KEY_STRING); + h = apr_hash_get(provider_group_hash, provider_version, APR_HASH_KEY_STRING); if (h == NULL) { - return ret; + goto out; } + ret = apr_array_make(pool, apr_hash_count(h), sizeof(ap_list_provider_names_t)); for (hi = apr_hash_first(pool, h); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, NULL, NULL, (void *)&val); entry = apr_array_push(ret); entry->provider_name = apr_pstrdup(pool, val); } + +out: + if (ret == NULL) { + ret = apr_array_make(pool, 1, sizeof(ap_list_provider_names_t)); + } return ret; } diff --git a/server/request.c b/server/request.c index dbe3e07..5599b2c 100644 --- a/server/request.c +++ b/server/request.c @@ -59,6 +59,7 @@ #define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX APR_HOOK_STRUCT( + APR_HOOK_LINK(pre_translate_name) APR_HOOK_LINK(translate_name) APR_HOOK_LINK(map_to_storage) APR_HOOK_LINK(check_user_id) @@ -74,6 +75,8 @@ APR_HOOK_STRUCT( APR_HOOK_LINK(force_authn) ) +AP_IMPLEMENT_HOOK_RUN_FIRST(int,pre_translate_name, + (request_rec *r), (r), DECLINED) AP_IMPLEMENT_HOOK_RUN_FIRST(int,translate_name, (request_rec *r), (r), DECLINED) AP_IMPLEMENT_HOOK_RUN_FIRST(int,map_to_storage, @@ -157,6 +160,25 @@ AP_DECLARE(int) ap_some_authn_required(request_rec *r) return rv; } +static int walk_location_and_if(request_rec *r) +{ + int access_status; + core_dir_config *d; + + if ((access_status = ap_location_walk(r))) { + return access_status; + } + if ((access_status = ap_if_walk(r))) { + return access_status; + } + + d = ap_get_core_module_config(r->per_dir_config); + if (d->log) + r->log = d->log; + + return OK; +} + /* This is the master logic for processing requests. Do NOT duplicate * this logic elsewhere, or the security model will be broken by future * API changes. Each phase must be individually optimized to pick up @@ -164,51 +186,95 @@ AP_DECLARE(int) ap_some_authn_required(request_rec *r) */ AP_DECLARE(int) ap_process_request_internal(request_rec *r) { + int access_status = DECLINED; int file_req = (r->main && r->filename); - int access_status; - core_dir_config *d; + core_server_config *sconf = + ap_get_core_module_config(r->server->module_config); + unsigned int normalize_flags; + + normalize_flags = AP_NORMALIZE_NOT_ABOVE_ROOT; + if (sconf->merge_slashes != AP_CORE_CONFIG_OFF) { + normalize_flags |= AP_NORMALIZE_MERGE_SLASHES; + } + if (file_req) { + /* File subrequests can have a relative path. */ + normalize_flags |= AP_NORMALIZE_ALLOW_RELATIVE; + } - /* Ignore embedded %2F's in path for proxy requests */ - if (!r->proxyreq && r->parsed_uri.path) { - d = ap_get_core_module_config(r->per_dir_config); - if (d->allow_encoded_slashes) { - access_status = ap_unescape_url_keep2f(r->parsed_uri.path, d->decode_encoded_slashes); + if (r->parsed_uri.path) { + /* Normalize: remove /./ and shrink /../ segments, plus + * decode unreserved chars (first time only to avoid + * double decoding after ap_unescape_url() below). + */ + if (!ap_normalize_path(r->parsed_uri.path, + normalize_flags | + AP_NORMALIZE_DECODE_UNRESERVED)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10244) + "invalid URI path (%s)", r->unparsed_uri); + return HTTP_BAD_REQUEST; } - else { - access_status = ap_unescape_url(r->parsed_uri.path); + } + + /* All file subrequests are a huge pain... they cannot bubble through the + * next several steps. Only file subrequests are allowed an empty uri, + * otherwise let (pre_)translate_name kill the request. + */ + if (!file_req) { + ap_conf_vector_t *per_dir_config = r->per_dir_config; + + if ((access_status = walk_location_and_if(r))) { + return access_status; + } + + /* Let pre_translate_name hooks work with non-decoded URIs, and + * eventually prevent further URI transformations (return DONE). + */ + access_status = ap_run_pre_translate_name(r); + if (ap_is_HTTP_ERROR(access_status)) { + return access_status; + } + + /* Throw away pre_trans only merging */ + r->per_dir_config = per_dir_config; + } + + /* Ignore URL unescaping for translated URIs already */ + if (access_status != DONE && r->parsed_uri.path) { + core_dir_config *d = ap_get_core_module_config(r->per_dir_config); + /* Unreserved chars were already decoded by ap_normalize_path() */ + unsigned int unescape_flags = AP_UNESCAPE_URL_KEEP_UNRESERVED; + if (!d->allow_encoded_slashes) { + unescape_flags |= AP_UNESCAPE_URL_FORBID_SLASHES; + } + else if (!d->decode_encoded_slashes) { + unescape_flags |= AP_UNESCAPE_URL_KEEP_SLASHES; } + access_status = ap_unescape_url_ex(r->parsed_uri.path, unescape_flags); if (access_status) { if (access_status == HTTP_NOT_FOUND) { if (! d->allow_encoded_slashes) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00026) - "found %%2f (encoded '/') in URI " - "(decoded='%s'), returning 404", - r->parsed_uri.path); + "found %%2f (encoded '/') in URI path (%s), " + "returning 404", r->unparsed_uri); } } return access_status; } - } - ap_getparents(r->uri); /* OK --- shrinking transformations... */ + if (d->allow_encoded_slashes && d->decode_encoded_slashes) { + /* Decoding slashes might have created new // or /./ or /../ + * segments (e.g. "/.%2F/"), so re-normalize. + */ + ap_normalize_path(r->parsed_uri.path, normalize_flags); + } + } - /* All file subrequests are a huge pain... they cannot bubble through the - * next several steps. Only file subrequests are allowed an empty uri, - * otherwise let translate_name kill the request. - */ + /* Same, translate_name is not suited for file subrequests */ if (!file_req) { - if ((access_status = ap_location_walk(r))) { - return access_status; - } - if ((access_status = ap_if_walk(r))) { + if ((access_status = walk_location_and_if(r))) { return access_status; } - d = ap_get_core_module_config(r->per_dir_config); - if (d->log) { - r->log = d->log; - } - if ((access_status = ap_run_translate_name(r))) { return decl_die(access_status, "translate", r); } @@ -225,17 +291,9 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) /* Rerun the location walk, which overrides any map_to_storage config. */ - if ((access_status = ap_location_walk(r))) { + if ((access_status = walk_location_and_if(r))) { return access_status; } - if ((access_status = ap_if_walk(r))) { - return access_status; - } - - d = ap_get_core_module_config(r->per_dir_config); - if (d->log) { - r->log = d->log; - } if ((access_status = ap_run_post_perdir_config(r))) { return access_status; @@ -1256,6 +1314,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) if (entry_core->refs && entry_core->refs->nelts) { if (!rxpool) { apr_pool_create(&rxpool, r->pool); + apr_pool_tag(rxpool, "directory_walk_rxpool"); } nmatch = entry_core->refs->nelts; pmatch = apr_palloc(rxpool, nmatch*sizeof(ap_regmatch_t)); @@ -1361,7 +1420,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) r->canonical_filename = r->filename; if (r->finfo.filetype == APR_DIR) { - cache->cached = r->filename; + cache->cached = apr_pstrdup(r->pool, r->filename); } else { cache->cached = ap_make_dirstr_parent(r->pool, r->filename); @@ -1412,12 +1471,12 @@ AP_DECLARE(int) ap_location_walk(request_rec *r) cache = prep_walk_cache(AP_NOTE_LOCATION_WALK, r); cached = (cache->cached != NULL); - /* Location and LocationMatch differ on their behaviour w.r.t. multiple - * slashes. Location matches multiple slashes with a single slash, - * LocationMatch doesn't. An exception, for backwards brokenness is - * absoluteURIs... in which case neither match multiple slashes. - */ - if (r->uri[0] != '/') { + /* + * When merge_slashes is set to AP_CORE_CONFIG_OFF the slashes in r->uri + * have not been merged. But for Location walks we always go with merged + * slashes no matter what merge_slashes is set to. + */ + if (sconf->merge_slashes != AP_CORE_CONFIG_OFF) { entry_uri = r->uri; } else { @@ -1458,7 +1517,7 @@ AP_DECLARE(int) ap_location_walk(request_rec *r) apr_pool_t *rxpool = NULL; cached &= auth_internal_per_conf; - cache->cached = entry_uri; + cache->cached = apr_pstrdup(r->pool, entry_uri); /* Go through the location entries, and check for matches. * We apply the directive sections in given order, we should @@ -1486,6 +1545,7 @@ AP_DECLARE(int) ap_location_walk(request_rec *r) if (entry_core->refs && entry_core->refs->nelts) { if (!rxpool) { apr_pool_create(&rxpool, r->pool); + apr_pool_tag(rxpool, "location_walk_rxpool"); } nmatch = entry_core->refs->nelts; pmatch = apr_palloc(rxpool, nmatch*sizeof(ap_regmatch_t)); @@ -1688,6 +1748,7 @@ AP_DECLARE(int) ap_file_walk(request_rec *r) if (entry_core->refs && entry_core->refs->nelts) { if (!rxpool) { apr_pool_create(&rxpool, r->pool); + apr_pool_tag(rxpool, "file_walk_rxpool"); } nmatch = entry_core->refs->nelts; pmatch = apr_palloc(rxpool, nmatch*sizeof(ap_regmatch_t)); diff --git a/server/scoreboard.c b/server/scoreboard.c index 23e3d70..49d1600 100644 --- a/server/scoreboard.c +++ b/server/scoreboard.c @@ -364,6 +364,18 @@ AP_DECLARE(int) ap_exists_scoreboard_image(void) return (ap_scoreboard_image ? 1 : 0); } +AP_DECLARE(void) ap_set_conn_count(ap_sb_handle_t *sb, request_rec *r, + unsigned short conn_count) +{ + worker_score *ws; + + if (!sb) + return; + + ws = &ap_scoreboard_image->servers[sb->child_num][sb->thread_num]; + ws->conn_count = conn_count; +} + AP_DECLARE(void) ap_increment_counts(ap_sb_handle_t *sb, request_rec *r) { worker_score *ws; @@ -376,7 +388,7 @@ AP_DECLARE(void) ap_increment_counts(ap_sb_handle_t *sb, request_rec *r) if (pfn_ap_logio_get_last_bytes != NULL) { bytes = pfn_ap_logio_get_last_bytes(r->connection); } - else if (r->method_number == M_GET && r->method[0] == 'H') { + else if (r->method_number == M_GET && r->method && r->method[0] == 'H') { bytes = 0; } else { @@ -641,7 +653,7 @@ AP_DECLARE(void) ap_time_process_request(ap_sb_handle_t *sbh, int status) } } -AP_DECLARE(int) ap_update_global_status() +AP_DECLARE(int) ap_update_global_status(void) { #ifdef HAVE_TIMES if (ap_scoreboard_image == NULL) { @@ -695,7 +707,7 @@ AP_DECLARE(process_score *) ap_get_scoreboard_process(int x) return &ap_scoreboard_image->parent[x]; } -AP_DECLARE(global_score *) ap_get_scoreboard_global() +AP_DECLARE(global_score *) ap_get_scoreboard_global(void) { return ap_scoreboard_image->global; } diff --git a/server/ssl.c b/server/ssl.c new file mode 100644 index 0000000..edf958b --- /dev/null +++ b/server/ssl.c @@ -0,0 +1,285 @@ +/* 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. + */ + +/* + * ssl.c --- routines for SSL/TLS server infrastructure. + * + */ + +#include "apr.h" +#include "apr_strings.h" +#include "apr_buckets.h" +#include "apr_lib.h" +#include "apr_signal.h" +#include "apr_strmatch.h" + +#define APR_WANT_STDIO /* for sscanf */ +#define APR_WANT_STRFUNC +#define APR_WANT_MEMFUNC +#include "apr_want.h" + +#include "util_filter.h" +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" +#include "http_connection.h" +#include "http_protocol.h" +#include "http_request.h" +#include "http_main.h" +#include "http_ssl.h" +#include "http_log.h" /* For errors detected in basic auth common + * support code... */ +#include "mod_core.h" + + +#if APR_HAVE_STDARG_H +#include <stdarg.h> +#endif +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +APR_HOOK_STRUCT( + APR_HOOK_LINK(ssl_conn_is_ssl) + APR_HOOK_LINK(ssl_var_lookup) + APR_HOOK_LINK(ssl_add_cert_files) + APR_HOOK_LINK(ssl_add_fallback_cert_files) + APR_HOOK_LINK(ssl_answer_challenge) + APR_HOOK_LINK(ssl_ocsp_prime_hook) + APR_HOOK_LINK(ssl_ocsp_get_resp_hook) + APR_HOOK_LINK(ssl_bind_outgoing) +) + +APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *)); +static APR_OPTIONAL_FN_TYPE(ssl_is_https) *module_ssl_is_https; +APR_DECLARE_OPTIONAL_FN(int, ssl_proxy_enable, (conn_rec *)); +static APR_OPTIONAL_FN_TYPE(ssl_proxy_enable) *module_ssl_proxy_enable; +APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec *)); +static APR_OPTIONAL_FN_TYPE(ssl_engine_disable) *module_ssl_engine_disable; +APR_DECLARE_OPTIONAL_FN(int, ssl_engine_set, (conn_rec *, + ap_conf_vector_t *, + int proxy, int enable)); +static APR_OPTIONAL_FN_TYPE(ssl_engine_set) *module_ssl_engine_set; + + +static int ssl_is_https(conn_rec *c) +{ + /* Someone retrieved the optional function., not knowing about the + * new API. We redirect them to what they should have invoked. */ + return ap_ssl_conn_is_ssl(c); +} + +AP_DECLARE(int) ap_ssl_conn_is_ssl(conn_rec *c) +{ + int r = (ap_run_ssl_conn_is_ssl(c) == OK); + if (r == 0 && module_ssl_is_https) { + r = module_ssl_is_https(c); + } + return r; +} + +static int ssl_engine_set(conn_rec *c, + ap_conf_vector_t *per_dir_config, + int proxy, int enable) +{ + if (proxy) { + return ap_ssl_bind_outgoing(c, per_dir_config, enable) == OK; + } + else if (module_ssl_engine_set) { + return module_ssl_engine_set(c, per_dir_config, 0, enable); + } + else if (enable && module_ssl_proxy_enable) { + return module_ssl_proxy_enable(c); + } + else if (!enable && module_ssl_engine_disable) { + return module_ssl_engine_disable(c); + } + return 0; +} + +static int ssl_proxy_enable(conn_rec *c) +{ + return ap_ssl_bind_outgoing(c, NULL, 1); +} + +static int ssl_engine_disable(conn_rec *c) +{ + return ap_ssl_bind_outgoing(c, NULL, 0); +} + +AP_DECLARE(int) ap_ssl_bind_outgoing(conn_rec *c, struct ap_conf_vector_t *dir_conf, + int enable_ssl) +{ + int rv, enabled = 0; + + c->outgoing = 1; + rv = ap_run_ssl_bind_outgoing(c, dir_conf, enable_ssl); + enabled = (rv == OK); + if (enable_ssl && !enabled) { + /* the hooks did not take over. Is there an old skool optional that will? */ + if (module_ssl_engine_set) { + enabled = module_ssl_engine_set(c, dir_conf, 1, 1); + } + else if (module_ssl_proxy_enable) { + enabled = module_ssl_proxy_enable(c); + } + } + else { + /* !enable_ssl || enabled + * any existing optional funcs need to not enable here */ + if (module_ssl_engine_set) { + module_ssl_engine_set(c, dir_conf, 1, 0); + } + else if (module_ssl_engine_disable) { + module_ssl_engine_disable(c); + } + } + if (enable_ssl && !enabled) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, + c, APLOGNO(01961) " failed to enable ssl support " + "[Hint: if using mod_ssl, see SSLProxyEngine]"); + return DECLINED; + } + return OK; +} + +AP_DECLARE(int) ap_ssl_has_outgoing_handlers(void) +{ + apr_array_header_t *hooks = ap_hook_get_ssl_bind_outgoing(); + return (hooks && hooks->nelts > 0) + || module_ssl_engine_set || module_ssl_proxy_enable; +} + +APR_DECLARE_OPTIONAL_FN(const char *, ssl_var_lookup, + (apr_pool_t *p, server_rec *s, + conn_rec *c, request_rec *r, + const char *name)); +static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *module_ssl_var_lookup; + +static const char *ssl_var_lookup(apr_pool_t *p, server_rec *s, + conn_rec *c, request_rec *r, + const char *name) +{ + /* Someone retrieved the optional function., not knowing about the + * new API. We redirect them to what they should have invoked. */ + return ap_ssl_var_lookup(p, s, c, r, name); +} + +AP_DECLARE(const char *) ap_ssl_var_lookup(apr_pool_t *p, server_rec *s, + conn_rec *c, request_rec *r, + const char *name) +{ + const char *val = ap_run_ssl_var_lookup(p, s, c, r, name); + if (val == NULL && module_ssl_var_lookup) { + val = module_ssl_var_lookup(p, s, c, r, name); + } + return val; +} + +AP_DECLARE(void) ap_setup_ssl_optional_fns(apr_pool_t *pool) +{ + /* Run as core's very early 'post config' hook, check for any already + * installed optional functions related to SSL and save them. Install + * our own instances that invoke the new hooks. */ + APR_OPTIONAL_FN_TYPE(ssl_is_https) *fn_is_https; + APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *fn_ssl_var_lookup; + + fn_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); + module_ssl_is_https = (fn_is_https + && fn_is_https != ssl_is_https)? fn_is_https : NULL; + APR_REGISTER_OPTIONAL_FN(ssl_is_https); + + fn_ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); + module_ssl_var_lookup = (fn_ssl_var_lookup + && fn_ssl_var_lookup != ssl_var_lookup)? fn_ssl_var_lookup : NULL; + APR_REGISTER_OPTIONAL_FN(ssl_var_lookup); + + module_ssl_proxy_enable = APR_RETRIEVE_OPTIONAL_FN(ssl_proxy_enable); + APR_REGISTER_OPTIONAL_FN(ssl_proxy_enable); + module_ssl_engine_disable = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_disable); + APR_REGISTER_OPTIONAL_FN(ssl_engine_disable); + module_ssl_engine_set = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_set); + APR_REGISTER_OPTIONAL_FN(ssl_engine_set); +} + +AP_DECLARE(apr_status_t) ap_ssl_add_cert_files(server_rec *s, apr_pool_t *p, + apr_array_header_t *cert_files, + apr_array_header_t *key_files) +{ + int rv = ap_run_ssl_add_cert_files(s, p, cert_files, key_files); + return (rv == OK || rv == DECLINED)? APR_SUCCESS : APR_EGENERAL; +} + +AP_DECLARE(apr_status_t) ap_ssl_add_fallback_cert_files(server_rec *s, apr_pool_t *p, + apr_array_header_t *cert_files, + apr_array_header_t *key_files) +{ + int rv = ap_run_ssl_add_fallback_cert_files(s, p, cert_files, key_files); + return (rv == OK || rv == DECLINED)? APR_SUCCESS : APR_EGENERAL; +} + +AP_DECLARE(int) ap_ssl_answer_challenge(conn_rec *c, const char *server_name, + const char **pcert_pem, const char **pkey_pem) +{ + return (ap_run_ssl_answer_challenge(c, server_name, pcert_pem, pkey_pem) == OK); +} + +AP_DECLARE(apr_status_t) ap_ssl_ocsp_prime(server_rec *s, apr_pool_t *p, + const char *id, apr_size_t id_len, + const char *pem) +{ + int rv = ap_run_ssl_ocsp_prime_hook(s, p, id, id_len, pem); + return rv == OK? APR_SUCCESS : (rv == DECLINED? APR_ENOENT : APR_EGENERAL); +} + +AP_DECLARE(apr_status_t) ap_ssl_ocsp_get_resp(server_rec *s, conn_rec *c, + const char *id, apr_size_t id_len, + ap_ssl_ocsp_copy_resp *cb, void *userdata) +{ + int rv = ap_run_ssl_ocsp_get_resp_hook(s, c, id, id_len, cb, userdata); + return rv == OK? APR_SUCCESS : (rv == DECLINED? APR_ENOENT : APR_EGENERAL); +} + +AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_conn_is_ssl, + (conn_rec *c), (c), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(const char *,ssl_var_lookup, + (apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, const char *name), + (p, s, c, r, name), NULL) +AP_IMPLEMENT_HOOK_RUN_ALL(int, ssl_add_cert_files, + (server_rec *s, apr_pool_t *p, + apr_array_header_t *cert_files, apr_array_header_t *key_files), + (s, p, cert_files, key_files), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_ALL(int, ssl_add_fallback_cert_files, + (server_rec *s, apr_pool_t *p, + apr_array_header_t *cert_files, apr_array_header_t *key_files), + (s, p, cert_files, key_files), OK, DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_answer_challenge, + (conn_rec *c, const char *server_name, const char **pcert_pem, const char **pkey_pem), + (c, server_name, pcert_pem, pkey_pem), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_ocsp_prime_hook, + (server_rec *s, apr_pool_t *p, const char *id, apr_size_t id_len, const char *pem), + (s, p, id, id_len, pem), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int, ssl_ocsp_get_resp_hook, + (server_rec *s, conn_rec *c, const char *id, apr_size_t id_len, + ap_ssl_ocsp_copy_resp *cb, void *userdata), + (s, c, id, id_len, cb, userdata), DECLINED) +AP_IMPLEMENT_HOOK_RUN_FIRST(int,ssl_bind_outgoing,(conn_rec *c, ap_conf_vector_t *dir_conf, int require_ssl), + (c, dir_conf, require_ssl), DECLINED) diff --git a/server/util.c b/server/util.c index fd7a0a1..45502b8 100644 --- a/server/util.c +++ b/server/util.c @@ -47,6 +47,7 @@ #include "ap_config.h" #include "apr_base64.h" +#include "apr_fnmatch.h" #include "httpd.h" #include "http_main.h" #include "http_log.h" @@ -74,15 +75,8 @@ */ #include "test_char.h" -/* we assume the folks using this ensure 0 <= c < 256... which means - * you need a cast to (unsigned char) first, you can't just plug a - * char in here and get it to work, because if char is signed then it - * will first be sign extended. - */ -#define TEST_CHAR(c, f) (test_char_table[(unsigned char)(c)] & (f)) - /* Win32/NetWare/OS2 need to check for both forward and back slashes - * in ap_getparents() and ap_escape_url. + * in ap_normalize_path() and ap_escape_url(). */ #ifdef CASE_BLIND_FILESYSTEM #define IS_SLASH(s) ((s == '/') || (s == '\\')) @@ -96,6 +90,11 @@ #undef APLOG_MODULE_INDEX #define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX +/* maximum nesting level for config directories */ +#ifndef AP_MAX_FNMATCH_DIR_DEPTH +#define AP_MAX_FNMATCH_DIR_DEPTH (128) +#endif + /* * Examine a field value (such as a media-/content-type) string and return * it sans any parameters; e.g., strip off any ';charset=foo' and the like. @@ -186,11 +185,9 @@ AP_DECLARE(char *) ap_ht_time(apr_pool_t *p, apr_time_t t, const char *fmt, */ AP_DECLARE(int) ap_strcmp_match(const char *str, const char *expected) { - int x, y; + apr_size_t x, y; for (x = 0, y = 0; expected[y]; ++y, ++x) { - if ((!str[x]) && (expected[y] != '*')) - return -1; if (expected[y] == '*') { while (expected[++y] == '*'); if (!expected[y]) @@ -202,6 +199,8 @@ AP_DECLARE(int) ap_strcmp_match(const char *str, const char *expected) } return -1; } + else if (!str[x]) + return -1; else if ((expected[y] != '?') && (str[x] != expected[y])) return 1; } @@ -210,7 +209,7 @@ AP_DECLARE(int) ap_strcmp_match(const char *str, const char *expected) AP_DECLARE(int) ap_strcasecmp_match(const char *str, const char *expected) { - int x, y; + apr_size_t x, y; for (x = 0, y = 0; expected[y]; ++y, ++x) { if (!str[x] && expected[y] != '*') @@ -251,10 +250,8 @@ AP_DECLARE(int) ap_os_is_path_absolute(apr_pool_t *p, const char *dir) AP_DECLARE(int) ap_is_matchexp(const char *str) { - int x; - - for (x = 0; str[x]; x++) - if ((str[x] == '*') || (str[x] == '?')) + for (; *str; str++) + if ((*str == '*') || (*str == '?')) return 1; return 0; } @@ -492,85 +489,149 @@ AP_DECLARE(apr_status_t) ap_pregsub_ex(apr_pool_t *p, char **result, return rc; } +/* Forward declare */ +static char x2c(const char *what); + +#define IS_SLASH_OR_NUL(s) (s == '\0' || IS_SLASH(s)) + /* - * Parse .. so we don't compromise security + * Inspired by mod_jk's jk_servlet_normalize(). */ -AP_DECLARE(void) ap_getparents(char *name) +AP_DECLARE(int) ap_normalize_path(char *path, unsigned int flags) { - char *next; - int l, w, first_dot; + int ret = 1; + apr_size_t l = 1, w = 1, n; + int decode_unreserved = (flags & AP_NORMALIZE_DECODE_UNRESERVED) != 0; + int merge_slashes = (flags & AP_NORMALIZE_MERGE_SLASHES) != 0; - /* Four paseses, as per RFC 1808 */ - /* a) remove ./ path segments */ - for (next = name; *next && (*next != '.'); next++) { - } + if (!IS_SLASH(path[0])) { + /* Besides "OPTIONS *", a request-target should start with '/' + * per RFC 7230 section 5.3, so anything else is invalid. + */ + if (path[0] == '*' && path[1] == '\0') { + return 1; + } + /* However, AP_NORMALIZE_ALLOW_RELATIVE can be used to bypass + * this restriction (e.g. for subrequest file lookups). + */ + if (!(flags & AP_NORMALIZE_ALLOW_RELATIVE) || path[0] == '\0') { + return 0; + } - l = w = first_dot = next - name; - while (name[l] != '\0') { - if (name[l] == '.' && IS_SLASH(name[l + 1]) - && (l == 0 || IS_SLASH(name[l - 1]))) - l += 2; - else - name[w++] = name[l++]; + l = w = 0; } - /* b) remove trailing . path, segment */ - if (w == 1 && name[0] == '.') - w--; - else if (w > 1 && name[w - 1] == '.' && IS_SLASH(name[w - 2])) - w--; - name[w] = '\0'; + while (path[l] != '\0') { + /* RFC-3986 section 2.3: + * For consistency, percent-encoded octets in the ranges of + * ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D), + * period (%2E), underscore (%5F), or tilde (%7E) should [...] + * be decoded to their corresponding unreserved characters by + * URI normalizers. + */ + if (decode_unreserved && path[l] == '%') { + if (apr_isxdigit(path[l + 1]) && apr_isxdigit(path[l + 2])) { + const char c = x2c(&path[l + 1]); + if (TEST_CHAR(c, T_URI_UNRESERVED)) { + /* Replace last char and fall through as the current + * read position */ + l += 2; + path[l] = c; + } + } + else { + /* Invalid encoding */ + ret = 0; + } + } - /* c) remove all xx/../ segments. (including leading ../ and /../) */ - l = first_dot; + if (w == 0 || IS_SLASH(path[w - 1])) { + /* Collapse ///// sequences to / */ + if (merge_slashes && IS_SLASH(path[l])) { + do { + l++; + } while (IS_SLASH(path[l])); + continue; + } - while (name[l] != '\0') { - if (name[l] == '.' && name[l + 1] == '.' && IS_SLASH(name[l + 2]) - && (l == 0 || IS_SLASH(name[l - 1]))) { - int m = l + 3, n; + if (path[l] == '.') { + /* Remove /./ segments */ + if (IS_SLASH_OR_NUL(path[l + 1])) { + l++; + if (path[l]) { + l++; + } + continue; + } - l = l - 2; - if (l >= 0) { - while (l >= 0 && !IS_SLASH(name[l])) - l--; - l++; + /* Remove /xx/../ segments (or /xx/.%2e/ when + * AP_NORMALIZE_DECODE_UNRESERVED is set since we + * decoded only the first dot above). + */ + n = l + 1; + if ((path[n] == '.' || (decode_unreserved + && path[n] == '%' + && path[++n] == '2' + && (path[++n] == 'e' + || path[n] == 'E'))) + && IS_SLASH_OR_NUL(path[n + 1])) { + /* Wind w back to remove the previous segment */ + if (w > 1) { + do { + w--; + } while (w && !IS_SLASH(path[w - 1])); + } + else { + /* Already at root, ignore and return a failure + * if asked to. + */ + if (flags & AP_NORMALIZE_NOT_ABOVE_ROOT) { + ret = 0; + } + } + + /* Move l forward to the next segment */ + l = n + 1; + if (path[l]) { + l++; + } + continue; + } } - else - l = 0; - n = l; - while ((name[n] = name[m])) - (++n, ++m); } - else - ++l; + + path[w++] = path[l++]; } + path[w] = '\0'; - /* d) remove trailing xx/.. segment. */ - if (l == 2 && name[0] == '.' && name[1] == '.') + return ret; +} + +/* + * Parse .. so we don't compromise security + */ +AP_DECLARE(void) ap_getparents(char *name) +{ + if (!ap_normalize_path(name, AP_NORMALIZE_NOT_ABOVE_ROOT | + AP_NORMALIZE_ALLOW_RELATIVE)) { name[0] = '\0'; - else if (l > 2 && name[l - 1] == '.' && name[l - 2] == '.' - && IS_SLASH(name[l - 3])) { - l = l - 4; - if (l >= 0) { - while (l >= 0 && !IS_SLASH(name[l])) - l--; - l++; - } - else - l = 0; - name[l] = '\0'; } } -AP_DECLARE(void) ap_no2slash(char *name) +AP_DECLARE(void) ap_no2slash_ex(char *name, int is_fs_path) { + char *d, *s; + if (!*name) { + return; + } + s = d = name; #ifdef HAVE_UNC_PATHS /* Check for UNC names. Leave leading two slashes. */ - if (s[0] == '/' && s[1] == '/') + if (is_fs_path && s[0] == '/' && s[1] == '/') *d++ = *s++; #endif @@ -587,6 +648,10 @@ AP_DECLARE(void) ap_no2slash(char *name) *d = '\0'; } +AP_DECLARE(void) ap_no2slash(char *name) +{ + ap_no2slash_ex(name, 1); +} /* * copy at most n leading directories of s into d @@ -934,7 +999,7 @@ AP_DECLARE(apr_status_t) ap_pcfg_openfile(ap_configfile_t **ret_cfg, if (finfo.filetype != APR_REG && #if defined(WIN32) || defined(OS2) || defined(NETWARE) - strcasecmp(apr_filepath_name_get(name), "nul") != 0) { + ap_cstr_casecmp(apr_filepath_name_get(name), "nul") != 0) { #else strcmp(name, "/dev/null") != 0) { #endif /* WIN32 || OS2 */ @@ -1691,7 +1756,7 @@ AP_DECLARE(int) ap_find_token(apr_pool_t *p, const char *line, const char *tok) while (*s && !TEST_CHAR(*s, T_HTTP_TOKEN_STOP)) { ++s; } - if (!strncasecmp((const char *)start_token, (const char *)tok, + if (!ap_cstr_casecmpn((const char *)start_token, (const char *)tok, s - start_token)) { return 1; } @@ -1701,14 +1766,13 @@ AP_DECLARE(int) ap_find_token(apr_pool_t *p, const char *line, const char *tok) } } - -AP_DECLARE(int) ap_find_last_token(apr_pool_t *p, const char *line, - const char *tok) +static const char *find_last_token(apr_pool_t *p, const char *line, + const char *tok) { int llen, tlen, lidx; if (!line) - return 0; + return NULL; llen = strlen(line); tlen = strlen(tok); @@ -1716,9 +1780,44 @@ AP_DECLARE(int) ap_find_last_token(apr_pool_t *p, const char *line, if (lidx < 0 || (lidx > 0 && !(apr_isspace(line[lidx - 1]) || line[lidx - 1] == ','))) + return NULL; + + if (ap_cstr_casecmpn(&line[lidx], tok, tlen) == 0) { + return &line[lidx]; + } + return NULL; +} + +AP_DECLARE(int) ap_find_last_token(apr_pool_t *p, const char *line, + const char *tok) +{ + return find_last_token(p, line, tok) != NULL; +} + +AP_DECLARE(int) ap_is_chunked(apr_pool_t *p, const char *line) +{ + const char *s; + + if (!line) return 0; + if (!ap_cstr_casecmp(line, "chunked")) { + return 1; + } + + s = find_last_token(p, line, "chunked"); - return (strncasecmp(&line[lidx], tok, tlen) == 0); + if (!s) return 0; + + /* eat spaces right-to-left to see what precedes "chunked" */ + while (--s > line) { + if (*s != ' ') break; + } + + /* found delim, or leading ws (input wasn't parsed by httpd as a header) */ + if (*s == ',' || *s == ' ') { + return 1; + } + return 0; } AP_DECLARE(char *) ap_escape_shell_cmd(apr_pool_t *p, const char *str) @@ -1786,8 +1885,12 @@ static char x2c(const char *what) * decoding %00 or a forbidden character returns HTTP_NOT_FOUND */ -static int unescape_url(char *url, const char *forbid, const char *reserved) +static int unescape_url(char *url, const char *forbid, const char *reserved, + unsigned int flags) { + const int keep_slashes = (flags & AP_UNESCAPE_URL_KEEP_SLASHES) != 0, + forbid_slashes = (flags & AP_UNESCAPE_URL_FORBID_SLASHES) != 0, + keep_unreserved = (flags & AP_UNESCAPE_URL_KEEP_UNRESERVED) != 0; int badesc, badpath; char *x, *y; @@ -1812,12 +1915,16 @@ static int unescape_url(char *url, const char *forbid, const char *reserved) char decoded; decoded = x2c(y + 1); if ((decoded == '\0') + || (forbid_slashes && IS_SLASH(decoded)) || (forbid && ap_strchr_c(forbid, decoded))) { badpath = 1; *x = decoded; y += 2; } - else if (reserved && ap_strchr_c(reserved, decoded)) { + else if ((keep_unreserved && TEST_CHAR(decoded, + T_URI_UNRESERVED)) + || (keep_slashes && IS_SLASH(decoded)) + || (reserved && ap_strchr_c(reserved, decoded))) { *x++ = *y++; *x++ = *y++; *x = *y; @@ -1843,19 +1950,24 @@ static int unescape_url(char *url, const char *forbid, const char *reserved) AP_DECLARE(int) ap_unescape_url(char *url) { /* Traditional */ - return unescape_url(url, SLASHES, NULL); + return unescape_url(url, SLASHES, NULL, 0); } AP_DECLARE(int) ap_unescape_url_keep2f(char *url, int decode_slashes) { /* AllowEncodedSlashes (corrected) */ if (decode_slashes) { /* no chars reserved */ - return unescape_url(url, NULL, NULL); + return unescape_url(url, NULL, NULL, 0); } else { /* reserve (do not decode) encoded slashes */ - return unescape_url(url, NULL, SLASHES); + return unescape_url(url, NULL, SLASHES, 0); } } +AP_DECLARE(int) ap_unescape_url_ex(char *url, unsigned int flags) +{ + return unescape_url(url, NULL, NULL, flags); +} + #ifdef NEW_APIS /* IFDEF these out until they've been thought through. * Just a germ of an API extension for now @@ -1865,7 +1977,7 @@ AP_DECLARE(int) ap_unescape_url_proxy(char *url) /* leave RFC1738 reserved characters intact, * so proxied URLs * don't get mangled. Where does that leave encoded '&' ? */ - return unescape_url(url, NULL, "/;?"); + return unescape_url(url, NULL, "/;?", 0); } AP_DECLARE(int) ap_unescape_url_reserved(char *url, const char *reserved) { @@ -1887,7 +1999,7 @@ AP_DECLARE(int) ap_unescape_urlencoded(char *query) } /* unescape everything else */ - return unescape_url(query, NULL, NULL); + return unescape_url(query, NULL, NULL, 0); } AP_DECLARE(char *) ap_construct_server(apr_pool_t *p, const char *hostname, @@ -1903,7 +2015,7 @@ AP_DECLARE(char *) ap_construct_server(apr_pool_t *p, const char *hostname, AP_DECLARE(int) ap_unescape_all(char *url) { - return unescape_url(url, NULL, NULL); + return unescape_url(url, NULL, NULL, 0); } /* c2x takes an unsigned, and expects the caller has guaranteed that @@ -2029,11 +2141,14 @@ AP_DECLARE(char *) ap_escape_urlencoded(apr_pool_t *p, const char *buffer) AP_DECLARE(char *) ap_escape_html2(apr_pool_t *p, const char *s, int toasc) { - int i, j; + apr_size_t i, j; char *x; /* first, count the number of extra characters */ - for (i = 0, j = 0; s[i] != '\0'; i++) + for (i = 0, j = 0; s[i] != '\0'; i++) { + if (i + j > APR_SIZE_MAX - 6) { + abort(); + } if (s[i] == '<' || s[i] == '>') j += 3; else if (s[i] == '&') @@ -2042,6 +2157,7 @@ AP_DECLARE(char *) ap_escape_html2(apr_pool_t *p, const char *s, int toasc) j += 5; else if (toasc && !apr_isascii(s[i])) j += 5; + } if (j == 0) return apr_pstrmemdup(p, s, i); @@ -2372,11 +2488,9 @@ char *ap_get_local_host(apr_pool_t *a) AP_DECLARE(char *) ap_pbase64decode(apr_pool_t *p, const char *bufcoded) { char *decoded; - int l; - decoded = (char *) apr_palloc(p, 1 + apr_base64_decode_len(bufcoded)); - l = apr_base64_decode(decoded, bufcoded); - decoded[l] = '\0'; /* make binary sequence into string */ + decoded = (char *) apr_palloc(p, apr_base64_decode_len(bufcoded)); + apr_base64_decode(decoded, bufcoded); return decoded; } @@ -2386,9 +2500,8 @@ AP_DECLARE(char *) ap_pbase64encode(apr_pool_t *p, char *string) char *encoded; int l = strlen(string); - encoded = (char *) apr_palloc(p, 1 + apr_base64_encode_len(l)); - l = apr_base64_encode(encoded, string, l); - encoded[l] = '\0'; /* make binary sequence into string */ + encoded = (char *) apr_palloc(p, apr_base64_encode_len(l)); + apr_base64_encode(encoded, string, l); return encoded; } @@ -2421,7 +2534,7 @@ AP_DECLARE(void) ap_content_type_tolower(char *str) */ AP_DECLARE(char *) ap_escape_quotes(apr_pool_t *p, const char *instring) { - int newlen = 0; + apr_size_t size, extra = 0; const char *inchr = instring; char *outchr, *outstring; @@ -2430,21 +2543,41 @@ AP_DECLARE(char *) ap_escape_quotes(apr_pool_t *p, const char *instring) * string up by an extra byte each time we find an unescaped ". */ while (*inchr != '\0') { - newlen++; if (*inchr == '"') { - newlen++; + extra++; } /* * If we find a slosh, and it's not the last byte in the string, * it's escaping something - advance past both bytes. */ - if ((*inchr == '\\') && (inchr[1] != '\0')) { + else if ((*inchr == '\\') && (inchr[1] != '\0')) { inchr++; - newlen++; } inchr++; } - outstring = apr_palloc(p, newlen + 1); + + if (!extra) { + return apr_pstrdup(p, instring); + } + + /* How large will the string become, once we escaped all the quotes? + * The tricky cases are + * - an `instring` that is already longer than `ptrdiff_t` + * can hold (which is an undefined case in C, as C defines ptrdiff_t as + * a signed difference between pointers into the same array and one index + * beyond). + * - an `instring` that, including the `extra` chars we want to add, becomes + * even larger than apr_size_t can handle. + * Since this function was not designed to ever return NULL for failure, we + * can only trigger a hard assertion failure. It seems more a programming + * mistake (or failure to verify the input causing this) that leads to this + * situation. + */ + ap_assert(inchr - instring > 0); + size = ((apr_size_t)(inchr - instring)) + 1; + ap_assert(size + extra > size); + + outstring = apr_palloc(p, size + extra); inchr = instring; outchr = outstring; /* @@ -2452,16 +2585,13 @@ AP_DECLARE(char *) ap_escape_quotes(apr_pool_t *p, const char *instring) * in front of every " that doesn't already have one. */ while (*inchr != '\0') { - if ((*inchr == '\\') && (inchr[1] != '\0')) { - *outchr++ = *inchr++; - *outchr++ = *inchr++; - } if (*inchr == '"') { *outchr++ = '\\'; } - if (*inchr != '\0') { + else if ((*inchr == '\\') && (inchr[1] != '\0')) { *outchr++ = *inchr++; } + *outchr++ = *inchr++; } *outchr = '\0'; return outstring; @@ -2499,6 +2629,7 @@ AP_DECLARE(char *) ap_append_pid(apr_pool_t *p, const char *string, * in timeout_parameter. * @return Status value indicating whether the parsing was successful or not. */ +#define CHECK_OVERFLOW(a, b) if (a > b) return APR_EGENERAL AP_DECLARE(apr_status_t) ap_timeout_parameter_parse( const char *timeout_parameter, apr_interval_time_t *timeout, @@ -2507,6 +2638,7 @@ AP_DECLARE(apr_status_t) ap_timeout_parameter_parse( char *endp; const char *time_str; apr_int64_t tout; + apr_uint64_t check; tout = apr_strtoi64(timeout_parameter, &endp, 10); if (errno) { @@ -2519,24 +2651,32 @@ AP_DECLARE(apr_status_t) ap_timeout_parameter_parse( time_str = endp; } + if (tout < 0) { + return APR_EGENERAL; + } + switch (*time_str) { /* Time is in seconds */ case 's': - *timeout = (apr_interval_time_t) apr_time_from_sec(tout); + CHECK_OVERFLOW(tout, apr_time_sec(APR_INT64_MAX)); + check = apr_time_from_sec(tout); break; - case 'h': /* Time is in hours */ - *timeout = (apr_interval_time_t) apr_time_from_sec(tout * 3600); + case 'h': + CHECK_OVERFLOW(tout, apr_time_sec(APR_INT64_MAX / 3600)); + check = apr_time_from_sec(tout * 3600); break; case 'm': switch (*(++time_str)) { /* Time is in milliseconds */ case 's': - *timeout = (apr_interval_time_t) tout * 1000; + CHECK_OVERFLOW(tout, apr_time_as_msec(APR_INT64_MAX)); + check = apr_time_from_msec(tout); break; /* Time is in minutes */ case 'i': - *timeout = (apr_interval_time_t) apr_time_from_sec(tout * 60); + CHECK_OVERFLOW(tout, apr_time_sec(APR_INT64_MAX / 60)); + check = apr_time_from_sec(tout * 60); break; default: return APR_EGENERAL; @@ -2545,8 +2685,20 @@ AP_DECLARE(apr_status_t) ap_timeout_parameter_parse( default: return APR_EGENERAL; } + + *timeout = (apr_interval_time_t)check; return APR_SUCCESS; } +#undef CHECK_OVERFLOW + +AP_DECLARE(int) ap_parse_strict_length(apr_off_t *len, const char *str) +{ + char *end; + + return (apr_isdigit(*str) + && apr_strtoff(len, str, &end, 10) == APR_SUCCESS + && *end == '\0'); +} /** * Determine if a request has a request body or not. @@ -2557,20 +2709,13 @@ AP_DECLARE(apr_status_t) ap_timeout_parameter_parse( AP_DECLARE(int) ap_request_has_body(request_rec *r) { apr_off_t cl; - char *estr; const char *cls; - int has_body; - has_body = (!r->header_only - && (r->kept_body - || apr_table_get(r->headers_in, "Transfer-Encoding") - || ( (cls = apr_table_get(r->headers_in, "Content-Length")) - && (apr_strtoff(&cl, cls, &estr, 10) == APR_SUCCESS) - && (!*estr) - && (cl > 0) ) - ) - ); - return has_body; + return (!r->header_only + && (r->kept_body + || apr_table_get(r->headers_in, "Transfer-Encoding") + || ((cls = apr_table_get(r->headers_in, "Content-Length")) + && ap_parse_strict_length(&cl, cls) && cl > 0))); } AP_DECLARE_NONSTD(apr_status_t) ap_pool_cleanup_set_null(void *data_) @@ -2669,7 +2814,7 @@ AP_DECLARE(int) ap_parse_form_data(request_rec *r, ap_filter_t *f, /* sanity check - we only support forms for now */ ct = apr_table_get(r->headers_in, "Content-Type"); - if (!ct || strncasecmp("application/x-www-form-urlencoded", ct, 33)) { + if (!ct || ap_cstr_casecmpn("application/x-www-form-urlencoded", ct, 33)) { return ap_discard_request_body(r); } @@ -2754,12 +2899,11 @@ AP_DECLARE(int) ap_parse_form_data(request_rec *r, ap_filter_t *f, case FORM_NAME: if (offset < HUGE_STRING_LEN) { if ('=' == c) { - buffer[offset] = 0; - offset = 0; pair = (ap_form_pair_t *) apr_array_push(pairs); - pair->name = apr_pstrdup(r->pool, buffer); + pair->name = apr_pstrmemdup(r->pool, buffer, offset); pair->value = apr_brigade_create(r->pool, r->connection->bucket_alloc); state = FORM_VALUE; + offset = 0; } else { buffer[offset++] = c; @@ -2820,7 +2964,7 @@ static apr_status_t varbuf_cleanup(void *info_) return APR_SUCCESS; } -const char nul = '\0'; +static const char nul = '\0'; static char * const varbuf_empty = (char *)&nul; AP_DECLARE(void) ap_varbuf_init(apr_pool_t *p, struct ap_varbuf *vb, @@ -2889,6 +3033,11 @@ AP_DECLARE(void) ap_varbuf_grow(struct ap_varbuf *vb, apr_size_t new_len) /* The required block is rather larger. Use allocator directly so that * the memory can be freed independently from the pool. */ allocator = apr_pool_allocator_get(vb->pool); + /* Happens if APR was compiled with APR_POOL_DEBUG */ + if (allocator == NULL) { + apr_allocator_create(&allocator); + ap_assert(allocator != NULL); + } if (new_len <= VARBUF_MAX_SIZE) new_node = apr_allocator_alloc(allocator, new_len + APR_ALIGN_DEFAULT(sizeof(*new_info))); @@ -2995,7 +3144,7 @@ AP_DECLARE(apr_status_t) ap_varbuf_regsub(struct ap_varbuf *vb, static const char * const oom_message = "[crit] Memory allocation failed, " "aborting process." APR_EOL_STR; -AP_DECLARE(void) ap_abort_on_oom() +AP_DECLARE(void) ap_abort_on_oom(void) { int written, count = strlen(oom_message); const char *buf = oom_message; @@ -3035,6 +3184,135 @@ AP_DECLARE(void *) ap_realloc(void *ptr, size_t size) return p; } +#if APR_HAS_THREADS + +#if APR_VERSION_AT_LEAST(1,8,0) && !defined(AP_NO_THREAD_LOCAL) + +#define ap_thread_current_create apr_thread_current_create + +#else /* APR_VERSION_AT_LEAST(1,8,0) && !defined(AP_NO_THREAD_LOCAL) */ + +#if AP_HAS_THREAD_LOCAL + +struct thread_ctx { + apr_thread_start_t func; + void *data; +}; + +static AP_THREAD_LOCAL apr_thread_t *current_thread = NULL; + +static void *APR_THREAD_FUNC thread_start(apr_thread_t *thread, void *data) +{ + struct thread_ctx *ctx = data; + + current_thread = thread; + return ctx->func(thread, ctx->data); +} + +AP_DECLARE(apr_status_t) ap_thread_create(apr_thread_t **thread, + apr_threadattr_t *attr, + apr_thread_start_t func, + void *data, apr_pool_t *pool) +{ + struct thread_ctx *ctx = apr_palloc(pool, sizeof(*ctx)); + + ctx->func = func; + ctx->data = data; + return apr_thread_create(thread, attr, thread_start, ctx, pool); +} + +#endif /* AP_HAS_THREAD_LOCAL */ + +AP_DECLARE(apr_status_t) ap_thread_current_create(apr_thread_t **current, + apr_threadattr_t *attr, + apr_pool_t *pool) +{ + apr_status_t rv; + apr_abortfunc_t abort_fn = apr_pool_abort_get(pool); + apr_allocator_t *allocator; + apr_os_thread_t osthd; + apr_pool_t *p; + + *current = ap_thread_current(); + if (*current) { + return APR_EEXIST; + } + + rv = apr_allocator_create(&allocator); + if (rv != APR_SUCCESS) { + if (abort_fn) + abort_fn(rv); + return rv; + } + rv = apr_pool_create_unmanaged_ex(&p, abort_fn, allocator); + if (rv != APR_SUCCESS) { + apr_allocator_destroy(allocator); + return rv; + } + apr_allocator_owner_set(allocator, p); + + osthd = apr_os_thread_current(); + rv = apr_os_thread_put(current, &osthd, p); + if (rv != APR_SUCCESS) { + apr_pool_destroy(p); + return rv; + } + +#if AP_HAS_THREAD_LOCAL + current_thread = *current; +#endif + return APR_SUCCESS; +} + +AP_DECLARE(void) ap_thread_current_after_fork(void) +{ +#if AP_HAS_THREAD_LOCAL + current_thread = NULL; +#endif +} + +AP_DECLARE(apr_thread_t *) ap_thread_current(void) +{ +#if AP_HAS_THREAD_LOCAL + return current_thread; +#else + return NULL; +#endif +} + +#endif /* APR_VERSION_AT_LEAST(1,8,0) && !defined(AP_NO_THREAD_LOCAL) */ + +static apr_status_t main_thread_cleanup(void *arg) +{ + apr_thread_t *thd = arg; + apr_pool_destroy(apr_thread_pool_get(thd)); + return APR_SUCCESS; +} + +AP_DECLARE(apr_status_t) ap_thread_main_create(apr_thread_t **thread, + apr_pool_t *pool) +{ + apr_status_t rv; + apr_threadattr_t *attr = NULL; + + /* Create an apr_thread_t for the main child thread to set up its Thread + * Local Storage. Since it's detached and won't apr_thread_exit(), destroy + * its pool before exiting via a cleanup of the given pool. + */ + if ((rv = apr_threadattr_create(&attr, pool)) + || (rv = apr_threadattr_detach_set(attr, 1)) + || (rv = ap_thread_current_create(thread, attr, pool))) { + *thread = NULL; + return rv; + } + + apr_pool_cleanup_register(pool, *thread, main_thread_cleanup, + apr_pool_cleanup_null); + return APR_SUCCESS; +} + +#endif /* APR_HAS_THREADS */ + AP_DECLARE(void) ap_get_sload(ap_sload_t *ld) { int i, j, server_limit, thread_limit; @@ -3307,3 +3585,206 @@ AP_DECLARE(int) ap_cstr_casecmpn(const char *s1, const char *s2, apr_size_t n) return 0; } +typedef struct { + const char *fname; +} fnames; + +static int fname_alphasort(const void *fn1, const void *fn2) +{ + const fnames *f1 = fn1; + const fnames *f2 = fn2; + + return strcmp(f1->fname, f2->fname); +} + +AP_DECLARE(ap_dir_match_t *)ap_dir_cfgmatch(cmd_parms *cmd, int flags, + const char *(*cb)(ap_dir_match_t *w, const char *fname), void *ctx) +{ + ap_dir_match_t *w = apr_palloc(cmd->temp_pool, sizeof(*w)); + + w->prefix = apr_pstrcat(cmd->pool, cmd->cmd->name, ": ", NULL); + w->p = cmd->pool; + w->ptemp = cmd->temp_pool; + w->flags = flags; + w->cb = cb; + w->ctx = ctx; + w->depth = 0; + + return w; +} + +AP_DECLARE(const char *)ap_dir_nofnmatch(ap_dir_match_t *w, const char *fname) +{ + const char *error; + apr_status_t rv; + + if ((w->flags & AP_DIR_FLAG_RECURSIVE) && ap_is_directory(w->ptemp, fname)) { + apr_dir_t *dirp; + apr_finfo_t dirent; + int current; + apr_array_header_t *candidates = NULL; + fnames *fnew; + char *path = apr_pstrdup(w->ptemp, fname); + + if (++w->depth > AP_MAX_FNMATCH_DIR_DEPTH) { + return apr_psprintf(w->p, "%sDirectory '%s' exceeds the maximum include " + "directory nesting level of %u. You have " + "probably a recursion somewhere.", w->prefix ? w->prefix : "", path, + AP_MAX_FNMATCH_DIR_DEPTH); + } + + /* + * first course of business is to grok all the directory + * entries here and store 'em away. Recall we need full pathnames + * for this. + */ + rv = apr_dir_open(&dirp, path, w->ptemp); + if (rv != APR_SUCCESS) { + return apr_psprintf(w->p, "%sCould not open directory %s: %pm", + w->prefix ? w->prefix : "", path, &rv); + } + + candidates = apr_array_make(w->ptemp, 1, sizeof(fnames)); + while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) { + /* strip out '.' and '..' */ + if (strcmp(dirent.name, ".") + && strcmp(dirent.name, "..")) { + fnew = (fnames *) apr_array_push(candidates); + fnew->fname = ap_make_full_path(w->ptemp, path, dirent.name); + } + } + + apr_dir_close(dirp); + if (candidates->nelts != 0) { + qsort((void *) candidates->elts, candidates->nelts, + sizeof(fnames), fname_alphasort); + + /* + * Now recurse these... we handle errors and subdirectories + * via the recursion, which is nice + */ + for (current = 0; current < candidates->nelts; ++current) { + fnew = &((fnames *) candidates->elts)[current]; + error = ap_dir_nofnmatch(w, fnew->fname); + if (error) { + return error; + } + } + } + + w->depth--; + + return NULL; + } + else if (w->flags & AP_DIR_FLAG_OPTIONAL) { + /* If the optional flag is set (like for IncludeOptional) we can + * tolerate that no file or directory is present and bail out. + */ + apr_finfo_t finfo; + if (apr_stat(&finfo, fname, APR_FINFO_TYPE, w->ptemp) != APR_SUCCESS + || finfo.filetype == APR_NOFILE) + return NULL; + } + + return w->cb(w, fname); +} + +AP_DECLARE(const char *)ap_dir_fnmatch(ap_dir_match_t *w, const char *path, + const char *fname) +{ + const char *rest; + apr_status_t rv; + apr_dir_t *dirp; + apr_finfo_t dirent; + apr_array_header_t *candidates = NULL; + fnames *fnew; + int current; + + /* find the first part of the filename */ + rest = ap_strchr_c(fname, '/'); + if (rest) { + fname = apr_pstrmemdup(w->ptemp, fname, rest - fname); + rest++; + } + + /* optimisation - if the filename isn't a wildcard, process it directly */ + if (!apr_fnmatch_test(fname)) { + path = path ? ap_make_full_path(w->ptemp, path, fname) : fname; + if (!rest) { + return ap_dir_nofnmatch(w, path); + } + else { + return ap_dir_fnmatch(w, path, rest); + } + } + + /* + * first course of business is to grok all the directory + * entries here and store 'em away. Recall we need full pathnames + * for this. + */ + rv = apr_dir_open(&dirp, path, w->ptemp); + if (rv != APR_SUCCESS) { + /* If the directory doesn't exist and the optional flag is set + * there is no need to return an error. + */ + if (rv == APR_ENOENT && (w->flags & AP_DIR_FLAG_OPTIONAL)) { + return NULL; + } + return apr_psprintf(w->p, "%sCould not open directory %s: %pm", + w->prefix ? w->prefix : "", path, &rv); + } + + candidates = apr_array_make(w->ptemp, 1, sizeof(fnames)); + while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp) == APR_SUCCESS) { + /* strip out '.' and '..' */ + if (strcmp(dirent.name, ".") + && strcmp(dirent.name, "..") + && (apr_fnmatch(fname, dirent.name, + APR_FNM_PERIOD) == APR_SUCCESS)) { + const char *full_path = ap_make_full_path(w->ptemp, path, dirent.name); + /* If matching internal to path, and we happen to match something + * other than a directory, skip it + */ + if (rest && (dirent.filetype != APR_DIR)) { + continue; + } + fnew = (fnames *) apr_array_push(candidates); + fnew->fname = full_path; + } + } + + apr_dir_close(dirp); + if (candidates->nelts != 0) { + const char *error; + + qsort((void *) candidates->elts, candidates->nelts, + sizeof(fnames), fname_alphasort); + + /* + * Now recurse these... we handle errors and subdirectories + * via the recursion, which is nice + */ + for (current = 0; current < candidates->nelts; ++current) { + fnew = &((fnames *) candidates->elts)[current]; + if (!rest) { + error = ap_dir_nofnmatch(w, fnew->fname); + } + else { + error = ap_dir_fnmatch(w, fnew->fname, rest); + } + if (error) { + return error; + } + } + } + else { + + if (!(w->flags & AP_DIR_FLAG_OPTIONAL)) { + return apr_psprintf(w->p, "%sNo matches for the wildcard '%s' in '%s', failing", + w->prefix ? w->prefix : "", fname, path); + } + } + + return NULL; +} diff --git a/server/util_expr_eval.c b/server/util_expr_eval.c index 0f92f41..db4be95 100644 --- a/server/util_expr_eval.c +++ b/server/util_expr_eval.c @@ -23,6 +23,7 @@ #include "http_core.h" #include "http_protocol.h" #include "http_request.h" +#include "http_ssl.h" #include "ap_provider.h" #include "util_expr_private.h" #include "util_md5.h" @@ -31,6 +32,10 @@ #include "apr_fnmatch.h" #include "apr_base64.h" #include "apr_sha1.h" +#include "apr_version.h" +#if APR_VERSION_AT_LEAST(1,5,0) +#include "apr_escape.h" +#endif #include <limits.h> /* for INT_MAX */ @@ -317,7 +322,7 @@ static int ap_expr_eval_comp(ap_expr_eval_ctx_t *ctx, const ap_expr_t *node) /* combined string/int comparison for compatibility with ssl_expr */ static int strcmplex(const char *str1, const char *str2) { - int i, n1, n2; + apr_size_t i, n1, n2; if (str1 == NULL) return -1; @@ -1075,7 +1080,7 @@ static const char *sha1_func(ap_expr_eval_ctx_t *ctx, const void *data, out = apr_palloc(ctx->p, APR_SHA1_DIGESTSIZE*2+1); apr_sha1_init(&context); - apr_sha1_update(&context, arg, strlen(arg)); + apr_sha1_update(&context, arg, (unsigned int)strlen(arg)); apr_sha1_final(sha1, &context); ap_bin2hex(sha1, APR_SHA1_DIGESTSIZE, out); @@ -1086,9 +1091,16 @@ static const char *sha1_func(ap_expr_eval_ctx_t *ctx, const void *data, static const char *md5_func(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) { - return ap_md5(ctx->p, (const unsigned char *)arg); + return ap_md5(ctx->p, (const unsigned char *)arg); } +#if APR_VERSION_AT_LEAST(1,6,0) +static const char *ldap_func(ap_expr_eval_ctx_t *ctx, const void *data, + const char *arg) +{ + return apr_pescape_ldap(ctx->p, arg, APR_ESCAPE_STRING, APR_ESCAPE_LDAP_ALL); +} +#endif #define MAX_FILE_SIZE 10*1024*1024 static const char *file_func(ap_expr_eval_ctx_t *ctx, const void *data, @@ -1256,13 +1268,10 @@ static int op_file_subr(ap_expr_eval_ctx_t *ctx, const void *data, const char *a } -APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *)); -static APR_OPTIONAL_FN_TYPE(ssl_is_https) *is_https = NULL; - APR_DECLARE_OPTIONAL_FN(int, http2_is_h2, (conn_rec *)); static APR_OPTIONAL_FN_TYPE(http2_is_h2) *is_http2 = NULL; -static const char *conn_var_names[] = { +static const char *const conn_var_names[] = { "HTTPS", /* 0 */ "IPV6", /* 1 */ "CONN_LOG_ID", /* 2 */ @@ -1280,7 +1289,7 @@ static const char *conn_var_fn(ap_expr_eval_ctx_t *ctx, const void *data) switch (index) { case 0: - if (is_https && is_https(c)) + if (ap_ssl_conn_is_ssl(c)) return "on"; else return "off"; @@ -1312,7 +1321,7 @@ static const char *conn_var_fn(ap_expr_eval_ctx_t *ctx, const void *data) } } -static const char *request_var_names[] = { +static const char *const request_var_names[] = { "REQUEST_METHOD", /* 0 */ "REQUEST_SCHEME", /* 1 */ "REQUEST_URI", /* 2 */ @@ -1440,7 +1449,7 @@ static const char *request_var_fn(ap_expr_eval_ctx_t *ctx, const void *data) } } -static const char *req_header_var_names[] = { +static const char *const req_header_var_names[] = { "HTTP_USER_AGENT", /* 0 */ "HTTP_PROXY_CONNECTION", /* 1 */ "HTTP_REFERER", /* 2 */ @@ -1451,7 +1460,7 @@ static const char *req_header_var_names[] = { NULL }; -static const char *req_header_header_names[] = { +static const char *const req_header_header_names[] = { "User-Agent", "Proxy-Connection", "Referer", @@ -1463,7 +1472,7 @@ static const char *req_header_header_names[] = { static const char *req_header_var_fn(ap_expr_eval_ctx_t *ctx, const void *data) { - const char **varname = (const char **)data; + const char **const varname = (const char **)data; int index = (varname - req_header_var_names); const char *name; @@ -1481,7 +1490,7 @@ static const char *req_header_var_fn(ap_expr_eval_ctx_t *ctx, const void *data) return apr_table_get(ctx->r->headers_in, name); } -static const char *misc_var_names[] = { +static const char *const misc_var_names[] = { "TIME_YEAR", /* 0 */ "TIME_MON", /* 1 */ "TIME_DAY", /* 2 */ @@ -1638,7 +1647,7 @@ struct expr_provider_single { struct expr_provider_multi { const void *func; - const char **names; + const char *const *names; }; static const struct expr_provider_multi var_providers[] = { @@ -1669,6 +1678,9 @@ static const struct expr_provider_single string_func_providers[] = { { unbase64_func, "unbase64", NULL, 0 }, { sha1_func, "sha1", NULL, 0 }, { md5_func, "md5", NULL, 0 }, +#if APR_VERSION_AT_LEAST(1,6,0) + { ldap_func, "ldap", NULL, 0 }, +#endif { NULL, NULL, NULL} }; @@ -1704,9 +1716,9 @@ static int core_expr_lookup(ap_expr_lookup_parms *parms) case AP_EXPR_FUNC_VAR: { const struct expr_provider_multi *prov = var_providers; while (prov->func) { - const char **name = prov->names; + const char *const *name = prov->names; while (*name) { - if (strcasecmp(*name, parms->name) == 0) { + if (ap_cstr_casecmp(*name, parms->name) == 0) { *parms->func = prov->func; *parms->data = name; return OK; @@ -1739,7 +1751,7 @@ static int core_expr_lookup(ap_expr_lookup_parms *parms) if (parms->type == AP_EXPR_FUNC_OP_UNARY) match = !strcmp(prov->name, parms->name); else - match = !strcasecmp(prov->name, parms->name); + match = !ap_cstr_casecmp(prov->name, parms->name); if (match) { if ((parms->flags & AP_EXPR_FLAG_RESTRICTED) && prov->restricted) { @@ -1791,7 +1803,7 @@ static int expr_lookup_not_found(ap_expr_lookup_parms *parms) type = "Binary operator"; break; default: - *parms->err = "Inavalid expression type in expr_lookup"; + *parms->err = "Invalid expression type in expr_lookup"; return !OK; } if ( parms->type == AP_EXPR_FUNC_OP_UNARY @@ -1806,10 +1818,7 @@ static int expr_lookup_not_found(ap_expr_lookup_parms *parms) static int ap_expr_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { - is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); is_http2 = APR_RETRIEVE_OPTIONAL_FN(http2_is_h2); - apr_pool_cleanup_register(pconf, &is_https, ap_pool_cleanup_set_null, - apr_pool_cleanup_null); return OK; } diff --git a/server/util_expr_parse.c b/server/util_expr_parse.c index bcf0173..ac4a323 100644 --- a/server/util_expr_parse.c +++ b/server/util_expr_parse.c @@ -1326,6 +1326,8 @@ YYSTYPE yylval; goto yysetstate; + /* TODO: comppiler warning that this is unused, and it seems to */ + (void)yynerrs; /*------------------------------------------------------------. | yynewstate -- Push a new state, which is found in yystate. | `------------------------------------------------------------*/ diff --git a/server/util_filter.c b/server/util_filter.c index 51e24f6..664f649 100644 --- a/server/util_filter.c +++ b/server/util_filter.c @@ -565,8 +565,9 @@ AP_DECLARE(apr_status_t) ap_pass_brigade(ap_filter_t *next, apr_bucket_brigade *bb) { if (next) { - apr_bucket *e; - if ((e = APR_BRIGADE_LAST(bb)) && APR_BUCKET_IS_EOS(e) && next->r) { + apr_bucket *e = APR_BRIGADE_LAST(bb); + + if (e != APR_BRIGADE_SENTINEL(bb) && APR_BUCKET_IS_EOS(e) && next->r) { /* This is only safe because HTTP_HEADER filter is always in * the filter stack. This ensures that there is ALWAYS a * request-based filter that we can attach this to. If the diff --git a/server/util_md5.c b/server/util_md5.c index 148c60c..bba3b88 100644 --- a/server/util_md5.c +++ b/server/util_md5.c @@ -54,7 +54,7 @@ AP_DECLARE(char *) ap_md5_binary(apr_pool_t *p, const unsigned char *buf, int le { apr_md5_ctx_t my_md5; unsigned char hash[APR_MD5_DIGESTSIZE]; - char result[2 * APR_MD5_DIGESTSIZE + 1]; + char *result; /* * Take the MD5 hash of the string argument. @@ -67,9 +67,9 @@ AP_DECLARE(char *) ap_md5_binary(apr_pool_t *p, const unsigned char *buf, int le apr_md5_update(&my_md5, buf, (unsigned int)length); apr_md5_final(hash, &my_md5); - ap_bin2hex(hash, APR_MD5_DIGESTSIZE, result); - - return apr_pstrndup(p, result, APR_MD5_DIGESTSIZE*2); + result = apr_palloc(p, 2 * APR_MD5_DIGESTSIZE + 1); + ap_bin2hex(hash, APR_MD5_DIGESTSIZE, result); /* sets final '\0' */ + return result; } AP_DECLARE(char *) ap_md5(apr_pool_t *p, const unsigned char *string) diff --git a/server/util_pcre.c b/server/util_pcre.c index f2cb1bb..0a9dc50 100644 --- a/server/util_pcre.c +++ b/server/util_pcre.c @@ -55,7 +55,21 @@ POSSIBILITY OF SUCH DAMAGE. #include "httpd.h" #include "apr_strings.h" #include "apr_tables.h" +#include "apr_thread_proc.h" + +#ifdef HAVE_PCRE2 +#define PCRE2_CODE_UNIT_WIDTH 8 +#include "pcre2.h" +#define PCREn(x) PCRE2_ ## x +#else #include "pcre.h" +#define PCREn(x) PCRE_ ## x +#endif + +/* PCRE_DUPNAMES is only present since version 6.7 of PCRE */ +#if !defined(PCRE_DUPNAMES) && !defined(HAVE_PCRE2) +#error PCRE Version 6.7 or later required! +#else #define APR_WANT_STRFUNC #include "apr_want.h" @@ -76,6 +90,26 @@ static const char *const pstring[] = { "match failed" /* AP_REG_NOMATCH */ }; +AP_DECLARE(const char *) ap_pcre_version_string(int which) +{ +#ifdef HAVE_PCRE2 + static char buf[80]; +#endif + switch (which) { + case AP_REG_PCRE_COMPILED: + return APR_STRINGIFY(PCREn(MAJOR)) "." APR_STRINGIFY(PCREn(MINOR)) " " APR_STRINGIFY(PCREn(DATE)); + case AP_REG_PCRE_LOADED: +#ifdef HAVE_PCRE2 + pcre2_config(PCRE2_CONFIG_VERSION, buf); + return buf; +#else + return pcre_version(); +#endif + default: + return "Unknown"; + } +} + AP_DECLARE(apr_size_t) ap_regerror(int errcode, const ap_regex_t *preg, char *errbuf, apr_size_t errbuf_size) { @@ -110,7 +144,11 @@ AP_DECLARE(apr_size_t) ap_regerror(int errcode, const ap_regex_t *preg, AP_DECLARE(void) ap_regfree(ap_regex_t *preg) { +#ifdef HAVE_PCRE2 + pcre2_code_free(preg->re_pcre); +#else (pcre_free)(preg->re_pcre); +#endif } @@ -120,7 +158,7 @@ AP_DECLARE(void) ap_regfree(ap_regex_t *preg) * Compile a regular expression * *************************************************/ -static int default_cflags = AP_REG_DOLLAR_ENDONLY; +static int default_cflags = AP_REG_DEFAULT; AP_DECLARE(int) ap_regcomp_get_default_cflags(void) { @@ -163,37 +201,53 @@ AP_DECLARE(int) ap_regcomp_default_cflag_by_name(const char *name) */ AP_DECLARE(int) ap_regcomp(ap_regex_t * preg, const char *pattern, int cflags) { +#ifdef HAVE_PCRE2 + uint32_t capcount; + size_t erroffset; +#else const char *errorptr; int erroffset; +#endif int errcode = 0; - int options = PCRE_DUPNAMES; + int options = PCREn(DUPNAMES); + + if ((cflags & AP_REG_NO_DEFAULT) == 0) + cflags |= default_cflags; - cflags |= default_cflags; if ((cflags & AP_REG_ICASE) != 0) - options |= PCRE_CASELESS; + options |= PCREn(CASELESS); if ((cflags & AP_REG_NEWLINE) != 0) - options |= PCRE_MULTILINE; + options |= PCREn(MULTILINE); if ((cflags & AP_REG_DOTALL) != 0) - options |= PCRE_DOTALL; + options |= PCREn(DOTALL); if ((cflags & AP_REG_DOLLAR_ENDONLY) != 0) - options |= PCRE_DOLLAR_ENDONLY; + options |= PCREn(DOLLAR_ENDONLY); + +#ifdef HAVE_PCRE2 + preg->re_pcre = pcre2_compile((const unsigned char *)pattern, + PCRE2_ZERO_TERMINATED, options, &errcode, + &erroffset, NULL); +#else + preg->re_pcre = pcre_compile2(pattern, options, &errcode, + &errorptr, &erroffset, NULL); +#endif - preg->re_pcre = - pcre_compile2(pattern, options, &errcode, &errorptr, &erroffset, NULL); preg->re_erroffset = erroffset; - if (preg->re_pcre == NULL) { - /* - * There doesn't seem to be constants defined for compile time error - * codes. 21 is "failed to get memory" according to pcreapi(3). - */ + /* Internal ERR21 is "failed to get memory" according to pcreapi(3) */ if (errcode == 21) return AP_REG_ESPACE; return AP_REG_INVARG; } +#ifdef HAVE_PCRE2 + pcre2_pattern_info((const pcre2_code *)preg->re_pcre, + PCRE2_INFO_CAPTURECOUNT, &capcount); + preg->re_nsub = capcount; +#else pcre_fullinfo((const pcre *)preg->re_pcre, NULL, - PCRE_INFO_CAPTURECOUNT, &(preg->re_nsub)); + PCRE_INFO_CAPTURECOUNT, &(preg->re_nsub)); +#endif return 0; } @@ -210,7 +264,134 @@ AP_DECLARE(int) ap_regcomp(ap_regex_t * preg, const char *pattern, int cflags) * ints. However, if the number of possible capturing brackets is small, use a * block of store on the stack, to reduce the use of malloc/free. The threshold * is in a macro that can be changed at configure time. + * Yet more unfortunately, PCRE2 wants an opaque context by providing the API + * to allocate and free it, so to minimize these calls we maintain one opaque + * context per thread (in Thread Local Storage, TLS) grown as needed, and while + * at it we do the same for PCRE1 ints vectors. Note that this requires a fast + * TLS mechanism to be worth it, which is the case of apr_thread_data_get/set() + * from/to ap_thread_current() when AP_HAS_THREAD_LOCAL; otherwise we'll do + * the allocation and freeing for each ap_regexec(). */ + +#ifdef HAVE_PCRE2 +typedef pcre2_match_data* match_data_pt; +typedef size_t* match_vector_pt; +#else +typedef int* match_data_pt; +typedef int* match_vector_pt; +#endif + +static APR_INLINE +match_data_pt alloc_match_data(apr_size_t size, + match_vector_pt small_vector) +{ + match_data_pt data; + +#ifdef HAVE_PCRE2 + data = pcre2_match_data_create(size, NULL); +#else + if (size > POSIX_MALLOC_THRESHOLD) { + data = malloc(size * sizeof(int) * 3); + } + else { + data = small_vector; + } +#endif + + return data; +} + +static APR_INLINE +void free_match_data(match_data_pt data, apr_size_t size) +{ +#ifdef HAVE_PCRE2 + pcre2_match_data_free(data); +#else + if (size > POSIX_MALLOC_THRESHOLD) { + free(data); + } +#endif +} + +#if AP_HAS_THREAD_LOCAL && !defined(APREG_NO_THREAD_LOCAL) + +struct apreg_tls { + match_data_pt data; + apr_size_t size; +}; + +#ifdef HAVE_PCRE2 +static apr_status_t apreg_tls_cleanup(void *arg) +{ + struct apreg_tls *tls = arg; + pcre2_match_data_free(tls->data); /* NULL safe */ + return APR_SUCCESS; +} +#endif + +static match_data_pt get_match_data(apr_size_t size, + match_vector_pt small_vector, + int *to_free) +{ + apr_thread_t *current; + struct apreg_tls *tls = NULL; + + /* Even though AP_HAS_THREAD_LOCAL, we may still be called by a + * native/non-apr thread, let's fall back to alloc/free in this case. + */ + current = ap_thread_current(); + if (!current) { + *to_free = 1; + return alloc_match_data(size, small_vector); + } + + apr_thread_data_get((void **)&tls, "apreg", current); + if (!tls || tls->size < size) { + apr_pool_t *tp = apr_thread_pool_get(current); + if (!tls) { + tls = apr_pcalloc(tp, sizeof(*tls)); +#ifdef HAVE_PCRE2 + apr_thread_data_set(tls, "apreg", apreg_tls_cleanup, current); +#else + apr_thread_data_set(tls, "apreg", NULL, current); +#endif + } + + tls->size *= 2; + if (tls->size < size) { + tls->size = size; + if (tls->size < POSIX_MALLOC_THRESHOLD) { + tls->size = POSIX_MALLOC_THRESHOLD; + } + } + +#ifdef HAVE_PCRE2 + pcre2_match_data_free(tls->data); /* NULL safe */ + tls->data = pcre2_match_data_create(tls->size, NULL); + if (!tls->data) { + tls->size = 0; + return NULL; + } +#else + tls->data = apr_palloc(tp, tls->size * sizeof(int) * 3); +#endif + } + + return tls->data; +} + +#else /* AP_HAS_THREAD_LOCAL && !defined(APREG_NO_THREAD_LOCAL) */ + +static APR_INLINE match_data_pt get_match_data(apr_size_t size, + match_vector_pt small_vector, + int *to_free) +{ + *to_free = 1; + return alloc_match_data(size, small_vector); +} + +#endif /* AP_HAS_THREAD_LOCAL && !defined(APREG_NO_THREAD_LOCAL) */ + AP_DECLARE(int) ap_regexec(const ap_regex_t *preg, const char *string, apr_size_t nmatch, ap_regmatch_t *pmatch, int eflags) @@ -224,75 +405,84 @@ AP_DECLARE(int) ap_regexec_len(const ap_regex_t *preg, const char *buff, ap_regmatch_t *pmatch, int eflags) { int rc; - int options = 0; - int *ovector = NULL; - int small_ovector[POSIX_MALLOC_THRESHOLD * 3]; - int allocated_ovector = 0; - - if ((eflags & AP_REG_NOTBOL) != 0) - options |= PCRE_NOTBOL; - if ((eflags & AP_REG_NOTEOL) != 0) - options |= PCRE_NOTEOL; - - ((ap_regex_t *)preg)->re_erroffset = (apr_size_t)(-1); /* Only has meaning after compile */ + int options = 0, to_free = 0; + match_vector_pt ovector = NULL; + apr_size_t ncaps = (apr_size_t)preg->re_nsub + 1; +#ifdef HAVE_PCRE2 + match_data_pt data = get_match_data(ncaps, NULL, &to_free); +#else + int small_vector[POSIX_MALLOC_THRESHOLD * 3]; + match_data_pt data = get_match_data(ncaps, small_vector, &to_free); +#endif - if (nmatch > 0) { - if (nmatch <= POSIX_MALLOC_THRESHOLD) { - ovector = &(small_ovector[0]); - } - else { - ovector = (int *)malloc(sizeof(int) * nmatch * 3); - if (ovector == NULL) - return AP_REG_ESPACE; - allocated_ovector = 1; - } + if (!data) { + return AP_REG_ESPACE; } + if ((eflags & AP_REG_NOTBOL) != 0) + options |= PCREn(NOTBOL); + if ((eflags & AP_REG_NOTEOL) != 0) + options |= PCREn(NOTEOL); + +#ifdef HAVE_PCRE2 + rc = pcre2_match((const pcre2_code *)preg->re_pcre, + (const unsigned char *)buff, len, + 0, options, data, NULL); + ovector = pcre2_get_ovector_pointer(data); +#else + ovector = data; rc = pcre_exec((const pcre *)preg->re_pcre, NULL, buff, (int)len, - 0, options, ovector, nmatch * 3); - - if (rc == 0) - rc = nmatch; /* All captured slots were filled in */ + 0, options, ovector, ncaps * 3); +#endif if (rc >= 0) { - apr_size_t i; - for (i = 0; i < (apr_size_t)rc; i++) { + apr_size_t n = rc, i; + if (n == 0 || n > nmatch) + rc = n = nmatch; /* All capture slots were filled in */ + for (i = 0; i < n; i++) { pmatch[i].rm_so = ovector[i * 2]; pmatch[i].rm_eo = ovector[i * 2 + 1]; } - if (allocated_ovector) - free(ovector); for (; i < nmatch; i++) pmatch[i].rm_so = pmatch[i].rm_eo = -1; + if (to_free) { + free_match_data(data, ncaps); + } return 0; } - else { - if (allocated_ovector) - free(ovector); + if (to_free) { + free_match_data(data, ncaps); + } +#ifdef HAVE_PCRE2 + if (rc <= PCRE2_ERROR_UTF8_ERR1 && rc >= PCRE2_ERROR_UTF8_ERR21) + return AP_REG_INVARG; +#endif switch (rc) { - case PCRE_ERROR_NOMATCH: + case PCREn(ERROR_NOMATCH): return AP_REG_NOMATCH; - case PCRE_ERROR_NULL: + case PCREn(ERROR_NULL): return AP_REG_INVARG; - case PCRE_ERROR_BADOPTION: + case PCREn(ERROR_BADOPTION): return AP_REG_INVARG; - case PCRE_ERROR_BADMAGIC: + case PCREn(ERROR_BADMAGIC): return AP_REG_INVARG; - case PCRE_ERROR_UNKNOWN_NODE: - return AP_REG_ASSERT; - case PCRE_ERROR_NOMEMORY: + case PCREn(ERROR_NOMEMORY): return AP_REG_ESPACE; -#ifdef PCRE_ERROR_MATCHLIMIT - case PCRE_ERROR_MATCHLIMIT: +#if defined(HAVE_PCRE2) || defined(PCRE_ERROR_MATCHLIMIT) + case PCREn(ERROR_MATCHLIMIT): return AP_REG_ESPACE; #endif -#ifdef PCRE_ERROR_BADUTF8 - case PCRE_ERROR_BADUTF8: +#if defined(PCRE_ERROR_UNKNOWN_NODE) + case PCRE_ERROR_UNKNOWN_NODE: + return AP_REG_ASSERT; +#endif +#if defined(PCRE_ERROR_BADUTF8) + case PCREn(ERROR_BADUTF8): return AP_REG_INVARG; #endif -#ifdef PCRE_ERROR_BADUTF8_OFFSET - case PCRE_ERROR_BADUTF8_OFFSET: +#if defined(PCRE_ERROR_BADUTF8_OFFSET) + case PCREn(ERROR_BADUTF8_OFFSET): return AP_REG_INVARG; #endif default: @@ -305,17 +495,29 @@ AP_DECLARE(int) ap_regname(const ap_regex_t *preg, apr_array_header_t *names, const char *prefix, int upper) { + char *nametable; + +#ifdef HAVE_PCRE2 + uint32_t namecount; + uint32_t nameentrysize; + uint32_t i; + pcre2_pattern_info((const pcre2_code *)preg->re_pcre, + PCRE2_INFO_NAMECOUNT, &namecount); + pcre2_pattern_info((const pcre2_code *)preg->re_pcre, + PCRE2_INFO_NAMEENTRYSIZE, &nameentrysize); + pcre2_pattern_info((const pcre2_code *)preg->re_pcre, + PCRE2_INFO_NAMETABLE, &nametable); +#else int namecount; int nameentrysize; int i; - char *nametable; - pcre_fullinfo((const pcre *)preg->re_pcre, NULL, - PCRE_INFO_NAMECOUNT, &namecount); + PCRE_INFO_NAMECOUNT, &namecount); pcre_fullinfo((const pcre *)preg->re_pcre, NULL, - PCRE_INFO_NAMEENTRYSIZE, &nameentrysize); + PCRE_INFO_NAMEENTRYSIZE, &nameentrysize); pcre_fullinfo((const pcre *)preg->re_pcre, NULL, - PCRE_INFO_NAMETABLE, &nametable); + PCRE_INFO_NAMETABLE, &nametable); +#endif for (i = 0; i < namecount; i++) { const char *offset = nametable + i * nameentrysize; @@ -340,4 +542,6 @@ AP_DECLARE(int) ap_regname(const ap_regex_t *preg, return namecount; } +#endif /* PCRE_DUPNAMES defined */ + /* End of pcreposix.c */ diff --git a/server/util_regex.c b/server/util_regex.c index 2a30d68..5405f8d 100644 --- a/server/util_regex.c +++ b/server/util_regex.c @@ -94,6 +94,7 @@ AP_DECLARE(ap_rxplus_t*) ap_rxplus_compile(apr_pool_t *pool, } /* anything after the current delimiter is flags */ + ret->flags = ap_regcomp_get_default_cflags() & AP_REG_DOLLAR_ENDONLY; while (*++endp) { switch (*endp) { case 'i': ret->flags |= AP_REG_ICASE; break; @@ -106,7 +107,7 @@ AP_DECLARE(ap_rxplus_t*) ap_rxplus_compile(apr_pool_t *pool, default: break; /* we should probably be stricter here */ } } - if (ap_regcomp(&ret->rx, rxstr, ret->flags) == 0) { + if (ap_regcomp(&ret->rx, rxstr, AP_REG_NO_DEFAULT | ret->flags) == 0) { apr_pool_cleanup_register(pool, &ret->rx, rxplus_cleanup, apr_pool_cleanup_null); } diff --git a/server/util_script.c b/server/util_script.c index 599ba58..1fa4276 100644 --- a/server/util_script.c +++ b/server/util_script.c @@ -45,7 +45,7 @@ /* * Various utility functions which are common to a whole lot of * script-type extensions mechanisms, and might as well be gathered - * in one place (if only to avoid creating inter-module dependancies + * in one place (if only to avoid creating inter-module dependencies * where there don't have to be). */ @@ -92,9 +92,21 @@ static void add_unless_null(apr_table_t *table, const char *name, const char *va } } -static void env2env(apr_table_t *table, const char *name) +/* Sets variable @name in table @dest from r->subprocess_env if + * available, else from the environment, else from @fallback if + * non-NULL. */ +static void env2env(apr_table_t *dest, request_rec *r, + const char *name, const char *fallback) { - add_unless_null(table, name, getenv(name)); + const char *val; + + val = apr_table_get(r->subprocess_env, name); + if (!val) + val = apr_pstrdup(r->pool, getenv(name)); + if (!val) + val = apr_pstrdup(r->pool, fallback); + if (val) + apr_table_addn(dest, name, val); } AP_DECLARE(char **) ap_create_environment(apr_pool_t *p, apr_table_t *t) @@ -180,10 +192,10 @@ AP_DECLARE(void) ap_add_common_vars(request_rec *r) * for no particular reason. */ - if (!strcasecmp(hdrs[i].key, "Content-type")) { + if (!ap_cstr_casecmp(hdrs[i].key, "Content-type")) { apr_table_addn(e, "CONTENT_TYPE", hdrs[i].val); } - else if (!strcasecmp(hdrs[i].key, "Content-length")) { + else if (!ap_cstr_casecmp(hdrs[i].key, "Content-length")) { apr_table_addn(e, "CONTENT_LENGTH", hdrs[i].val); } /* HTTP_PROXY collides with a popular envvar used to configure @@ -200,8 +212,8 @@ AP_DECLARE(void) ap_add_common_vars(request_rec *r) * in the environment with "ps -e". But, if you must... */ #ifndef SECURITY_HOLE_PASS_AUTHORIZATION - else if (!strcasecmp(hdrs[i].key, "Authorization") - || !strcasecmp(hdrs[i].key, "Proxy-Authorization")) { + else if (!ap_cstr_casecmp(hdrs[i].key, "Authorization") + || !ap_cstr_casecmp(hdrs[i].key, "Proxy-Authorization")) { if (conf->cgi_pass_auth == AP_CGI_PASS_AUTH_ON) { add_unless_null(e, http2env(r, hdrs[i].key), hdrs[i].val); } @@ -211,37 +223,29 @@ AP_DECLARE(void) ap_add_common_vars(request_rec *r) add_unless_null(e, http2env(r, hdrs[i].key), hdrs[i].val); } - env_temp = apr_table_get(r->subprocess_env, "PATH"); - if (env_temp == NULL) { - env_temp = getenv("PATH"); - } - if (env_temp == NULL) { - env_temp = DEFAULT_PATH; - } - apr_table_addn(e, "PATH", apr_pstrdup(r->pool, env_temp)); - + env2env(e, r, "PATH", DEFAULT_PATH); #if defined(WIN32) - env2env(e, "SystemRoot"); - env2env(e, "COMSPEC"); - env2env(e, "PATHEXT"); - env2env(e, "WINDIR"); + env2env(e, r, "SystemRoot", NULL); + env2env(e, r, "COMSPEC", NULL); + env2env(e, r, "PATHEXT", NULL); + env2env(e, r, "WINDIR", NULL); #elif defined(OS2) - env2env(e, "COMSPEC"); - env2env(e, "ETC"); - env2env(e, "DPATH"); - env2env(e, "PERLLIB_PREFIX"); + env2env(e, r, "COMSPEC", NULL); + env2env(e, r, "ETC", NULL); + env2env(e, r, "DPATH", NULL); + env2env(e, r, "PERLLIB_PREFIX", NULL); #elif defined(BEOS) - env2env(e, "LIBRARY_PATH"); + env2env(e, r, "LIBRARY_PATH", NULL); #elif defined(DARWIN) - env2env(e, "DYLD_LIBRARY_PATH"); + env2env(e, r, "DYLD_LIBRARY_PATH", NULL); #elif defined(_AIX) - env2env(e, "LIBPATH"); + env2env(e, r, "LIBPATH", NULL); #elif defined(__HPUX__) /* HPUX PARISC 2.0W knows both, otherwise redundancy is harmless */ - env2env(e, "SHLIB_PATH"); - env2env(e, "LD_LIBRARY_PATH"); + env2env(e, r, "SHLIB_PATH", NULL); + env2env(e, r, "LD_LIBRARY_PATH", NULL); #else /* Some Unix */ - env2env(e, "LD_LIBRARY_PATH"); + env2env(e, r, "LD_LIBRARY_PATH", NULL); #endif apr_table_addn(e, "SERVER_SIGNATURE", ap_psignature("", r)); @@ -620,7 +624,7 @@ AP_DECLARE(int) ap_scan_script_header_err_core_ex(request_rec *r, char *buffer, ++l; } - if (!strcasecmp(w, "Content-type")) { + if (!ap_cstr_casecmp(w, "Content-type")) { char *tmp; /* Nuke trailing whitespace */ @@ -638,7 +642,7 @@ AP_DECLARE(int) ap_scan_script_header_err_core_ex(request_rec *r, char *buffer, * If the script returned a specific status, that's what * we'll use - otherwise we assume 200 OK. */ - else if (!strcasecmp(w, "Status")) { + else if (!ap_cstr_casecmp(w, "Status")) { r->status = cgi_status = atoi(l); if (!ap_is_HTTP_VALID_RESPONSE(cgi_status)) /* Intentional no APLOGNO */ @@ -652,30 +656,73 @@ AP_DECLARE(int) ap_scan_script_header_err_core_ex(request_rec *r, char *buffer, apr_filepath_name_get(r->filename), l); r->status_line = apr_pstrdup(r->pool, l); } - else if (!strcasecmp(w, "Location")) { + else if (!ap_cstr_casecmp(w, "Location")) { apr_table_set(r->headers_out, w, l); } - else if (!strcasecmp(w, "Content-Length")) { + else if (!ap_cstr_casecmp(w, "Content-Length")) { apr_table_set(r->headers_out, w, l); } - else if (!strcasecmp(w, "Content-Range")) { + else if (!ap_cstr_casecmp(w, "Content-Range")) { apr_table_set(r->headers_out, w, l); } - else if (!strcasecmp(w, "Transfer-Encoding")) { + else if (!ap_cstr_casecmp(w, "Transfer-Encoding")) { apr_table_set(r->headers_out, w, l); } - else if (!strcasecmp(w, "ETag")) { + else if (!ap_cstr_casecmp(w, "ETag")) { apr_table_set(r->headers_out, w, l); } /* * If the script gave us a Last-Modified header, we can't just - * pass it on blindly because of restrictions on future values. + * pass it on blindly because of restrictions on future or invalid values. */ - else if (!strcasecmp(w, "Last-Modified")) { - ap_update_mtime(r, apr_date_parse_http(l)); - ap_set_last_modified(r); + else if (!ap_cstr_casecmp(w, "Last-Modified")) { + apr_time_t parsed_date = apr_date_parse_http(l); + if (parsed_date != APR_DATE_BAD) { + ap_update_mtime(r, parsed_date); + ap_set_last_modified(r); + if (APLOGrtrace1(r)) { + apr_time_t last_modified_date = apr_date_parse_http(apr_table_get(r->headers_out, + "Last-Modified")); + /* + * A Last-Modified header value coming from a (F)CGI source + * is considered HTTP input so we assume the GMT timezone. + * The following logs should inform the admin about violations + * and related actions taken by httpd. + * The apr_date_parse_rfc function is 'timezone aware' + * and it will be used to generate a more informative set of logs + * (we don't use it as a replacement of apr_date_parse_http + * for the aforementioned reason). + */ + apr_time_t parsed_date_tz_aware = apr_date_parse_rfc(l); + + /* + * The parsed Last-Modified header datestring has been replaced by httpd. + */ + if (parsed_date > last_modified_date) { + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_TRACE1, 0, r, + "The Last-Modified header value %s (%s) " + "has been replaced with '%s'", l, + parsed_date != parsed_date_tz_aware ? "not in GMT" + : "in the future", + apr_table_get(r->headers_out, "Last-Modified")); + /* + * Last-Modified header datestring not in GMT and not considered in the future + * by httpd (like now() + 1 hour in the PST timezone). No action is taken but + * the admin is warned about the violation. + */ + } else if (parsed_date != parsed_date_tz_aware) { + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_TRACE1, 0, r, + "The Last-Modified header value is not set " + "within the GMT timezone (as required)"); + } + } + } + else { + ap_log_rerror(SCRIPT_LOG_MARK, APLOG_INFO, 0, r, APLOGNO(10247) + "Ignored invalid header value: Last-Modified: '%s'", l); + } } - else if (!strcasecmp(w, "Set-Cookie")) { + else if (!ap_cstr_casecmp(w, "Set-Cookie")) { apr_table_add(cookie_table, w, l); } else { diff --git a/server/util_time.c b/server/util_time.c index 3632d0d..299b53c 100644 --- a/server/util_time.c +++ b/server/util_time.c @@ -22,9 +22,11 @@ * */ #define AP_CTIME_USEC_LENGTH 7 -/* Length of ISO 8601 date/time */ +/* Length of ISO 8601 date/time (including trailing '\0') */ #define AP_CTIME_COMPACT_LEN 20 +/* Length of timezone offset from GMT ([+-]hhmm) plus leading space */ +#define AP_CTIME_GMTOFF_LEN 6 /* Cache for exploded values of recent timestamps */ @@ -181,7 +183,13 @@ AP_DECLARE(apr_status_t) ap_recent_ctime_ex(char *date_str, apr_time_t t, needed += AP_CTIME_USEC_LENGTH; } - /* Check the provided buffer length */ + if (option & AP_CTIME_OPTION_GMTOFF) { + needed += AP_CTIME_GMTOFF_LEN; + } + + /* Check the provided buffer length (note: above AP_CTIME_COMPACT_LEN + * and APR_CTIME_LEN include the trailing '\0'; so does 'needed' then). + */ if (len && *len >= needed) { *len = needed; } @@ -193,9 +201,10 @@ AP_DECLARE(apr_status_t) ap_recent_ctime_ex(char *date_str, apr_time_t t, } /* example without options: "Wed Jun 30 21:49:08 1993" */ - /* 123456789012345678901234 */ /* example for compact format: "1993-06-30 21:49:08" */ - /* 1234567890123456789 */ + /* example for compact+usec+gmtoff format: + * "1993-06-30 22:49:08.123456 +0100" + */ ap_explode_recent_localtime(&xt, t); real_year = 1900 + xt.tm_year; @@ -249,7 +258,23 @@ AP_DECLARE(apr_status_t) ap_recent_ctime_ex(char *date_str, apr_time_t t, *date_str++ = real_year % 100 / 10 + '0'; *date_str++ = real_year % 10 + '0'; } - *date_str++ = 0; + if (option & AP_CTIME_OPTION_GMTOFF) { + int off = xt.tm_gmtoff, off_hh, off_mm; + char sign = '+'; + if (off < 0) { + off = -off; + sign = '-'; + } + off_hh = off / 3600; + off_mm = off % 3600 / 60; + *date_str++ = ' '; + *date_str++ = sign; + *date_str++ = off_hh / 10 + '0'; + *date_str++ = off_hh % 10 + '0'; + *date_str++ = off_mm / 10 + '0'; + *date_str++ = off_mm % 10 + '0'; + } + *date_str = 0; return APR_SUCCESS; } diff --git a/server/util_xml.c b/server/util_xml.c index 4845194..22806fa 100644 --- a/server/util_xml.c +++ b/server/util_xml.c @@ -85,7 +85,7 @@ AP_DECLARE(int) ap_xml_parse_input(request_rec * r, apr_xml_doc **pdoc) } total_read += len; - if (limit_xml_body && total_read > limit_xml_body) { + if (total_read > limit_xml_body) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00539) "XML request body is larger than the configured " "limit of %lu", (unsigned long)limit_xml_body); diff --git a/server/vhost.c b/server/vhost.c index b23b2dd..489c141 100644 --- a/server/vhost.c +++ b/server/vhost.c @@ -34,6 +34,7 @@ #include "http_vhost.h" #include "http_protocol.h" #include "http_core.h" +#include "http_main.h" #if APR_HAVE_ARPA_INET_H #include <arpa/inet.h> @@ -973,7 +974,13 @@ AP_DECLARE(int) ap_matches_request_vhost(request_rec *r, const char *host, } -static void check_hostalias(request_rec *r) +/* + * Updates r->server from ServerName/ServerAlias. Per the interaction + * of ip and name-based vhosts, it only looks in the best match from the + * connection-level ip-based matching. + * Returns HTTP_BAD_REQUEST if there was no match. + */ +static int update_server_from_aliases(request_rec *r) { /* * Even if the request has a Host: header containing a port we ignore @@ -1031,7 +1038,6 @@ static void check_hostalias(request_rec *r) goto found; } } - last_s = s; /* Fallback: does it match the virthost from the sar? */ if (!strcasecmp(host, sar->virthost)) { @@ -1040,6 +1046,8 @@ static void check_hostalias(request_rec *r) virthost_s = s; } } + + last_s = s; } /* If ServerName and ServerAlias check failed, we end up here. If it @@ -1050,11 +1058,18 @@ static void check_hostalias(request_rec *r) goto found; } - return; + if (!r->connection->vhost_lookup_data) { + if (matches_aliases(r->server, host)) { + s = r->server; + goto found; + } + } + return HTTP_BAD_REQUEST; found: /* s is the first matching server, we're done */ r->server = s; + return HTTP_OK; } @@ -1071,7 +1086,7 @@ static void check_serverpath(request_rec *r) * This is in conjunction with the ServerPath code in http_core, so we * get the right host attached to a non- Host-sending request. * - * See the comment in check_hostalias about how each vhost can be + * See the comment in update_server_from_aliases about how each vhost can be * listed multiple times. */ @@ -1135,10 +1150,16 @@ static APR_INLINE const char *construct_host_header(request_rec *r, AP_DECLARE(void) ap_update_vhost_from_headers(request_rec *r) { + ap_update_vhost_from_headers_ex(r, 0); +} + +AP_DECLARE(int) ap_update_vhost_from_headers_ex(request_rec *r, int require_match) +{ core_server_config *conf = ap_get_core_module_config(r->server->module_config); const char *host_header = apr_table_get(r->headers_in, "Host"); int is_v6literal = 0; int have_hostname_from_url = 0; + int rc = HTTP_OK; if (r->hostname) { /* @@ -1151,8 +1172,8 @@ AP_DECLARE(void) ap_update_vhost_from_headers(request_rec *r) else if (host_header != NULL) { is_v6literal = fix_hostname(r, host_header, conf->http_conformance); } - if (r->status != HTTP_OK) - return; + if (!require_match && r->status != HTTP_OK) + return HTTP_OK; if (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE) { /* @@ -1173,10 +1194,16 @@ AP_DECLARE(void) ap_update_vhost_from_headers(request_rec *r) /* check if we tucked away a name_chain */ if (r->connection->vhost_lookup_data) { if (r->hostname) - check_hostalias(r); + rc = update_server_from_aliases(r); else check_serverpath(r); } + else if (require_match && r->hostname) { + /* check the base server config */ + rc = update_server_from_aliases(r); + } + + return rc; } /** |