/* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2004 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.0 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_0.txt. | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Bart Vanbrabant | | Pierre-A. Joye | +----------------------------------------------------------------------+ */ /* $Id: htscanner.c,v 1.21 2007/03/21 00:00:05 pajoye Exp $ */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" #include "SAPI.h" #include "php_htscanner.h" #include "ext/standard/file.h" #include "ext/standard/php_string.h" ZEND_DECLARE_MODULE_GLOBALS(htscanner) #define FILE_BUFFER 1000 #define HTSCANNER_DEBUG 1 #define HTSCANNER_ENABLE_CACHE 0 #if HTSCANNER_DEBUG static void htscanner_debug(char *format, ...) /* {{{ */ { char output_buf[512]; va_list args; char *debug; debug = getenv("PHP_HTSCANNER_DEBUG"); if (debug == NULL) return; va_start (args, format); vsnprintf (output_buf, sizeof (output_buf), format, args); va_end (args); fputs (output_buf, stderr); fflush (stderr); } /* }}} */ #endif #define PHP_HTSCANNER_LTRIM(p) { \ while ((*p == ' ' || *p == '\t' || *p == '\r' || *p == '\v') && (*p != '\0')) { \ p++; \ } \ } #define RETURN_FAILURE(msg) { \ if (HTG(stop_on_error) > 0) { \ if (msg) { \ zend_error(E_WARNING, msg); \ } \ return FAILURE; \ } else { \ return SUCCESS; \ } \ } static int php_htscanner_ini_check_path(char *option_name, int option_len, char *new_option_name, int new_option_len) /* {{{ */ { if ( option_len != (new_option_len-1) ) { return 0; } return !strncmp(option_name, new_option_name, option_len); } /* }}} */ /* {{{ value_hnd * Parse an option and try to set the option */ static void value_hnd(char *string, int flag, int status, HashTable *ini_entries TSRMLS_DC) { char *name = string; char *value; int name_len; name = string; /* strip any leading whitespaces or tabs from the name */ PHP_HTSCANNER_LTRIM(name); value = strchr(name, ' '); if (value) { int len; *value = 0; name_len = strlen(name); ++value; /* strip any leading whitespaces or tabs from the value */ len = strlen(value); PHP_HTSCANNER_LTRIM(value); if (len > 2 && value[len - 2] == '\r') { value[len - 2] = 0; len -= 2; } else { value[len - 1] = 0; len--; } if (flag) { /* it's a flag */ if (!strcasecmp(value, "On") || (value[0] == '1' && value[1] == '\0')) { value = "1"; } else { value = "0"; } len = 1; } else { /* it's a value */ if (!strncasecmp(value, "none", sizeof("none"))) { value = ""; len = 0; } } #define _CHECK_PATH(var, var_len, ini) php_htscanner_ini_check_path(var, var_len, ini, sizeof(ini)) /* safe_mode & basedir check */ if (PG(safe_mode) || PG(open_basedir)) { if (_CHECK_PATH(name, name_len, "error_log") || _CHECK_PATH(name, name_len, "java.class.path") || _CHECK_PATH(name, name_len, "java.home") || _CHECK_PATH(name, name_len, "java.library.path") || _CHECK_PATH(name, name_len, "session.save_path") || _CHECK_PATH(name, name_len, "vpopmail.directory")) { if (PG(safe_mode) && !php_checkuid(value, NULL, CHECKUID_CHECK_FILE_AND_DIR)) { return; } if (php_check_open_basedir(value TSRMLS_CC)) { return; } } } /* checks that ensure the user does not overwrite certain ini settings when safe_mode is enabled */ if (PG(safe_mode)) { if (!strncmp("max_execution_time", name, sizeof("max_execution_time")) || !strncmp("memory_limit", name, sizeof("memory_limit")) || !strncmp("child_terminate", name, sizeof("child_terminate")) || !strncmp("open_basedir", name, sizeof("open_basedir")) || !strncmp("safe_mode", name, sizeof("safe_mode")) ) { return; } } if (zend_alter_ini_entry(name, name_len + 1, value, len, status, PHP_INI_STAGE_RUNTIME) == FAILURE) { zend_error(E_WARNING, "Adding option (Name: %s Value: %s) (%i, %i) failed!\n", name, value, name_len, len); return; } #if HTSCANNER_ENABLE_CACHE zend_hash_update(ini_entries, name, name_len + 1, value, len + 1, NULL); #endif } } /* }}} */ /* {{{ parse_config_file * Parse the configuration file */ static void parse_config_file(char *file, HashTable *ini_entries TSRMLS_DC) { php_stream *stream; /* see main/safemode.c:70 * for whatever reasons, it is not possible to call this function * without getting an error if the file does not exist, not * when safemode is On. * if one knows why, please let me know. * pierre@php.net */ if (PG(safe_mode)) { struct stat sb; if (VCWD_STAT(file, &sb) != 0) { return; } } stream = php_stream_open_wrapper(file, "rb", ENFORCE_SAFE_MODE, NULL); if (stream != NULL) { char buf[FILE_BUFFER]; char *pos; while ((pos = php_stream_gets(stream, buf, FILE_BUFFER)) != NULL) { /* strip leading spaces or tabs */ PHP_HTSCANNER_LTRIM(pos); if (strncmp(pos, "php_value", sizeof("php_value") - 1) == 0) { value_hnd(pos + sizeof("php_value"), 0, PHP_INI_PERDIR, ini_entries TSRMLS_CC); } else if (strncmp(pos, "php_flag", sizeof("php_flag") - 1) == 0) { value_hnd(pos + sizeof("php_flag"), 1, PHP_INI_PERDIR, ini_entries TSRMLS_CC); } } php_stream_close(stream); } } /* }}} */ /* {{{ get_doc_root * doc_root only seems to be available in _SERVER["doc_root"] * so get it there. */ static char* get_doc_root(TSRMLS_D) { zval **server, **data; if (zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **) &server) != FAILURE && (Z_TYPE_PP(server)==IS_ARRAY)) { zend_hash_internal_pointer_reset(Z_ARRVAL_PP(server)); if (zend_hash_find(Z_ARRVAL_PP(server), "DOCUMENT_ROOT", sizeof("DOCUMENT_ROOT"), (void **) &data) != FAILURE) { if (Z_TYPE_PP(data) == IS_STRING) { return Z_STRVAL_PP(data); } } } else { return HTG(default_docroot); } return NULL; } /* }}} */ /* True global resources - no need for thread safety here */ /* {{{ htscanner_functions[] * */ function_entry htscanner_functions[] = { {NULL, NULL, NULL} /* Must be the last line in htscanner_functions[] */ }; /* }}} */ /* {{{ htscanner_module_entry */ zend_module_entry htscanner_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif "htscanner", htscanner_functions, PHP_MINIT(htscanner), PHP_MSHUTDOWN(htscanner), PHP_RINIT(htscanner), PHP_RSHUTDOWN(htscanner), PHP_MINFO(htscanner), #if ZEND_MODULE_API_NO >= 20010901 HTSCANNER_VERSION, #endif STANDARD_MODULE_PROPERTIES }; /* }}} */ #ifdef COMPILE_DL_HTSCANNER ZEND_GET_MODULE(htscanner) #endif /* {{{ PHP_INI */ PHP_INI_BEGIN() STD_PHP_INI_ENTRY("htscanner.config_file", ".htaccess", PHP_INI_SYSTEM, OnUpdateString, config_file, zend_htscanner_globals, htscanner_globals) STD_PHP_INI_ENTRY("htscanner.default_docroot", "/", PHP_INI_SYSTEM, OnUpdateString, default_docroot, zend_htscanner_globals, htscanner_globals) #if (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 0) STD_PHP_INI_ENTRY("htscanner.default_ttl", "300", PHP_INI_SYSTEM, OnUpdateLong, default_ttl, zend_htscanner_globals, htscanner_globals) STD_PHP_INI_ENTRY("htscanner.stop_on_error", "0", PHP_INI_SYSTEM, OnUpdateLong, stop_on_error, zend_htscanner_globals, htscanner_globals) #else STD_PHP_INI_ENTRY("htscanner.default_ttl", "300", PHP_INI_SYSTEM, OnUpdateInt, default_ttl, zend_htscanner_globals, htscanner_globals) STD_PHP_INI_ENTRY("htscanner.stop_on_error", "0", PHP_INI_SYSTEM, OnUpdateInt, stop_on_error, zend_htscanner_globals, htscanner_globals) #endif PHP_INI_END() /* }}} */ #if HTSCANNER_ENABLE_CACHE htscannerMutexDeclare(ini_entries_cache_mutex); static htscanner_cache *ini_entries_cache = NULL; static void ini_cache_entry_dtor(void *data) /* {{{ */ { HashTable *entries = (HashTable *)data; if (entries) { zend_hash_destroy(entries); } } /* }}} */ static int php_htscanner_create_cache() /* {{{ */ { if (ini_entries_cache) { /* Already set up */ return SUCCESS; } htscannerMutexSetup(ini_entries_cache_mutex); ini_entries_cache = pemalloc(sizeof(htscanner_cache), 1); if (!ini_entries_cache) { return FAILURE; } ini_entries_cache->paths = malloc(sizeof(HashTable)); zend_hash_init(ini_entries_cache->paths, 0, NULL, ini_cache_entry_dtor, 1); return SUCCESS; } /* }}} */ #endif static void php_htscanner_init_globals(zend_htscanner_globals *htscanner_globals) /* {{{ */ { htscanner_globals->config_file = NULL; htscanner_globals->default_docroot = NULL; htscanner_globals->default_ttl = 5*60; htscanner_globals->stop_on_error = 0; } /* }}} */ PHP_MINIT_FUNCTION(htscanner) /* {{{ */ { ZEND_INIT_MODULE_GLOBALS(htscanner, php_htscanner_init_globals, NULL); REGISTER_INI_ENTRIES(); return SUCCESS; } /* }}} */ PHP_MSHUTDOWN_FUNCTION(htscanner) /* {{{ */ { #if HTSCANNER_ENABLE_CACHE if (ini_entries_cache) { htscannerMutexLock(ini_entries_cache_mutex); if (ini_entries_cache->paths) { free(ini_entries_cache->paths); } htscannerMutexShutdown(ini_entries_cache_mutex); } #endif UNREGISTER_INI_ENTRIES(); return SUCCESS; } /* }}} */ PHP_RINIT_FUNCTION(htscanner) /* {{{ */ { char *doc_root; char cwd[MAXPATHLEN + 1]; int cwd_len, doc_root_len; HashTable *ini_entries; time_t t; htscanner_cache_entry entry; htscanner_cache_entry *entry_fetched; doc_root = get_doc_root(TSRMLS_C); if (doc_root == NULL) { RETURN_FAILURE(NULL) } doc_root_len = strlen(doc_root); /* VCWD_GETCWD cannot be used in rinit, cwd is not yet initialized. Get it from path_translated. */ if (!SG(request_info).path_translated) { RETURN_FAILURE("No path translated, cannot determine the current script") } strcpy(cwd, SG(request_info).path_translated); php_dirname(cwd, strlen(cwd)); cwd_len = strlen(cwd); if (cwd[cwd_len] != PHP_DIR_SEPARATOR) { cwd[cwd_len] = PHP_DIR_SEPARATOR; cwd_len++; } cwd[cwd_len] = '\0'; #if HTSCANNER_ENABLE_CACHE if (!ini_entries_cache) { if (php_htscanner_create_cache() != SUCCESS) { RETURN_FAILURE("Cannot create the cache"); } } #if PHP_API_VERSION <= 20041225 t = time(0); #else t = sapi_get_request_time(TSRMLS_C); #endif htscannerMutexLock(ini_entries_cache_mutex); if (zend_hash_find(ini_entries_cache->paths, cwd, cwd_len, (void**)&entry_fetched) == SUCCESS) { /* fetch cache and assign */ if ((t - entry_fetched->created_on) < HTG(default_ttl)) { char *value, *name; HashPosition pos; uint len; ulong num; zend_hash_internal_pointer_reset_ex(entry_fetched->ini_entries, &pos); while (SUCCESS == zend_hash_get_current_data_ex(entry_fetched->ini_entries, (void**)&value, &pos)) { zend_hash_get_current_key_ex(entry_fetched->ini_entries, &name, &len, &num, 0, &pos); if (zend_alter_ini_entry(name, len, value, strlen(value), PHP_INI_PERDIR, PHP_INI_STAGE_RUNTIME) == FAILURE) { char msg[1024]; htscannerMutexUnlock(ini_entries_cache_mutex); vsnprintf (msg, sizeof (msg), "Adding option from cache (Name: '%s' Value: '%s') failed!\n", name, value); RETURN_FAILURE(msg); } zend_hash_move_forward_ex(entry_fetched->ini_entries, &pos); } htscannerMutexUnlock(ini_entries_cache_mutex); return SUCCESS; } } /* parse, insert, assign */ entry.created_on = t; ini_entries = malloc(sizeof(HashTable)); entry.ini_entries = ini_entries; zend_hash_init(ini_entries, 0, NULL, NULL, 1); #endif if (cwd != NULL && doc_root != NULL) { size_t i, ht_len, tmp; ht_len = strlen(HTG(config_file)); for (i = doc_root_len - 1; i < cwd_len; i++) { if (cwd[i] == PHP_DIR_SEPARATOR) { char file[MAXPATHLEN + 1]; tmp = i + 1 + ht_len; /* + 1 for trailing slash */ memset(file, 0, tmp); strncpy(file, cwd, i + 1); /* add a trailing 0 */ file[i + 1] = '\0'; strcat(file, HTG(config_file)); parse_config_file(file, ini_entries TSRMLS_CC); } } } #if HTSCANNER_ENABLE_CACHE zend_hash_add(ini_entries_cache->paths, cwd, cwd_len, &entry, sizeof(htscanner_cache_entry), NULL); htscannerMutexUnlock(ini_entries_cache_mutex); #endif return SUCCESS; } /* }}} */ PHP_RSHUTDOWN_FUNCTION(htscanner) /* {{{ */ { return SUCCESS; } /* }}} */ PHP_MINFO_FUNCTION(htscanner) /* {{{ */ { php_info_print_table_start(); php_info_print_table_row(2, "htscanner support", "enabled"); php_info_print_table_row(2, "Extension Version","$Id: htscanner.c,v 1.21 2007/03/21 00:00:05 pajoye Exp $"); php_info_print_table_row(2, "htaccess version", "0.8.1"); php_info_print_table_end(); DISPLAY_INI_ENTRIES(); } /* }}} */ /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: noet sw=4 ts=4 fdm=marker * vim<600: noet sw=4 ts=4 */