Project

General

Profile

Download (33.9 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-2021 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
/****f* config/encrypted_configxml
30
 * NAME
31
 *   encrypted_configxml - Checks to see if config.xml is encrypted and if so, prompts to unlock.
32
 * INPUTS
33
 *   None
34
 * RESULT
35
 *   $config 	- rewrites config.xml without encryption
36
 ******/
37
function encrypted_configxml() {
38
	global $g, $config;
39

    
40
	if (!file_exists($g['conf_path'] . "/config.xml")) {
41
		return;
42
	}
43

    
44
	if (!platform_booting()) {
45
		return;
46
	}
47

    
48
	$configtxt = file_get_contents($g['conf_path'] . "/config.xml");
49
	if (tagfile_deformat($configtxt, $configtxt, "config.xml")) {
50
		$fp = fopen('php://stdin', 'r');
51
		$data = "";
52
		echo "\n\n*** Encrypted config.xml detected ***\n";
53
		while ($data == "") {
54
			echo "\nEnter the password to decrypt config.xml: ";
55
			$decrypt_password = chop(fgets($fp));
56
			$data = decrypt_data($configtxt, $decrypt_password);
57
			if (!strstr($data, "<pfsense>")) {
58
				$data = "";
59
			}
60
			if ($data) {
61
				$fd = fopen($g['conf_path'] . "/config.xml.tmp", "w");
62
				fwrite($fd, $data);
63
				fclose($fd);
64
				exec("/bin/mv {$g['conf_path']}/config.xml.tmp {$g['conf_path']}/config.xml");
65
				echo "\n" . gettext("Config.xml unlocked.") . "\n";
66
				fclose($fp);
67
				//pfSense_fsync("{$g['conf_path']}/config.xml");
68
			} else {
69
				echo "\n" . gettext("Invalid password entered.  Please try again.") . "\n";
70
			}
71
		}
72
	}
73
}
74

    
75
/****f* config/parse_config
76
 * NAME
77
 *   parse_config - Read in config.cache or config.xml if needed and return $config array
78
 * INPUTS
79
 *   $parse       - boolean to force parse_config() to read config.xml and generate config.cache
80
 * RESULT
81
 *   $config      - array containing all configuration variables
82
 ******/
83
function parse_config($parse = false) {
84
	global $g, $config_parsed, $config_extra;
85

    
86
	$lockkey = lock('config');
87
	$config_parsed = false;
88

    
89
	if (!file_exists("{$g['conf_path']}/config.xml") || filesize("{$g['conf_path']}/config.xml") == 0) {
90
		$last_backup = discover_last_backup();
91
		if ($last_backup) {
92
			log_error(gettext("No config.xml found, attempting last known config restore."));
93
			file_notice("config.xml", gettext("No config.xml found, attempting last known config restore."), "pfSenseConfigurator", "");
94
			restore_backup("{$g['conf_path']}/backup/{$last_backup}");
95
		} else {
96
			unlock($lockkey);
97
			die(gettext("Config.xml is corrupted and is 0 bytes.  Could not restore a previous backup."));
98
		}
99
	}
100

    
101
	if (platform_booting(true)) {
102
		echo ".";
103
	}
104

    
105
	// Check for encrypted config.xml
106
	encrypted_configxml();
107

    
108
	if (!$parse) {
109
		if (file_exists($g['tmp_path'] . '/config.cache')) {
110
			$config = unserialize(file_get_contents($g['tmp_path'] . '/config.cache'));
111
			if (!is_array($config)) {
112
				$parse = true;
113
			}
114
		} else {
115
			$parse = true;
116
		}
117
	}
118
	if ($parse == true) {
119
		if (!file_exists($g['conf_path'] . "/config.xml")) {
120
			if (platform_booting(true)) {
121
				echo ".";
122
			}
123
			log_error("No config.xml found, attempting last known config restore.");
124
			file_notice("config.xml", "No config.xml found, attempting last known config restore.", "pfSenseConfigurator", "");
125
			$last_backup = discover_last_backup();
126
			if ($last_backup) {
127
				restore_backup("/cf/conf/backup/{$last_backup}");
128
			} else {
129
				log_error(gettext("Could not restore config.xml."));
130
				unlock($lockkey);
131
				die(gettext("Config.xml is corrupted and is 0 bytes.  Could not restore a previous backup."));
132
			}
133
		}
134
		$config = parse_xml_config($g['conf_path'] . '/config.xml', array($g['xml_rootobj'], 'pfsense'));
135
		if ($config == -1) {
136
			$last_backup = discover_last_backup();
137
			if ($last_backup) {
138
				restore_backup("/cf/conf/backup/{$last_backup}");
139
			} else {
140
				log_error(gettext("Could not restore config.xml."));
141
				unlock($lockkey);
142
				die("Config.xml is corrupted and is 0 bytes.  Could not restore a previous backup.");
143
			}
144
		}
145
		generate_config_cache($config);
146
	}
147

    
148
	if (platform_booting(true)) {
149
		echo ".";
150
	}
151

    
152
	$config_parsed = true;
153
	unlock($lockkey);
154

    
155
	alias_make_table();
156

    
157
	return $config;
158
}
159

    
160
/****f* config/generate_config_cache
161
 * NAME
162
 *   generate_config_cache - Write serialized configuration to cache.
163
 * INPUTS
164
 *   $config	- array containing current firewall configuration
165
 * RESULT
166
 *   boolean	- true on completion
167
 ******/
168
function generate_config_cache($config) {
169
	global $g, $config_extra;
170

    
171
	$configcache = fopen($g['tmp_path'] . '/config.cache', "w");
172
	fwrite($configcache, serialize($config));
173
	fclose($configcache);
174
	//pfSense_fsync("{$g['tmp_path']}/config.cache");
175

    
176
	unset($configcache);
177
	/* Used for config.extra.xml */
178
	if (file_exists($g['tmp_path'] . '/config.extra.cache') && $config_extra) {
179
		$configcacheextra = fopen($g['tmp_path'] . '/config.extra.cache', "w");
180
		fwrite($configcacheextra, serialize($config_extra));
181
		fclose($configcacheextra);
182
		//pfSense_fsync("{$g['tmp_path']}/config.extra.cache");
183
		unset($configcacheextra);
184
	}
185
}
186

    
187
function discover_last_backup() {
188
	$backups = glob('/cf/conf/backup/*.xml');
189
	$last_backup = "";
190
	$last_mtime = 0;
191
	foreach ($backups as $backup) {
192
		if (filemtime($backup) > $last_mtime) {
193
			$last_mtime = filemtime($backup);
194
			$last_backup = $backup;
195
		}
196
	}
197

    
198
	return basename($last_backup);
199
}
200

    
201
function restore_backup($file) {
202
	global $g;
203

    
204
	if (file_exists($file)) {
205
		/* restore rrddata/xmldata and clear appropriate data,
206
		 * see https://redmine.pfsense.org/issues/11050 */
207
		$data = file_get_contents("$file");
208
		$conf = parse_xml_config("$file", $g['xml_rootobj']);
209
		if ($conf['rrddata']) {
210
			restore_rrddata($conf);
211
			$data = clear_tagdata("rrd", $data);
212
		}
213
		foreach ($g['backuppath'] as $bk => $path) {
214
			if (!empty($conf[$bk][$bk.'data'])) {
215
				restore_xmldatafile($bk, $conf);
216
				$data = clear_tagdata($bk, $data);
217
			}
218
		}
219
		file_put_contents($file, $data);
220
		unlink_if_exists("{$g['tmp_path']}/config.cache");
221
		copy("$file", "/cf/conf/config.xml");
222
		//pfSense_fsync("/cf/conf/config.xml");
223
		//pfSense_fsync($g['conf_path']);
224
		disable_security_checks();
225
		log_error(sprintf(gettext('%1$s is restoring the configuration %2$s'), $g['product_label'], $file));
226
		file_notice("config.xml", sprintf(gettext('%1$s is restoring the configuration %2$s'), $g['product_label'], $file), "pfSenseConfigurator", "");
227
	}
228
}
229

    
230
/*
231
 *	Backup RRD/XML Data
232
 */
233

    
234
/* If the config on disk had rrddata/xmldata tags already, remove that section first.
235
 * See https://redmine.pfsense.org/issues/8994,
236
 *     https://redmine.pfsense.org/issues/10508, 
237
 *     https://redmine.pfsense.org/issues/11050 */
238
function clear_tagdata($tag = "rrd", $data) {
239
	$data = preg_replace("/[[:blank:]]*<{$tag}data>.*<\\/{$tag}data>[[:blank:]]*\n*/s", "", $data);
240
	$data = preg_replace("/[[:blank:]]*<{$tag}data\\/>[[:blank:]]*\n*/", "", $data);
241

    
242
	return $data;
243
}
244

    
245
function restore_xmldatafile($type='voucher', $conf = false) {
246
	global $config, $g;
247

    
248
	if (!$conf) {
249
		$conf = & $config;
250
	}
251

    
252
	foreach ($conf[$type]["{$type}data"]["xmldatafile"] as $file) {
253
		$basename = basename($file['filename']);
254
		$dirname = dirname($g['backuppath'][$type]);
255
		$xmldata_file = "{$dirname}/{$basename}";
256
		if (file_put_contents($xmldata_file, gzinflate(base64_decode($file['data']))) === false) {
257
			log_error(sprintf(gettext("Cannot write %s"), $xmldata_file));
258
			continue;
259
		}
260
	}
261
}
262

    
263
function restore_rrddata($conf = false) {
264
	global $config, $g, $rrdtool, $input_errors;
265

    
266
	if (!$conf) {
267
		$conf = & $config;
268
	}
269

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

    
317
/****f* config/parse_config_bootup
318
 * NAME
319
 *   parse_config_bootup - Bootup-specific configuration checks.
320
 * RESULT
321
 *   null
322
 ******/
323
function parse_config_bootup() {
324
	global $config, $g;
325

    
326
	if (platform_booting()) {
327
		echo ".";
328
	}
329

    
330
	$lockkey = lock('config');
331
	if (!file_exists("{$g['conf_path']}/config.xml")) {
332
		if (platform_booting()) {
333
			$last_backup = discover_last_backup();
334
			if ($last_backup) {
335
				log_error("No config.xml found, attempting last known config restore.");
336
				file_notice("config.xml", gettext("No config.xml found, attempting last known config restore."), "pfSenseConfigurator", "");
337
				restore_backup("/cf/conf/backup/{$last_backup}");
338
			}
339
			if (!file_exists("{$g['conf_path']}/config.xml")) {
340
				echo sprintf(gettext("XML configuration file not found.  %s cannot continue booting."), $g['product_label']) . "\n";
341
				unlock($lockkey);
342
				die(gettext("Could not find a usable configuration file or it's backup! Exiting...."));
343
			} else {
344
				log_error("Last known config found and restored.  Please double check the configuration file for accuracy.");
345
				file_notice("config.xml", gettext("Last known config found and restored.  Please double check the configuration file for accuracy."), "pfSenseConfigurator", "");
346
			}
347
		} else {
348
			unlock($lockkey);
349
			log_error(gettext("Could not find a usable configuration file! Exiting...."));
350
			exit(0);
351
		}
352
	}
353

    
354
	if (filesize("{$g['conf_path']}/config.xml") == 0) {
355
		$last_backup = discover_last_backup();
356
		if ($last_backup) {
357
			log_error(gettext("No config.xml found, attempting last known config restore."));
358
			file_notice("config.xml", gettext("No config.xml found, attempting last known config restore."), "pfSenseConfigurator", "");
359
			restore_backup("{$g['conf_path']}/backup/{$last_backup}");
360
		} else {
361
			unlock($lockkey);
362
			die(gettext("Config.xml is corrupted and is 0 bytes.  Could not restore a previous backup."));
363
		}
364
	}
365
	unlock($lockkey);
366

    
367
	$config = parse_config(true);
368

    
369
	if ((float)$config['version'] > (float)$g['latest_config']) {
370
		echo <<<EOD
371

    
372

    
373
*******************************************************************************
374
* WARNING!                                                                    *
375
* The current configuration has been created with a newer version of {$g['product_label']}  *
376
* than this one! This can lead to serious misbehavior and even security       *
377
* holes! You are urged to either upgrade to a newer version of {$g['product_label']} or     *
378
* revert to the default configuration immediately!                            *
379
*******************************************************************************
380

    
381

    
382
EOD;
383
		}
384

    
385
	/* make alias table (for faster lookups) */
386
	alias_make_table();
387
}
388

    
389
/****f* config/conf_mount_rw
390
 * NAME
391
 *   conf_mount_rw - Mount filesystems read/write.
392
 * RESULT
393
 *   null
394
 ******/
395
/* mount flash card read/write */
396
function conf_mount_rw() {
397
	/* Obsoleted. Keep it here until all calls are removed */
398
	return;
399
}
400

    
401
/****f* config/conf_mount_ro
402
 * NAME
403
 *   conf_mount_ro - Mount filesystems readonly.
404
 * RESULT
405
 *   null
406
 ******/
407
function conf_mount_ro() {
408
	/* Obsoleted. Keep it here until all calls are removed */
409
	return;
410
}
411

    
412
/****f* config/convert_config
413
 * NAME
414
 *   convert_config - Attempt to update config.xml.
415
 * DESCRIPTION
416
 *   convert_config() reads the current global configuration
417
 *   and attempts to convert it to conform to the latest
418
 *   config.xml version. This allows major formatting changes
419
 *   to be made with a minimum of breakage.
420
 * RESULT
421
 *   null
422
 ******/
423
/* convert configuration, if necessary */
424
function convert_config() {
425
	global $config, $g;
426
	$now = date("H:i:s");
427
	log_error(sprintf(gettext("Start Configuration upgrade at %s, set execution timeout to 15 minutes"), $now));
428
	//ini_set("max_execution_time", "900");
429

    
430
	/* special case upgrades */
431
	/* fix every minute crontab bogons entry */
432
	if (is_array($config['cron'])) {
433
		$cron_item_count = count($config['cron']['item']);
434
		for ($x = 0; $x < $cron_item_count; $x++) {
435
			if (stristr($config['cron']['item'][$x]['command'], "rc.update_bogons.sh")) {
436
				if ($config['cron']['item'][$x]['hour'] == "*") {
437
					$config['cron']['item'][$x]['hour'] = "3";
438
					write_config(gettext("Updated bogon update frequency to 3am"));
439
					log_error(gettext("Updated bogon update frequency to 3am"));
440
				}
441
			}
442
		}
443
	}
444

    
445
	// Save off config version
446
	$prev_version = $config['version'];
447

    
448
	include_once('auth.inc');
449
	include_once('upgrade_config.inc');
450
	if (file_exists("/etc/inc/upgrade_config_custom.inc")) {
451
		include_once("upgrade_config_custom.inc");
452
	}
453

    
454
	if ($config['version'] == $g['latest_config']) {
455
		additional_config_upgrade();
456
		return;		/* already at latest version */
457
	}
458

    
459
	if (!is_array($config['system']['already_run_config_upgrade'])) {
460
		$config['system']['already_run_config_upgrade'] = array();
461
	}
462
	$already_run = $config['system']['already_run_config_upgrade'];
463

    
464
	/* Loop and run upgrade_VER_to_VER() until we're at current version */
465
	while ($config['version'] < $g['latest_config']) {
466
		$cur = $config['version'] * 10;
467
		$next = $cur + 1;
468
		$migration_function = sprintf('upgrade_%03d_to_%03d', $cur,
469
		    $next);
470

    
471
		foreach (array("", "_custom") as $suffix) {
472
			$migration_function .= $suffix;
473
			if (!function_exists($migration_function)) {
474
				continue;
475
			}
476
			if (isset($already_run[$migration_function])) {
477
				/* Already executed, skip now */
478
				unset($config['system']
479
				    ['already_run_config_upgrade']
480
				    [$migration_function]);
481
			} else {
482
				$migration_function();
483
			}
484
		}
485
		$config['version'] = sprintf('%.1f', $next / 10);
486
		if (platform_booting()) {
487
			echo ".";
488
		}
489
	}
490

    
491
	if ($prev_version != $config['version']) {
492
		$now = date("H:i:s");
493
		log_error(sprintf(gettext("Ended Configuration upgrade at %s"), $now));
494

    
495
		write_config(sprintf(gettext('Upgraded config version level from %1$s to %2$s'), $prev_version, $config['version']));
496
	}
497

    
498
	additional_config_upgrade();
499
}
500

    
501
/****f* config/safe_write_file
502
 * NAME
503
 *   safe_write_file - Write a file out atomically
504
 * DESCRIPTION
505
 *   safe_write_file() Writes a file out atomically by first writing to a
506
 *   temporary file of the same name but ending with the pid of the current
507
 *   process, them renaming the temporary file over the original.
508
 * INPUTS
509
 *   $filename  - string containing the filename of the file to write
510
 *   $content   - string or array containing the file content to write to file
511
 *   $force_binary      - boolean denoting whether we should force binary
512
 *   mode writing.
513
 * RESULT
514
 *   boolean - true if successful, false if not
515
 ******/
516
function safe_write_file($file, $content, $force_binary = false) {
517
	$tmp_file = $file . "." . getmypid();
518
	$write_mode = $force_binary ? "wb" : "w";
519

    
520
	$fd = fopen($tmp_file, $write_mode);
521
	if (!$fd) {
522
		// Unable to open temporary file for writing
523
		return false;
524
	}
525
	if (is_array($content)) {
526
		foreach ($content as $line) {
527
			if (!fwrite($fd, $line . "\n")) {
528
				// Unable to write to temporary file
529
				fclose($fd);
530
				return false;
531
			}
532
		}
533
	} elseif (!fwrite($fd, $content)) {
534
		// Unable to write to temporary file
535
		fclose($fd);
536
		return false;
537
	}
538
	fflush($fd);
539
	fclose($fd);
540

    
541
	/* XXX Re-add pfSense_fsync() call here after it's fixed */
542
	// if (!pfSense_fsync($tmp_file) || !rename($tmp_file, $file)) {
543
	if (!rename($tmp_file, $file)) {
544
		// Unable to move temporary file to original
545
		@unlink($tmp_file);
546
		return false;
547
	}
548

    
549
	// Sync file before returning
550
	//return pfSense_fsync($file);
551
	return true;
552
}
553

    
554
/****f* config/write_config
555
 * NAME
556
 *   write_config - Backup and write the firewall configuration.
557
 * DESCRIPTION
558
 *   write_config() handles backing up the current configuration,
559
 *   applying changes, and regenerating the configuration cache.
560
 * INPUTS
561
 *   $desc	- string containing the a description of configuration changes
562
 *   $backup	- boolean: do not back up current configuration if false.
563
 *   $write_config_only	- boolean: do not sync or reload anything; just save the configuration if true.
564
 * RESULT
565
 *   null
566
 ******/
567
/* save the system configuration */
568
function write_config($desc="Unknown", $backup = true, $write_config_only = false) {
569
	global $config, $g;
570

    
571
	// Certain strings may be embedded in the $desc (reason) parameter to trigger certain behavior.
572
	// If detected, those strings are removed and a variable set.
573
	$doacb = true;
574
	$manual_acb = false;
575
	$rcnt = 0;
576

    
577
	$desc = str_replace("-MaNuAlBaCkUp", "", $desc, $rcnt);
578
	if ($rcnt > 0) {
579
		$manual_acb = true; // Manual backups require special processing on the server
580
	}
581

    
582
	$rcnt = 0;
583
	$desc = str_replace("-NoReMoTeBaCkUp", "", $desc, $rcnt);
584
	if ($rcnt > 0) {
585
		$doacb = false; // No ACB will be performed if this string is detected
586
	}
587

    
588
	/*
589
	* Syncing vouchers happens every minute and sometimes multiple times. We don't
590
	* want to fill up our db with a lot of the same config so just ignore that case.
591
	*/
592
	if((strpos($desc, 'Syncing vouchers') !== false ||
593
		strpos($desc, 'Captive Portal Voucher database synchronized') !== false) ) {
594
		$doacb = false;
595
	}
596

    
597
	if (!empty($_SERVER['REMOTE_ADDR'])) {
598
		@phpsession_begin();
599
		if (!empty($_SESSION['Username']) && ($_SESSION['Username'] != "admin")) {
600
			$user = getUserEntry($_SESSION['Username']);
601
			if (is_array($user) && userHasPrivilege($user, "user-config-readonly")) {
602
				syslog(LOG_AUTHPRIV, sprintf(gettext("Save config permission denied by the 'User - Config: Deny Config Write' permission for user '%s'."), get_config_user()));
603
				phpsession_end(true);
604
				return false;
605
			}
606
		}
607
		if (!isset($argc)) {
608
			phpsession_end(true);
609
		}
610
	}
611

    
612
	if (isset($config['reset_factory_defaults'])) {
613
		/*
614
		   We have put a default config.xml on disk and are about to reboot
615
		   or reload it. Do not let any system or package code try to save
616
		   state to config because that would overwrite the default config
617
		   with the running config.
618
		*/
619
		return false;
620
	}
621

    
622
	if ($backup) {
623
		backup_config();
624
	}
625

    
626
	if ($desc == "Unknown") {
627
		file_notice("config.xml", gettext(
628
		    'WARNING: write_config() was called without description'));
629
	}
630
	$config['revision'] = make_config_revision_entry($desc);
631

    
632
	$lockkey = lock('config', LOCK_EX);
633

    
634
	/* generate configuration XML */
635
	$xmlconfig = dump_xml_config($config, $g['xml_rootobj']);
636

    
637
	/* write new configuration */
638
	if (!safe_write_file("{$g['cf_conf_path']}/config.xml", $xmlconfig)) {
639
		log_error(gettext("WARNING: Config contents could not be saved. Could not open file!"));
640
		unlock($lockkey);
641
		file_notice("config.xml", sprintf(gettext('Unable to open %1$s/config.xml for writing in write_config()%2$s'), $g['cf_conf_path'], "\n"));
642
		return -1;
643
	}
644

    
645
	cleanup_backupcache(true);
646

    
647
	/* re-read configuration */
648
	/* NOTE: We assume that the file can be parsed since we wrote it. */
649
	$config = parse_xml_config("{$g['conf_path']}/config.xml", $g['xml_rootobj']);
650
	if ($config == -1) {
651
		copy("{$g['conf_path']}/config.xml", "{$g['conf_path']}/config.xml.bad");
652
		$last_backup = discover_last_backup();
653
		if ($last_backup) {
654
			restore_backup("/cf/conf/backup/{$last_backup}");
655
			$config = parse_xml_config("{$g['conf_path']}/config.xml", $g['xml_rootobj']);
656
			if (platform_booting()) {
657
				echo "\n\n ************** WARNING **************";
658
				echo "\n\n Configuration could not be validated. A previous configuration was restored. \n";
659
				echo "\n The failed configuration file has been saved as {$g['conf_path']}/config.xml.bad \n\n";
660
			}
661
		} else {
662
			log_error(gettext("Could not restore config.xml."));
663
		}
664
	} else {
665
		generate_config_cache($config);
666
	}
667

    
668
	unlock($lockkey);
669

    
670
	if ($write_config_only) {
671
		return $config;
672
	}
673

    
674
	unlink_if_exists("/usr/local/pkg/pf/carp_sync_client.php");
675

    
676
	/* sync carp entries to other firewalls */
677
	carp_sync_client();
678

    
679
	if (is_dir("/usr/local/pkg/write_config")) {
680
		/* process packager manager custom rules */
681
		run_plugins("/usr/local/pkg/write_config/");
682
	}
683

    
684
	// Try the core AutoConfigBackup system
685
	if (is_array($config['system']['acb']) && $config['system']['acb']['enable'] == "yes" &&
686
	    (!isset($config['system']['acb']['frequency']) || $config['system']['acb']['frequency'] == "every") || file_exists("/tmp/forceacb")) {
687
	    if ($doacb) {
688
			require_once("acb.inc");
689
			upload_config($manual_acb);
690
		}
691

    
692
		if (file_exists("/tmp/forceacb")) {
693
			unlink("/tmp/forceacb");
694
		}
695
	}
696

    
697
	return $config;
698
}
699

    
700
/****f* config/reset_factory_defaults
701
 * NAME
702
 *   reset_factory_defaults - Reset the system to its default configuration.
703
 * RESULT
704
 *   integer	- indicates completion
705
 ******/
706
function reset_factory_defaults($lock = false, $reboot_required = true) {
707
	global $config, $g;
708

    
709
	/* Remove all additional packages */
710
	mwexec("/bin/sh /usr/local/sbin/{$g['product_name']}-upgrade " .
711
	    "-r ALL_PACKAGES -f");
712

    
713
	if (!$lock) {
714
		$lockkey = lock('config', LOCK_EX);
715
	}
716

    
717
	/* create conf directory, if necessary */
718
	safe_mkdir($g['cf_conf_path']);
719

    
720
	/* clear out /conf */
721
	$dh = opendir($g['conf_path']);
722
	while ($filename = readdir($dh)) {
723
		if (($filename != ".") && ($filename != "..") &&
724
		    (!is_dir($g['conf_path'] . "/" . $filename))) {
725
			unlink_if_exists($g['conf_path'] . "/" . $filename);
726
		}
727
	}
728
	closedir($dh);
729
	unlink_if_exists($g['tmp_path'] . "/config.cache");
730

    
731
	/* copy default configuration */
732
	copy("{$g['conf_default_path']}/config.xml",
733
	    "{$g['cf_conf_path']}/config.xml");
734

    
735
	disable_security_checks();
736

    
737
	/*
738
	   Let write_config know that we are awaiting reload of the current config
739
	   to factory defaults. Either the system is about to reboot, throwing away
740
	   the current in-memory config as it shuts down, or the in-memory config
741
	   is about to be reloaded on-the-fly by parse_config.
742

    
743
	   In both cases, we want to ensure that write_config does not flush the
744
	   in-memory config back to disk.
745
	*/
746
	$config['reset_factory_defaults'] = true;
747

    
748
	/* call the wizard */
749
	if ($reboot_required) {
750
		// If we need a reboot first then touch a different trigger file.
751
		touch("/conf/trigger_initial_wizard_after_reboot");
752
	} else {
753
		touch("/conf/trigger_initial_wizard");
754
	}
755
	if (!$lock) {
756
		unlock($lockkey);
757
	}
758
	console_configure();
759
	return 0;
760
}
761

    
762
function config_restore($conffile) {
763
	global $config, $g;
764

    
765
	if (!file_exists($conffile)) {
766
		return 1;
767
	}
768

    
769
	backup_config();
770

    
771

    
772
	$lockkey = lock('config', LOCK_EX);
773

    
774
	unlink_if_exists("{$g['tmp_path']}/config.cache");
775
	copy($conffile, "{$g['cf_conf_path']}/config.xml");
776

    
777
	disable_security_checks();
778

    
779
	unlock($lockkey);
780

    
781
	$config = parse_config(true);
782

    
783

    
784
	write_config(sprintf(gettext("Reverted to %s."), array_pop(explode("/", $conffile))), false);
785

    
786
	return 0;
787
}
788

    
789
function config_install($conffile) {
790
	global $config, $g;
791

    
792
	if (!file_exists($conffile)) {
793
		return 1;
794
	}
795

    
796
	if (!config_validate("{$conffile}")) {
797
		return 1;
798
	}
799

    
800
	if (platform_booting()) {
801
		echo gettext("Installing configuration...") . "\n";
802
	} else {
803
		log_error(gettext("Installing configuration ...."));
804
	}
805

    
806
	$lockkey = lock('config', LOCK_EX);
807

    
808
	copy($conffile, "{$g['conf_path']}/config.xml");
809

    
810
	disable_security_checks();
811

    
812
	/* unlink cache file if it exists */
813
	if (file_exists("{$g['tmp_path']}/config.cache")) {
814
		unlink("{$g['tmp_path']}/config.cache");
815
	}
816

    
817
	unlock($lockkey);
818

    
819
	return 0;
820
}
821

    
822
/*
823
 * Disable security checks for DNS rebind and HTTP referrer until next time
824
 * they pass (or reboot), to aid in preventing accidental lockout when
825
 * restoring settings like hostname, domain, IP addresses, and settings
826
 * related to the DNS rebind and HTTP referrer checks.
827
 * Intended for use when restoring a configuration or directly
828
 * modifying config.xml without an unconditional reboot.
829
 */
830
function disable_security_checks() {
831
	global $g;
832
	touch("{$g['tmp_path']}/disable_security_checks");
833
}
834

    
835
/* Restores security checks.  Should be called after all succeed. */
836
function restore_security_checks() {
837
	global $g;
838
	unlink_if_exists("{$g['tmp_path']}/disable_security_checks");
839
}
840

    
841
/* Returns status of security check temporary disable. */
842
function security_checks_disabled() {
843
	global $g;
844
	return file_exists("{$g['tmp_path']}/disable_security_checks");
845
}
846

    
847
function config_validate($conffile) {
848

    
849
	global $g, $xmlerr;
850

    
851
	$xml_parser = xml_parser_create();
852

    
853
	if (!($fp = fopen($conffile, "r"))) {
854
		$xmlerr = gettext("XML error: unable to open file");
855
		return false;
856
	}
857

    
858
	while ($data = fread($fp, 4096)) {
859
		if (!xml_parse($xml_parser, $data, feof($fp))) {
860
			$xmlerr = sprintf(gettext('%1$s at line %2$d'),
861
						xml_error_string(xml_get_error_code($xml_parser)),
862
						xml_get_current_line_number($xml_parser));
863
			return false;
864
		}
865
	}
866
	xml_parser_free($xml_parser);
867

    
868
	fclose($fp);
869

    
870
	return true;
871
}
872

    
873
function cleanup_backupcache($lock = false) {
874
	global $config, $g;
875
	$i = false;
876

    
877
	$revisions = intval(is_numericint($config['system']['backupcount']) ? $config['system']['backupcount'] : $g['default_config_backup_count']);
878

    
879
	if (!$lock) {
880
		$lockkey = lock('config');
881
	}
882

    
883

    
884
	$backups = get_backups();
885
	if ($backups) {
886
		$baktimes = $backups['versions'];
887
		unset($backups['versions']);
888
	} else {
889
		$backups = array();
890
		$baktimes = array();
891
	}
892
	$newbaks = array();
893
	$bakfiles = glob($g['cf_conf_path'] . "/backup/config-*");
894
	$tocache = array();
895

    
896
	foreach ($bakfiles as $backup) { // Check for backups in the directory not represented in the cache.
897
		$backupsize = filesize($backup);
898
		if ($backupsize == 0) {
899
			unlink($backup);
900
			continue;
901
		}
902
		$backupexp = explode('-', $backup);
903
		$backupexp = explode('.', array_pop($backupexp));
904
		$tocheck = array_shift($backupexp);
905
		unset($backupexp);
906
		if (!in_array($tocheck, $baktimes)) {
907
			$i = true;
908
			if (platform_booting()) {
909
				echo ".";
910
			}
911
			try {
912
				$newxml = parse_xml_config($backup, array($g['xml_rootobj'], 'pfsense'));
913
			} catch (Exception $exc) {
914
				log_error(sprintf(gettext("The backup cache file %s is corrupted. Parser error message: %s"), $backup, $exc->getMessage()));
915
				$newxml = "-1";
916
			}
917

    
918
			if ($newxml == "-1") {
919
				log_error(sprintf(gettext("The backup cache file %s is corrupted.  Unlinking."), $backup));
920
				unlink($backup);
921
				continue;
922
			}
923
			if ($newxml['revision']['description'] == "") {
924
				$newxml['revision']['description'] = "Unknown";
925
			}
926
			if ($newxml['version'] == "") {
927
				$newxml['version'] = "?";
928
			}
929
			$tocache[$tocheck] = array('description' => $newxml['revision']['description'], 'version' => $newxml['version'], 'filesize' => $backupsize);
930
		}
931
	}
932
	foreach ($backups as $checkbak) {
933
		if (count(preg_grep('/' . $checkbak['time'] . '/i', $bakfiles)) != 0) {
934
			$newbaks[] = $checkbak;
935
		} else {
936
			$i = true;
937
			if (platform_booting()) print " " . $tocheck . "r";
938
		}
939
	}
940
	foreach ($newbaks as $todo) {
941
		$tocache[$todo['time']] = array('description' => $todo['description'], 'version' => $todo['version'], 'filesize' => $todo['filesize']);
942
	}
943
	if (is_int($revisions) and (count($tocache) > $revisions)) {
944
		$toslice = array_slice(array_keys($tocache), 0, $revisions);
945
		$newcache = array();
946
		foreach ($toslice as $sliced) {
947
			$newcache[$sliced] = $tocache[$sliced];
948
		}
949
		foreach ($tocache as $version => $versioninfo) {
950
			if (!in_array($version, array_keys($newcache))) {
951
				unlink_if_exists($g['conf_path'] . '/backup/config-' . $version . '.xml');
952
			}
953
		}
954
		$tocache = $newcache;
955
	}
956
	$bakout = fopen($g['cf_conf_path'] . '/backup/backup.cache', "w");
957
	fwrite($bakout, serialize($tocache));
958
	fclose($bakout);
959
	//pfSense_fsync("{$g['cf_conf_path']}/backup/backup.cache");
960

    
961
	if (!$lock) {
962
		unlock($lockkey);
963
	}
964
}
965

    
966
function get_backups() {
967
	global $g;
968
	if (file_exists("{$g['cf_conf_path']}/backup/backup.cache")) {
969
		$confvers = unserialize(file_get_contents("{$g['cf_conf_path']}/backup/backup.cache"));
970
		$bakvers = array_keys($confvers);
971
		$toreturn = array();
972
		sort($bakvers);
973
		// 	$bakvers = array_reverse($bakvers);
974
		foreach (array_reverse($bakvers) as $bakver) {
975
			$toreturn[] = array('time' => $bakver, 'description' => $confvers[$bakver]['description'], 'version' => $confvers[$bakver]['version'], 'filesize' => $confvers[$bakver]['filesize']);
976
		}
977
	} else {
978
		return false;
979
	}
980
	$toreturn['versions'] = $bakvers;
981
	return $toreturn;
982
}
983

    
984
function backup_config() {
985
	global $config, $g;
986

    
987

    
988
	/* Create backup directory if needed */
989
	safe_mkdir("{$g['cf_conf_path']}/backup");
990
	if ($config['revision']['time'] == "") {
991
		$baktime = 0;
992
	} else {
993
		$baktime = $config['revision']['time'];
994
	}
995

    
996
	if ($config['revision']['description'] == "") {
997
		$bakdesc = "Unknown";
998
	} else {
999
		$bakdesc = $config['revision']['description'];
1000
	}
1001

    
1002
	$bakver = ($config['version'] == "") ? "?" : $config['version'];
1003
	$bakfilename = $g['cf_conf_path'] . '/backup/config-' . $baktime . '.xml';
1004
	copy($g['cf_conf_path'] . '/config.xml', $bakfilename);
1005

    
1006
	if (file_exists($g['cf_conf_path'] . '/backup/backup.cache')) {
1007
		$backupcache = unserialize(file_get_contents($g['cf_conf_path'] . '/backup/backup.cache'));
1008
	} else {
1009
		$backupcache = array();
1010
	}
1011
	$backupcache[$baktime] = array('description' => $bakdesc, 'version' => $bakver, 'filesize' => filesize($bakfilename));
1012
	$bakout = fopen($g['cf_conf_path'] . '/backup/backup.cache', "w");
1013
	fwrite($bakout, serialize($backupcache));
1014
	fclose($bakout);
1015
	//pfSense_fsync("{$g['cf_conf_path']}/backup/backup.cache");
1016

    
1017

    
1018
	return true;
1019
}
1020

    
1021
function backup_info($backup_info, $number) {
1022
	if ($backup_info['time'] != 0) {
1023
		$date = date(gettext("n/j/y H:i:s"), $backup_info['time']);
1024
	} else {
1025
		$date = gettext("Unknown");
1026
	}
1027

    
1028
	list($page, $reason) = explode(": ", $backup_info['description'], 2);
1029
	if (empty($reason)) {
1030
		$reason = $page;
1031
		$page = gettext("Unknown Page");
1032
	}
1033

    
1034
	$backup_info = sprintf("%02d", $number) . ". {$date}\tv{$backup_info['version']}\t{$page}\n";
1035
	if ($reason) {
1036
		$backup_info .= "    {$reason}\n";
1037
	}
1038
	return $backup_info;
1039
}
1040

    
1041
function set_device_perms() {
1042
	$devices = array(
1043
		'pf' => array(
1044
			'user' => 'root',
1045
			'group' => 'proxy',
1046
			'mode' => 0660),
1047
		);
1048

    
1049
	foreach ($devices as $name => $attr) {
1050
		$path = "/dev/$name";
1051
		if (file_exists($path)) {
1052
			chown($path, $attr['user']);
1053
			chgrp($path, $attr['group']);
1054
			chmod($path, $attr['mode']);
1055
		}
1056
	}
1057
}
1058

    
1059
function get_config_user() {
1060
	if (empty($_SESSION["Username"])) {
1061
		$username = getenv("USER");
1062
		if (empty($conuser) || $conuser == "root") {
1063
			$username = "(system)";
1064
		}
1065
	} else {
1066
		$username = $_SESSION["Username"];
1067
	}
1068

    
1069
	if (!empty($_SERVER['REMOTE_ADDR'])) {
1070
		$username .= '@' . get_user_remote_address() . get_user_remote_authsource();
1071
	}
1072

    
1073
	return $username;
1074
}
1075

    
1076
function make_config_revision_entry($desc = null, $override_user = null) {
1077
	if (empty($override_user)) {
1078
		$username = get_config_user();
1079
	} else {
1080
		$username = $override_user;
1081
	}
1082

    
1083
	$revision = array();
1084

    
1085
	if (time() > mktime(0, 0, 0, 9, 1, 2004)) {     /* make sure the clock settings are plausible */
1086
		$revision['time'] = time();
1087
	}
1088

    
1089
	/* Log the running script so it's not entirely unlogged what changed */
1090
	if ($desc == "Unknown") {
1091
		$desc = sprintf(gettext("%s made unknown change"), $_SERVER['SCRIPT_NAME']);
1092
	}
1093
	if (!empty($desc)) {
1094
		$revision['description'] = "{$username}: " . $desc;
1095
	}
1096
	$revision['username'] = $username;
1097
	return $revision;
1098
}
1099

    
1100
function pfSense_clear_globals() {
1101
	global $config, $FilterIfList, $GatewaysList, $filterdns, $aliases, $aliastable;
1102

    
1103
	$error = error_get_last();
1104

    
1105
	// Errors generated by user code (diag_commands.php) are identified by path and not added to notices
1106
	if ($error !== NULL && !preg_match('|^' . preg_quote($g['tmp_path_user_code']) . '/[^/]{1,16}$|', $error['file'])) {
1107
		if (in_array($error['type'], array(E_ERROR, E_COMPILE_ERROR, E_CORE_ERROR, E_RECOVERABLE_ERROR))) {
1108
			$errorstr = "PHP ERROR: Type: {$error['type']}, File: {$error['file']}, Line: {$error['line']}, Message: {$error['message']}";
1109
			print($errorstr);
1110
			log_error($errorstr);
1111
			file_notice("phperror", $errorstr, 'PHP errors');
1112
		} else if ($error['type'] != E_NOTICE) {
1113
			$errorstr = "PHP WARNING: Type: {$error['type']}, File: {$error['file']}, Line: {$error['line']}, Message: {$error['message']}";
1114
			// XXX: comment out for now, should re-enable post-2.2
1115
			//print($errorstr);
1116
			//log_error($errorstr);
1117
			//file_notice("phpwarning", $errorstr, 'PHP warning');
1118
		}
1119
	}
1120

    
1121
	if (isset($FilterIfList)) {
1122
		unset($FilterIfList);
1123
	}
1124

    
1125
	if (isset($GatewaysList)) {
1126
		unset($GatewaysList);
1127
	}
1128

    
1129
	/* Used for the hostname dns resolver */
1130
	if (isset($filterdns)) {
1131
		unset($filterdns);
1132
	}
1133

    
1134
	/* Used for aliases and interface macros */
1135
	if (isset($aliases)) {
1136
		unset($aliases);
1137
	}
1138
	if (isset($aliastable)) {
1139
		unset($aliastable);
1140
	}
1141

    
1142
	unset($config);
1143
}
1144

    
1145
/* Initialize a config array multiple levels deep only if unset
1146
 * Pass it an array of keys to test and create
1147
 * init_config_arr(array('virtualip', 'vip'));
1148
 */
1149
function init_config_arr($keys) {
1150
	global $config;
1151
	$c = &$config;
1152
	if (!is_array($keys)) {
1153
		return null;
1154
	}
1155
	foreach ($keys as $k) {
1156
		if (!is_array($c[$k])) {
1157
			$c[$k] = array();
1158
		}
1159
		$c = &$c[$k];
1160
	}
1161
}
1162

    
1163
register_shutdown_function('pfSense_clear_globals');
1164

    
1165
?>
(11-11/61)