xref: /PHP-8.2/main/php_ini.c (revision 2ecd46f4)
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 HAVE_SCANDIR && HAVE_ALPHASORT && 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_sectin 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 
398 /* {{{ php_init_config */
php_init_config(void)399 int php_init_config(void)
400 {
401 	char *php_ini_file_name = NULL;
402 	char *php_ini_search_path = NULL;
403 	int php_ini_scanned_path_len;
404 	char *open_basedir;
405 	int free_ini_search_path = 0;
406 	zend_string *opened_path = NULL;
407 
408 	zend_hash_init(&configuration_hash, 8, NULL, config_zval_dtor, 1);
409 
410 	if (sapi_module.ini_defaults) {
411 		sapi_module.ini_defaults(&configuration_hash);
412 	}
413 
414 	zend_llist_init(&extension_lists.engine, sizeof(char *), (llist_dtor_func_t) free_estring, 1);
415 	zend_llist_init(&extension_lists.functions, sizeof(char *), (llist_dtor_func_t) free_estring, 1);
416 
417 	open_basedir = PG(open_basedir);
418 
419 	if (sapi_module.php_ini_path_override) {
420 		php_ini_file_name = sapi_module.php_ini_path_override;
421 		php_ini_search_path = sapi_module.php_ini_path_override;
422 		free_ini_search_path = 0;
423 	} else if (!sapi_module.php_ini_ignore) {
424 		int search_path_size;
425 		char *default_location;
426 		char *env_location;
427 		static const char paths_separator[] = { ZEND_PATHS_SEPARATOR, 0 };
428 #ifdef PHP_WIN32
429 		char *reg_location;
430 		char phprc_path[MAXPATHLEN];
431 #endif
432 
433 		env_location = getenv("PHPRC");
434 
435 #ifdef PHP_WIN32
436 		if (!env_location) {
437 			char dummybuf;
438 			int size;
439 
440 			SetLastError(0);
441 
442 			/*If the given buffer is not large enough to hold the data, the return value is
443 			the buffer size,  in characters, required to hold the string and its terminating
444 			null character. We use this return value to alloc the final buffer. */
445 			size = GetEnvironmentVariableA("PHPRC", &dummybuf, 0);
446 			if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
447 				/* The environment variable doesn't exist. */
448 				env_location = "";
449 			} else {
450 				if (size == 0) {
451 					env_location = "";
452 				} else {
453 					size = GetEnvironmentVariableA("PHPRC", phprc_path, size);
454 					if (size == 0) {
455 						env_location = "";
456 					} else {
457 						env_location = phprc_path;
458 					}
459 				}
460 			}
461 		}
462 #else
463 		if (!env_location) {
464 			env_location = "";
465 		}
466 #endif
467 		/*
468 		 * Prepare search path
469 		 */
470 
471 		search_path_size = MAXPATHLEN * 4 + (int)strlen(env_location) + 3 + 1;
472 		php_ini_search_path = (char *) emalloc(search_path_size);
473 		free_ini_search_path = 1;
474 		php_ini_search_path[0] = 0;
475 
476 		/* Add environment location */
477 		if (env_location[0]) {
478 			if (*php_ini_search_path) {
479 				strlcat(php_ini_search_path, paths_separator, search_path_size);
480 			}
481 			strlcat(php_ini_search_path, env_location, search_path_size);
482 			php_ini_file_name = env_location;
483 		}
484 
485 #ifdef PHP_WIN32
486 		/* Add registry location */
487 		reg_location = GetIniPathFromRegistry();
488 		if (reg_location != NULL) {
489 			if (*php_ini_search_path) {
490 				strlcat(php_ini_search_path, paths_separator, search_path_size);
491 			}
492 			strlcat(php_ini_search_path, reg_location, search_path_size);
493 			efree(reg_location);
494 		}
495 #endif
496 
497 		/* Add cwd (not with CLI) */
498 		if (!sapi_module.php_ini_ignore_cwd) {
499 			if (*php_ini_search_path) {
500 				strlcat(php_ini_search_path, paths_separator, search_path_size);
501 			}
502 			strlcat(php_ini_search_path, ".", search_path_size);
503 		}
504 
505 		if (PG(php_binary)) {
506 			char *separator_location, *binary_location;
507 
508 			binary_location = estrdup(PG(php_binary));
509 			separator_location = strrchr(binary_location, DEFAULT_SLASH);
510 
511 			if (separator_location && separator_location != binary_location) {
512 				*(separator_location) = 0;
513 			}
514 			if (*php_ini_search_path) {
515 				strlcat(php_ini_search_path, paths_separator, search_path_size);
516 			}
517 			strlcat(php_ini_search_path, binary_location, search_path_size);
518 			efree(binary_location);
519 		}
520 
521 		/* Add default location */
522 #ifdef PHP_WIN32
523 		default_location = (char *) emalloc(MAXPATHLEN + 1);
524 
525 		if (0 < GetWindowsDirectory(default_location, MAXPATHLEN)) {
526 			if (*php_ini_search_path) {
527 				strlcat(php_ini_search_path, paths_separator, search_path_size);
528 			}
529 			strlcat(php_ini_search_path, default_location, search_path_size);
530 		}
531 
532 		/* For people running under terminal services, GetWindowsDirectory will
533 		 * return their personal Windows directory, so lets add the system
534 		 * windows directory too */
535 		if (0 < GetSystemWindowsDirectory(default_location, MAXPATHLEN)) {
536 			if (*php_ini_search_path) {
537 				strlcat(php_ini_search_path, paths_separator, search_path_size);
538 			}
539 			strlcat(php_ini_search_path, default_location, search_path_size);
540 		}
541 		efree(default_location);
542 
543 #else
544 		default_location = PHP_CONFIG_FILE_PATH;
545 		if (*php_ini_search_path) {
546 			strlcat(php_ini_search_path, paths_separator, search_path_size);
547 		}
548 		strlcat(php_ini_search_path, default_location, search_path_size);
549 #endif
550 	}
551 
552 	PG(open_basedir) = NULL;
553 
554 	/*
555 	 * Find and open actual ini file
556 	 */
557 
558 	FILE *fp = NULL;
559 	char *filename = NULL;
560 	bool free_filename = false;
561 
562 	/* If SAPI does not want to ignore all ini files OR an overriding file/path is given.
563 	 * This allows disabling scanning for ini files in the PHP_CONFIG_FILE_SCAN_DIR but still
564 	 * load an optional ini file. */
565 	if (!sapi_module.php_ini_ignore || sapi_module.php_ini_path_override) {
566 
567 		/* Check if php_ini_file_name is a file and can be opened */
568 		if (php_ini_file_name && php_ini_file_name[0]) {
569 			zend_stat_t statbuf = {0};
570 
571 			if (!VCWD_STAT(php_ini_file_name, &statbuf)) {
572 				if (!((statbuf.st_mode & S_IFMT) == S_IFDIR)) {
573 					fp = VCWD_FOPEN(php_ini_file_name, "r");
574 					if (fp) {
575 						filename = expand_filepath(php_ini_file_name, NULL);
576 						free_filename = true;
577 					}
578 				}
579 			}
580 		}
581 
582 		/* Otherwise search for php-%sapi-module-name%.ini file in search path */
583 		if (!fp) {
584 			const char *fmt = "php-%s.ini";
585 			char *ini_fname;
586 			spprintf(&ini_fname, 0, fmt, sapi_module.name);
587 			fp = php_fopen_with_path(ini_fname, "r", php_ini_search_path, &opened_path);
588 			efree(ini_fname);
589 			if (fp) {
590 				filename = ZSTR_VAL(opened_path);
591 			}
592 		}
593 
594 		/* If still no ini file found, search for php.ini file in search path */
595 		if (!fp) {
596 			fp = php_fopen_with_path("php.ini", "r", php_ini_search_path, &opened_path);
597 			if (fp) {
598 				filename = ZSTR_VAL(opened_path);
599 			}
600 		}
601 	}
602 
603 	if (free_ini_search_path) {
604 		efree(php_ini_search_path);
605 	}
606 
607 	PG(open_basedir) = open_basedir;
608 
609 	if (fp) {
610 		zend_file_handle fh;
611 		zend_stream_init_fp(&fh, fp, filename);
612 		RESET_ACTIVE_INI_HASH();
613 
614 		zend_parse_ini_file(&fh, 1, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t) php_ini_parser_cb, &configuration_hash);
615 
616 		{
617 			zval tmp;
618 
619 			ZVAL_NEW_STR(&tmp, zend_string_init(filename, strlen(filename), 1));
620 			zend_hash_str_update(&configuration_hash, "cfg_file_path", sizeof("cfg_file_path")-1, &tmp);
621 			if (opened_path) {
622 				zend_string_release_ex(opened_path, 0);
623 			}
624 			php_ini_opened_path = zend_strndup(Z_STRVAL(tmp), Z_STRLEN(tmp));
625 		}
626 		zend_destroy_file_handle(&fh);
627 
628 		if (free_filename) {
629 			efree(filename);
630 		}
631 	}
632 
633 	/* Check for PHP_INI_SCAN_DIR environment variable to override/set config file scan directory */
634 	php_ini_scanned_path = getenv("PHP_INI_SCAN_DIR");
635 	if (!php_ini_scanned_path) {
636 		/* Or fall back using possible --with-config-file-scan-dir setting (defaults to empty string!) */
637 		php_ini_scanned_path = PHP_CONFIG_FILE_SCAN_DIR;
638 	}
639 	php_ini_scanned_path_len = (int)strlen(php_ini_scanned_path);
640 
641 	/* Scan and parse any .ini files found in scan path if path not empty. */
642 	if (!sapi_module.php_ini_ignore && php_ini_scanned_path_len) {
643 		struct dirent **namelist;
644 		int ndir, i;
645 		zend_stat_t sb = {0};
646 		char ini_file[MAXPATHLEN];
647 		char *p;
648 		zend_llist scanned_ini_list;
649 		zend_llist_element *element;
650 		int l, total_l = 0;
651 		char *bufpath, *debpath, *endpath;
652 		int lenpath;
653 
654 		zend_llist_init(&scanned_ini_list, sizeof(char *), (llist_dtor_func_t) free_estring, 1);
655 
656 		bufpath = estrdup(php_ini_scanned_path);
657 		for (debpath = bufpath ; debpath ; debpath=endpath) {
658 			endpath = strchr(debpath, DEFAULT_DIR_SEPARATOR);
659 			if (endpath) {
660 				*(endpath++) = 0;
661 			}
662 			if (!debpath[0]) {
663 				/* empty string means default builtin value
664 				   to allow "/foo/php.d:" or ":/foo/php.d" */
665 				debpath = PHP_CONFIG_FILE_SCAN_DIR;
666 			}
667 			lenpath = (int)strlen(debpath);
668 
669 			if (lenpath > 0 && (ndir = php_scandir(debpath, &namelist, 0, php_alphasort)) > 0) {
670 
671 				for (i = 0; i < ndir; i++) {
672 
673 					/* check for any file with .ini extension */
674 					if (!(p = strrchr(namelist[i]->d_name, '.')) || (p && strcmp(p, ".ini"))) {
675 						free(namelist[i]);
676 						continue;
677 					}
678 					/* Reset active ini section */
679 					RESET_ACTIVE_INI_HASH();
680 
681 					if (IS_SLASH(debpath[lenpath - 1])) {
682 						snprintf(ini_file, MAXPATHLEN, "%s%s", debpath, namelist[i]->d_name);
683 					} else {
684 						snprintf(ini_file, MAXPATHLEN, "%s%c%s", debpath, DEFAULT_SLASH, namelist[i]->d_name);
685 					}
686 					if (VCWD_STAT(ini_file, &sb) == 0) {
687 						if (S_ISREG(sb.st_mode)) {
688 							zend_file_handle fh;
689 							FILE *file = VCWD_FOPEN(ini_file, "r");
690 							if (file) {
691 								zend_stream_init_fp(&fh, file, ini_file);
692 								if (zend_parse_ini_file(&fh, 1, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t) php_ini_parser_cb, &configuration_hash) == SUCCESS) {
693 									/* Here, add it to the list of ini files read */
694 									l = (int)strlen(ini_file);
695 									total_l += l + 2;
696 									p = estrndup(ini_file, l);
697 									zend_llist_add_element(&scanned_ini_list, &p);
698 								}
699 								zend_destroy_file_handle(&fh);
700 							}
701 						}
702 					}
703 					free(namelist[i]);
704 				}
705 				free(namelist);
706 			}
707 		}
708 		efree(bufpath);
709 
710 		if (total_l) {
711 			int php_ini_scanned_files_len = (php_ini_scanned_files) ? (int)strlen(php_ini_scanned_files) + 1 : 0;
712 			php_ini_scanned_files = (char *) realloc(php_ini_scanned_files, php_ini_scanned_files_len + total_l + 1);
713 			if (!php_ini_scanned_files_len) {
714 				*php_ini_scanned_files = '\0';
715 			}
716 			total_l += php_ini_scanned_files_len;
717 			for (element = scanned_ini_list.head; element; element = element->next) {
718 				if (php_ini_scanned_files_len) {
719 					strlcat(php_ini_scanned_files, ",\n", total_l);
720 				}
721 				strlcat(php_ini_scanned_files, *(char **)element->data, total_l);
722 				strlcat(php_ini_scanned_files, element->next ? ",\n" : "\n", total_l);
723 			}
724 		}
725 		zend_llist_destroy(&scanned_ini_list);
726 	} else {
727 		/* Make sure an empty php_ini_scanned_path ends up as NULL */
728 		php_ini_scanned_path = NULL;
729 	}
730 
731 	if (sapi_module.ini_entries) {
732 		/* Reset active ini section */
733 		RESET_ACTIVE_INI_HASH();
734 		zend_parse_ini_string(sapi_module.ini_entries, 1, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t) php_ini_parser_cb, &configuration_hash);
735 	}
736 
737 	return SUCCESS;
738 }
739 /* }}} */
740 
741 /* {{{ php_shutdown_config */
php_shutdown_config(void)742 int php_shutdown_config(void)
743 {
744 	zend_hash_destroy(&configuration_hash);
745 	if (php_ini_opened_path) {
746 		free(php_ini_opened_path);
747 		php_ini_opened_path = NULL;
748 	}
749 	if (php_ini_scanned_files) {
750 		free(php_ini_scanned_files);
751 		php_ini_scanned_files = NULL;
752 	}
753 	return SUCCESS;
754 }
755 /* }}} */
756 
757 /* {{{ php_ini_register_extensions */
php_ini_register_extensions(void)758 void php_ini_register_extensions(void)
759 {
760 	zend_llist_apply(&extension_lists.engine, php_load_zend_extension_cb);
761 	zend_llist_apply(&extension_lists.functions, php_load_php_extension_cb);
762 
763 	zend_llist_destroy(&extension_lists.engine);
764 	zend_llist_destroy(&extension_lists.functions);
765 }
766 /* }}} */
767 
768 /* {{{ php_parse_user_ini_file */
php_parse_user_ini_file(const char * dirname,const char * ini_filename,HashTable * target_hash)769 PHPAPI int php_parse_user_ini_file(const char *dirname, const char *ini_filename, HashTable *target_hash)
770 {
771 	zend_stat_t sb = {0};
772 	char ini_file[MAXPATHLEN];
773 
774 	snprintf(ini_file, MAXPATHLEN, "%s%c%s", dirname, DEFAULT_SLASH, ini_filename);
775 
776 	if (VCWD_STAT(ini_file, &sb) == 0) {
777 		if (S_ISREG(sb.st_mode)) {
778 			zend_file_handle fh;
779 			int ret = FAILURE;
780 
781 			zend_stream_init_fp(&fh, VCWD_FOPEN(ini_file, "r"), ini_file);
782 			if (fh.handle.fp) {
783 				/* Reset active ini section */
784 				RESET_ACTIVE_INI_HASH();
785 
786 #if ZEND_RC_DEBUG
787 				/* User inis are parsed during SAPI activate (part of the request),
788 				 * but persistently allocated to allow caching. This is fine as long as
789 				 * strings are duplicated in php_ini_activate_config(). */
790 				bool orig_rc_debug = zend_rc_debug;
791 				zend_rc_debug = false;
792 #endif
793 				ret = zend_parse_ini_file(&fh, 1, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t) php_ini_parser_cb, target_hash);
794 #if ZEND_RC_DEBUG
795 				zend_rc_debug = orig_rc_debug;
796 #endif
797 				if (ret == SUCCESS) {
798 					/* FIXME: Add parsed file to the list of user files read? */
799 				}
800 			}
801 			zend_destroy_file_handle(&fh);
802 			return ret;
803 		}
804 	}
805 	return FAILURE;
806 }
807 /* }}} */
808 
809 /* {{{ php_ini_activate_config */
php_ini_activate_config(HashTable * source_hash,int modify_type,int stage)810 PHPAPI void php_ini_activate_config(HashTable *source_hash, int modify_type, int stage)
811 {
812 	zend_string *str;
813 	zval *data;
814 
815 	/* Walk through config hash and alter matching ini entries using the values found in the hash */
816 	ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(source_hash, str, data) {
817 		zend_string *data_str = zend_string_dup(Z_STR_P(data), 0);
818 		zend_alter_ini_entry_ex(str, data_str, modify_type, stage, 0);
819 		zend_string_release(data_str);
820 	} ZEND_HASH_FOREACH_END();
821 }
822 /* }}} */
823 
824 /* {{{ php_ini_has_per_dir_config */
php_ini_has_per_dir_config(void)825 PHPAPI int php_ini_has_per_dir_config(void)
826 {
827 	return has_per_dir_config;
828 }
829 /* }}} */
830 
831 /* {{{ php_ini_activate_per_dir_config */
php_ini_activate_per_dir_config(char * path,size_t path_len)832 PHPAPI void php_ini_activate_per_dir_config(char *path, size_t path_len)
833 {
834 	zval *tmp2;
835 	char *ptr;
836 
837 #ifdef PHP_WIN32
838 	char path_bak[MAXPATHLEN];
839 #endif
840 
841 #ifdef PHP_WIN32
842 	/* MAX_PATH is \0-terminated, path_len == MAXPATHLEN would overrun path_bak */
843 	if (path_len >= MAXPATHLEN) {
844 #else
845 	if (path_len > MAXPATHLEN) {
846 #endif
847 		return;
848 	}
849 
850 #ifdef PHP_WIN32
851 	memcpy(path_bak, path, path_len);
852 	path_bak[path_len] = 0;
853 	TRANSLATE_SLASHES_LOWER(path_bak);
854 	path = path_bak;
855 #endif
856 
857 	/* Walk through each directory in path and apply any found per-dir-system-configuration from configuration_hash */
858 	if (has_per_dir_config && path && path_len) {
859 		ptr = path + 1;
860 		while ((ptr = strchr(ptr, '/')) != NULL) {
861 			*ptr = 0;
862 			/* Search for source array matching the path from configuration_hash */
863 			if ((tmp2 = zend_hash_str_find(&configuration_hash, path, strlen(path))) != NULL) {
864 				php_ini_activate_config(Z_ARRVAL_P(tmp2), PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE);
865 			}
866 			*ptr = '/';
867 			ptr++;
868 		}
869 	}
870 }
871 /* }}} */
872 
873 /* {{{ php_ini_has_per_host_config */
874 PHPAPI int php_ini_has_per_host_config(void)
875 {
876 	return has_per_host_config;
877 }
878 /* }}} */
879 
880 /* {{{ php_ini_activate_per_host_config */
881 PHPAPI void php_ini_activate_per_host_config(const char *host, size_t host_len)
882 {
883 	zval *tmp;
884 
885 	if (has_per_host_config && host && host_len) {
886 		/* Search for source array matching the host from configuration_hash */
887 		if ((tmp = zend_hash_str_find(&configuration_hash, host, host_len)) != NULL) {
888 			php_ini_activate_config(Z_ARRVAL_P(tmp), PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE);
889 		}
890 	}
891 }
892 /* }}} */
893 
894 /* {{{ cfg_get_entry */
895 PHPAPI zval *cfg_get_entry_ex(zend_string *name)
896 {
897 	return zend_hash_find(&configuration_hash, name);
898 }
899 /* }}} */
900 
901 /* {{{ cfg_get_entry */
902 PHPAPI zval *cfg_get_entry(const char *name, size_t name_length)
903 {
904 	return zend_hash_str_find(&configuration_hash, name, name_length);
905 }
906 /* }}} */
907 
908 /* {{{ cfg_get_long */
909 PHPAPI int cfg_get_long(const char *varname, zend_long *result)
910 {
911 	zval *tmp;
912 
913 	if ((tmp = zend_hash_str_find(&configuration_hash, varname, strlen(varname))) == NULL) {
914 		*result = 0;
915 		return FAILURE;
916 	}
917 	*result = zval_get_long(tmp);
918 	return SUCCESS;
919 }
920 /* }}} */
921 
922 /* {{{ cfg_get_double */
923 PHPAPI int cfg_get_double(const char *varname, double *result)
924 {
925 	zval *tmp;
926 
927 	if ((tmp = zend_hash_str_find(&configuration_hash, varname, strlen(varname))) == NULL) {
928 		*result = (double) 0;
929 		return FAILURE;
930 	}
931 	*result = zval_get_double(tmp);
932 	return SUCCESS;
933 }
934 /* }}} */
935 
936 /* {{{ cfg_get_string */
937 PHPAPI int cfg_get_string(const char *varname, char **result)
938 {
939 	zval *tmp;
940 
941 	if ((tmp = zend_hash_str_find(&configuration_hash, varname, strlen(varname))) == NULL) {
942 		*result = NULL;
943 		return FAILURE;
944 	}
945 	*result = Z_STRVAL_P(tmp);
946 	return SUCCESS;
947 }
948 /* }}} */
949 
950 PHPAPI HashTable* php_ini_get_configuration_hash(void) /* {{{ */
951 {
952 	return &configuration_hash;
953 } /* }}} */
954