Project

General

Profile

Download (37.1 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-2022 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('util.inc');
30

    
31
/****f* config/encrypted_configxml
32
 * NAME
33
 *   encrypted_configxml - Checks to see if config.xml is encrypted and if so, prompts to unlock.
34
 * INPUTS
35
 *   None
36
 * RESULT
37
 *   $config 	- rewrites config.xml without encryption
38
 ******/
39
function encrypted_configxml() {
40
	global $g, $config;
41

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

    
46
	if (!platform_booting()) {
47
		return;
48
	}
49

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

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

    
88
	$lockkey = lock('config');
89
	$config_parsed = false;
90

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

    
103
	if (platform_booting(true)) {
104
		echo ".";
105
	}
106

    
107
	// Check for encrypted config.xml
108
	encrypted_configxml();
109

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

    
150
	if (platform_booting(true)) {
151
		echo ".";
152
	}
153

    
154
	$config_parsed = true;
155
	unlock($lockkey);
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
	global $g;
189

    
190
	$backups = glob('/cf/conf/backup/*.xml');
191
	foreach (array_reverse($backups) as $backup) {
192
		/* checking multiple backups when detecting invalid configuration
193
		 * https://redmine.pfsense.org/issues/11748 */
194
		if (filesize($backup) != 0) {
195
			$testconfig = parse_xml_config($backup, $g['xml_rootobj']);
196
			if ($testconfig != -1) {
197
				return basename($backup);
198
			}
199
		} 
200
	}
201

    
202
	return false;
203
}
204

    
205
function restore_backup($file) {
206
	global $g;
207

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

    
238
/*
239
 *	Backup RRD/XML Data
240
 */
241

    
242
/* If the config on disk had rrddata/xmldata tags already, remove that section first.
243
 * See https://redmine.pfsense.org/issues/8994,
244
 *     https://redmine.pfsense.org/issues/10508, 
245
 *     https://redmine.pfsense.org/issues/11050 */
246
function clear_tagdata($tag, $data) {
247
	$data = preg_replace("/[[:blank:]]*<{$tag}data>.*<\\/{$tag}data>[[:blank:]]*\n*/s", "", $data);
248
	$data = preg_replace("/[[:blank:]]*<{$tag}data\\/>[[:blank:]]*\n*/", "", $data);
249

    
250
	return $data;
251
}
252

    
253
function restore_xmldatafile($type='voucher', $conf = false) {
254
	global $config, $g;
255

    
256
	if (!$conf) {
257
		$conf = & $config;
258
	}
259

    
260
	foreach ($conf[$type]["{$type}data"]["xmldatafile"] as $file) {
261
		$basename = basename($file['filename']);
262
		$dirname = dirname($g['backuppath'][$type]);
263
		$xmldata_file = "{$dirname}/{$basename}";
264
		if (!is_dir($dirname)) {
265
			safe_mkdir($dirname);
266
		}
267
		if (file_put_contents($xmldata_file, gzinflate(base64_decode($file['data']))) === false) {
268
			log_error(sprintf(gettext("Cannot write %s"), $xmldata_file));
269
			continue;
270
		}
271
	}
272
}
273

    
274
function restore_rrddata($conf = false) {
275
	global $config, $g, $rrdtool, $input_errors;
276

    
277
	if (!$conf) {
278
		$conf = & $config;
279
	}
280

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

    
328
function restore_sshdata($conf = false) {
329
	global $config, $sshConfigDir;
330

    
331
	if (!$conf) {
332
		$conf = & $config;
333
	}
334

    
335
	$oldmask = umask();
336
	foreach ($conf["sshdata"]["sshkeyfile"] as $sshkey) {
337
		$keypath = "{$sshConfigDir}/{$sshkey['filename']}";
338
		if (strstr($sshkey['filename'], 'pub')) {
339
			umask(0133);
340
		} else {
341
			umask(0177);
342
		}
343
		if (file_put_contents($keypath, gzinflate(base64_decode($sshkey['xmldata']))) === false) {
344
			log_error(sprintf(gettext("Cannot write %s"), $sshkey['filename']));
345
			continue;
346
		}
347
	}
348
	umask($oldmask);
349
}
350

    
351
/****f* config/parse_config_bootup
352
 * NAME
353
 *   parse_config_bootup - Bootup-specific configuration checks.
354
 * RESULT
355
 *   null
356
 ******/
357
function parse_config_bootup() {
358
	global $config, $g;
359

    
360
	if (platform_booting()) {
361
		echo ".";
362
	}
363

    
364
	$lockkey = lock('config');
365
	if (!file_exists("{$g['conf_path']}/config.xml")) {
366
		if (platform_booting()) {
367
			$last_backup = discover_last_backup();
368
			if ($last_backup) {
369
				log_error("No config.xml found, attempting last known config restore.");
370
				file_notice("config.xml", gettext("No config.xml found, attempting last known config restore."), "pfSenseConfigurator", "");
371
				restore_backup("/cf/conf/backup/{$last_backup}");
372
			}
373
			if (!file_exists("{$g['conf_path']}/config.xml")) {
374
				echo sprintf(gettext("XML configuration file not found.  %s cannot continue booting."), $g['product_label']) . "\n";
375
				unlock($lockkey);
376
				die(gettext("Could not find a usable configuration file or it's backup! Exiting...."));
377
			} else {
378
				log_error("Last known config found and restored.  Please double check the configuration file for accuracy.");
379
				file_notice("config.xml", gettext("Last known config found and restored.  Please double check the configuration file for accuracy."), "pfSenseConfigurator", "");
380
			}
381
		} else {
382
			unlock($lockkey);
383
			log_error(gettext("Could not find a usable configuration file! Exiting...."));
384
			exit(0);
385
		}
386
	}
387

    
388
	if (filesize("{$g['conf_path']}/config.xml") == 0) {
389
		$last_backup = discover_last_backup();
390
		if ($last_backup) {
391
			log_error(gettext("No config.xml found, attempting last known config restore."));
392
			file_notice("config.xml", gettext("No config.xml found, attempting last known config restore."), "pfSenseConfigurator", "");
393
			restore_backup("{$g['conf_path']}/backup/{$last_backup}");
394
		} else {
395
			unlock($lockkey);
396
			die(gettext("Config.xml is corrupted and is 0 bytes.  Could not restore a previous backup."));
397
		}
398
	}
399
	unlock($lockkey);
400

    
401
	$config = parse_config(true);
402

    
403
	if ((float)$config['version'] > (float)$g['latest_config']) {
404
		echo <<<EOD
405

    
406

    
407
*******************************************************************************
408
* WARNING!                                                                    *
409
* The current configuration has been created with a newer version of {$g['product_label']}  *
410
* than this one! This can lead to serious misbehavior and even security       *
411
* holes! You are urged to either upgrade to a newer version of {$g['product_label']} or     *
412
* revert to the default configuration immediately!                            *
413
*******************************************************************************
414

    
415

    
416
EOD;
417
		}
418

    
419
	/* make alias table (for faster lookups) */
420
	alias_make_table();
421
}
422

    
423
/****f* config/conf_mount_rw
424
 * NAME
425
 *   conf_mount_rw - Mount filesystems read/write.
426
 * RESULT
427
 *   null
428
 ******/
429
/* mount flash card read/write */
430
function conf_mount_rw() {
431
	/* Obsoleted. Keep it here until all calls are removed */
432
	return;
433
}
434

    
435
/****f* config/conf_mount_ro
436
 * NAME
437
 *   conf_mount_ro - Mount filesystems readonly.
438
 * RESULT
439
 *   null
440
 ******/
441
function conf_mount_ro() {
442
	/* Obsoleted. Keep it here until all calls are removed */
443
	return;
444
}
445

    
446
/****f* config/convert_config
447
 * NAME
448
 *   convert_config - Attempt to update config.xml.
449
 * DESCRIPTION
450
 *   convert_config() reads the current global configuration
451
 *   and attempts to convert it to conform to the latest
452
 *   config.xml version. This allows major formatting changes
453
 *   to be made with a minimum of breakage.
454
 * RESULT
455
 *   null
456
 ******/
457
/* convert configuration, if necessary */
458
function convert_config() {
459
	global $config, $g;
460
	$now = date("H:i:s");
461
	log_error(sprintf(gettext("Start Configuration upgrade at %s, set execution timeout to 15 minutes"), $now));
462
	//ini_set("max_execution_time", "900");
463

    
464
	/* special case upgrades */
465
	/* fix every minute crontab bogons entry */
466
	if (is_array($config['cron'])) {
467
		$cron_item_count = count($config['cron']['item']);
468
		for ($x = 0; $x < $cron_item_count; $x++) {
469
			if (stristr($config['cron']['item'][$x]['command'], "rc.update_bogons.sh")) {
470
				if ($config['cron']['item'][$x]['hour'] == "*") {
471
					$config['cron']['item'][$x]['hour'] = "3";
472
					write_config(gettext("Updated bogon update frequency to 3am"));
473
					log_error(gettext("Updated bogon update frequency to 3am"));
474
				}
475
			}
476
		}
477
	}
478

    
479
	// Save off config version
480
	$prev_version = $config['version'];
481

    
482
	include_once('auth.inc');
483
	include_once('upgrade_config.inc');
484
	if (file_exists("/etc/inc/upgrade_config_custom.inc")) {
485
		include_once("upgrade_config_custom.inc");
486
	}
487

    
488
	if ($config['version'] == $g['latest_config']) {
489
		additional_config_upgrade();
490
		return;		/* already at latest version */
491
	}
492

    
493
	if (!is_array($config['system']['already_run_config_upgrade'])) {
494
		$config['system']['already_run_config_upgrade'] = array();
495
	}
496
	$already_run = $config['system']['already_run_config_upgrade'];
497

    
498
	/* Loop and run upgrade_VER_to_VER() until we're at current version */
499
	while ($config['version'] < $g['latest_config']) {
500
		$cur = $config['version'] * 10;
501
		$next = $cur + 1;
502
		$migration_function = sprintf('upgrade_%03d_to_%03d', $cur,
503
		    $next);
504

    
505
		foreach (array("", "_custom") as $suffix) {
506
			$migration_function .= $suffix;
507
			if (!function_exists($migration_function)) {
508
				continue;
509
			}
510
			if (isset($already_run[$migration_function])) {
511
				/* Already executed, skip now */
512
				unset($config['system']
513
				    ['already_run_config_upgrade']
514
				    [$migration_function]);
515
			} else {
516
				$migration_function();
517
			}
518
		}
519
		$config['version'] = sprintf('%.1f', $next / 10);
520
		if (platform_booting()) {
521
			echo ".";
522
		}
523
	}
524

    
525
	if ($prev_version != $config['version']) {
526
		$now = date("H:i:s");
527
		log_error(sprintf(gettext("Ended Configuration upgrade at %s"), $now));
528

    
529
		write_config(sprintf(gettext('Upgraded config version level from %1$s to %2$s'), $prev_version, $config['version']));
530
	}
531

    
532
	additional_config_upgrade();
533
}
534

    
535
/****f* config/safe_write_file
536
 * NAME
537
 *   safe_write_file - Write a file out atomically
538
 * DESCRIPTION
539
 *   safe_write_file() Writes a file out atomically by first writing to a
540
 *   temporary file of the same name but ending with the pid of the current
541
 *   process, them renaming the temporary file over the original.
542
 * INPUTS
543
 *   $filename  - string containing the filename of the file to write
544
 *   $content   - string or array containing the file content to write to file
545
 *   $force_binary      - boolean denoting whether we should force binary
546
 *   mode writing.
547
 * RESULT
548
 *   boolean - true if successful, false if not
549
 ******/
550
function safe_write_file($file, $content, $force_binary = false) {
551
	$tmp_file = $file . "." . getmypid();
552
	$write_mode = $force_binary ? "wb" : "w";
553

    
554
	$fd = fopen($tmp_file, $write_mode);
555
	if (!$fd) {
556
		// Unable to open temporary file for writing
557
		return false;
558
	}
559
	if (is_array($content)) {
560
		foreach ($content as $line) {
561
			if (!fwrite($fd, $line . "\n")) {
562
				// Unable to write to temporary file
563
				fclose($fd);
564
				return false;
565
			}
566
		}
567
	} elseif (!fwrite($fd, $content)) {
568
		// Unable to write to temporary file
569
		fclose($fd);
570
		return false;
571
	}
572
	fflush($fd);
573
	fclose($fd);
574

    
575
	/* XXX Re-add pfSense_fsync() call here after it's fixed */
576
	// if (!pfSense_fsync($tmp_file) || !rename($tmp_file, $file)) {
577
	if (!rename($tmp_file, $file)) {
578
		// Unable to move temporary file to original
579
		@unlink($tmp_file);
580
		return false;
581
	}
582

    
583
	// Sync file before returning
584
	//return pfSense_fsync($file);
585
	return true;
586
}
587

    
588
/****f* config/write_config
589
 * NAME
590
 *   write_config - Backup and write the firewall configuration.
591
 * DESCRIPTION
592
 *   write_config() handles backing up the current configuration,
593
 *   applying changes, and regenerating the configuration cache.
594
 * INPUTS
595
 *   $desc	- string containing the a description of configuration changes
596
 *   $backup	- boolean: do not back up current configuration if false.
597
 *   $write_config_only	- boolean: do not sync or reload anything; just save the configuration if true.
598
 * RESULT
599
 *   null
600
 ******/
601
/* save the system configuration */
602
function write_config($desc="Unknown", $backup = true, $write_config_only = false) {
603
	global $config, $g;
604

    
605
	// Certain strings may be embedded in the $desc (reason) parameter to trigger certain behavior.
606
	// If detected, those strings are removed and a variable set.
607
	$doacb = true;
608
	$manual_acb = false;
609
	$rcnt = 0;
610

    
611
	$desc = str_replace("-MaNuAlBaCkUp", "", $desc, $rcnt);
612
	if ($rcnt > 0) {
613
		$manual_acb = true; // Manual backups require special processing on the server
614
	}
615

    
616
	$rcnt = 0;
617
	$desc = str_replace("-NoReMoTeBaCkUp", "", $desc, $rcnt);
618
	if ($rcnt > 0) {
619
		$doacb = false; // No ACB will be performed if this string is detected
620
	}
621

    
622
	/*
623
	* Syncing vouchers happens every minute and sometimes multiple times. We don't
624
	* want to fill up our db with a lot of the same config so just ignore that case.
625
	*/
626
	if((strpos($desc, 'Syncing vouchers') !== false ||
627
		strpos($desc, 'Captive Portal Voucher database synchronized') !== false) ) {
628
		$doacb = false;
629
	}
630

    
631
	if (!empty($_SERVER['REMOTE_ADDR'])) {
632
		@phpsession_begin();
633
		if (!empty($_SESSION['Username']) && ($_SESSION['Username'] != "admin")) {
634
			$user = getUserEntry($_SESSION['Username']);
635
			if (is_array($user) && userHasPrivilege($user, "user-config-readonly")) {
636
				syslog(LOG_AUTHPRIV, sprintf(gettext("Save config permission denied by the 'User - Config: Deny Config Write' permission for user '%s'."), get_config_user()));
637
				phpsession_end(true);
638
				return false;
639
			}
640
		}
641
		if (!isset($argc)) {
642
			phpsession_end(true);
643
		}
644
	}
645

    
646
	if (isset($config['reset_factory_defaults'])) {
647
		/*
648
		   We have put a default config.xml on disk and are about to reboot
649
		   or reload it. Do not let any system or package code try to save
650
		   state to config because that would overwrite the default config
651
		   with the running config.
652
		*/
653
		return false;
654
	}
655

    
656
	if ($backup) {
657
		backup_config();
658
	}
659

    
660
	if ($desc == "Unknown") {
661
		file_notice("config.xml", gettext(
662
		    'WARNING: write_config() was called without description'));
663
	}
664
	$config['revision'] = make_config_revision_entry($desc);
665

    
666
	$lockkey = lock('config', LOCK_EX);
667

    
668
	/* generate configuration XML */
669
	$xmlconfig = dump_xml_config($config, $g['xml_rootobj']);
670

    
671
	/* write new configuration */
672
	if (!safe_write_file("{$g['cf_conf_path']}/config.xml", $xmlconfig)) {
673
		log_error(gettext("WARNING: Config contents could not be saved. Could not open file!"));
674
		unlock($lockkey);
675
		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"));
676
		return -1;
677
	}
678

    
679
	if (config_get_path('syslog/logconfigchanges') != "disabled") {
680
		log_error(gettext("Configuration Change") . ": {$config['revision']['description']}");
681
	}
682

    
683
	cleanup_backupcache(true);
684

    
685
	/* re-read configuration */
686
	/* NOTE: We assume that the file can be parsed since we wrote it. */
687
	$config = parse_xml_config("{$g['conf_path']}/config.xml", $g['xml_rootobj']);
688
	if ($config == -1) {
689
		copy("{$g['conf_path']}/config.xml", "{$g['conf_path']}/config.xml.bad");
690
		$last_backup = discover_last_backup();
691
		if ($last_backup) {
692
			restore_backup("/cf/conf/backup/{$last_backup}");
693
			$config = parse_xml_config("{$g['conf_path']}/config.xml", $g['xml_rootobj']);
694
			if (platform_booting()) {
695
				echo "\n\n ************** WARNING **************";
696
				echo "\n\n Configuration could not be validated. A previous configuration was restored. \n";
697
				echo "\n The failed configuration file has been saved as {$g['conf_path']}/config.xml.bad \n\n";
698
			}
699
		} else {
700
			log_error(gettext("Could not restore config.xml."));
701
		}
702
	} else {
703
		generate_config_cache($config);
704
	}
705

    
706
	unlock($lockkey);
707

    
708
	if ($write_config_only) {
709
		return $config;
710
	}
711

    
712
	unlink_if_exists("/usr/local/pkg/pf/carp_sync_client.php");
713

    
714
	/* sync carp entries to other firewalls */
715
	carp_sync_client();
716

    
717
	if (is_dir("/usr/local/pkg/write_config")) {
718
		/* process packager manager custom rules */
719
		run_plugins("/usr/local/pkg/write_config/");
720
	}
721

    
722
	// Try the core AutoConfigBackup system
723
	if (is_array($config['system']['acb']) && $config['system']['acb']['enable'] == "yes" &&
724
	    (!isset($config['system']['acb']['frequency']) || $config['system']['acb']['frequency'] == "every") || file_exists("/tmp/forceacb")) {
725
	    if ($doacb) {
726
			require_once("acb.inc");
727
			upload_config($manual_acb);
728
		}
729

    
730
		if (file_exists("/tmp/forceacb")) {
731
			unlink("/tmp/forceacb");
732
		}
733
	}
734

    
735
	return $config;
736
}
737

    
738
/****f* config/reset_factory_defaults
739
 * NAME
740
 *   reset_factory_defaults - Reset the system to its default configuration.
741
 * RESULT
742
 *   integer	- indicates completion
743
 ******/
744
function reset_factory_defaults($lock = false, $reboot_required = true) {
745
	global $config, $g;
746

    
747
	/* Remove all additional packages */
748
	mwexec("/bin/sh /usr/local/sbin/{$g['product_name']}-upgrade " .
749
	    "-r ALL_PACKAGES -f");
750

    
751
	if (!$lock) {
752
		$lockkey = lock('config', LOCK_EX);
753
	}
754

    
755
	/* create conf directory, if necessary */
756
	safe_mkdir($g['cf_conf_path']);
757

    
758
	/* clear out /conf */
759
	$dh = opendir($g['conf_path']);
760
	while ($filename = readdir($dh)) {
761
		if (($filename != ".") && ($filename != "..") &&
762
		    (!is_dir($g['conf_path'] . "/" . $filename))) {
763
			if ($filename == "enableserial_force")
764
				continue;
765
			unlink_if_exists($g['conf_path'] . "/" . $filename);
766
		}
767
	}
768
	closedir($dh);
769
	unlink_if_exists($g['tmp_path'] . "/config.cache");
770

    
771
	/* copy default configuration */
772
	copy("{$g['conf_default_path']}/config.xml",
773
	    "{$g['cf_conf_path']}/config.xml");
774

    
775
	disable_security_checks();
776

    
777
	/*
778
	   Let write_config know that we are awaiting reload of the current config
779
	   to factory defaults. Either the system is about to reboot, throwing away
780
	   the current in-memory config as it shuts down, or the in-memory config
781
	   is about to be reloaded on-the-fly by parse_config.
782

    
783
	   In both cases, we want to ensure that write_config does not flush the
784
	   in-memory config back to disk.
785
	*/
786
	$config['reset_factory_defaults'] = true;
787

    
788
	/* call the wizard */
789
	if ($reboot_required) {
790
		// If we need a reboot first then touch a different trigger file.
791
		touch("/conf/trigger_initial_wizard_after_reboot");
792
	} else {
793
		touch("/conf/trigger_initial_wizard");
794
	}
795
	if (!$lock) {
796
		unlock($lockkey);
797
	}
798
	console_configure();
799
	return 0;
800
}
801

    
802
function config_restore($conffile) {
803
	global $config, $g;
804

    
805
	if (!file_exists($conffile)) {
806
		return 1;
807
	}
808

    
809
	backup_config();
810

    
811

    
812
	$lockkey = lock('config', LOCK_EX);
813

    
814
	unlink_if_exists("{$g['tmp_path']}/config.cache");
815
	copy($conffile, "{$g['cf_conf_path']}/config.xml");
816

    
817
	disable_security_checks();
818

    
819
	unlock($lockkey);
820

    
821
	$config = parse_config(true);
822

    
823

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

    
826
	return 0;
827
}
828

    
829
function config_install($conffile) {
830
	global $config, $g;
831

    
832
	if (!file_exists($conffile)) {
833
		return 1;
834
	}
835

    
836
	if (!config_validate("{$conffile}")) {
837
		return 1;
838
	}
839

    
840
	if (platform_booting()) {
841
		echo gettext("Installing configuration...") . "\n";
842
	} else {
843
		log_error(gettext("Installing configuration ...."));
844
	}
845

    
846
	$lockkey = lock('config', LOCK_EX);
847

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

    
850
	disable_security_checks();
851

    
852
	/* unlink cache file if it exists */
853
	if (file_exists("{$g['tmp_path']}/config.cache")) {
854
		unlink("{$g['tmp_path']}/config.cache");
855
	}
856

    
857
	unlock($lockkey);
858

    
859
	return 0;
860
}
861

    
862
/*
863
 * Disable security checks for DNS rebind and HTTP referrer until next time
864
 * they pass (or reboot), to aid in preventing accidental lockout when
865
 * restoring settings like hostname, domain, IP addresses, and settings
866
 * related to the DNS rebind and HTTP referrer checks.
867
 * Intended for use when restoring a configuration or directly
868
 * modifying config.xml without an unconditional reboot.
869
 */
870
function disable_security_checks() {
871
	global $g;
872
	touch("{$g['tmp_path']}/disable_security_checks");
873
}
874

    
875
/* Restores security checks.  Should be called after all succeed. */
876
function restore_security_checks() {
877
	global $g;
878
	unlink_if_exists("{$g['tmp_path']}/disable_security_checks");
879
}
880

    
881
/* Returns status of security check temporary disable. */
882
function security_checks_disabled() {
883
	global $g;
884
	return file_exists("{$g['tmp_path']}/disable_security_checks");
885
}
886

    
887
function config_validate($conffile) {
888

    
889
	global $g, $xmlerr;
890

    
891
	$xml_parser = xml_parser_create();
892

    
893
	if (!($fp = fopen($conffile, "r"))) {
894
		$xmlerr = gettext("XML error: unable to open file");
895
		return false;
896
	}
897

    
898
	while ($data = fread($fp, 4096)) {
899
		if (!xml_parse($xml_parser, $data, feof($fp))) {
900
			$xmlerr = sprintf(gettext('%1$s at line %2$d'),
901
						xml_error_string(xml_get_error_code($xml_parser)),
902
						xml_get_current_line_number($xml_parser));
903
			return false;
904
		}
905
	}
906
	xml_parser_free($xml_parser);
907

    
908
	fclose($fp);
909

    
910
	return true;
911
}
912

    
913
function cleanup_backupcache($lock = false) {
914
	global $config, $g;
915
	$i = false;
916

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

    
919
	if (!$lock) {
920
		$lockkey = lock('config');
921
	}
922

    
923

    
924
	$backups = get_backups();
925
	if ($backups) {
926
		$baktimes = $backups['versions'];
927
		unset($backups['versions']);
928
	} else {
929
		$backups = array();
930
		$baktimes = array();
931
	}
932
	$newbaks = array();
933
	$bakfiles = glob($g['cf_conf_path'] . "/backup/config-*");
934
	$tocache = array();
935

    
936
	foreach ($bakfiles as $backup) { // Check for backups in the directory not represented in the cache.
937
		$backupsize = filesize($backup);
938
		if ($backupsize == 0) {
939
			unlink($backup);
940
			continue;
941
		}
942
		$backupexp = explode('-', $backup);
943
		$backupexp = explode('.', array_pop($backupexp));
944
		$tocheck = array_shift($backupexp);
945
		unset($backupexp);
946
		if (!in_array($tocheck, $baktimes)) {
947
			$i = true;
948
			if (platform_booting()) {
949
				echo ".";
950
			}
951
			try {
952
				$newxml = parse_xml_config($backup, array($g['xml_rootobj'], 'pfsense'));
953
			} catch (Exception $exc) {
954
				log_error(sprintf(gettext("The backup cache file %s is corrupted. Parser error message: %s"), $backup, $exc->getMessage()));
955
				$newxml = "-1";
956
			}
957

    
958
			if ($newxml == "-1") {
959
				log_error(sprintf(gettext("The backup cache file %s is corrupted.  Unlinking."), $backup));
960
				unlink($backup);
961
				continue;
962
			}
963
			if ($newxml['revision']['description'] == "") {
964
				$newxml['revision']['description'] = "Unknown";
965
			}
966
			if ($newxml['version'] == "") {
967
				$newxml['version'] = "?";
968
			}
969
			$tocache[$tocheck] = array('description' => $newxml['revision']['description'], 'version' => $newxml['version'], 'filesize' => $backupsize);
970
		}
971
	}
972
	foreach ($backups as $checkbak) {
973
		if (count(preg_grep('/' . $checkbak['time'] . '/i', $bakfiles)) != 0) {
974
			$newbaks[] = $checkbak;
975
		} else {
976
			$i = true;
977
			if (platform_booting()) print " " . $tocheck . "r";
978
		}
979
	}
980
	foreach ($newbaks as $todo) {
981
		$tocache[$todo['time']] = array('description' => $todo['description'], 'version' => $todo['version'], 'filesize' => $todo['filesize']);
982
	}
983
	if (is_int($revisions) and (count($tocache) > $revisions)) {
984
		$toslice = array_slice(array_keys($tocache), 0, $revisions);
985
		$newcache = array();
986
		foreach ($toslice as $sliced) {
987
			$newcache[$sliced] = $tocache[$sliced];
988
		}
989
		foreach ($tocache as $version => $versioninfo) {
990
			if (!in_array($version, array_keys($newcache))) {
991
				unlink_if_exists($g['conf_path'] . '/backup/config-' . $version . '.xml');
992
			}
993
		}
994
		$tocache = $newcache;
995
	}
996
	$bakout = fopen($g['cf_conf_path'] . '/backup/backup.cache', "w");
997
	fwrite($bakout, serialize($tocache));
998
	fclose($bakout);
999
	//pfSense_fsync("{$g['cf_conf_path']}/backup/backup.cache");
1000

    
1001
	if (!$lock) {
1002
		unlock($lockkey);
1003
	}
1004
}
1005

    
1006
function get_backups() {
1007
	global $g;
1008
	if (file_exists("{$g['cf_conf_path']}/backup/backup.cache")) {
1009
		$confvers = unserialize(file_get_contents("{$g['cf_conf_path']}/backup/backup.cache"));
1010
		$bakvers = array_keys($confvers);
1011
		$toreturn = array();
1012
		sort($bakvers);
1013
		// 	$bakvers = array_reverse($bakvers);
1014
		foreach (array_reverse($bakvers) as $bakver) {
1015
			$toreturn[] = array('time' => $bakver, 'description' => $confvers[$bakver]['description'], 'version' => $confvers[$bakver]['version'], 'filesize' => $confvers[$bakver]['filesize']);
1016
		}
1017
	} else {
1018
		return false;
1019
	}
1020
	$toreturn['versions'] = $bakvers;
1021
	return $toreturn;
1022
}
1023

    
1024
function backup_config() {
1025
	global $config, $g;
1026

    
1027

    
1028
	/* Create backup directory if needed */
1029
	safe_mkdir("{$g['cf_conf_path']}/backup");
1030
	if ($config['revision']['time'] == "") {
1031
		$baktime = 0;
1032
	} else {
1033
		$baktime = $config['revision']['time'];
1034
	}
1035

    
1036
	if ($config['revision']['description'] == "") {
1037
		$bakdesc = "Unknown";
1038
	} else {
1039
		$bakdesc = $config['revision']['description'];
1040
	}
1041

    
1042
	$bakver = ($config['version'] == "") ? "?" : $config['version'];
1043
	$bakfilename = $g['cf_conf_path'] . '/backup/config-' . $baktime . '.xml';
1044
	copy($g['cf_conf_path'] . '/config.xml', $bakfilename);
1045

    
1046
	if (file_exists($g['cf_conf_path'] . '/backup/backup.cache')) {
1047
		$backupcache = unserialize(file_get_contents($g['cf_conf_path'] . '/backup/backup.cache'));
1048
	} else {
1049
		$backupcache = array();
1050
	}
1051
	$backupcache[$baktime] = array('description' => $bakdesc, 'version' => $bakver, 'filesize' => filesize($bakfilename));
1052
	$bakout = fopen($g['cf_conf_path'] . '/backup/backup.cache', "w");
1053
	fwrite($bakout, serialize($backupcache));
1054
	fclose($bakout);
1055
	//pfSense_fsync("{$g['cf_conf_path']}/backup/backup.cache");
1056

    
1057

    
1058
	return true;
1059
}
1060

    
1061
function backup_info($backup_info, $number) {
1062
	if ($backup_info['time'] != 0) {
1063
		$date = date(gettext("n/j/y H:i:s"), $backup_info['time']);
1064
	} else {
1065
		$date = gettext("Unknown");
1066
	}
1067

    
1068
	list($page, $reason) = explode(": ", $backup_info['description'], 2);
1069
	if (empty($reason)) {
1070
		$reason = $page;
1071
		$page = gettext("Unknown Page");
1072
	}
1073

    
1074
	$backup_info = sprintf("%02d", $number) . ". {$date}\tv{$backup_info['version']}\t{$page}\n";
1075
	if ($reason) {
1076
		$backup_info .= "    {$reason}\n";
1077
	}
1078
	return $backup_info;
1079
}
1080

    
1081
function set_device_perms() {
1082
	$devices = array(
1083
		'pf' => array(
1084
			'user' => 'root',
1085
			'group' => 'proxy',
1086
			'mode' => 0660),
1087
		);
1088

    
1089
	foreach ($devices as $name => $attr) {
1090
		$path = "/dev/$name";
1091
		if (file_exists($path)) {
1092
			chown($path, $attr['user']);
1093
			chgrp($path, $attr['group']);
1094
			chmod($path, $attr['mode']);
1095
		}
1096
	}
1097
}
1098

    
1099
function get_config_user() {
1100
	if (empty($_SESSION["Username"])) {
1101
		$username = getenv("USER");
1102
		if (empty($conuser) || $conuser == "root") {
1103
			$username = "(system)";
1104
		}
1105
	} else {
1106
		$username = $_SESSION["Username"];
1107
	}
1108

    
1109
	if (!empty($_SERVER['REMOTE_ADDR'])) {
1110
		$username .= '@' . get_user_remote_address() . get_user_remote_authsource();
1111
	}
1112

    
1113
	return $username;
1114
}
1115

    
1116
function make_config_revision_entry($desc = null, $override_user = null) {
1117
	if (empty($override_user)) {
1118
		$username = get_config_user();
1119
	} else {
1120
		$username = $override_user;
1121
	}
1122

    
1123
	$revision = array();
1124

    
1125
	if (time() > mktime(0, 0, 0, 9, 1, 2004)) {     /* make sure the clock settings are plausible */
1126
		$revision['time'] = time();
1127
	}
1128

    
1129
	/* Log the running script so it's not entirely unlogged what changed */
1130
	if ($desc == "Unknown") {
1131
		$desc = sprintf(gettext("%s made unknown change"), $_SERVER['SCRIPT_NAME']);
1132
	}
1133
	if (!empty($desc)) {
1134
		$revision['description'] = "{$username}: " . $desc;
1135
	}
1136
	$revision['username'] = $username;
1137
	return $revision;
1138
}
1139

    
1140
function pfSense_clear_globals() {
1141
	global $config, $g, $FilterIfList, $GatewaysList, $filterdns, $aliases, $aliastable;
1142

    
1143
	$error = error_get_last();
1144

    
1145
	// Errors generated by user code (diag_commands.php) are identified by path and not added to notices
1146
	if ($error !== NULL && !preg_match('|^' . preg_quote($g['tmp_path_user_code']) . '/[^/]{1,16}$|', $error['file'])) {
1147
		if (in_array($error['type'], array(E_ERROR, E_COMPILE_ERROR, E_CORE_ERROR, E_RECOVERABLE_ERROR))) {
1148
			$errorstr = "PHP ERROR: Type: {$error['type']}, File: {$error['file']}, Line: {$error['line']}, Message: {$error['message']}";
1149
			print($errorstr);
1150
			log_error($errorstr);
1151
			file_notice("phperror", $errorstr, 'PHP errors');
1152
		} else if ($error['type'] != E_NOTICE) {
1153
			$errorstr = "PHP WARNING: Type: {$error['type']}, File: {$error['file']}, Line: {$error['line']}, Message: {$error['message']}";
1154
			// XXX: comment out for now, should re-enable post-2.2
1155
			//print($errorstr);
1156
			//log_error($errorstr);
1157
			//file_notice("phpwarning", $errorstr, 'PHP warning');
1158
		}
1159
	}
1160

    
1161
	if (isset($FilterIfList)) {
1162
		unset($FilterIfList);
1163
	}
1164

    
1165
	if (isset($GatewaysList)) {
1166
		unset($GatewaysList);
1167
	}
1168

    
1169
	/* Used for the hostname dns resolver */
1170
	if (isset($filterdns)) {
1171
		unset($filterdns);
1172
	}
1173

    
1174
	/* Used for aliases and interface macros */
1175
	if (isset($aliases)) {
1176
		unset($aliases);
1177
	}
1178
	if (isset($aliastable)) {
1179
		unset($aliastable);
1180
	}
1181

    
1182
	unset($config);
1183
}
1184

    
1185
/*
1186
 * Same semantics as init_config_arr(), but with the new
1187
 * path string interface.
1188
 */
1189
function config_init_path(string $path) {
1190
	global $config;
1191
	array_init_path($config, $path);
1192
}
1193

    
1194
/* 
1195
 * Notice: Use config_init_path() instead, if you must...
1196
 *
1197
 * This is retained for compatibility with older code
1198
 *
1199
 * Initialize a config array multiple levels deep only if unset
1200
 * Pass it an array of keys to test and create
1201
 * init_config_arr(array('virtualip', 'vip'));
1202
 */
1203
function init_config_arr($keys) {
1204
	// just translate the old signature to the new one
1205
	config_init_path(implode('/', $keys));
1206
}
1207

    
1208
/**
1209
 * Return a value specified by path in the config, if it exists.
1210
 * @param $path string path with '/' separators
1211
 * @param $default mixed value to return if the path is not found
1212
 * @returns mixed value at path or $default if the path does not exist or if the
1213
 *          path keys an empty string and $default is non-null
1214
 */
1215
function config_get_path(string $path, $default = null) {
1216
	global $config;
1217
	return(array_get_path($config, $path, $default));
1218
}
1219

    
1220
/**
1221
 * Set a value by path in the config, creating arrays for intermediary keys as
1222
 * necessary. If the path cannot be reached because an intermediary exists but
1223
 * is not empty or an array, return $default.
1224
 * @param $path string path with '/' separators
1225
 * @param $val mixed 
1226
 * @param $default mixed value to return if the path is not found
1227
 * @returns mixed $val or $default if the path prefix does not exist
1228
 */
1229
function config_set_path(string $path, $value, $default = null) {
1230
	global $config;
1231
	return (array_set_path($config, $path, $value, $default));
1232
}
1233

    
1234
/**
1235
 * Determine whether a path in the config has a non-null value keyed by
1236
 * $enable_key. Some parts of the config historically identify services as
1237
 * enabled by having a key to a non-null value named 'enable', and checking it
1238
 * with isset(). This can be counter-intuitive as isset() will return true if
1239
 * the array element is any non-null value that evaluates to false.
1240
 * @param $path string path with '/' separators
1241
 * @param $enable_key string an optional alternative key value for the enable key
1242
 * @returns bool true if $enable_key exists in the array at $path, and has a
1243
 * non-null value, otherwise false.
1244
 */
1245
function config_path_enabled(string $path, $enable_key = "enable") {
1246
	global $config;
1247
	return (array_path_enabled($config, $path, $enable_key));
1248
}
1249

    
1250
/**
1251
 * Remove a key from the config by path
1252
 * @param $path string path with '/' separators
1253
 * @returns array copy of the removed value or null
1254
 */
1255
function config_del_path(string $path) {
1256
	global $config;
1257
	return (array_del_path($config, $path));
1258
}
1259
register_shutdown_function('pfSense_clear_globals');
1260

    
1261
?>
(12-12/62)