Project

General

Profile

Download (40.7 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * config.lib.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2013 BSD Perimeter
7
 * Copyright (c) 2013-2016 Electric Sheep Fencing
8
 * Copyright (c) 2014-2024 Rubicon Communications, LLC (Netgate)
9
 * Copyright (c) 2009 Erik Kristensen
10
 * All rights reserved.
11
 *
12
 * originally part of m0n0wall (http://m0n0.ch/wall)
13
 * Copyright (c) 2003-2004 Manuel Kasper <mk@neon1.net>.
14
 * All rights reserved.
15
 *
16
 * Licensed under the Apache License, Version 2.0 (the "License");
17
 * you may not use this file except in compliance with the License.
18
 * You may obtain a copy of the License at
19
 *
20
 * http://www.apache.org/licenses/LICENSE-2.0
21
 *
22
 * Unless required by applicable law or agreed to in writing, software
23
 * distributed under the License is distributed on an "AS IS" BASIS,
24
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25
 * See the License for the specific language governing permissions and
26
 * limitations under the License.
27
 */
28

    
29
require_once("xmlparse.inc");
30
require_once('util.inc');
31
require_once('globals.inc');
32

    
33
/****f* config/encrypted_configxml
34
 * NAME
35
 *   encrypted_configxml - Checks to see if config.xml is encrypted and if so, prompts to unlock.
36
 * INPUTS
37
 *   None
38
 * RESULT
39
 *   Rewrites config.xml without encryption
40
 ******/
41
function encrypted_configxml(string $file_name = null) {
42
	// we can only prompt for password if the PHP script is running in a cli
43
	if (!is_cli_sapi()) {
44
		return false;
45
	}
46

    
47
	if (!isset($file_name)) {
48
		$file_name = g_get('conf_path') . "/config.xml";
49
	}
50

    
51
	if (!file_exists($file_name)) {
52
		return false;
53
	}
54

    
55
	$configtxt = file_get_contents($file_name);
56
	if (tagfile_deformat($configtxt, $configtxt, "config.xml")) {
57
		$fp = fopen('php://stdin', 'r');
58
		$data = "";
59
		echo "\n\n*** Encrypted config.xml detected ***\n";
60
		while ($data == "") {
61
			echo "\nEnter the password to decrypt config.xml: ";
62
			$decrypt_password = chop(fgets($fp));
63
			$data = decrypt_data($configtxt, $decrypt_password);
64
			if (!strstr($data, "<pfsense>")) {
65
				$data = "";
66
			}
67
			if ($data) {
68
				$fd = fopen("{$file_name}.tmp", "w");
69
				fwrite($fd, $data);
70
				fclose($fd);
71
				exec("/bin/mv '{$file_name}.tmp' '{$file_name}.decrypted'");
72
				echo "\n" . gettext("config.xml unlocked.") . "\n";
73
				fclose($fp);
74
				return "{$file_name}.decrypted";
75
			} else {
76
				echo "\n" . gettext("Invalid password entered. Please try again.") . "\n";
77
			}
78
		}
79
	}
80

    
81
	return false;
82
}
83

    
84
/****f* config/generate_config_cache
85
 * NAME
86
 *   generate_config_cache - Write serialized configuration to cache.
87
 * INPUTS
88
 *   $config	- array containing current firewall configuration
89
 * RESULT
90
 *   boolean	- true on completion
91
 ******/
92
function generate_config_cache($config) {
93
	global $g, $config_extra;
94

    
95
	$configcache = fopen(g_get('tmp_path') . '/config.cache', "w");
96
	if (!$configcache) {
97
		/* Cannot open file for writing, so stop here. */
98
		return false;
99
	}
100
	fwrite($configcache, serialize($config));
101
	fclose($configcache);
102

    
103
	unset($configcache);
104
	/* Used for config.extra.xml */
105
	if (file_exists(g_get('tmp_path') . '/config.extra.cache') && $config_extra) {
106
		$configcacheextra = fopen(g_get('tmp_path') . '/config.extra.cache', "w");
107
		fwrite($configcacheextra, serialize($config_extra));
108
		fclose($configcacheextra);
109
		unset($configcacheextra);
110
	}
111
	return true;
112
}
113

    
114
function discover_last_backup() {
115
	global $g;
116

    
117
	$backup_files = glob('/cf/conf/backup/*.xml');
118
	if (!is_array($backup_files)) {
119
		return false;
120
	}
121
	$backup_files = array_filter($backup_files,'is_file');
122
	natsort($backup_files);
123

    
124
	// check newest file first
125
	foreach (array_reverse($backup_files) as $file_name) {
126
		/* checking multiple backups when detecting invalid configuration
127
		 * https://redmine.pfsense.org/issues/11748 */
128
		if (empty(filesize($file_name))) {
129
			continue;
130
		}
131

    
132
		// Check for encrypted config.xml
133
		$backup_decrypted = encrypted_configxml($file_name);
134
		if (is_string($backup_decrypted)) {
135
			$file_name = $backup_decrypted;
136
		}
137

    
138
		$backup_config = parse_xml_config($file_name, g_get('xml_rootobj'));
139
		if (is_array($backup_config)) {
140
			return [
141
				'path' => $file_name,
142
				'config' => $backup_config
143
			];
144
		}
145
	}
146

    
147
	return false;
148
}
149

    
150
function restore_backup(string|array $backup) {
151
	if (is_array($backup)) {
152
		if (!is_string($backup['path']) || empty($backup['path'])) {
153
			return false;
154
		}
155
		$file = $backup['path'];
156

    
157
		if (is_array($backup['config']) && !empty($backup['config'])) {
158
			$conf = $backup['config'];
159
		} else {
160
			$conf = parse_xml_config($file, g_get('xml_rootobj'));
161
			if (!is_array($conf)) {
162
				return false;
163
			}
164
		}
165
	} else {
166
		$file = $backup;
167
		if (!file_exists($file)) {
168
			return false;
169
		}
170
		$conf = parse_xml_config($file, g_get('xml_rootobj'));
171
		if (!is_array($conf)) {
172
			return false;
173
		}
174
	}
175

    
176
	$data = file_get_contents($file);
177
	// restore RRD
178
	if ($conf['rrddata']) {
179
		restore_rrddata($conf);
180
		$data = clear_tagdata("rrd", $data);
181
	}
182
	// restore SSH keys
183
	if ($conf['sshdata']) {
184
		restore_sshdata($conf);
185
		$data = clear_tagdata("ssh", $data);
186
	}
187
	// restore additional data
188
	foreach (g_get('backuppath') as $bk => $path) {
189
		if (!is_array(array_get_path($conf, "{$bk}/{$bk}data/xmldatafile"))) {
190
			continue;
191
		}
192
		restore_xmldatafile($bk, $conf);
193
		$data = clear_tagdata($bk, $data);
194
	}
195
	file_put_contents($file, $data);
196

    
197
	$lockkey = lock('config', LOCK_EX);
198
	if (!isset($lockkey)) {
199
		return false;
200
	}
201
	unlink_if_exists(g_get('tmp_path') . '/config.cache');
202
	copy($file, g_get('cf_conf_path') . '/config.xml');
203
	unlock($lockkey);
204

    
205
	disable_security_checks();
206

    
207
	return true;
208
}
209

    
210
/* If the config on disk had rrddata/xmldata tags already, remove that section first.
211
 * See https://redmine.pfsense.org/issues/8994,
212
 *     https://redmine.pfsense.org/issues/10508,
213
 *     https://redmine.pfsense.org/issues/11050 */
214
function clear_tagdata($tag, $data) {
215
	$data = preg_replace("/[[:blank:]]*<{$tag}data>.*<\\/{$tag}data>[[:blank:]]*\n*/s", "", $data);
216
	$data = preg_replace("/[[:blank:]]*<{$tag}data\\/>[[:blank:]]*\n*/", "", $data);
217

    
218
	return $data;
219
}
220

    
221
function restore_xmldatafile($type='voucher', $conf = false) {
222
	global $g;
223

    
224
	if (!$conf) {
225
		$conf = config_get_path('');
226
	}
227

    
228
	foreach ($conf[$type]["{$type}data"]["xmldatafile"] as $file) {
229
		$basename = basename($file['filename']);
230
		$dirname = '';
231
		if (isset($file['path'])) {
232
			$dirname = $file['path'];
233
		} else {
234
			/* Handle restoring older backups without a path. If
235
			   multiple paths are given, use the first path with a
236
			   matching file name. If there are no matching names,
237
			   default to the first path. */
238
			$paths = explode(',', trim($g['backuppath'][$type], '{}'));
239
			$dirname = dirname($paths[array_key_first($paths)]);
240
			foreach ($paths as $path) {
241
				if (basename($path) == $basename) {
242
					$dirname = dirname($path);
243
					break;
244
				}
245
			}
246
		}
247

    
248
		$xmldata_file = "{$dirname}/{$basename}";
249
		if (!is_dir($dirname)) {
250
			safe_mkdir($dirname);
251
		}
252
		if (file_put_contents($xmldata_file, gzinflate(base64_decode($file['data']))) === false) {
253
			log_error(sprintf(gettext("Cannot write %s"), $xmldata_file));
254
			continue;
255
		}
256
	}
257
}
258

    
259
function restore_rrddata($conf = false) {
260
	global $g, $rrdtool, $input_errors;
261

    
262
	if (!$conf) {
263
		$conf = config_get_path('');
264
	}
265

    
266
	foreach ($conf['rrddata']['rrddatafile'] as $rrd) {
267
		if ($rrd['xmldata']) {
268
			$rrd_file = "{$g['vardb_path']}/rrd/" . basename($rrd['filename']);
269
			$xml_file = preg_replace('/\.rrd$/', ".xml", $rrd_file);
270
			if (file_put_contents($xml_file, gzinflate(base64_decode($rrd['xmldata']))) === false) {
271
				log_error(sprintf(gettext("Cannot write %s"), $xml_file));
272
				continue;
273
			}
274
			$output = array();
275
			$status = null;
276
			exec("{$rrdtool} restore -f " . escapeshellarg($xml_file) . ' ' . escapeshellarg($rrd_file), $output, $status);
277
			if ($status) {
278
				log_error("rrdtool restore -f '{$xml_file}' '{$rrd_file}' failed returning {$status}.");
279
				continue;
280
			}
281
			unlink($xml_file);
282
		} else if ($rrd['data']) {
283
			$rrd_file = "{$g['vardb_path']}/rrd/" . basename($rrd['filename']);
284
			$rrd_fd = fopen($rrd_file, "w");
285
			if (!$rrd_fd) {
286
				log_error(sprintf(gettext("Cannot write %s"), $rrd_file));
287
				continue;
288
			}
289
			$data = base64_decode($rrd['data']);
290
			/* Try to decompress the data. */
291
			$dcomp = @gzinflate($data);
292
			if ($dcomp) {
293
				/* If the decompression worked, write the decompressed data */
294
				if (fwrite($rrd_fd, $dcomp) === false) {
295
					log_error(sprintf(gettext("fwrite %s failed"), $rrd_file));
296
					continue;
297
				}
298
			} else {
299
				/* If the decompression failed, it wasn't compressed, so write raw data */
300
				if (fwrite($rrd_fd, $data) === false) {
301
					log_error(sprintf(gettext("fwrite %s failed"), $rrd_file));
302
					continue;
303
				}
304
			}
305
			if (fclose($rrd_fd) === false) {
306
				log_error(sprintf(gettext("fclose %s failed"), $rrd_file));
307
				continue;
308
			}
309
		}
310
	}
311
}
312

    
313
function restore_sshdata($conf = false) {
314
	global $sshConfigDir;
315

    
316
	if (!$conf) {
317
		$conf = config_get_path('');
318
	}
319

    
320
	$oldmask = umask();
321
	foreach ($conf["sshdata"]["sshkeyfile"] as $sshkey) {
322
		$keypath = "{$sshConfigDir}/{$sshkey['filename']}";
323
		if (strstr($sshkey['filename'], 'pub')) {
324
			umask(0133);
325
		} else {
326
			umask(0177);
327
		}
328
		if (file_put_contents($keypath, gzinflate(base64_decode($sshkey['xmldata']))) === false) {
329
			log_error(sprintf(gettext("Cannot write %s"), $sshkey['filename']));
330
			continue;
331
		}
332
	}
333
	umask($oldmask);
334
}
335

    
336
/****f* config/parse_config_bootup
337
 * NAME
338
 *   parse_config_bootup - Bootup-specific configuration checks.
339
 * RESULT
340
 *   null
341
 ******/
342
function parse_config_bootup() {
343
	if (!config_read_file(true)) {
344
		die(gettext("Could not find a usable configuration file or it's backup! Exiting...."));
345
	}
346
	if ((float)config_get_path('version') > (float)g_get('latest_config')) {
347
		$product = g_get('product_label');
348
		echo <<<EOD
349

    
350

    
351
*******************************************************************************
352
* WARNING!                                                                    *
353
* The current configuration has been created with a newer version of {$product}  *
354
* than this one! This can lead to serious misbehavior and even security       *
355
* holes! You are urged to either upgrade to a newer version of {$product} or     *
356
* revert to the default configuration immediately!                            *
357
*******************************************************************************
358

    
359

    
360
EOD;
361
		}
362
	/* make alias table (for faster lookups) */
363
	alias_make_table();
364
}
365

    
366
/****f* config/conf_mount_rw
367
 * NAME
368
 *   conf_mount_rw - Mount filesystems read/write.
369
 * RESULT
370
 *   null
371
 ******/
372
/* mount flash card read/write */
373
function conf_mount_rw() {
374
	/* Obsoleted. Keep it here until all calls are removed */
375
	return;
376
}
377

    
378
/****f* config/conf_mount_ro
379
 * NAME
380
 *   conf_mount_ro - Mount filesystems readonly.
381
 * RESULT
382
 *   null
383
 ******/
384
function conf_mount_ro() {
385
	/* Obsoleted. Keep it here until all calls are removed */
386
	return;
387
}
388

    
389
/****f* config/convert_config
390
 * NAME
391
 *   convert_config - Attempt to update config.xml.
392
 * DESCRIPTION
393
 *   convert_config() reads the current global configuration
394
 *   and attempts to convert it to conform to the latest
395
 *   config.xml version. This allows major formatting changes
396
 *   to be made with a minimum of breakage.
397
 * RESULT
398
 *   null
399
 ******/
400
/* convert configuration, if necessary */
401
function convert_config() {
402
	global $g;
403
	$now = date("H:i:s");
404
	log_error(sprintf(gettext("Start Configuration upgrade at %s, set execution timeout to 15 minutes"), $now));
405

    
406
	/* special case upgrades */
407
	/* fix every minute crontab bogons entry */
408
	$cron_config = config_get_path('cron');
409
	if (is_array($cron_config)) {
410
		$cron_item_count = count($cron_config['item']);
411
		for ($x = 0; $x < $cron_item_count; $x++) {
412
			if (stristr($cron_config['item'][$x]['command'], "rc.update_bogons.sh")) {
413
				if ($cron_config['item'][$x]['hour'] == "*") {
414
					$cron_config['item'][$x]['hour'] = "3";
415
					write_config(gettext("Updated bogon update frequency to 3am"));
416
					log_error(gettext("Updated bogon update frequency to 3am"));
417
				}
418
			}
419
		}
420
		config_set_path('cron', $cron_config);
421
	}
422

    
423
	// Save off config version
424
	$prev_version = config_get_path('version');
425

    
426
	include_once('auth.inc');
427
	include_once('upgrade_config.inc');
428
	if (file_exists("/etc/inc/upgrade_config_custom.inc")) {
429
		include_once("upgrade_config_custom.inc");
430
	}
431

    
432
	if (config_get_path('version') == g_get('latest_config')) {
433
		/* already at latest version */
434
		additional_config_upgrade();
435
		return;
436
	}
437

    
438
	$already_run = config_get_path('system/already_run_config_upgrade', []);
439

    
440
	/* Loop and run upgrade_VER_to_VER() until we're at current version */
441
	while (config_get_path('version') < g_get('latest_config')) {
442
		$cur = config_get_path('version') * 10;
443
		$next = $cur + 1;
444
		$migration_function = sprintf('upgrade_%03d_to_%03d', $cur,
445
		    $next);
446

    
447
		foreach (array("", "_custom") as $suffix) {
448
			$migration_function .= $suffix;
449
			if (!function_exists($migration_function)) {
450
				continue;
451
			}
452
			if (isset($already_run[$migration_function])) {
453
				config_del_path("system/already_run_config_upgrade/{$migration_function}");
454
			} else {
455
				$migration_function();
456
			}
457
		}
458
		config_set_path('version', sprintf('%.1f', $next / 10));
459
		if (is_platform_booting()) {
460
			echo ".";
461
		}
462
	}
463

    
464
	if ($prev_version != config_get_path('version')) {
465
		$now = date("H:i:s");
466
		log_error(sprintf(gettext("Ended Configuration upgrade at %s"), $now));
467

    
468
		write_config(sprintf(gettext('Upgraded config version level from %1$s to %2$s'), $prev_version, config_get_path('version')));
469
	}
470

    
471
	additional_config_upgrade();
472
}
473

    
474
/****f* config/safe_write_file
475
 * NAME
476
 *   safe_write_file - Write a file out atomically
477
 * DESCRIPTION
478
 *   safe_write_file() Writes a file out atomically by first writing to a
479
 *   temporary file of the same name but ending with the pid of the current
480
 *   process, them renaming the temporary file over the original.
481
 * INPUTS
482
 *   $filename - string containing the filename of the file to write
483
 *   $content - string or array containing the file content to write to file
484
 *   $force_binary - boolean denoting whether we should force binary
485
 *   mode writing.
486
 * RESULT
487
 *   boolean - true if successful, false if not
488
 ******/
489
function safe_write_file($file, $content, $force_binary = false) {
490
	$tmp_file = $file . "." . getmypid();
491
	$write_mode = $force_binary ? "wb" : "w";
492

    
493
	$fd = fopen($tmp_file, $write_mode);
494
	if (!$fd) {
495
		// Unable to open temporary file for writing
496
		return false;
497
	}
498
	if (is_array($content)) {
499
		foreach ($content as $line) {
500
			if (!fwrite($fd, $line . "\n")) {
501
				// Unable to write to temporary file
502
				fclose($fd);
503
				return false;
504
			}
505
		}
506
	} elseif (!fwrite($fd, $content)) {
507
		// Unable to write to temporary file
508
		fclose($fd);
509
		return false;
510
	}
511
	fflush($fd);
512
	fclose($fd);
513

    
514
	if (!rename($tmp_file, $file)) {
515
		// Unable to move temporary file to original
516
		@unlink($tmp_file);
517
		return false;
518
	}
519

    
520
	return true;
521
}
522

    
523
/****f* config/write_config
524
 * NAME
525
 *   write_config - Backup and write the firewall configuration.
526
 * DESCRIPTION
527
 *   write_config() handles backing up the current configuration,
528
 *   applying changes, and regenerating the configuration cache.
529
 * INPUTS
530
 *   $desc	- string containing the a description of configuration changes
531
 *   $backup	- boolean: do not back up current configuration if false.
532
 *   $write_config_only	- boolean: do not sync or reload anything; just save the configuration if true.
533
 * RESULT
534
 *   null
535
 ******/
536
/* save the system configuration */
537
function write_config($desc="Unknown", $backup = true, $write_config_only = false) {
538
	global $g;
539

    
540
	// Certain strings may be embedded in the $desc (reason) parameter to trigger certain behavior.
541
	// If detected, those strings are removed and a variable set.
542
	$doacb = true;
543
	$manual_acb = false;
544
	$rcnt = 0;
545

    
546
	$desc = str_replace("-MaNuAlBaCkUp", "", $desc, $rcnt);
547
	if ($rcnt > 0) {
548
		// Manual backups require special processing on the server
549
		$manual_acb = true;
550
	}
551

    
552
	$rcnt = 0;
553
	$desc = str_replace("-NoReMoTeBaCkUp", "", $desc, $rcnt);
554
	if ($rcnt > 0) {
555
		// No ACB will be performed if this string is detected
556
		$doacb = false;
557
	}
558

    
559
	/*
560
	* Syncing vouchers happens every minute and sometimes multiple times. We don't
561
	* want to fill up our db with a lot of the same config so just ignore that case.
562
	*/
563
	if((strpos($desc, 'Syncing vouchers') !== false ||
564
		strpos($desc, 'Captive Portal Voucher database synchronized') !== false) ) {
565
		$doacb = false;
566
	}
567

    
568
	if (!empty($_SERVER['REMOTE_ADDR'])) {
569
		@phpsession_begin();
570
		if (!empty($_SESSION['Username']) && ($_SESSION['Username'] != "admin")) {
571
			$user = getUserEntry($_SESSION['Username']);
572
			$user = $user['item'];
573
			if (is_array($user) && userHasPrivilege($user, "user-config-readonly")) {
574
				syslog(LOG_AUTHPRIV, sprintf(gettext("Save config permission denied by the 'User - Config: Deny Config Write' permission for user '%s'."), get_config_user()));
575
				phpsession_end(true);
576
				return false;
577
			}
578
		}
579
		if (!isset($argc)) {
580
			phpsession_end(true);
581
		}
582
	}
583

    
584
	if (config_path_enabled('', 'reset_factory_defaults')) {
585
		/*
586
		   We have put a default config.xml on disk and are about to reboot
587
		   or reload it. Do not let any system or package code try to save
588
		   state to config because that would overwrite the default config
589
		   with the running config.
590
		*/
591
		return false;
592
	}
593

    
594
	if ($backup) {
595
		backup_config();
596
	}
597

    
598
	if ($desc == "Unknown") {
599
		file_notice("config.xml", gettext(
600
		    'WARNING: write_config() was called without description'));
601
	}
602
	config_set_path('revision', make_config_revision_entry($desc));
603

    
604
	// write the config file
605
	$write_result = config_write_file();
606
	if ($write_result !== true) {
607
		file_notice("config.xml", $write_result . PHP_EOL);
608
		return -1;
609
	}
610

    
611
	if (config_get_path('syslog/logconfigchanges') != "disabled") {
612
		log_error(gettext('Configuration Change: ' . config_get_path('revision/description')));
613
	}
614

    
615
	cleanup_backupcache(true);
616

    
617
	/* re-read configuration */
618
	/* NOTE: We assume that the file can be parsed since we wrote it. */
619
	config_read_file(true);
620

    
621
	if ($write_config_only) {
622
		return config_get_path('');
623
	}
624

    
625
	unlink_if_exists("/usr/local/pkg/pf/carp_sync_client.php");
626

    
627
	/* sync carp entries to other firewalls */
628
	carp_sync_client();
629

    
630
	if (is_dir("/usr/local/pkg/write_config")) {
631
		/* process packager manager custom rules */
632
		run_plugins("/usr/local/pkg/write_config/");
633
	}
634

    
635
	// Try the core AutoConfigBackup system
636
	$acb_config = config_get_path('system/acb', []);
637
	if ($acb_config['enable'] == "yes" &&
638
	    (!isset($acb_config['frequency']) || $acb_config['frequency'] == "every") || file_exists("/tmp/forceacb")) {
639
	    if ($doacb) {
640
			require_once("acb.inc");
641
			upload_config($manual_acb);
642
		}
643

    
644
		if (file_exists("/tmp/forceacb")) {
645
			unlink("/tmp/forceacb");
646
		}
647
	}
648

    
649
	return config_get_path('');
650
}
651

    
652
/****f* config/reset_factory_defaults
653
 * NAME
654
 *   reset_factory_defaults - Reset the system to its default configuration.
655
 * RESULT
656
 *   integer	- indicates completion
657
 ******/
658
function reset_factory_defaults($lock = false, $reboot_required = true) {
659
	global $g;
660

    
661
	/* Remove all additional packages */
662
	mwexec("/bin/sh /usr/local/sbin/{$g['product_name']}-upgrade " .
663
	    "-r ALL_PACKAGES -f");
664

    
665
	/*
666
	   Let write_config know that we are awaiting reload of the current config
667
	   to factory defaults. Either the system is about to reboot, throwing away
668
	   the current in-memory config as it shuts down, or the in-memory config
669
	   is about to be reloaded on-the-fly by parse_config.
670

    
671
	   In both cases, we want to ensure that write_config does not flush the
672
	   in-memory config back to disk.
673
	*/
674
	config_set_path('reset_factory_defaults', true);
675

    
676
	/* create conf directory, if necessary */
677
	safe_mkdir(g_get('cf_conf_path'));
678

    
679
	if (!$lock) {
680
		$lockkey = lock('config', LOCK_EX);
681
	}
682

    
683
	/* clear out /conf */
684
	$dh = opendir(g_get('conf_path'));
685
	while ($filename = readdir($dh)) {
686
		if (($filename != ".") && ($filename != "..") &&
687
		    (!is_dir(g_get('conf_path') . "/" . $filename))) {
688
			if ($filename == "enableserial_force")
689
				continue;
690
			unlink_if_exists(g_get('conf_path') . "/" . $filename);
691
		}
692
	}
693
	closedir($dh);
694
	unlink_if_exists(g_get('tmp_path') . "/config.cache");
695

    
696
	/* copy default configuration */
697
	copy("{$g['conf_default_path']}/config.xml", "{$g['cf_conf_path']}/config.xml");
698

    
699
	if (!$lock) {
700
		unlock($lockkey);
701
	}
702

    
703
	disable_security_checks();
704

    
705
	/* call the wizard */
706
	if ($reboot_required) {
707
		// If we need a reboot first then touch a different trigger file.
708
		touch("/conf/trigger_initial_wizard_after_reboot");
709
	} else {
710
		touch("/conf/trigger_initial_wizard");
711
	}
712
	console_configure();
713
	return 0;
714
}
715

    
716
function config_restore(string $path, string $descr): bool {
717
	if (!file_exists($path) || !backup_config()) {
718
		return false;
719
	}
720

    
721
	$lockkey = lock('config', LOCK_EX);
722
	if (!isset($lockkey)) {
723
		return false;
724
	}
725
	unlink_if_exists(g_get('tmp_path'). '/config.cache');
726
	$copy_result = copy($path, g_get('cf_conf_path') . '/config.xml');
727
	unlock($lockkey);
728

    
729
	if ($copy_result) {
730
		disable_security_checks();
731
		config_read_file(true);
732
		write_config(sprintf(gettext('Reverted to "%s"'), $descr), false);
733
		return true;
734
	}
735

    
736
	return false;
737
}
738

    
739
function config_install($conffile) {
740
	global $g;
741

    
742
	if (!file_exists($conffile)) {
743
		return 1;
744
	}
745

    
746
	if (!config_validate("{$conffile}")) {
747
		return 1;
748
	}
749

    
750
	if (is_platform_booting()) {
751
		echo gettext("Installing configuration...") . "\n";
752
	} else {
753
		log_error(gettext("Installing configuration ...."));
754
	}
755

    
756
	$lockkey = lock('config', LOCK_EX);
757
	if (!isset($lockkey)) {
758
		return 1;
759
	}
760
	unlink_if_exists("{$g['tmp_path']}/config.cache");
761
	$copy_result = copy($conffile, "{$g['conf_path']}/config.xml");
762
	unlock($lockkey);
763

    
764
	if ($copy_result) {
765
		disable_security_checks();
766
		return 0;
767
	}
768

    
769
	return 1;
770
}
771

    
772
/*
773
 * Disable security checks for DNS rebind and HTTP referrer until next time
774
 * they pass (or reboot), to aid in preventing accidental lockout when
775
 * restoring settings like hostname, domain, IP addresses, and settings
776
 * related to the DNS rebind and HTTP referrer checks.
777
 * Intended for use when restoring a configuration or directly
778
 * modifying config.xml without an unconditional reboot.
779
 */
780
function disable_security_checks() {
781
	global $g;
782
	touch("{$g['tmp_path']}/disable_security_checks");
783
}
784

    
785
/* Restores security checks. Should be called after all succeed. */
786
function restore_security_checks() {
787
	global $g;
788
	unlink_if_exists("{$g['tmp_path']}/disable_security_checks");
789
}
790

    
791
/* Returns status of security check temporary disable. */
792
function security_checks_disabled() {
793
	global $g;
794
	return file_exists("{$g['tmp_path']}/disable_security_checks");
795
}
796

    
797
function config_validate($conffile) {
798

    
799
	global $g, $xmlerr;
800

    
801
	$xml_parser = xml_parser_create();
802

    
803
	if (!($fp = fopen($conffile, "r"))) {
804
		$xmlerr = gettext("XML error: unable to open file");
805
		return false;
806
	}
807

    
808
	while ($data = fread($fp, 4096)) {
809
		if (!xml_parse($xml_parser, $data, feof($fp))) {
810
			$xmlerr = sprintf(gettext('%1$s at line %2$d'),
811
						xml_error_string(xml_get_error_code($xml_parser)),
812
						xml_get_current_line_number($xml_parser));
813
			return false;
814
		}
815
	}
816
	xml_parser_free($xml_parser);
817

    
818
	fclose($fp);
819

    
820
	return true;
821
}
822

    
823
function cleanup_backupcache($lock = false) {
824
	global $g;
825
	$i = false;
826

    
827
	$revisions = config_get_path('system/backupcount');
828
	$revisions = intval(is_numericint($revisions) ? $revisions : g_get('default_config_backup_count'));
829

    
830
	if (!$lock) {
831
		$lockkey = lock('config');
832
	}
833

    
834
	$backups = get_backups();
835
	if ($backups) {
836
		$baktimes = $backups['versions'];
837
		unset($backups['versions']);
838
	} else {
839
		$backups = array();
840
		$baktimes = array();
841
	}
842
	$newbaks = array();
843
	$bakfiles = glob(g_get('cf_conf_path') . "/backup/config-*");
844
	$tocache = array();
845

    
846
	// Check for backups in the directory not represented in the cache.
847
	foreach ($bakfiles as $backup) {
848
		$backupsize = filesize($backup);
849
		if ($backupsize == 0) {
850
			unlink($backup);
851
			continue;
852
		}
853
		$backupexp = explode('-', $backup);
854
		$backupexp = explode('.', array_pop($backupexp));
855
		$tocheck = array_shift($backupexp);
856
		unset($backupexp);
857
		if (!in_array($tocheck, $baktimes)) {
858
			$i = true;
859
			if (is_platform_booting()) {
860
				echo ".";
861
			}
862
			try {
863
				$newxml = parse_xml_config($backup, array(g_get('xml_rootobj'), 'pfsense'));
864
			} catch (Exception $exc) {
865
				log_error(sprintf(gettext("The backup cache file %s is corrupted. Parser error message: %s"), $backup, $exc->getMessage()));
866
				$newxml = "-1";
867
			}
868

    
869
			if ($newxml == "-1") {
870
				log_error(sprintf(gettext("The backup cache file %s is corrupted. Unlinking."), $backup));
871
				unlink($backup);
872
				continue;
873
			}
874
			if ($newxml['revision']['description'] == "") {
875
				$newxml['revision']['description'] = "Unknown";
876
			}
877
			if ($newxml['version'] == "") {
878
				$newxml['version'] = "?";
879
			}
880
			$tocache[$tocheck] = array('description' => $newxml['revision']['description'], 'version' => $newxml['version'], 'filesize' => $backupsize);
881
		}
882
	}
883
	foreach ($backups as $checkbak) {
884
		if (count(preg_grep('/' . $checkbak['time'] . '/i', $bakfiles)) != 0) {
885
			$newbaks[] = $checkbak;
886
		} else {
887
			$i = true;
888
			if (is_platform_booting()) print " " . $tocheck . "r";
889
		}
890
	}
891
	foreach ($newbaks as $todo) {
892
		$tocache[$todo['time']] = array('description' => $todo['description'], 'version' => $todo['version'], 'filesize' => $todo['filesize']);
893
	}
894
	if (is_int($revisions) and (count($tocache) > $revisions)) {
895
		$toslice = array_slice(array_keys($tocache), 0, $revisions);
896
		$newcache = array();
897
		foreach ($toslice as $sliced) {
898
			$newcache[$sliced] = $tocache[$sliced];
899
		}
900
		foreach ($tocache as $version => $versioninfo) {
901
			if (!in_array($version, array_keys($newcache))) {
902
				unlink_if_exists(g_get('conf_path') . '/backup/config-' . $version . '.xml');
903
			}
904
		}
905
		$tocache = $newcache;
906
	}
907
	$bakout = fopen(g_get('cf_conf_path') . '/backup/backup.cache', "w");
908
	fwrite($bakout, serialize($tocache));
909
	fclose($bakout);
910

    
911
	if (!$lock) {
912
		unlock($lockkey);
913
	}
914
}
915

    
916
function get_backups() {
917
	global $g;
918
	if (file_exists("{$g['cf_conf_path']}/backup/backup.cache")) {
919
		$confvers = unserialize_data(file_get_contents("{$g['cf_conf_path']}/backup/backup.cache"), []);
920
		$bakvers = array_keys($confvers);
921
		$toreturn = array();
922
		sort($bakvers);
923
		foreach (array_reverse($bakvers) as $bakver) {
924
			$toreturn[] = array('time' => $bakver, 'description' => $confvers[$bakver]['description'], 'version' => $confvers[$bakver]['version'], 'filesize' => $confvers[$bakver]['filesize']);
925
		}
926
	} else {
927
		return false;
928
	}
929
	$toreturn['versions'] = $bakvers;
930
	return $toreturn;
931
}
932

    
933
function backup_config(): bool
934
{
935
	$backup_dir = g_get('cf_conf_path') . '/backup';
936

    
937
	/* Create backup directory if needed */
938
	safe_mkdir($backup_dir);
939

    
940
	/* Generate config backup file name */
941
	$backup_time = config_get_path('revision/time', 0);
942
	$backup_file = $backup_dir . '/config-' . $backup_time . '.xml';
943

    
944
	/* Handle backup cache */
945
	$bakdesc = config_get_path('revision/description', 'Unknown');
946
	$bakver = config_get_path('version', "?");
947
	if (file_exists(g_get('cf_conf_path') . '/backup/backup.cache')) {
948
		$backupcache = unserialize_data(file_get_contents(g_get('cf_conf_path') . '/backup/backup.cache'), []);
949
	} else {
950
		$backupcache = array();
951
	}
952
	$backupcache[$backup_time] = array('description' => $bakdesc, 'version' => $bakver, 'filesize' => filesize($backup_file));
953
	$bakout = fopen(g_get('cf_conf_path') . '/backup/backup.cache', "w");
954
	fwrite($bakout, serialize($backupcache));
955
	fclose($bakout);
956

    
957
	/* Do the backup */
958
	return copy(g_get('cf_conf_path') . '/config.xml', $backup_file);
959
}
960

    
961
function backup_info($backup_info, $number) {
962
	if ($backup_info['time'] != 0) {
963
		$date = date(gettext("n/j/y H:i:s"), $backup_info['time']);
964
	} else {
965
		$date = gettext("Unknown");
966
	}
967

    
968
	list($page, $reason) = explode(": ", $backup_info['description'], 2);
969
	if (empty($reason)) {
970
		$reason = $page;
971
		$page = gettext("Unknown Page");
972
	}
973

    
974
	$backup_info = sprintf("%02d", $number) . ". {$date}\tv{$backup_info['version']}\t{$page}\n";
975
	if ($reason) {
976
		$backup_info .= "    {$reason}\n";
977
	}
978
	return $backup_info;
979
}
980

    
981
function set_device_perms() {
982
	$devices = array(
983
		'pf' => array(
984
			'user' => 'root',
985
			'group' => 'proxy',
986
			'mode' => 0660),
987
		);
988

    
989
	foreach ($devices as $name => $attr) {
990
		$path = "/dev/{$name}";
991
		if (file_exists($path)) {
992
			chown($path, $attr['user']);
993
			chgrp($path, $attr['group']);
994
			chmod($path, $attr['mode']);
995
		}
996
	}
997
}
998

    
999
function get_config_user() {
1000
	if (empty($_SESSION["Username"])) {
1001
		$username = getenv("USER");
1002
		if (empty($conuser) || $conuser == "root") {
1003
			$username = "(system)";
1004
		}
1005
	} else {
1006
		$username = $_SESSION["Username"];
1007
	}
1008

    
1009
	if (!empty($_SERVER['REMOTE_ADDR'])) {
1010
		$username .= '@' . get_user_remote_address() . get_user_remote_authsource();
1011
	}
1012

    
1013
	return $username;
1014
}
1015

    
1016
function make_config_revision_entry($desc = null, $override_user = null) {
1017
	if (empty($override_user)) {
1018
		$username = get_config_user();
1019
	} else {
1020
		$username = $override_user;
1021
	}
1022

    
1023
	$revision = array();
1024

    
1025
	/* make sure the clock settings are plausible */
1026
	if (time() > mktime(0, 0, 0, 9, 1, 2004)) {
1027
		$revision['time'] = time();
1028
	}
1029

    
1030
	/* Log the running script so it's not entirely unlogged what changed */
1031
	if ($desc == "Unknown") {
1032
		$desc = sprintf(gettext("%s made unknown change"), $_SERVER['SCRIPT_NAME']);
1033
	}
1034
	if (!empty($desc)) {
1035
		$revision['description'] = "{$username}: " . $desc;
1036
	}
1037
	$revision['username'] = $username;
1038
	return $revision;
1039
}
1040

    
1041
function pfSense_clear_globals() {
1042
	global $config, $g, $FilterIfList, $GatewaysList, $filterdns, $aliases, $aliastable;
1043

    
1044
	$error = error_get_last();
1045

    
1046
	// Errors generated by user code (diag_commands.php) are identified by path and not added to notices
1047
	$is_user_code = preg_match('|^' . preg_quote(g_get('tmp_path_user_code')) . '/[^/]{1,16}$|', $error['file']);
1048

    
1049
	if (!is_null($error)) {
1050
		if (in_array($error['type'], array(E_ERROR, E_COMPILE_ERROR, E_CORE_ERROR, E_RECOVERABLE_ERROR))) {
1051
			$errortype = "error";
1052
		} elseif ($error['type'] != E_NOTICE) {
1053
			$errortype = "warning";
1054
		}
1055
		$errorstr = "PHP " . strtoupper($errortype) . ": " .
1056
				"Type: {$error['type']}, " .
1057
				"File: {$error['file']}, " .
1058
				"Line: {$error['line']}, " .
1059
				"Message: {$error['message']}";
1060

    
1061
		if (!$is_user_code) {
1062
			if (($errortype == "error") ||
1063
			    (config_path_enabled('system','developerspew') &&
1064
			    ($errortype = "warning"))) {
1065
				print('<pre style="white-space: pre-wrap;">' . htmlentities($errorstr) . '</pre>');
1066
				log_error($errorstr);
1067
				file_notice("php{$errortype}", $errorstr, 'PHP {$errortype}s');
1068
			}
1069
		} else {
1070
			print(str_replace(',', "\n", $errorstr));
1071
		}
1072
	}
1073

    
1074
	if (isset($FilterIfList)) {
1075
		unset($FilterIfList);
1076
	}
1077

    
1078
	if (isset($GatewaysList)) {
1079
		unset($GatewaysList);
1080
	}
1081

    
1082
	/* Used for the hostname dns resolver */
1083
	if (isset($filterdns)) {
1084
		unset($filterdns);
1085
	}
1086

    
1087
	/* Used for aliases and interface macros */
1088
	if (isset($aliases)) {
1089
		unset($aliases);
1090
	}
1091
	if (isset($aliastable)) {
1092
		unset($aliastable);
1093
	}
1094

    
1095
	unset($config);
1096
}
1097

    
1098
/**
1099
 * Initialize the leaf node as an array. Any scalars in the path, including the
1100
 * leaf node, are overridden with an array.
1101
 * 
1102
 * @param string $path Path with '/' separators
1103
 */
1104
function config_init_path(string $path) {
1105
	// safety check in case $path was called with null keys
1106
	if (str_ends_with(trim($path), '/') || str_contains($path, '//')) {
1107
		$error_text = gettext('config warning: invalid path') . " \"{$path}\"";
1108
		$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
1109
		if ((count($backtrace) == 2) && isset($backtrace[1]['file'])) {
1110
			$error_text .= ' ' . gettext('at') . " {$backtrace[1]['file']}:{$backtrace[1]['line']}";
1111
		}
1112
		log_error($error_text);
1113
		return false;
1114
	}
1115
	global $config;
1116
	return array_init_path($config, $path);
1117
}
1118

    
1119
/**
1120
 * Return a value specified by path in the config, if it exists.
1121
 * 
1122
 * @param string $path Path with '/' separators
1123
 * @param mixed $default Value to return if the path is not found
1124
 * 
1125
 * @return mixed Value at path or $default if the path does not exist or if the
1126
 *               path keys an empty string and $default is non-null
1127
 */
1128
function config_get_path(string $path, $default = null) {
1129
	// safety check in case $path was called with null keys
1130
	if (str_ends_with(trim($path), '/') || str_contains($path, '//')) {
1131
		$error_text = gettext('config warning: invalid path') . " \"{$path}\"";
1132
		$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
1133
		if ((count($backtrace) == 2) && isset($backtrace[1]['file'])) {
1134
			$error_text .= ' ' . gettext('at') . " {$backtrace[1]['file']}:{$backtrace[1]['line']}";
1135
		}
1136
		log_error($error_text);
1137
		return $default;
1138
	}
1139
	global $config;
1140
	return is_array($config) ? array_get_path($config, $path, $default) : $default;
1141
}
1142

    
1143
/**
1144
 * Set a value by path in the config, creating arrays for intermediary keys as
1145
 * necessary. If the path cannot be reached because an intermediary exists but
1146
 * is not empty or an array, return $default.
1147
 * 
1148
 * @param string $path Path with '/' separators
1149
 * @param mixed $val Value to set
1150
 * @param mixed $default Value to return if the path is not found
1151
 * 
1152
 * @return mixed $val or $default if the path prefix does not exist
1153
 */
1154
function config_set_path(string $path, $value, $default = null) {
1155
	// safety check in case $path was called with null keys
1156
	if (str_contains($path, '//')) {
1157
		$error_text = gettext('config warning: invalid path') . " \"{$path}\"";
1158
		$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
1159
		if ((count($backtrace) == 2) && isset($backtrace[1]['file'])) {
1160
			$error_text .= ' ' . gettext('at') . " {$backtrace[1]['file']}:{$backtrace[1]['line']}";
1161
		}
1162
		log_error($error_text);
1163
		return $default;
1164
	}
1165
	global $config;
1166

    
1167
	if ($path == '' && !is_array($config)) {
1168
		// initialize $config when it's being completely replaced
1169
		$config = [];
1170
	} elseif ($path == '/') {
1171
		// prevent unintentionally treating $config as a list array
1172
		return ($default);
1173
	}
1174

    
1175
	return is_array($config) ? array_set_path($config, $path, $value, $default) : $default;
1176
}
1177

    
1178
/**
1179
 * Determine whether a path in the config has a non-null value keyed by
1180
 * $enable_key. Some parts of the config historically identify services as
1181
 * enabled by having a key to a non-null value named 'enable', and checking it
1182
 * with isset(). This can be counter-intuitive as isset() will return true if
1183
 * the array element is any non-null value that evaluates to false.
1184
 * 
1185
 * @param string $path Path with '/' separators
1186
 * @param string $enable_key Optional alternative key value for the enable key
1187
 * 
1188
 * @return mixed|bool true if $enable_key exists in the array at $path, and has
1189
 *                    a non-null value, otherwise false; or $default if the
1190
 *                    path prefix does not exist.
1191
 */
1192
function config_path_enabled(string $path, $enable_key = "enable", $default = false) {
1193
	// safety check in case $path was called with null keys
1194
	if (str_ends_with(trim($path), '/') || str_contains($path, '//')) {
1195
		$error_text = gettext('config warning: invalid path') . " \"{$path}\"";
1196
		$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
1197
		if ((count($backtrace) == 2) && isset($backtrace[1]['file'])) {
1198
			$error_text .= ' ' . gettext('at') . " {$backtrace[1]['file']}:{$backtrace[1]['line']}";
1199
		}
1200
		log_error($error_text);
1201
		return $default;
1202
	}
1203
	global $config;
1204
	return is_array($config) ? array_path_enabled($config, $path, $enable_key, $default) : $default;
1205
}
1206

    
1207
/**
1208
 * Remove a key from the config by path.
1209
 * 
1210
 * @param string $path Path with '/' separators
1211
 * 
1212
 * @return mixed|array Copy of the removed value or null; or $default if the
1213
 *                     path prefix does not exist.
1214
 */
1215
function config_del_path(string $path, $default = null) {
1216
	// safety check in case $path was called with null keys
1217
	if (str_ends_with(trim($path), '/') || str_contains($path, '//')) {
1218
		$error_text = gettext('config warning: invalid path') . " \"{$path}\"";
1219
		$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
1220
		if ((count($backtrace) == 2) && isset($backtrace[1]['file'])) {
1221
			$error_text .= ' ' . gettext('at') . " {$backtrace[1]['file']}:{$backtrace[1]['line']}";
1222
		}
1223
		log_error($error_text);
1224
		return $default;
1225
	}
1226
	global $config;
1227
	return is_array($config) ? array_del_path($config, $path, $default) : $default;
1228
}
1229

    
1230
/**
1231
 * Writes the in-memory configuration to config.xml.
1232
 *
1233
 * @return bool|string True if the config.xml file was successfully
1234
 *                     written; otherwise an error string.
1235
 */
1236
function config_write_file() {
1237
		// generate configuration XML
1238
		$tmp_config = config_get_path('');
1239
		if (!is_array($tmp_config)) {
1240
			return gettext("Config contents could not be saved. Config is empty!");
1241
		}
1242

    
1243
		// write configuration XML
1244
		$lockkey = lock('config', LOCK_EX);
1245
		if (!isset($lockkey)) {
1246
			return gettext("Config contents could not be saved. Could not create temporary file!");
1247
		}
1248
		$result = safe_write_file(g_get('cf_conf_path') . "/config.xml", dump_xml_config($tmp_config, g_get('xml_rootobj')));
1249
		unlock($lockkey);
1250

    
1251
		if (!$result) {
1252
			return sprintf(gettext('Unable to open %1$s for writing.'), g_get('cf_conf_path') . '/config.xml');
1253
		}
1254

    
1255
		// success
1256
		return true;
1257
}
1258

    
1259
/**
1260
 * Reloads the in-memory configuration with config.xml.
1261
 *
1262
 * @param bool $use_backup If True, try to restore config backups
1263
 *                         when a valid config.xml is not available.
1264
 * @param bool $use_cache If True, try to use the config cache file.
1265
 *
1266
 * @return bool True if the configuration was successfully reloaded.
1267
 */
1268
function config_read_file(bool $use_backup = false, bool $use_cache = false) {
1269
	/* We leave calls to file_notice() for the end before returning.
1270
	 * This mitigates the potential for PHP errors to interfere with
1271
	 * the config recovery. */
1272
	$config_read = function () {
1273
		$update_cache = false;
1274
		$parsed_config = @file_get_contents(g_get('tmp_path') . '/config.cache');
1275
		if (is_string($parsed_config)) {
1276
			$parsed_config = unserialize($parsed_config);
1277
		}
1278
		if (!is_array($parsed_config)) {
1279
			$parsed_config = parse_xml_config((g_get('conf_path') . '/config.xml'), [g_get('xml_rootobj')]);
1280
			$update_cache = true;
1281
		}
1282
	
1283
		if (is_array($parsed_config) && !empty($parsed_config) &&
1284
			is_array(config_set_path('', $parsed_config))) {
1285
			if ($update_cache) {
1286
				generate_config_cache($parsed_config);
1287
			}
1288
			return true;
1289
		}
1290
	
1291
		return false;
1292
	};
1293

    
1294
	if (!$use_cache) {
1295
		@unlink(g_get('tmp_path') . '/config.cache');
1296
	}
1297
	global $config, $config_parsed;
1298
	$config_parsed = false;
1299

    
1300
	$config_parsed = $config_read();
1301

    
1302
	if ($config_parsed || !$use_backup) {
1303
		if (!$config_parsed) {
1304
			$log_message = gettext("A valid config file could not be found.");
1305
			log_error($log_message);
1306
			file_notice("config.xml", $log_message, "pfSenseConfigurator", "", 1, true);
1307
		}
1308
		return $config_parsed;
1309
	}
1310

    
1311
	log_error(gettext("A valid config file backup could not be found."));
1312
	if (file_exists(g_get('conf_path') . '/config.xml')) {
1313
		// adapted from write_config()
1314
		copy((g_get('conf_path') . '/config.xml'), (g_get('conf_path') . '/config.xml.bad'));
1315
		log_error(gettext("The failed configuration file has been saved to ") . (g_get('conf_path') . '/config.xml.bad'));
1316
	}
1317

    
1318
	if (is_platform_booting()) {
1319
		// adapted from parse_config() and parse_config_bootup()
1320
		if (is_cli_sapi()) {
1321
			echo ".";
1322
		}
1323

    
1324
		// adapted from write_config()
1325
		$log_message = "\n\n************** " . gettext("WARNING") . " **************";
1326
		$log_message .= "\n\n " . gettext("Configuration could not be validated. A previous configuration was restored.");
1327
		$log_message .= "\n\n " . gettext("The failed configuration file has been saved to ") . (g_get('conf_path') . '/config.xml.bad') . "\n\n";
1328
		echo $log_message;
1329
	}
1330

    
1331
	$last_backup = discover_last_backup();
1332
	if (!is_array($last_backup)) {
1333
		$log_message = gettext("A valid config file backup could not be found.");
1334
		log_error($log_message);
1335

    
1336
		// adapted from parse_config() - alternatively it could return false
1337
		die($log_message);
1338
	}
1339

    
1340
	if (restore_backup($last_backup)) {
1341
		// update in-memory config and generate a cache file
1342
		$config_parsed = $config_read();
1343
		if ($config_parsed) {
1344
			$log_message = sprintf(gettext('Restored "%1$s" because "%2$s" is invalid or does not exist.'),
1345
			    $last_backup['path'], (g_get('cf_conf_path') . '/config.xml')
1346
			);
1347
			$log_message .= ' ' . gettext('Currently running PHP scripts may encounter errors.');
1348
			log_error($log_message);
1349

    
1350
			// adapted from parse_config()
1351
			file_notice("config.xml", $log_message, "pfSenseConfigurator", "", 1, true);
1352
		} else {
1353
			$log_message = gettext("A valid config file could not be recovered.");
1354
			$log_message .= ' ' . gettext('Currently running PHP scripts may encounter errors.');
1355
			log_error($log_message);
1356

    
1357
			// adapted from parse_config()
1358
			die($log_message);
1359
		}
1360
	}
1361

    
1362
	return $config_parsed;
1363
}
1364

    
1365
/**
1366
 * Reloads the in-memory configuration with config.xml.
1367
 *
1368
 * @param bool $parse If False, use the config cache when available.
1369
 *
1370
 * @return mixed Config array.
1371
 *
1372
 * @deprecated Retained for backwards compatibility with versions <= 24.03.
1373
 *
1374
 * @see config_read_file() Supports updated recovery code.
1375
 */
1376
function parse_config($parse = false) {
1377
	config_read_file(!$parse);
1378
	return config_get_path('');
1379
}
1380

    
1381
/**
1382
 * Initialize the config path.
1383
 * 
1384
 * @param array $keys The path as an array of keys.
1385
 * 
1386
 * @deprecated Retained for backwards compatibility with versions <= 24.03.
1387
 * 
1388
 * @see config_init_path() Uses a string path.
1389
 */
1390
function init_config_arr($keys) {
1391
	config_init_path(implode('/', $keys));
1392
}
1393

    
1394
register_shutdown_function('pfSense_clear_globals');
(11-11/61)