/* * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. * The contents of this file constitute Original Code as defined in and are * subject to the Apple Public Source License Version 1.2 (the 'License'). You * may not use this file except in compliance with the License. Please obtain a * copy of the License at http://www.apple.com/publicsource and read it before * using this file. * This Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, * QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the specific * language governing rights and limitations under the License. */ /* * mod_hfs_apple Apache module (enforce casing in URLs which need it) * * When a statement is found in the configuration file (this * discussion does not apply if .htaccess files are used instead) then * its directory path is supposed to apply to any URL which URI uses * that directory. In other words, a statement usually * defines some restrictions and any URL that goes to the targeted * directory (or its sub-directories) should "follow" those restrictions. * * On UFS volumes, since the file system is case-sensitive, a URI must * always match the actual path, in order for the file to be fetched. Any * statement will consequently be enforced. Because if there * is a case-mismatch a file-not-found error will be returned and if * there is no case-mismatch then relevant statements will * be walked through while parsing the URI. * * On HFS volumes, since the file system is case-insensitive, a URI may * not always case-match the actual path to the file that needs to be * fetched. That means that statements may not be walked * through if a case-mismatch appears in the URI (or in the statement) * in regards to the actual path stored on disk. Consequently, some * restrictive statements may be missed but the target file may still be * returned as response. In this situation we have a problem: to solve * it we should refuse such URL that case-mismatches part of the path * which, if not miscased, would actually make a statement * currently configured applies. * * That is what this modules does. Consequently, when this module is * installed, some "pseudo-case-sensitivity" is enforced when Apache * deals with HFS volumes. * * 13-JUN-2001 [JFA, Apple Computer, Inc.] * Initial version for Mac OS X Server 10.0. */ #ifdef DARWIN #define CORE_PRIVATE #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_request.h" #include "http_protocol.h" #include "http_log.h" #include "http_main.h" #include "util_script.h" #include "http_conf_globals.h" #define __MACHINEEXCEPTIONS__ #define __DRIVERSERVICES__ #include #include #include module MODULE_VAR_EXPORT hfs_apple_module; /* * Support routine: check_file_system * Check if the file system is case-sensitive assuming they all are * case-sensitive except HFS volumes. Return a Mac error. * The input path may not necessarily exist but its volume MUST. * This routine walks the path up if the specified file or directory * is not found. */ static int check_file_system(char *path, int *case_sensitive) { int len; /*OSStatus err;*/ int err; char *unixPath; struct statfs fsInfo; #define VOLUME "/Volumes" *case_sensitive = 1; len = (int) strlen(path); unixPath = malloc(len + 1); if (unixPath == NULL) return (int) memFullErr; strcpy(unixPath, path); err = statfs(unixPath, &fsInfo); /* Now check the type... */ if (-1 == err) { #if DEBUG perror("statfs failed because"); #endif } else { *case_sensitive = strcmp(fsInfo.f_fstypename, "hfs"); } free(unixPath); return (int) err; } /* * Our core data structure: each entry in the table is composed * of a key (the path of a statement, no matter what * server it applies to) and a value that tells whether its * volume is HFS or not (case-sensitive=0 or 1). Unfortunately * the work required to fill this table will be repeated for * each Apache child process (but there is nothing new here!) */ static pool *g_pool = NULL; static array_header *directories = NULL; typedef struct dir_rec { char *dir_path; int case_sens; } dir_rec; /* * Support routine that populates our table of directories * to be considered. We ignore what server configuration is * attached to the directory because it does not matter. */ static void add_directory_entry(request_rec *r, char *path) { char *dir_path; int i,case_sens = 0; dir_rec **elt; int len = strlen(path)+2; /* malloc dir_path so we can explicitly free it if the path * already exists in the cache, rather than leaving it in * apache's main pool. */ dir_path = malloc(len); if( dir_path == NULL ) return; memset(dir_path, 0, len); strcpy(dir_path, path); /* Make sure input path has a trailing slash */ if (path[strlen(path) - 1] != '/') strcat(dir_path, "/"); /* If the entry already exists then get out */ for (i = 0; i < directories->nelts; i++) { dir_rec *entry = ((dir_rec**) directories->elts)[i]; if (strcmp(dir_path, entry->dir_path) == 0) { free(dir_path); return; } } /* Figure whether the targeted volume is case-sensitive */ if (check_file_system(path, &case_sens) != 0) { case_sens = 0; } /* Add new entry to the table (ignore errors) */ elt = ap_push_array(directories); *elt = (dir_rec*) ap_pcalloc(g_pool, sizeof(dir_rec)); if (*elt == NULL) return; /* duplicate the path into apache's main pool (along with the rest * of the structure) so everything stays together. Then free what * we've malloc'd. */ (*elt)->dir_path = ap_pstrdup(g_pool, dir_path); free(dir_path); (*elt)->case_sens = case_sens; /* Print a debug notice */ #if DEBUG & 0 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_NOTICE, r, "%ld: %s is %s", (long) getpid(), (*elt)->dir_path, (*elt)->case_sens ? "UFS" : "HFS"); #endif }; /* * Support routine that updates our table of directory entries, * should be called whenever a request is received. */ static void update_directory_entries(request_rec *r) { core_server_config *sconf = (core_server_config*) ap_get_module_config(r->server->module_config, &core_module); void **sec = (void**) sconf->sec->elts; int i,num_sec = sconf->sec->nelts; /* Parse all "" statements for 'r->server' */ for (i = 0; i < num_sec; ++i) { core_dir_config *entry_core = (core_dir_config*) ap_get_module_config(sec[i], &core_module); if (entry_core == NULL || entry_core->d == NULL) continue; add_directory_entry(r, entry_core->d); } }; /* * Support routine that does a string compare of two paths (do not * care if trailing slashes are present). Return the number of * characters matched (or 0 else) if both paths are equal or if * 'child' is a sub-directory of 'parent'. In that very case also * returns 'related'=1. */ static int compare_paths(const char *parent, const char *child, int *related) { size_t pl,cl,i; const char *p,*c; size_t n = 0; *related = 0; /* Strip out trailing slashes */ pl = (size_t) strlen(parent); if (pl == 0) return 0; if (parent[pl - 1] == '/') pl--; cl = (size_t) strlen(child); if (cl == 0) return 0; if (child[cl - 1] == '/') cl--; if (cl < pl) return 0; /* Compare both paths */ for (p = parent,c = child,i = pl; i > 0; i--) { if (tolower(*p++) != tolower(*c++)) break; n++; } if (i > 0 || (cl > pl && *c != '/')) return 0; *related = cl >= pl; return n; }; #pragma mark- /* * Pre-run fixups: refuse a URL that is mis-cased if it happens * there is at least one statement that should have * applied. As input, this routine is passed a valid 'filename' * that can be a path to a directory or to a file. */ static int hfs_apple_module_fixups(request_rec *r) { int i,found; size_t max_n_matches; char *url_path; /* First update table of directory entries if necessary */ update_directory_entries(r); /* * Then compare our path to each statement we * found (case-insensitive compare) in order to find which * one applies, example (the second one would apply here): * 'filename'= * /Library/WebServer/Documents/MyFolder/printenv.cgi * 'directories' table= * /Library/WebServer/Documents/ * /Library/WebServer/Documents/MyFolder/ * /Library/WebServer/Documents/MyFolder/Zero/ * /Library/WebServer/Documents/MyFolder/Zero/One/ */ max_n_matches = 0; found = -1; if (r->filename[strlen(r->filename) - 1] != '/') { url_path = malloc(strlen(r->filename) +2); if( url_path == NULL ) return FORBIDDEN; strcpy(url_path, r->filename); strcat(url_path, "/"); url_path[strlen(url_path)-1] = '\0'; } else { url_path = malloc(strlen(r->filename) +1); if( url_path == NULL ) return FORBIDDEN; strcpy(url_path, r->filename); url_path[strlen(url_path)-1] = '\0'; } for (i = 0; i < directories->nelts; i++) { int related; size_t n_matches; dir_rec *entry = ((dir_rec**) directories->elts)[i]; if (entry->case_sens == 1) continue; n_matches = compare_paths( entry->dir_path, url_path, &related); if (n_matches > 0 && n_matches > max_n_matches && related == 1) { max_n_matches = n_matches; found = i; } } if (found < 0) { free(url_path); return OK; } /* * We found at least one statement that defines * the most immediate parent of 'filename'. Do a regular * case-sensitive compare on the directory portion of it. If * not-equal then return an error. */ if (strncmp(((dir_rec**) directories->elts)[found]->dir_path, url_path, max_n_matches) != 0) { #if DEBUG ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "Mis-cased URI: %s, wants: %s", r->filename, ((dir_rec**) directories->elts)[found]->dir_path); #endif free(url_path); return FORBIDDEN; } free(url_path); return OK; } /* * Initialization (called only once by Apache parent process). * We will be using the main pool not the request's one! */ static void hfs_apple_module_init(server_rec *s, pool *p) { g_pool = p; directories = ap_make_array(g_pool, 4, sizeof(dir_rec*)); }; #pragma mark DispatchTable /* * Module dispatch table. */ module MODULE_VAR_EXPORT hfs_apple_module = { STANDARD_MODULE_STUFF, hfs_apple_module_init, /* initializer */ NULL, /* dir config creater */ NULL, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server config */ NULL, /* command table */ NULL, /* handlers */ NULL, /* filename translation */ NULL, /* check user_id */ NULL, /* check auth */ NULL, /* check access */ NULL, /* type_checker */ hfs_apple_module_fixups, /* fixups */ NULL, /* logger */ NULL, /* header parser */ NULL, /* child_init */ NULL, /* child_exit */ NULL /* post read-request */ }; #endif