Project

General

Profile

Download (36.5 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
	alias_make_table();
158

    
159
	return $config;
160
}
161

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

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

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

    
189
function discover_last_backup() {
190
	global $g;
191

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

    
204
	return false;
205
}
206

    
207
function restore_backup($file) {
208
	global $g;
209

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

    
240
/*
241
 *	Backup RRD/XML Data
242
 */
243

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

    
252
	return $data;
253
}
254

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

    
258
	if (!$conf) {
259
		$conf = & $config;
260
	}
261

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

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

    
279
	if (!$conf) {
280
		$conf = & $config;
281
	}
282

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

    
330
function restore_sshdata($conf = false) {
331
	global $config, $sshConfigDir;
332

    
333
	if (!$conf) {
334
		$conf = & $config;
335
	}
336

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

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

    
362
	if (platform_booting()) {
363
		echo ".";
364
	}
365

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

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

    
403
	$config = parse_config(true);
404

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

    
408

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

    
417

    
418
EOD;
419
		}
420

    
421
	/* make alias table (for faster lookups) */
422
	alias_make_table();
423
}
424

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

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

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

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

    
481
	// Save off config version
482
	$prev_version = $config['version'];
483

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

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

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

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

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

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

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

    
534
	additional_config_upgrade();
535
}
536

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

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

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

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

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

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

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

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

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

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

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

    
658
	if ($backup) {
659
		backup_config();
660
	}
661

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

    
668
	$lockkey = lock('config', LOCK_EX);
669

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

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

    
681
	init_config_arr('syslog');
682
	if ($config['syslog']['logconfigchanges'] != "disabled") {
683
		log_error(gettext("Configuration Change") . ": {$config['revision']['description']}");
684
	}
685

    
686
	cleanup_backupcache(true);
687

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

    
709
	unlock($lockkey);
710

    
711
	if ($write_config_only) {
712
		return $config;
713
	}
714

    
715
	unlink_if_exists("/usr/local/pkg/pf/carp_sync_client.php");
716

    
717
	/* sync carp entries to other firewalls */
718
	carp_sync_client();
719

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

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

    
733
		if (file_exists("/tmp/forceacb")) {
734
			unlink("/tmp/forceacb");
735
		}
736
	}
737

    
738
	return $config;
739
}
740

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

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

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

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

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

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

    
778
	disable_security_checks();
779

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

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

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

    
805
function config_restore($conffile) {
806
	global $config, $g;
807

    
808
	if (!file_exists($conffile)) {
809
		return 1;
810
	}
811

    
812
	backup_config();
813

    
814

    
815
	$lockkey = lock('config', LOCK_EX);
816

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

    
820
	disable_security_checks();
821

    
822
	unlock($lockkey);
823

    
824
	$config = parse_config(true);
825

    
826

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

    
829
	return 0;
830
}
831

    
832
function config_install($conffile) {
833
	global $config, $g;
834

    
835
	if (!file_exists($conffile)) {
836
		return 1;
837
	}
838

    
839
	if (!config_validate("{$conffile}")) {
840
		return 1;
841
	}
842

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

    
849
	$lockkey = lock('config', LOCK_EX);
850

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

    
853
	disable_security_checks();
854

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

    
860
	unlock($lockkey);
861

    
862
	return 0;
863
}
864

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

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

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

    
890
function config_validate($conffile) {
891

    
892
	global $g, $xmlerr;
893

    
894
	$xml_parser = xml_parser_create();
895

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

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

    
911
	fclose($fp);
912

    
913
	return true;
914
}
915

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

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

    
922
	if (!$lock) {
923
		$lockkey = lock('config');
924
	}
925

    
926

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

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

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

    
1004
	if (!$lock) {
1005
		unlock($lockkey);
1006
	}
1007
}
1008

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

    
1027
function backup_config() {
1028
	global $config, $g;
1029

    
1030

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

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

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

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

    
1060

    
1061
	return true;
1062
}
1063

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

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

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

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

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

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

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

    
1116
	return $username;
1117
}
1118

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

    
1126
	$revision = array();
1127

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

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

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

    
1146
	$error = error_get_last();
1147

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

    
1164
	if (isset($FilterIfList)) {
1165
		unset($FilterIfList);
1166
	}
1167

    
1168
	if (isset($GatewaysList)) {
1169
		unset($GatewaysList);
1170
	}
1171

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

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

    
1185
	unset($config);
1186
}
1187

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

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

    
1217
/**
1218
 * Set a value by path in the config. If the path cannot be reached because an
1219
 * intermediary does not exist, return null
1220
 * @param $path string path with '/' separators
1221
 * @param $val mixed 
1222
 * @param $default mixed value to return if the path is not found
1223
 * @returns mixed $val or $default if the path prefix does not exist
1224
 */
1225
function config_set_path(string $path, $value, $default = null) {
1226
	global $config;
1227
	return (array_set_path($config, $path, $default));
1228
}
1229

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

    
1246
register_shutdown_function('pfSense_clear_globals');
1247

    
1248
?>
(12-12/62)