From e04108cee2dddb8f96472ecae1871f7253bec6a3 Mon Sep 17 00:00:00 2001 From: "William A. Rowe Jr" Date: Mon, 8 Oct 2001 17:38:52 +0000 Subject: [PATCH] Revamped ap_directory_walk logic, without a path_info helper is now activated. It may be bumpy for a few days, and we have more optimizations to put in place, but it's time to get this in the developer's test code. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@91359 13f79535-47bb-0310-9956-ffa450edef68 --- server/request.c | 456 +---------------------------------------------- 1 file changed, 1 insertion(+), 455 deletions(-) diff --git a/server/request.c b/server/request.c index 7ced634d872..ed9a8eb6927 100644 --- a/server/request.c +++ b/server/request.c @@ -415,455 +415,6 @@ static int resolve_symlink(char *d, apr_finfo_t *lfi, int opts, apr_pool_t *p) return OK; } -/* #define REPLACE_PATH_INFO_METHOD - */ -#ifndef REPLACE_PATH_INFO_METHOD - -static int check_symlinks(char *d, int opts, apr_pool_t *p) -{ -#if defined(OS2) - /* OS/2 doesn't have symlinks */ - return OK; -#else - apr_finfo_t lfi, fi; - char *lastp; - int res; - - if (opts & OPT_SYM_LINKS) - return OK; - - /* - * Strip trailing '/', if any, off what we're checking; trailing slashes - * make some systems follow symlinks to directories even in lstat(). - * After we've done the lstat, put it back. Also, don't bother checking - * '/' at all... - * - * Note that we don't have to worry about multiple slashes here because of - * no2slash() below... - */ - - lastp = d + strlen(d) - 1; - if (lastp == d) - return OK; /* Root directory, '/' */ - - if (*lastp == '/') - *lastp = '\0'; - else - lastp = NULL; - - res = apr_lstat(&lfi, d, APR_FINFO_TYPE | APR_FINFO_OWNER, p); - - if (lastp) - *lastp = '/'; - - /* - * Note that we don't reject accesses to nonexistent files (multiviews or - * the like may cons up a way to run the transaction anyway)... - */ - - if ((res != APR_SUCCESS && res != APR_INCOMPLETE) - || (lfi.filetype != APR_LNK)) - return OK; - - /* OK, it's a symlink. May still be OK with OPT_SYM_OWNER */ - - if (!(opts & OPT_SYM_OWNER)) - return HTTP_FORBIDDEN; - - /* OPT_SYM_OWNER only works if we can get the owner from the file */ - - if (res != APR_SUCCESS) - return HTTP_FORBIDDEN; - - if (apr_stat(&fi, d, APR_FINFO_OWNER, p) != APR_SUCCESS) - return HTTP_FORBIDDEN; - - /* TODO: replace with an apr_compare_users() fn */ - return (fi.user == lfi.user) ? OK : HTTP_FORBIDDEN; - -#endif -} - - -/* Dealing with the file system to get PATH_INFO - */ -static int get_path_info(request_rec *r) -{ - char *cp; - char *path = r->filename; - char *end = &path[strlen(path)]; - char *last_cp = NULL; - int rv; -#if defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) - char bStripSlash=1; -#endif - - if (r->finfo.filetype != APR_NOFILE) { - /* assume path_info already set */ - return OK; - } - -#ifdef HAVE_DRIVE_LETTERS - /* If the directory is x:\, then we don't want to strip - * the trailing slash since x: is not a valid directory. - */ - if (strlen(path) == 3 && path[1] == ':' && path[2] == '/') - bStripSlash = 0; -#endif - -#ifdef HAVE_UNC_PATHS - /* If UNC name == //machine/share/, do not - * advance over the trailing slash. Any other - * UNC name is OK to strip the slash. - */ - cp = end; - if (path[0] == '/' && path[1] == '/' && - path[2] != '/' && cp[-1] == '/') { - char *p; - int iCount=0; - p = path; - while ((p = strchr(p,'/')) != NULL) { - p++; - iCount++; - } - - if (iCount == 4) - bStripSlash = 0; - } -#endif - -#if defined(HAVE_DRIVE_LETTERS) || defined(HAVE_UNC_PATHS) - if (bStripSlash) -#endif - /* Advance over trailing slashes ... NOT part of filename - * if file is not a UNC name (Win32 only). - */ - for (cp = end; cp > path && cp[-1] == '/'; --cp) - continue; - - while (cp > path) { - - /* See if the pathname ending here exists... */ - *cp = '\0'; - - /* ### We no longer need the test ap_os_is_filename_valid() here - * since apr_stat isn't a posix thing - it's apr_stat's responsibility - * to handle whatever path string arrives at its door - by platform - * and volume restrictions as applicable... - * TODO: This code becomes even simpler if apr_stat grows - * an APR_PATHINCOMPLETE result to indicate that we are staring at - * an partial virtual root. Only OS2/Win32/Netware need apply it :-) - */ - rv = apr_stat(&r->finfo, path, APR_FINFO_MIN, r->pool); - - if (cp != end) - *cp = '/'; - - if (rv == APR_SUCCESS || rv == APR_INCOMPLETE) { - /* - * Aha! Found something. If it was a directory, we will search - * contents of that directory for a multi_match, so the PATH_INFO - * argument starts with the component after that. - */ - if (r->finfo.filetype == APR_DIR && last_cp) { - r->finfo.filetype = APR_NOFILE; /* No such file... */ - cp = last_cp; - } - - r->path_info = apr_pstrdup(r->pool, cp); - *cp = '\0'; - return OK; - } - - if (APR_STATUS_IS_ENOENT(rv) || APR_STATUS_IS_ENOTDIR(rv)) { - last_cp = cp; - - while (--cp > path && *cp != '/') - continue; - - while (cp > path && cp[-1] == '/') - --cp; - } - else { - if (APR_STATUS_IS_EACCES(rv)) - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, - "access to %s denied", r->uri); - else - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, - "access to %s failed", r->uri); - return HTTP_FORBIDDEN; - } - } - return OK; -} - -AP_DECLARE(int) ap_directory_walk(request_rec *r) -{ - core_server_config *sconf = ap_get_module_config(r->server->module_config, - &core_module); - ap_conf_vector_t *per_dir_defaults = r->server->lookup_defaults; - ap_conf_vector_t **sec_dir = (ap_conf_vector_t **) sconf->sec_dir->elts; - int num_sec = sconf->sec_dir->nelts; - char *test_filename; - char *test_dirname; - int res; - unsigned i, num_dirs; - int j, test_filename_len; - unsigned iStart = 1; - ap_conf_vector_t *entry_config; - ap_conf_vector_t *this_conf; - core_dir_config *entry_core; - - /* "OK" as a response to a real problem is not _OK_, but to allow broken - * modules to proceed, we will permit the not-a-path filename to pass here. - * We must catch it later if it's heading for the core handler. Leave an - * INFO note here for module debugging. - */ - if (r->filename == NULL || !ap_os_is_path_absolute(r->pool, r->filename)) { - ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r, - "Module bug? Request filename path %s is missing or " - "or not absolute for uri %s", - r->filename ? r->filename : "", r->uri); - return OK; - } - - /* - * Go down the directory hierarchy. Where we have to check for symlinks, - * do so. Where a .htaccess file has permission to override anything, - * try to find one. If either of these things fails, we could poke - * around, see why, and adjust the lookup_rec accordingly --- this might - * save us a call to get_path_info (with the attendant stat()s); however, - * for the moment, that's not worth the trouble. - */ - res = get_path_info(r); - if (res != OK) { - return res; - } - - /* XXX Momentary period of extreme danger, Will Robinson. - * Removed ap_os_canonical_filename. Anybody munging the - * r->filename better have pre-canonicalized the name that - * they just changed. Since the two most key functions - * in the entire server, ap_server_root_relative() and - * ap_make_full_path now canonicalize as they go. - * - * To be very safe, the server is in hyper-paranoid mode. - * That means that non-canonical paths will be captured and - * denied. This is very cpu/fs intensive, we need to finish - * auditing, and remove the paranoia trigger. - */ - if (r->filename == r->canonical_filename) -#ifdef NO_LONGER_PARANOID - test_filename = apr_pstrdup(r->pool, r->filename); -#else - { - if (apr_filepath_merge(&test_filename, "", r->filename, - APR_FILEPATH_NOTRELATIVE | APR_FILEPATH_TRUENAME, - r->pool) != APR_SUCCESS - || strcmp(test_filename, r->filename) != 0) { - ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r, - "Module bug? Filepath: %s is not the canonical %s", - r->filename, test_filename); - return HTTP_FORBIDDEN; - } - } -#endif - else { - /* Apparently, somebody didn't know to update r->canonical_filename - * which is lucky, since they didn't canonicalize r->filename either. - */ - if (apr_filepath_merge(&test_filename, NULL, r->filename, - APR_FILEPATH_NOTRELATIVE | APR_FILEPATH_TRUENAME, - r->pool) != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r, - "Module bug? Filepath: %s is not an absolute path", - r->filename); - return HTTP_FORBIDDEN; - } - if (strcmp(r->filename, test_filename) != 0) - r->filename = apr_pstrdup(r->pool, test_filename); - r->canonical_filename = r->filename; - } - - num_dirs = ap_count_dirs(test_filename); - - if ((res = check_safe_file(r))) { - return res; - } - - test_filename_len = strlen(test_filename); - if (test_filename[test_filename_len - 1] == '/') - --num_dirs; - - if (r->finfo.filetype == APR_DIR) - ++num_dirs; - - /* - * We will use test_dirname as scratch space while we build directory - * names during the walk. Profiling shows directory_walk to be a busy - * function so we try to avoid allocating lots of extra memory here. - * We need 2 extra bytes, one for trailing \0 and one because - * make_dirstr_prefix will add potentially one extra /. - */ - test_dirname = apr_palloc(r->pool, test_filename_len + 2); - - /* XXX The garbage below disappears in the new directory_walk; - */ - -#if defined(HAVE_UNC_PATHS) - /* If the name is a UNC name, then do not perform any true file test - * against the machine name (start at //machine/share/) - * This is optimized to use the normal walk (skips the redundant '/' root) - */ - if (num_dirs > 3 && test_filename[0] == '/' && test_filename[1] == '/') - iStart = 4; -#endif - -#if defined(NETWARE) - /* If the name is a fully qualified volume name, then do not perform any - * true file test on the machine name (start at machine/share:/) - * XXX: The implementation eludes me at this moment... - * Does this make sense? Please test! - */ - if (num_dirs > 1 && strchr(test_filename, '/') < strchr(test_filename, ':')) - iStart = 2; -#endif - - /* i keeps track of how many segments we are testing - * j keeps track of which section we're on, see core_reorder_directories - */ - j = 0; - for (i = 1; i <= num_dirs; ++i) { - int overrides_here; - core_dir_config *core_dir = ap_get_module_config(per_dir_defaults, - &core_module); - - /* - * XXX: this could be made faster by only copying the next component - * rather than copying the entire thing all over. - */ - ap_make_dirstr_prefix(test_dirname, test_filename, i); - - /* - * Do symlink checks first, because they are done with the - * permissions appropriate to the *parent* directory... - */ - - /* Test only real names (after the root) against the real filesystem */ - if ((i > iStart) && (res = check_symlinks(test_dirname, core_dir->opts, r->pool))) { - ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r, - "Symbolic link not allowed: %s", test_dirname); - return res; - } - - /* - * Begin *this* level by looking for matching sections - * from access.conf. - */ - - for (; j < num_sec; ++j) { - char *entry_dir; - - entry_config = sec_dir[j]; - entry_core = ap_get_module_config(entry_config, &core_module); - entry_dir = entry_core->d; - - if (entry_core->r || entry_core->d_components > i) - break; - - this_conf = NULL; - /* We will always add in '0' element components, e.g. plain old - * , and is classified as zero - * so that Win32/Netware/OS2 etc all pick that up. - */ - if (!entry_core->d_components) { - this_conf = entry_config; - } - else if (entry_core->d_is_fnmatch) { - if (!apr_fnmatch(entry_dir, test_dirname, FNM_PATHNAME)) { - this_conf = entry_config; - } - } - else if (!strcmp(test_dirname, entry_dir)) - this_conf = entry_config; - - if (this_conf) { - per_dir_defaults = ap_merge_per_dir_configs(r->pool, - per_dir_defaults, - this_conf); - core_dir = ap_get_module_config(per_dir_defaults, - &core_module); - } - } - overrides_here = core_dir->override; - - /* If .htaccess files are enabled, check for one. */ - - /* Test only legal names against the real filesystem */ - if ((i >= iStart) && overrides_here) { - ap_conf_vector_t *htaccess_conf = NULL; - - res = ap_parse_htaccess(&htaccess_conf, r, overrides_here, - apr_pstrdup(r->pool, test_dirname), - sconf->access_name); - if (res) - return res; - - if (htaccess_conf) { - per_dir_defaults = ap_merge_per_dir_configs(r->pool, - per_dir_defaults, - htaccess_conf); - r->per_dir_config = per_dir_defaults; - } - } - } - - /* - * Now we'll deal with the regexes. - */ - for (; j < num_sec; ++j) { - - entry_config = sec_dir[j]; - entry_core = ap_get_module_config(entry_config, &core_module); - - if (!entry_core->r) { - continue; - } - if (!ap_regexec(entry_core->r, test_dirname, 0, NULL, REG_NOTEOL)) { - per_dir_defaults = ap_merge_per_dir_configs(r->pool, - per_dir_defaults, - entry_config); - } - } - r->per_dir_config = per_dir_defaults; - - /* - * Symlink permissions are determined by the parent. If the request is - * for a directory then applying the symlink test here would use the - * permissions of the directory as opposed to its parent. Consider a - * symlink pointing to a dir with a .htaccess disallowing symlinks. If - * you access /symlink (or /symlink/) you would get a 403 without this - * APR_DIR test. But if you accessed /symlink/index.html, for example, - * you would *not* get the 403. - */ - if (r->finfo.filetype != APR_DIR - && (res = check_symlinks(r->filename, ap_allow_options(r), r->pool))) { - ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r, - "Symbolic link not allowed: %s", r->filename); - return res; - } - - /* Save a dummy userdata element till we optimize this function. - * If this userdata is set, directory_walk has run. - */ - apr_pool_userdata_set((void *)1, "ap_directory_walk::cache", - apr_pool_cleanup_null, r->pool); - - return OK; /* Can only "fail" if access denied by the - * symlink goop. */ -} - -#else /* defined REPLACE_PATH_INFO_METHOD */ /***************************************************************** * @@ -1372,8 +923,6 @@ minimerge2: return OK; } -#endif /* defined REPLACE_PATH_INFO_METHOD */ - AP_DECLARE(int) ap_location_walk(request_rec *r) { @@ -1820,7 +1369,6 @@ AP_DECLARE(request_rec *) ap_sub_req_lookup_dirent(const apr_finfo_t *dirent, ap_parse_uri(rnew, rnew->uri); /* fill in parsed_uri values */ -#ifdef REPLACE_PATH_INFO_METHOD /* Preserve the apr_stat results, and perhaps we also tag that * symlinks were tested and/or found for r->filename. */ @@ -1850,7 +1398,6 @@ AP_DECLARE(request_rec *) ap_sub_req_lookup_dirent(const apr_finfo_t *dirent, else { memcpy (&rnew->finfo, dirent, sizeof(apr_finfo_t)); } -#endif /* defined REPLACE_PATH_INFO_METHOD */ if ((res = ap_process_request_internal(rnew))) { rnew->status = res; @@ -1901,7 +1448,6 @@ AP_DECLARE(request_rec *) ap_sub_req_lookup_file(const char *new_file, && rnew->filename[fdirlen] && ap_strchr_c(rnew->filename + fdirlen, '/') == NULL) { -#ifdef REPLACE_PATH_INFO_METHOD apr_status_t rv; if (ap_allow_options(rnew) & OPT_SYM_LINKS) { if (((rv = apr_stat(&rnew->finfo, rnew->filename, @@ -1915,7 +1461,7 @@ AP_DECLARE(request_rec *) ap_sub_req_lookup_file(const char *new_file, && (rv != APR_INCOMPLETE)) rnew->finfo.filetype = 0; } -#endif /* defined REPLACE_PATH_INFO_METHOD */ + if (r->uri && *r->uri) { char *udir = ap_make_dirstr_parent(rnew->pool, r->uri); rnew->uri = ap_make_full_path(rnew->pool, udir, rnew->filename + fdirlen); -- 2.47.3