xref: /PHP-8.4/main/php_ini.c (revision 84a0da15)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Author: Zeev Suraski <zeev@php.net>                                  |
14    +----------------------------------------------------------------------+
15  */
16 
17 #include "php.h"
18 #include "ext/standard/info.h"
19 #include "zend_ini.h"
20 #include "zend_ini_scanner.h"
21 #include "php_ini.h"
22 #include "ext/standard/dl.h"
23 #include "zend_extensions.h"
24 #include "zend_highlight.h"
25 #include "SAPI.h"
26 #include "php_main.h"
27 #include "php_scandir.h"
28 #ifdef PHP_WIN32
29 #include "win32/php_registry.h"
30 #include "win32/winutil.h"
31 #endif
32 
33 #if defined(HAVE_SCANDIR) && defined(HAVE_ALPHASORT) && defined(HAVE_DIRENT_H)
34 #include <dirent.h>
35 #endif
36 
37 #ifdef PHP_WIN32
38 #define TRANSLATE_SLASHES_LOWER(path) \
39 	{ \
40 		char *tmp = path; \
41 		while (*tmp) { \
42 			if (*tmp == '\\') *tmp = '/'; \
43 			else *tmp = tolower(*tmp); \
44 				tmp++; \
45 		} \
46 	}
47 #else
48 #define TRANSLATE_SLASHES_LOWER(path)
49 #endif
50 
51 
52 typedef struct _php_extension_lists {
53 	zend_llist engine;
54 	zend_llist functions;
55 } php_extension_lists;
56 
57 /* True globals */
58 static int is_special_section = 0;
59 static HashTable *active_ini_hash;
60 static HashTable configuration_hash;
61 static int has_per_dir_config = 0;
62 static int has_per_host_config = 0;
63 PHPAPI char *php_ini_opened_path=NULL;
64 static php_extension_lists extension_lists;
65 PHPAPI char *php_ini_scanned_path=NULL;
66 PHPAPI char *php_ini_scanned_files=NULL;
67 
68 /* {{{ php_ini_displayer_cb */
php_ini_displayer_cb(zend_ini_entry * ini_entry,int type)69 static ZEND_COLD void php_ini_displayer_cb(zend_ini_entry *ini_entry, int type)
70 {
71 	if (ini_entry->displayer) {
72 		ini_entry->displayer(ini_entry, type);
73 	} else {
74 		char *display_string;
75 		size_t display_string_length;
76 		int esc_html=0;
77 
78 		if (type == ZEND_INI_DISPLAY_ORIG && ini_entry->modified) {
79 			if (ini_entry->orig_value && ZSTR_VAL(ini_entry->orig_value)[0]) {
80 				display_string = ZSTR_VAL(ini_entry->orig_value);
81 				display_string_length = ZSTR_LEN(ini_entry->orig_value);
82 				esc_html = !sapi_module.phpinfo_as_text;
83 			} else {
84 				if (!sapi_module.phpinfo_as_text) {
85 					display_string = "<i>no value</i>";
86 					display_string_length = sizeof("<i>no value</i>") - 1;
87 				} else {
88 					display_string = "no value";
89 					display_string_length = sizeof("no value") - 1;
90 				}
91 			}
92 		} else if (ini_entry->value && ZSTR_VAL(ini_entry->value)[0]) {
93 			display_string = ZSTR_VAL(ini_entry->value);
94 			display_string_length = ZSTR_LEN(ini_entry->value);
95 			esc_html = !sapi_module.phpinfo_as_text;
96 		} else {
97 			if (!sapi_module.phpinfo_as_text) {
98 				display_string = "<i>no value</i>";
99 				display_string_length = sizeof("<i>no value</i>") - 1;
100 			} else {
101 				display_string = "no value";
102 				display_string_length = sizeof("no value") - 1;
103 			}
104 		}
105 
106 		if (esc_html) {
107 			php_html_puts(display_string, display_string_length);
108 		} else {
109 			PHPWRITE(display_string, display_string_length);
110 		}
111 	}
112 }
113 /* }}} */
114 
115 /* {{{ display_ini_entries */
display_ini_entries(zend_module_entry * module)116 PHPAPI ZEND_COLD void display_ini_entries(zend_module_entry *module)
117 {
118 	int module_number;
119 	zend_ini_entry *ini_entry;
120 	bool first = 1;
121 
122 	if (module) {
123 		module_number = module->module_number;
124 	} else {
125 		module_number = 0;
126 	}
127 
128 	ZEND_HASH_MAP_FOREACH_PTR(EG(ini_directives), ini_entry) {
129 		if (ini_entry->module_number != module_number) {
130 			continue;
131 		}
132 		if (first) {
133 			php_info_print_table_start();
134 			php_info_print_table_header(3, "Directive", "Local Value", "Master Value");
135 			first = 0;
136 		}
137 		if (!sapi_module.phpinfo_as_text) {
138 			PUTS("<tr>");
139 			PUTS("<td class=\"e\">");
140 			PHPWRITE(ZSTR_VAL(ini_entry->name), ZSTR_LEN(ini_entry->name));
141 			PUTS("</td><td class=\"v\">");
142 			php_ini_displayer_cb(ini_entry, ZEND_INI_DISPLAY_ACTIVE);
143 			PUTS("</td><td class=\"v\">");
144 			php_ini_displayer_cb(ini_entry, ZEND_INI_DISPLAY_ORIG);
145 			PUTS("</td></tr>\n");
146 		} else {
147 			PHPWRITE(ZSTR_VAL(ini_entry->name), ZSTR_LEN(ini_entry->name));
148 			PUTS(" => ");
149 			php_ini_displayer_cb(ini_entry, ZEND_INI_DISPLAY_ACTIVE);
150 			PUTS(" => ");
151 			php_ini_displayer_cb(ini_entry, ZEND_INI_DISPLAY_ORIG);
152 			PUTS("\n");
153 		}
154 	} ZEND_HASH_FOREACH_END();
155 	if (!first) {
156 		php_info_print_table_end();
157 	}
158 }
159 /* }}} */
160 
161 /* php.ini support */
162 #define PHP_EXTENSION_TOKEN		"extension"
163 #define ZEND_EXTENSION_TOKEN	"zend_extension"
164 
165 /* {{{ config_zval_dtor */
config_zval_dtor(zval * zvalue)166 PHPAPI void config_zval_dtor(zval *zvalue)
167 {
168 	if (Z_TYPE_P(zvalue) == IS_ARRAY) {
169 		zend_hash_destroy(Z_ARRVAL_P(zvalue));
170 		free(Z_ARR_P(zvalue));
171 	} else if (Z_TYPE_P(zvalue) == IS_STRING) {
172 		zend_string_release_ex(Z_STR_P(zvalue), 1);
173 	}
174 }
175 /* Reset / free active_ini_section global */
176 #define RESET_ACTIVE_INI_HASH() do { \
177 	active_ini_hash = NULL;          \
178 	is_special_section = 0;          \
179 } while (0)
180 /* }}} */
181 
182 /* {{{ php_ini_parser_cb */
php_ini_parser_cb(zval * arg1,zval * arg2,zval * arg3,int callback_type,HashTable * target_hash)183 static void php_ini_parser_cb(zval *arg1, zval *arg2, zval *arg3, int callback_type, HashTable *target_hash)
184 {
185 	zval *entry;
186 	HashTable *active_hash;
187 	char *extension_name;
188 
189 	if (active_ini_hash) {
190 		active_hash = active_ini_hash;
191 	} else {
192 		active_hash = target_hash;
193 	}
194 
195 	switch (callback_type) {
196 		case ZEND_INI_PARSER_ENTRY: {
197 				if (!arg2) {
198 					/* bare string - nothing to do */
199 					break;
200 				}
201 
202 				/* PHP and Zend extensions are not added into configuration hash! */
203 				if (!is_special_section && zend_string_equals_literal_ci(Z_STR_P(arg1), PHP_EXTENSION_TOKEN)) { /* load PHP extension */
204 					extension_name = estrndup(Z_STRVAL_P(arg2), Z_STRLEN_P(arg2));
205 					zend_llist_add_element(&extension_lists.functions, &extension_name);
206 				} else if (!is_special_section && zend_string_equals_literal_ci(Z_STR_P(arg1), ZEND_EXTENSION_TOKEN)) { /* load Zend extension */
207 					extension_name = estrndup(Z_STRVAL_P(arg2), Z_STRLEN_P(arg2));
208 					zend_llist_add_element(&extension_lists.engine, &extension_name);
209 
210 				/* All other entries are added into either configuration_hash or active ini section array */
211 				} else {
212 					/* Store in active hash */
213 					entry = zend_hash_update(active_hash, Z_STR_P(arg1), arg2);
214 					Z_STR_P(entry) = zend_string_dup(Z_STR_P(entry), 1);
215 				}
216 			}
217 			break;
218 
219 		case ZEND_INI_PARSER_POP_ENTRY: {
220 				zval option_arr;
221 				zval *find_arr;
222 
223 				if (!arg2) {
224 					/* bare string - nothing to do */
225 					break;
226 				}
227 
228 /* fprintf(stdout, "ZEND_INI_PARSER_POP_ENTRY: %s[%s] = %s\n",Z_STRVAL_P(arg1), Z_STRVAL_P(arg3), Z_STRVAL_P(arg2)); */
229 
230 				/* If option not found in hash or is not an array -> create array, otherwise add to existing array */
231 				if ((find_arr = zend_hash_find(active_hash, Z_STR_P(arg1))) == NULL || Z_TYPE_P(find_arr) != IS_ARRAY) {
232 					ZVAL_NEW_PERSISTENT_ARR(&option_arr);
233 					zend_hash_init(Z_ARRVAL(option_arr), 8, NULL, config_zval_dtor, 1);
234 					find_arr = zend_hash_update(active_hash, Z_STR_P(arg1), &option_arr);
235 				}
236 
237 				/* arg3 is possible option offset name */
238 				if (arg3 && Z_STRLEN_P(arg3) > 0) {
239 					entry = zend_symtable_update(Z_ARRVAL_P(find_arr), Z_STR_P(arg3), arg2);
240 				} else {
241 					entry = zend_hash_next_index_insert(Z_ARRVAL_P(find_arr), arg2);
242 				}
243 				Z_STR_P(entry) = zend_string_dup(Z_STR_P(entry), 1);
244 			}
245 			break;
246 
247 		case ZEND_INI_PARSER_SECTION: { /* Create an array of entries of each section */
248 
249 /* fprintf(stdout, "ZEND_INI_PARSER_SECTION: %s\n",Z_STRVAL_P(arg1)); */
250 
251 				char *key = NULL;
252 				size_t key_len;
253 
254 				/* PATH sections */
255 				if (!zend_binary_strncasecmp(Z_STRVAL_P(arg1), Z_STRLEN_P(arg1), "PATH", sizeof("PATH") - 1, sizeof("PATH") - 1)) {
256 					key = Z_STRVAL_P(arg1);
257 					key = key + sizeof("PATH") - 1;
258 					key_len = Z_STRLEN_P(arg1) - sizeof("PATH") + 1;
259 					is_special_section = 1;
260 					has_per_dir_config = 1;
261 
262 					/* make the path lowercase on Windows, for case insensitivity. Does nothing for other platforms */
263 					TRANSLATE_SLASHES_LOWER(key);
264 
265 				/* HOST sections */
266 				} else if (!zend_binary_strncasecmp(Z_STRVAL_P(arg1), Z_STRLEN_P(arg1), "HOST", sizeof("HOST") - 1, sizeof("HOST") - 1)) {
267 					key = Z_STRVAL_P(arg1);
268 					key = key + sizeof("HOST") - 1;
269 					key_len = Z_STRLEN_P(arg1) - sizeof("HOST") + 1;
270 					is_special_section = 1;
271 					has_per_host_config = 1;
272 					zend_str_tolower(key, key_len); /* host names are case-insensitive. */
273 
274 				} else {
275 					is_special_section = 0;
276 				}
277 
278 				if (key && key_len > 0) {
279 					/* Strip any trailing slashes */
280 					while (key_len > 0 && (key[key_len - 1] == '/' || key[key_len - 1] == '\\')) {
281 						key_len--;
282 						key[key_len] = 0;
283 					}
284 
285 					/* Strip any leading whitespace and '=' */
286 					while (*key && (
287 						*key == '=' ||
288 						*key == ' ' ||
289 						*key == '\t'
290 					)) {
291 						key++;
292 						key_len--;
293 					}
294 
295 					/* Search for existing entry and if it does not exist create one */
296 					if ((entry = zend_hash_str_find(target_hash, key, key_len)) == NULL) {
297 						zval section_arr;
298 
299 						ZVAL_NEW_PERSISTENT_ARR(&section_arr);
300 						zend_hash_init(Z_ARRVAL(section_arr), 8, NULL, (dtor_func_t) config_zval_dtor, 1);
301 						entry = zend_hash_str_update(target_hash, key, key_len, &section_arr);
302 					}
303 					if (Z_TYPE_P(entry) == IS_ARRAY) {
304 						active_ini_hash = Z_ARRVAL_P(entry);
305 					}
306 				}
307 			}
308 			break;
309 	}
310 }
311 /* }}} */
312 
313 /* {{{ php_load_php_extension_cb */
php_load_php_extension_cb(void * arg)314 static void php_load_php_extension_cb(void *arg)
315 {
316 #ifdef HAVE_LIBDL
317 	php_load_extension(*((char **) arg), MODULE_PERSISTENT, 0);
318 #endif
319 }
320 /* }}} */
321 
322 /* {{{ php_load_zend_extension_cb */
323 #ifdef HAVE_LIBDL
php_load_zend_extension_cb(void * arg)324 static void php_load_zend_extension_cb(void *arg)
325 {
326 	char *filename = *((char **) arg);
327 	const size_t length = strlen(filename);
328 
329 #ifndef PHP_WIN32
330 	(void) length;
331 #endif
332 
333 	if (IS_ABSOLUTE_PATH(filename, length)) {
334 		zend_load_extension(filename);
335 	} else {
336 		DL_HANDLE handle;
337 		char *libpath;
338 		char *extension_dir = INI_STR("extension_dir");
339 		int slash_suffix = 0;
340 		char *err1, *err2;
341 
342 		if (extension_dir && extension_dir[0]) {
343 			slash_suffix = IS_SLASH(extension_dir[strlen(extension_dir)-1]);
344 		}
345 
346 		/* Try as filename first */
347 		if (slash_suffix) {
348 			spprintf(&libpath, 0, "%s%s", extension_dir, filename); /* SAFE */
349 		} else {
350 			spprintf(&libpath, 0, "%s%c%s", extension_dir, DEFAULT_SLASH, filename); /* SAFE */
351 		}
352 
353 		handle = (DL_HANDLE)php_load_shlib(libpath, &err1);
354 		if (!handle) {
355 			/* If file does not exist, consider as extension name and build file name */
356 			char *orig_libpath = libpath;
357 
358 			if (slash_suffix) {
359 				spprintf(&libpath, 0, "%s" PHP_SHLIB_EXT_PREFIX "%s." PHP_SHLIB_SUFFIX, extension_dir, filename); /* SAFE */
360 			} else {
361 				spprintf(&libpath, 0, "%s%c" PHP_SHLIB_EXT_PREFIX "%s." PHP_SHLIB_SUFFIX, extension_dir, DEFAULT_SLASH, filename); /* SAFE */
362 			}
363 
364 			handle = (DL_HANDLE)php_load_shlib(libpath, &err2);
365 			if (!handle) {
366 				php_error(E_CORE_WARNING, "Failed loading Zend extension '%s' (tried: %s (%s), %s (%s))",
367 					filename, orig_libpath, err1, libpath, err2);
368 				efree(orig_libpath);
369 				efree(err1);
370 				efree(libpath);
371 				efree(err2);
372 				return;
373 			}
374 
375 			efree(orig_libpath);
376 			efree(err1);
377 		}
378 
379 #ifdef PHP_WIN32
380 		if (!php_win32_image_compatible(handle, &err1)) {
381 				php_error(E_CORE_WARNING, err1);
382 				efree(err1);
383 				efree(libpath);
384 				DL_UNLOAD(handle);
385 				return;
386 		}
387 #endif
388 
389 		zend_load_extension_handle(handle, libpath);
390 		efree(libpath);
391 	}
392 }
393 #else
php_load_zend_extension_cb(void * arg)394 static void php_load_zend_extension_cb(void *arg) { }
395 #endif
396 /* }}} */
397 
append_ini_path(char * php_ini_search_path,size_t search_path_size,const char * path)398 static void append_ini_path(char *php_ini_search_path, size_t search_path_size, const char *path)
399 {
400 	static const char paths_separator[] = { ZEND_PATHS_SEPARATOR, 0 };
401 
402 	if (*php_ini_search_path) {
403 		strlcat(php_ini_search_path, paths_separator, search_path_size);
404 	}
405 
406 	strlcat(php_ini_search_path, path, search_path_size);
407 }
408 
409 /* {{{ php_init_config */
php_init_config(void)410 int php_init_config(void)
411 {
412 	char *php_ini_file_name = NULL;
413 	char *php_ini_search_path = NULL;
414 	int php_ini_scanned_path_len;
415 	char *open_basedir;
416 	int free_ini_search_path = 0;
417 	zend_string *opened_path = NULL;
418 
419 	zend_hash_init(&configuration_hash, 8, NULL, config_zval_dtor, 1);
420 
421 	if (sapi_module.ini_defaults) {
422 		sapi_module.ini_defaults(&configuration_hash);
423 	}
424 
425 	zend_llist_init(&extension_lists.engine, sizeof(char *), (llist_dtor_func_t) free_estring, 1);
426 	zend_llist_init(&extension_lists.functions, sizeof(char *), (llist_dtor_func_t) free_estring, 1);
427 
428 	open_basedir = PG(open_basedir);
429 
430 	if (sapi_module.php_ini_path_override) {
431 		php_ini_file_name = sapi_module.php_ini_path_override;
432 		php_ini_search_path = sapi_module.php_ini_path_override;
433 		free_ini_search_path = 0;
434 	} else if (!sapi_module.php_ini_ignore) {
435 		size_t search_path_size;
436 		char *default_location;
437 		char *env_location;
438 #ifdef PHP_WIN32
439 		char *reg_location;
440 		char phprc_path[MAXPATHLEN];
441 #endif
442 
443 		env_location = getenv("PHPRC");
444 
445 #ifdef PHP_WIN32
446 		if (!env_location) {
447 			char dummybuf;
448 			int size;
449 
450 			SetLastError(0);
451 
452 			/*If the given buffer is not large enough to hold the data, the return value is
453 			the buffer size,  in characters, required to hold the string and its terminating
454 			null character. We use this return value to alloc the final buffer. */
455 			size = GetEnvironmentVariableA("PHPRC", &dummybuf, 0);
456 			if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
457 				/* The environment variable doesn't exist. */
458 				env_location = "";
459 			} else {
460 				if (size == 0) {
461 					env_location = "";
462 				} else {
463 					size = GetEnvironmentVariableA("PHPRC", phprc_path, size);
464 					if (size == 0) {
465 						env_location = "";
466 					} else {
467 						env_location = phprc_path;
468 					}
469 				}
470 			}
471 		}
472 #else
473 		if (!env_location) {
474 			env_location = "";
475 		}
476 #endif
477 		/*
478 		 * Prepare search path
479 		 */
480 
481 		search_path_size = MAXPATHLEN * 4 + strlen(env_location) + 3 + 1;
482 		php_ini_search_path = (char *) emalloc(search_path_size);
483 		free_ini_search_path = 1;
484 		php_ini_search_path[0] = 0;
485 
486 		/* Add environment location */
487 		if (env_location[0]) {
488 			append_ini_path(php_ini_search_path, search_path_size, env_location);
489 			php_ini_file_name = env_location;
490 		}
491 
492 #ifdef PHP_WIN32
493 		/* Add registry location */
494 		reg_location = GetIniPathFromRegistry();
495 		if (reg_location != NULL) {
496 			append_ini_path(php_ini_search_path, search_path_size, reg_location);
497 			efree(reg_location);
498 		}
499 #endif
500 
501 		/* Add cwd (not with CLI) */
502 		if (!sapi_module.php_ini_ignore_cwd) {
503 			append_ini_path(php_ini_search_path, search_path_size, ".");
504 		}
505 
506 		if (PG(php_binary)) {
507 			char *separator_location, *binary_location;
508 
509 			binary_location = estrdup(PG(php_binary));
510 			separator_location = strrchr(binary_location, DEFAULT_SLASH);
511 
512 			if (separator_location && separator_location != binary_location) {
513 				*(separator_location) = 0;
514 			}
515 			append_ini_path(php_ini_search_path, search_path_size, binary_location);
516 
517 			efree(binary_location);
518 		}
519 
520 		/* Add default location */
521 #ifdef PHP_WIN32
522 		default_location = (char *) emalloc(MAXPATHLEN + 1);
523 
524 		if (0 < GetWindowsDirectory(default_location, MAXPATHLEN)) {
525 			append_ini_path(php_ini_search_path, search_path_size, default_location);
526 		}
527 
528 		/* For people running under terminal services, GetWindowsDirectory will
529 		 * return their personal Windows directory, so lets add the system
530 		 * windows directory too */
531 		if (0 < GetSystemWindowsDirectory(default_location, MAXPATHLEN)) {
532 			append_ini_path(php_ini_search_path, search_path_size, default_location);
533 		}
534 		efree(default_location);
535 
536 #else
537 		default_location = PHP_CONFIG_FILE_PATH;
538 		append_ini_path(php_ini_search_path, search_path_size, default_location);
539 #endif
540 	}
541 
542 	PG(open_basedir) = NULL;
543 
544 	/*
545 	 * Find and open actual ini file
546 	 */
547 
548 	FILE *fp = NULL;
549 	char *filename = NULL;
550 	bool free_filename = false;
551 
552 	/* If SAPI does not want to ignore all ini files OR an overriding file/path is given.
553 	 * This allows disabling scanning for ini files in the PHP_CONFIG_FILE_SCAN_DIR but still
554 	 * load an optional ini file. */
555 	if (!sapi_module.php_ini_ignore || sapi_module.php_ini_path_override) {
556 
557 		/* Check if php_ini_file_name is a file and can be opened */
558 		if (php_ini_file_name && php_ini_file_name[0]) {
559 			zend_stat_t statbuf = {0};
560 
561 			if (!VCWD_STAT(php_ini_file_name, &statbuf)) {
562 				if (!((statbuf.st_mode & S_IFMT) == S_IFDIR)) {
563 					fp = VCWD_FOPEN(php_ini_file_name, "r");
564 					if (fp) {
565 						filename = expand_filepath(php_ini_file_name, NULL);
566 						free_filename = true;
567 					}
568 				}
569 			}
570 		}
571 
572 		/* Otherwise search for php-%sapi-module-name%.ini file in search path */
573 		if (!fp) {
574 			const char *fmt = "php-%s.ini";
575 			char *ini_fname;
576 			spprintf(&ini_fname, 0, fmt, sapi_module.name);
577 			fp = php_fopen_with_path(ini_fname, "r", php_ini_search_path, &opened_path);
578 			efree(ini_fname);
579 			if (fp) {
580 				filename = ZSTR_VAL(opened_path);
581 			}
582 		}
583 
584 		/* If still no ini file found, search for php.ini file in search path */
585 		if (!fp) {
586 			fp = php_fopen_with_path("php.ini", "r", php_ini_search_path, &opened_path);
587 			if (fp) {
588 				filename = ZSTR_VAL(opened_path);
589 			}
590 		}
591 	}
592 
593 	if (free_ini_search_path) {
594 		efree(php_ini_search_path);
595 	}
596 
597 	PG(open_basedir) = open_basedir;
598 
599 	if (fp) {
600 		zend_file_handle fh;
601 		zend_stream_init_fp(&fh, fp, filename);
602 		RESET_ACTIVE_INI_HASH();
603 
604 		zend_parse_ini_file(&fh, 1, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t) php_ini_parser_cb, &configuration_hash);
605 
606 		{
607 			zval tmp;
608 
609 			ZVAL_NEW_STR(&tmp, zend_string_init(filename, strlen(filename), 1));
610 			zend_hash_str_update(&configuration_hash, "cfg_file_path", sizeof("cfg_file_path")-1, &tmp);
611 			if (opened_path) {
612 				zend_string_release_ex(opened_path, 0);
613 			}
614 			php_ini_opened_path = zend_strndup(Z_STRVAL(tmp), Z_STRLEN(tmp));
615 		}
616 		zend_destroy_file_handle(&fh);
617 
618 		if (free_filename) {
619 			efree(filename);
620 		}
621 	}
622 
623 	/* Check for PHP_INI_SCAN_DIR environment variable to override/set config file scan directory */
624 	php_ini_scanned_path = getenv("PHP_INI_SCAN_DIR");
625 	if (!php_ini_scanned_path) {
626 		/* Or fall back using possible --with-config-file-scan-dir setting (defaults to empty string!) */
627 		php_ini_scanned_path = PHP_CONFIG_FILE_SCAN_DIR;
628 	}
629 	php_ini_scanned_path_len = (int)strlen(php_ini_scanned_path);
630 
631 	/* Scan and parse any .ini files found in scan path if path not empty. */
632 	if (!sapi_module.php_ini_ignore && php_ini_scanned_path_len) {
633 		struct dirent **namelist;
634 		int ndir, i;
635 		zend_stat_t sb = {0};
636 		char ini_file[MAXPATHLEN];
637 		char *p;
638 		zend_llist scanned_ini_list;
639 		zend_llist_element *element;
640 		int l, total_l = 0;
641 		char *bufpath, *debpath, *endpath;
642 		int lenpath;
643 
644 		zend_llist_init(&scanned_ini_list, sizeof(char *), (llist_dtor_func_t) free_estring, 1);
645 
646 		bufpath = estrdup(php_ini_scanned_path);
647 		for (debpath = bufpath ; debpath ; debpath=endpath) {
648 			endpath = strchr(debpath, DEFAULT_DIR_SEPARATOR);
649 			if (endpath) {
650 				*(endpath++) = 0;
651 			}
652 			if (!debpath[0]) {
653 				/* empty string means default builtin value
654 				   to allow "/foo/php.d:" or ":/foo/php.d" */
655 				debpath = PHP_CONFIG_FILE_SCAN_DIR;
656 			}
657 			lenpath = (int)strlen(debpath);
658 
659 			if (lenpath > 0 && (ndir = php_scandir(debpath, &namelist, 0, php_alphasort)) > 0) {
660 
661 				for (i = 0; i < ndir; i++) {
662 
663 					/* check for any file with .ini extension */
664 					if (!(p = strrchr(namelist[i]->d_name, '.')) || (p && strcmp(p, ".ini"))) {
665 						free(namelist[i]);
666 						continue;
667 					}
668 					/* Reset active ini section */
669 					RESET_ACTIVE_INI_HASH();
670 
671 					if (IS_SLASH(debpath[lenpath - 1])) {
672 						snprintf(ini_file, MAXPATHLEN, "%s%s", debpath, namelist[i]->d_name);
673 					} else {
674 						snprintf(ini_file, MAXPATHLEN, "%s%c%s", debpath, DEFAULT_SLASH, namelist[i]->d_name);
675 					}
676 					if (VCWD_STAT(ini_file, &sb) == 0) {
677 						if (S_ISREG(sb.st_mode)) {
678 							zend_file_handle fh;
679 							FILE *file = VCWD_FOPEN(ini_file, "r");
680 							if (file) {
681 								zend_stream_init_fp(&fh, file, ini_file);
682 								if (zend_parse_ini_file(&fh, 1, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t) php_ini_parser_cb, &configuration_hash) == SUCCESS) {
683 									/* Here, add it to the list of ini files read */
684 									l = (int)strlen(ini_file);
685 									total_l += l + 2;
686 									p = estrndup(ini_file, l);
687 									zend_llist_add_element(&scanned_ini_list, &p);
688 								}
689 								zend_destroy_file_handle(&fh);
690 							}
691 						}
692 					}
693 					free(namelist[i]);
694 				}
695 				free(namelist);
696 			}
697 		}
698 		efree(bufpath);
699 
700 		if (total_l) {
701 			int php_ini_scanned_files_len = (php_ini_scanned_files) ? (int)strlen(php_ini_scanned_files) + 1 : 0;
702 			php_ini_scanned_files = (char *) realloc(php_ini_scanned_files, php_ini_scanned_files_len + total_l + 1);
703 			if (!php_ini_scanned_files_len) {
704 				*php_ini_scanned_files = '\0';
705 			}
706 			total_l += php_ini_scanned_files_len;
707 			for (element = scanned_ini_list.head; element; element = element->next) {
708 				if (php_ini_scanned_files_len) {
709 					strlcat(php_ini_scanned_files, ",\n", total_l);
710 				}
711 				strlcat(php_ini_scanned_files, *(char **)element->data, total_l);
712 				strlcat(php_ini_scanned_files, element->next ? ",\n" : "\n", total_l);
713 			}
714 		}
715 		zend_llist_destroy(&scanned_ini_list);
716 	} else {
717 		/* Make sure an empty php_ini_scanned_path ends up as NULL */
718 		php_ini_scanned_path = NULL;
719 	}
720 
721 	if (sapi_module.ini_entries) {
722 		/* Reset active ini section */
723 		RESET_ACTIVE_INI_HASH();
724 		zend_parse_ini_string(sapi_module.ini_entries, 1, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t) php_ini_parser_cb, &configuration_hash);
725 	}
726 
727 	return SUCCESS;
728 }
729 /* }}} */
730 
731 /* {{{ php_shutdown_config */
php_shutdown_config(void)732 int php_shutdown_config(void)
733 {
734 	zend_hash_destroy(&configuration_hash);
735 	if (php_ini_opened_path) {
736 		free(php_ini_opened_path);
737 		php_ini_opened_path = NULL;
738 	}
739 	if (php_ini_scanned_files) {
740 		free(php_ini_scanned_files);
741 		php_ini_scanned_files = NULL;
742 	}
743 	return SUCCESS;
744 }
745 /* }}} */
746 
747 /* {{{ php_ini_register_extensions */
php_ini_register_extensions(void)748 void php_ini_register_extensions(void)
749 {
750 	zend_llist_apply(&extension_lists.engine, php_load_zend_extension_cb);
751 	zend_llist_apply(&extension_lists.functions, php_load_php_extension_cb);
752 
753 	zend_llist_destroy(&extension_lists.engine);
754 	zend_llist_destroy(&extension_lists.functions);
755 }
756 /* }}} */
757 
758 /* {{{ php_parse_user_ini_file */
php_parse_user_ini_file(const char * dirname,const char * ini_filename,HashTable * target_hash)759 PHPAPI int php_parse_user_ini_file(const char *dirname, const char *ini_filename, HashTable *target_hash)
760 {
761 	zend_stat_t sb = {0};
762 	char ini_file[MAXPATHLEN];
763 
764 	snprintf(ini_file, MAXPATHLEN, "%s%c%s", dirname, DEFAULT_SLASH, ini_filename);
765 
766 	if (VCWD_STAT(ini_file, &sb) == 0) {
767 		if (S_ISREG(sb.st_mode)) {
768 			zend_file_handle fh;
769 			int ret = FAILURE;
770 
771 			zend_stream_init_fp(&fh, VCWD_FOPEN(ini_file, "r"), ini_file);
772 			if (fh.handle.fp) {
773 				/* Reset active ini section */
774 				RESET_ACTIVE_INI_HASH();
775 
776 #if ZEND_RC_DEBUG
777 				/* User inis are parsed during SAPI activate (part of the request),
778 				 * but persistently allocated to allow caching. This is fine as long as
779 				 * strings are duplicated in php_ini_activate_config(). */
780 				bool orig_rc_debug = zend_rc_debug;
781 				zend_rc_debug = false;
782 #endif
783 				ret = zend_parse_ini_file(&fh, 1, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t) php_ini_parser_cb, target_hash);
784 #if ZEND_RC_DEBUG
785 				zend_rc_debug = orig_rc_debug;
786 #endif
787 				if (ret == SUCCESS) {
788 					/* FIXME: Add parsed file to the list of user files read? */
789 				}
790 			}
791 			zend_destroy_file_handle(&fh);
792 			return ret;
793 		}
794 	}
795 	return FAILURE;
796 }
797 /* }}} */
798 
799 /* {{{ php_ini_activate_config */
php_ini_activate_config(HashTable * source_hash,int modify_type,int stage)800 PHPAPI void php_ini_activate_config(HashTable *source_hash, int modify_type, int stage)
801 {
802 	zend_string *str;
803 	zval *data;
804 
805 	/* Walk through config hash and alter matching ini entries using the values found in the hash */
806 	ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(source_hash, str, data) {
807 		zend_string *data_str = zend_string_dup(Z_STR_P(data), 0);
808 		zend_alter_ini_entry_ex(str, data_str, modify_type, stage, 0);
809 		zend_string_release(data_str);
810 	} ZEND_HASH_FOREACH_END();
811 }
812 /* }}} */
813 
814 /* {{{ php_ini_has_per_dir_config */
php_ini_has_per_dir_config(void)815 PHPAPI int php_ini_has_per_dir_config(void)
816 {
817 	return has_per_dir_config;
818 }
819 /* }}} */
820 
821 /* {{{ php_ini_activate_per_dir_config */
php_ini_activate_per_dir_config(char * path,size_t path_len)822 PHPAPI void php_ini_activate_per_dir_config(char *path, size_t path_len)
823 {
824 	zval *tmp2;
825 	char *ptr;
826 
827 #ifdef PHP_WIN32
828 	char path_bak[MAXPATHLEN];
829 #endif
830 
831 #ifdef PHP_WIN32
832 	/* MAX_PATH is \0-terminated, path_len == MAXPATHLEN would overrun path_bak */
833 	if (path_len >= MAXPATHLEN) {
834 #else
835 	if (path_len > MAXPATHLEN) {
836 #endif
837 		return;
838 	}
839 
840 #ifdef PHP_WIN32
841 	memcpy(path_bak, path, path_len);
842 	path_bak[path_len] = 0;
843 	TRANSLATE_SLASHES_LOWER(path_bak);
844 	path = path_bak;
845 #endif
846 
847 	/* Walk through each directory in path and apply any found per-dir-system-configuration from configuration_hash */
848 	if (has_per_dir_config && path && path_len) {
849 		ptr = path + 1;
850 		while ((ptr = strchr(ptr, '/')) != NULL) {
851 			*ptr = 0;
852 			/* Search for source array matching the path from configuration_hash */
853 			if ((tmp2 = zend_hash_str_find(&configuration_hash, path, strlen(path))) != NULL) {
854 				php_ini_activate_config(Z_ARRVAL_P(tmp2), PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE);
855 			}
856 			*ptr = '/';
857 			ptr++;
858 		}
859 	}
860 }
861 /* }}} */
862 
863 /* {{{ php_ini_has_per_host_config */
864 PHPAPI int php_ini_has_per_host_config(void)
865 {
866 	return has_per_host_config;
867 }
868 /* }}} */
869 
870 /* {{{ php_ini_activate_per_host_config */
871 PHPAPI void php_ini_activate_per_host_config(const char *host, size_t host_len)
872 {
873 	zval *tmp;
874 
875 	if (has_per_host_config && host && host_len) {
876 		/* Search for source array matching the host from configuration_hash */
877 		if ((tmp = zend_hash_str_find(&configuration_hash, host, host_len)) != NULL) {
878 			php_ini_activate_config(Z_ARRVAL_P(tmp), PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE);
879 		}
880 	}
881 }
882 /* }}} */
883 
884 /* {{{ cfg_get_entry */
885 PHPAPI zval *cfg_get_entry_ex(zend_string *name)
886 {
887 	return zend_hash_find(&configuration_hash, name);
888 }
889 /* }}} */
890 
891 /* {{{ cfg_get_entry */
892 PHPAPI zval *cfg_get_entry(const char *name, size_t name_length)
893 {
894 	return zend_hash_str_find(&configuration_hash, name, name_length);
895 }
896 /* }}} */
897 
898 /* {{{ cfg_get_long */
899 PHPAPI int cfg_get_long(const char *varname, zend_long *result)
900 {
901 	zval *tmp;
902 
903 	if ((tmp = zend_hash_str_find(&configuration_hash, varname, strlen(varname))) == NULL) {
904 		*result = 0;
905 		return FAILURE;
906 	}
907 	*result = zval_get_long(tmp);
908 	return SUCCESS;
909 }
910 /* }}} */
911 
912 /* {{{ cfg_get_double */
913 PHPAPI int cfg_get_double(const char *varname, double *result)
914 {
915 	zval *tmp;
916 
917 	if ((tmp = zend_hash_str_find(&configuration_hash, varname, strlen(varname))) == NULL) {
918 		*result = (double) 0;
919 		return FAILURE;
920 	}
921 	*result = zval_get_double(tmp);
922 	return SUCCESS;
923 }
924 /* }}} */
925 
926 /* {{{ cfg_get_string */
927 PHPAPI int cfg_get_string(const char *varname, char **result)
928 {
929 	zval *tmp;
930 
931 	if ((tmp = zend_hash_str_find(&configuration_hash, varname, strlen(varname))) == NULL) {
932 		*result = NULL;
933 		return FAILURE;
934 	}
935 	*result = Z_STRVAL_P(tmp);
936 	return SUCCESS;
937 }
938 /* }}} */
939 
940 PHPAPI HashTable* php_ini_get_configuration_hash(void) /* {{{ */
941 {
942 	return &configuration_hash;
943 } /* }}} */
944