Project

General

Profile

Download (36.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-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
/****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
	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
	init_config_arr('syslog');
680
	if ($config['syslog']['logconfigchanges'] != "disabled") {
681
		log_error(gettext("Configuration Change") . ": {$config['revision']['description']}");
682
	}
683

    
684
	cleanup_backupcache(true);
685

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

    
707
	unlock($lockkey);
708

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

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

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

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

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

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

    
736
	return $config;
737
}
738

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

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

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

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

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

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

    
776
	disable_security_checks();
777

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

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

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

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

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

    
810
	backup_config();
811

    
812

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

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

    
818
	disable_security_checks();
819

    
820
	unlock($lockkey);
821

    
822
	$config = parse_config(true);
823

    
824

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

    
827
	return 0;
828
}
829

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

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

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

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

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

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

    
851
	disable_security_checks();
852

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

    
858
	unlock($lockkey);
859

    
860
	return 0;
861
}
862

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

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

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

    
888
function config_validate($conffile) {
889

    
890
	global $g, $xmlerr;
891

    
892
	$xml_parser = xml_parser_create();
893

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

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

    
909
	fclose($fp);
910

    
911
	return true;
912
}
913

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

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

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

    
924

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

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

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

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

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

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

    
1028

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

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

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

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

    
1058

    
1059
	return true;
1060
}
1061

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

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

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

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

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

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

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

    
1114
	return $username;
1115
}
1116

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

    
1124
	$revision = array();
1125

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

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

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

    
1144
	$error = error_get_last();
1145

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

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

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

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

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

    
1183
	unset($config);
1184
}
1185

    
1186
/* Initialize a config array multiple levels deep only if unset
1187
 * Pass it an array of keys to test and create
1188
 * init_config_arr(array('virtualip', 'vip'));
1189
 */
1190
function init_config_arr($keys) {
1191
	global $config;
1192
	$c = &$config;
1193
	if (!is_array($keys)) {
1194
		return null;
1195
	}
1196
	foreach ($keys as $k) {
1197
		if (!is_array($c[$k])) {
1198
			$c[$k] = array();
1199
		}
1200
		$c = &$c[$k];
1201
	}
1202
}
1203

    
1204
/**
1205
 * Return a value specified by path in the config, if it exists
1206
 * @param $path string path with '/' separators
1207
 * @param $default mixed value to return if the path is not found
1208
 * @returns mixed value at path or $default if the path does not exist
1209
 */
1210
function config_get_path(string $path, $default = null) {
1211
	global $config;
1212
	$vpath = mb_split('/', $path);
1213
	$el = $config;
1214
	foreach ($vpath as $key) {
1215
		if (is_array($el) && array_key_exists($key, $el)) {
1216
			$el = $el[$key];
1217
		} else {
1218
			return ($default);
1219
		}
1220
	}
1221
	return ($el);
1222
}
1223

    
1224
/**
1225
 * Set a value by path in the config. If the path cannot be reached because an
1226
 * intermediary does not exist, return null
1227
 * @param $path string path with '/' separators
1228
 * @param $val mixed 
1229
 * @param $default mixed value to return if the path is not found
1230
 * @returns mixed $val or $default if the path prefix does not exist
1231
 */
1232
function config_set_path(string $path, $value, $default = null) {
1233
	global $config;
1234
	$vpath = mb_split('/', $path);
1235
	$vkey = array_pop($vpath);
1236
	$el =& $config;
1237
	foreach ($vpath as $key) {
1238
		if (is_array($el) && array_key_exists($key, $el)) {
1239
			$el =& $el[$key];
1240
		} else {
1241
			return ($default);
1242
		}
1243
	}
1244
	$el[$vkey] =& $value;
1245
	return ($value);
1246
}
1247

    
1248
/**
1249
 * Determine whether a section of the config has a non-null value keyed
1250
 * 'enabled'. Some parts of the config historically identify services as enabled
1251
 * by having a key to a non-null value named 'enable', and checking it with
1252
 * isset(). This can be counter-intuitive as isset() will return true if the
1253
 * array element is any non-null value that evaluates to false.
1254
 * @param $path string path with '/' separators
1255
 * @param $enable_key string an optional alternative key value for the enable key
1256
 * @returns bool true if $enable_key exists in the array at $path, and has a
1257
 * non-null value, otherwise false
1258
 */
1259
function config_path_enabled(string $path, $enable_key = "enable") {
1260
	$el = config_get_path($path, []);
1261
	if (is_array($el) && isset($el[$enable_key])) {
1262
		return (true);
1263
	}
1264
	return (false);
1265
}
1266

    
1267
register_shutdown_function('pfSense_clear_globals');
1268

    
1269
?>
(12-12/62)