Project

General

Profile

Download (36.6 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;
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;
170

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

    
176
function discover_last_backup() {
177
	global $g;
178

    
179
	$backups = glob('/cf/conf/backup/*.xml');
180
	foreach (array_reverse($backups) as $backup) {
181
		/* checking multiple backups when detecting invalid configuration
182
		 * https://redmine.pfsense.org/issues/11748 */
183
		if (filesize($backup) != 0) {
184
			$testconfig = parse_xml_config($backup, $g['xml_rootobj']);
185
			if ($testconfig != -1) {
186
				return basename($backup);
187
			}
188
		} 
189
	}
190

    
191
	return false;
192
}
193

    
194
function restore_backup($file) {
195
	global $g;
196

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

    
227
/*
228
 *	Backup RRD/XML Data
229
 */
230

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

    
239
	return $data;
240
}
241

    
242
function restore_xmldatafile($type='voucher', $conf = false) {
243
	global $config, $g;
244

    
245
	if (!$conf) {
246
		$conf = & $config;
247
	}
248

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

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

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

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

    
317
function restore_sshdata($conf = false) {
318
	global $config, $sshConfigDir;
319

    
320
	if (!$conf) {
321
		$conf = & $config;
322
	}
323

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

    
340
/****f* config/parse_config_bootup
341
 * NAME
342
 *   parse_config_bootup - Bootup-specific configuration checks.
343
 * RESULT
344
 *   null
345
 ******/
346
function parse_config_bootup() {
347
	global $config, $g;
348

    
349
	if (platform_booting()) {
350
		echo ".";
351
	}
352

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

    
377
	if (filesize("{$g['conf_path']}/config.xml") == 0) {
378
		$last_backup = discover_last_backup();
379
		if ($last_backup) {
380
			log_error(gettext("No config.xml found, attempting last known config restore."));
381
			file_notice("config.xml", gettext("No config.xml found, attempting last known config restore."), "pfSenseConfigurator", "");
382
			restore_backup("{$g['conf_path']}/backup/{$last_backup}");
383
		} else {
384
			unlock($lockkey);
385
			die(gettext("Config.xml is corrupted and is 0 bytes.  Could not restore a previous backup."));
386
		}
387
	}
388
	unlock($lockkey);
389

    
390
	$config = parse_config(true);
391

    
392
	if ((float)$config['version'] > (float)$g['latest_config']) {
393
		echo <<<EOD
394

    
395

    
396
*******************************************************************************
397
* WARNING!                                                                    *
398
* The current configuration has been created with a newer version of {$g['product_label']}  *
399
* than this one! This can lead to serious misbehavior and even security       *
400
* holes! You are urged to either upgrade to a newer version of {$g['product_label']} or     *
401
* revert to the default configuration immediately!                            *
402
*******************************************************************************
403

    
404

    
405
EOD;
406
		}
407

    
408
	/* make alias table (for faster lookups) */
409
	alias_make_table();
410
}
411

    
412
/****f* config/conf_mount_rw
413
 * NAME
414
 *   conf_mount_rw - Mount filesystems read/write.
415
 * RESULT
416
 *   null
417
 ******/
418
/* mount flash card read/write */
419
function conf_mount_rw() {
420
	/* Obsoleted. Keep it here until all calls are removed */
421
	return;
422
}
423

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

    
435
/****f* config/convert_config
436
 * NAME
437
 *   convert_config - Attempt to update config.xml.
438
 * DESCRIPTION
439
 *   convert_config() reads the current global configuration
440
 *   and attempts to convert it to conform to the latest
441
 *   config.xml version. This allows major formatting changes
442
 *   to be made with a minimum of breakage.
443
 * RESULT
444
 *   null
445
 ******/
446
/* convert configuration, if necessary */
447
function convert_config() {
448
	global $config, $g;
449
	$now = date("H:i:s");
450
	log_error(sprintf(gettext("Start Configuration upgrade at %s, set execution timeout to 15 minutes"), $now));
451
	//ini_set("max_execution_time", "900");
452

    
453
	/* special case upgrades */
454
	/* fix every minute crontab bogons entry */
455
	if (is_array($config['cron'])) {
456
		$cron_item_count = count($config['cron']['item']);
457
		for ($x = 0; $x < $cron_item_count; $x++) {
458
			if (stristr($config['cron']['item'][$x]['command'], "rc.update_bogons.sh")) {
459
				if ($config['cron']['item'][$x]['hour'] == "*") {
460
					$config['cron']['item'][$x]['hour'] = "3";
461
					write_config(gettext("Updated bogon update frequency to 3am"));
462
					log_error(gettext("Updated bogon update frequency to 3am"));
463
				}
464
			}
465
		}
466
	}
467

    
468
	// Save off config version
469
	$prev_version = config_get_path('version');
470

    
471
	include_once('auth.inc');
472
	include_once('upgrade_config.inc');
473
	if (file_exists("/etc/inc/upgrade_config_custom.inc")) {
474
		include_once("upgrade_config_custom.inc");
475
	}
476

    
477
	if ($config['version'] == $g['latest_config']) {
478
		additional_config_upgrade();
479
		return;		/* already at latest version */
480
	}
481

    
482
	if (!is_array($config['system']['already_run_config_upgrade'])) {
483
		$config['system']['already_run_config_upgrade'] = array();
484
	}
485
	$already_run = config_get_path('system/already_run_config_upgrade');
486

    
487
	/* Loop and run upgrade_VER_to_VER() until we're at current version */
488
	while ($config['version'] < $g['latest_config']) {
489
		$cur = $config['version'] * 10;
490
		$next = $cur + 1;
491
		$migration_function = sprintf('upgrade_%03d_to_%03d', $cur,
492
		    $next);
493

    
494
		foreach (array("", "_custom") as $suffix) {
495
			$migration_function .= $suffix;
496
			if (!function_exists($migration_function)) {
497
				continue;
498
			}
499
			if (isset($already_run[$migration_function])) {
500
				config_del_path("system/already_run_config_upgrade/{$migration_function}");
501
			} else {
502
				$migration_function();
503
			}
504
		}
505
		$config['version'] = sprintf('%.1f', $next / 10);
506
		if (platform_booting()) {
507
			echo ".";
508
		}
509
	}
510

    
511
	if ($prev_version != $config['version']) {
512
		$now = date("H:i:s");
513
		log_error(sprintf(gettext("Ended Configuration upgrade at %s"), $now));
514

    
515
		write_config(sprintf(gettext('Upgraded config version level from %1$s to %2$s'), $prev_version, $config['version']));
516
	}
517

    
518
	additional_config_upgrade();
519
}
520

    
521
/****f* config/safe_write_file
522
 * NAME
523
 *   safe_write_file - Write a file out atomically
524
 * DESCRIPTION
525
 *   safe_write_file() Writes a file out atomically by first writing to a
526
 *   temporary file of the same name but ending with the pid of the current
527
 *   process, them renaming the temporary file over the original.
528
 * INPUTS
529
 *   $filename  - string containing the filename of the file to write
530
 *   $content   - string or array containing the file content to write to file
531
 *   $force_binary      - boolean denoting whether we should force binary
532
 *   mode writing.
533
 * RESULT
534
 *   boolean - true if successful, false if not
535
 ******/
536
function safe_write_file($file, $content, $force_binary = false) {
537
	$tmp_file = $file . "." . getmypid();
538
	$write_mode = $force_binary ? "wb" : "w";
539

    
540
	$fd = fopen($tmp_file, $write_mode);
541
	if (!$fd) {
542
		// Unable to open temporary file for writing
543
		return false;
544
	}
545
	if (is_array($content)) {
546
		foreach ($content as $line) {
547
			if (!fwrite($fd, $line . "\n")) {
548
				// Unable to write to temporary file
549
				fclose($fd);
550
				return false;
551
			}
552
		}
553
	} elseif (!fwrite($fd, $content)) {
554
		// Unable to write to temporary file
555
		fclose($fd);
556
		return false;
557
	}
558
	fflush($fd);
559
	fclose($fd);
560

    
561
	/* XXX Re-add pfSense_fsync() call here after it's fixed */
562
	// if (!pfSense_fsync($tmp_file) || !rename($tmp_file, $file)) {
563
	if (!rename($tmp_file, $file)) {
564
		// Unable to move temporary file to original
565
		@unlink($tmp_file);
566
		return false;
567
	}
568

    
569
	// Sync file before returning
570
	//return pfSense_fsync($file);
571
	return true;
572
}
573

    
574
/****f* config/write_config
575
 * NAME
576
 *   write_config - Backup and write the firewall configuration.
577
 * DESCRIPTION
578
 *   write_config() handles backing up the current configuration,
579
 *   applying changes, and regenerating the configuration cache.
580
 * INPUTS
581
 *   $desc	- string containing the a description of configuration changes
582
 *   $backup	- boolean: do not back up current configuration if false.
583
 *   $write_config_only	- boolean: do not sync or reload anything; just save the configuration if true.
584
 * RESULT
585
 *   null
586
 ******/
587
/* save the system configuration */
588
function write_config($desc="Unknown", $backup = true, $write_config_only = false) {
589
	global $config, $g;
590

    
591
	// Certain strings may be embedded in the $desc (reason) parameter to trigger certain behavior.
592
	// If detected, those strings are removed and a variable set.
593
	$doacb = true;
594
	$manual_acb = false;
595
	$rcnt = 0;
596

    
597
	$desc = str_replace("-MaNuAlBaCkUp", "", $desc, $rcnt);
598
	if ($rcnt > 0) {
599
		$manual_acb = true; // Manual backups require special processing on the server
600
	}
601

    
602
	$rcnt = 0;
603
	$desc = str_replace("-NoReMoTeBaCkUp", "", $desc, $rcnt);
604
	if ($rcnt > 0) {
605
		$doacb = false; // No ACB will be performed if this string is detected
606
	}
607

    
608
	/*
609
	* Syncing vouchers happens every minute and sometimes multiple times. We don't
610
	* want to fill up our db with a lot of the same config so just ignore that case.
611
	*/
612
	if((strpos($desc, 'Syncing vouchers') !== false ||
613
		strpos($desc, 'Captive Portal Voucher database synchronized') !== false) ) {
614
		$doacb = false;
615
	}
616

    
617
	if (!empty($_SERVER['REMOTE_ADDR'])) {
618
		@phpsession_begin();
619
		if (!empty($_SESSION['Username']) && ($_SESSION['Username'] != "admin")) {
620
			$user = getUserEntry($_SESSION['Username']);
621
			if (is_array($user) && userHasPrivilege($user, "user-config-readonly")) {
622
				syslog(LOG_AUTHPRIV, sprintf(gettext("Save config permission denied by the 'User - Config: Deny Config Write' permission for user '%s'."), get_config_user()));
623
				phpsession_end(true);
624
				return false;
625
			}
626
		}
627
		if (!isset($argc)) {
628
			phpsession_end(true);
629
		}
630
	}
631

    
632
	if (isset($config['reset_factory_defaults'])) {
633
		/*
634
		   We have put a default config.xml on disk and are about to reboot
635
		   or reload it. Do not let any system or package code try to save
636
		   state to config because that would overwrite the default config
637
		   with the running config.
638
		*/
639
		return false;
640
	}
641

    
642
	if ($backup) {
643
		backup_config();
644
	}
645

    
646
	if ($desc == "Unknown") {
647
		file_notice("config.xml", gettext(
648
		    'WARNING: write_config() was called without description'));
649
	}
650
	$config['revision'] = make_config_revision_entry($desc);
651

    
652
	$lockkey = lock('config', LOCK_EX);
653

    
654
	/* generate configuration XML */
655
	$xmlconfig = dump_xml_config($config, $g['xml_rootobj']);
656

    
657
	/* write new configuration */
658
	if (!safe_write_file("{$g['cf_conf_path']}/config.xml", $xmlconfig)) {
659
		log_error(gettext("WARNING: Config contents could not be saved. Could not open file!"));
660
		unlock($lockkey);
661
		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"));
662
		return -1;
663
	}
664

    
665
	if (config_get_path('syslog/logconfigchanges') != "disabled") {
666
		log_error(gettext("Configuration Change") . ": {$config['revision']['description']}");
667
	}
668

    
669
	cleanup_backupcache(true);
670

    
671
	/* re-read configuration */
672
	/* NOTE: We assume that the file can be parsed since we wrote it. */
673
	$config = parse_xml_config("{$g['conf_path']}/config.xml", $g['xml_rootobj']);
674
	if ($config == -1) {
675
		copy("{$g['conf_path']}/config.xml", "{$g['conf_path']}/config.xml.bad");
676
		$last_backup = discover_last_backup();
677
		if ($last_backup) {
678
			restore_backup("/cf/conf/backup/{$last_backup}");
679
			$config = parse_xml_config("{$g['conf_path']}/config.xml", $g['xml_rootobj']);
680
			if (platform_booting()) {
681
				echo "\n\n ************** WARNING **************";
682
				echo "\n\n Configuration could not be validated. A previous configuration was restored. \n";
683
				echo "\n The failed configuration file has been saved as {$g['conf_path']}/config.xml.bad \n\n";
684
			}
685
		} else {
686
			log_error(gettext("Could not restore config.xml."));
687
		}
688
	} else {
689
		generate_config_cache($config);
690
	}
691

    
692
	unlock($lockkey);
693

    
694
	if ($write_config_only) {
695
		return $config;
696
	}
697

    
698
	unlink_if_exists("/usr/local/pkg/pf/carp_sync_client.php");
699

    
700
	/* sync carp entries to other firewalls */
701
	carp_sync_client();
702

    
703
	if (is_dir("/usr/local/pkg/write_config")) {
704
		/* process packager manager custom rules */
705
		run_plugins("/usr/local/pkg/write_config/");
706
	}
707

    
708
	// Try the core AutoConfigBackup system
709
	if (is_array($config['system']['acb']) && $config['system']['acb']['enable'] == "yes" &&
710
	    (!isset($config['system']['acb']['frequency']) || $config['system']['acb']['frequency'] == "every") || file_exists("/tmp/forceacb")) {
711
	    if ($doacb) {
712
			require_once("acb.inc");
713
			upload_config($manual_acb);
714
		}
715

    
716
		if (file_exists("/tmp/forceacb")) {
717
			unlink("/tmp/forceacb");
718
		}
719
	}
720

    
721
	return $config;
722
}
723

    
724
/****f* config/reset_factory_defaults
725
 * NAME
726
 *   reset_factory_defaults - Reset the system to its default configuration.
727
 * RESULT
728
 *   integer	- indicates completion
729
 ******/
730
function reset_factory_defaults($lock = false, $reboot_required = true) {
731
	global $config, $g;
732

    
733
	/* Remove all additional packages */
734
	mwexec("/bin/sh /usr/local/sbin/{$g['product_name']}-upgrade " .
735
	    "-r ALL_PACKAGES -f");
736

    
737
	if (!$lock) {
738
		$lockkey = lock('config', LOCK_EX);
739
	}
740

    
741
	/* create conf directory, if necessary */
742
	safe_mkdir($g['cf_conf_path']);
743

    
744
	/* clear out /conf */
745
	$dh = opendir($g['conf_path']);
746
	while ($filename = readdir($dh)) {
747
		if (($filename != ".") && ($filename != "..") &&
748
		    (!is_dir($g['conf_path'] . "/" . $filename))) {
749
			if ($filename == "enableserial_force")
750
				continue;
751
			unlink_if_exists($g['conf_path'] . "/" . $filename);
752
		}
753
	}
754
	closedir($dh);
755
	unlink_if_exists($g['tmp_path'] . "/config.cache");
756

    
757
	/* copy default configuration */
758
	copy("{$g['conf_default_path']}/config.xml",
759
	    "{$g['cf_conf_path']}/config.xml");
760

    
761
	disable_security_checks();
762

    
763
	/*
764
	   Let write_config know that we are awaiting reload of the current config
765
	   to factory defaults. Either the system is about to reboot, throwing away
766
	   the current in-memory config as it shuts down, or the in-memory config
767
	   is about to be reloaded on-the-fly by parse_config.
768

    
769
	   In both cases, we want to ensure that write_config does not flush the
770
	   in-memory config back to disk.
771
	*/
772
	$config['reset_factory_defaults'] = true;
773

    
774
	/* call the wizard */
775
	if ($reboot_required) {
776
		// If we need a reboot first then touch a different trigger file.
777
		touch("/conf/trigger_initial_wizard_after_reboot");
778
	} else {
779
		touch("/conf/trigger_initial_wizard");
780
	}
781
	if (!$lock) {
782
		unlock($lockkey);
783
	}
784
	console_configure();
785
	return 0;
786
}
787

    
788
function config_restore($conffile) {
789
	global $config, $g;
790

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

    
795
	backup_config();
796

    
797

    
798
	$lockkey = lock('config', LOCK_EX);
799

    
800
	unlink_if_exists("{$g['tmp_path']}/config.cache");
801
	copy($conffile, "{$g['cf_conf_path']}/config.xml");
802

    
803
	disable_security_checks();
804

    
805
	unlock($lockkey);
806

    
807
	$config = parse_config(true);
808

    
809

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

    
812
	return 0;
813
}
814

    
815
function config_install($conffile) {
816
	global $config, $g;
817

    
818
	if (!file_exists($conffile)) {
819
		return 1;
820
	}
821

    
822
	if (!config_validate("{$conffile}")) {
823
		return 1;
824
	}
825

    
826
	if (platform_booting()) {
827
		echo gettext("Installing configuration...") . "\n";
828
	} else {
829
		log_error(gettext("Installing configuration ...."));
830
	}
831

    
832
	$lockkey = lock('config', LOCK_EX);
833

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

    
836
	disable_security_checks();
837

    
838
	/* unlink cache file if it exists */
839
	if (file_exists("{$g['tmp_path']}/config.cache")) {
840
		unlink("{$g['tmp_path']}/config.cache");
841
	}
842

    
843
	unlock($lockkey);
844

    
845
	return 0;
846
}
847

    
848
/*
849
 * Disable security checks for DNS rebind and HTTP referrer until next time
850
 * they pass (or reboot), to aid in preventing accidental lockout when
851
 * restoring settings like hostname, domain, IP addresses, and settings
852
 * related to the DNS rebind and HTTP referrer checks.
853
 * Intended for use when restoring a configuration or directly
854
 * modifying config.xml without an unconditional reboot.
855
 */
856
function disable_security_checks() {
857
	global $g;
858
	touch("{$g['tmp_path']}/disable_security_checks");
859
}
860

    
861
/* Restores security checks.  Should be called after all succeed. */
862
function restore_security_checks() {
863
	global $g;
864
	unlink_if_exists("{$g['tmp_path']}/disable_security_checks");
865
}
866

    
867
/* Returns status of security check temporary disable. */
868
function security_checks_disabled() {
869
	global $g;
870
	return file_exists("{$g['tmp_path']}/disable_security_checks");
871
}
872

    
873
function config_validate($conffile) {
874

    
875
	global $g, $xmlerr;
876

    
877
	$xml_parser = xml_parser_create();
878

    
879
	if (!($fp = fopen($conffile, "r"))) {
880
		$xmlerr = gettext("XML error: unable to open file");
881
		return false;
882
	}
883

    
884
	while ($data = fread($fp, 4096)) {
885
		if (!xml_parse($xml_parser, $data, feof($fp))) {
886
			$xmlerr = sprintf(gettext('%1$s at line %2$d'),
887
						xml_error_string(xml_get_error_code($xml_parser)),
888
						xml_get_current_line_number($xml_parser));
889
			return false;
890
		}
891
	}
892
	xml_parser_free($xml_parser);
893

    
894
	fclose($fp);
895

    
896
	return true;
897
}
898

    
899
function cleanup_backupcache($lock = false) {
900
	global $config, $g;
901
	$i = false;
902

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

    
905
	if (!$lock) {
906
		$lockkey = lock('config');
907
	}
908

    
909

    
910
	$backups = get_backups();
911
	if ($backups) {
912
		$baktimes = $backups['versions'];
913
		unset($backups['versions']);
914
	} else {
915
		$backups = array();
916
		$baktimes = array();
917
	}
918
	$newbaks = array();
919
	$bakfiles = glob($g['cf_conf_path'] . "/backup/config-*");
920
	$tocache = array();
921

    
922
	foreach ($bakfiles as $backup) { // Check for backups in the directory not represented in the cache.
923
		$backupsize = filesize($backup);
924
		if ($backupsize == 0) {
925
			unlink($backup);
926
			continue;
927
		}
928
		$backupexp = explode('-', $backup);
929
		$backupexp = explode('.', array_pop($backupexp));
930
		$tocheck = array_shift($backupexp);
931
		unset($backupexp);
932
		if (!in_array($tocheck, $baktimes)) {
933
			$i = true;
934
			if (platform_booting()) {
935
				echo ".";
936
			}
937
			try {
938
				$newxml = parse_xml_config($backup, array($g['xml_rootobj'], 'pfsense'));
939
			} catch (Exception $exc) {
940
				log_error(sprintf(gettext("The backup cache file %s is corrupted. Parser error message: %s"), $backup, $exc->getMessage()));
941
				$newxml = "-1";
942
			}
943

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

    
987
	if (!$lock) {
988
		unlock($lockkey);
989
	}
990
}
991

    
992
function get_backups() {
993
	global $g;
994
	if (file_exists("{$g['cf_conf_path']}/backup/backup.cache")) {
995
		$confvers = unserialize(file_get_contents("{$g['cf_conf_path']}/backup/backup.cache"));
996
		$bakvers = array_keys($confvers);
997
		$toreturn = array();
998
		sort($bakvers);
999
		// 	$bakvers = array_reverse($bakvers);
1000
		foreach (array_reverse($bakvers) as $bakver) {
1001
			$toreturn[] = array('time' => $bakver, 'description' => $confvers[$bakver]['description'], 'version' => $confvers[$bakver]['version'], 'filesize' => $confvers[$bakver]['filesize']);
1002
		}
1003
	} else {
1004
		return false;
1005
	}
1006
	$toreturn['versions'] = $bakvers;
1007
	return $toreturn;
1008
}
1009

    
1010
function backup_config() {
1011
	global $config, $g;
1012

    
1013

    
1014
	/* Create backup directory if needed */
1015
	safe_mkdir("{$g['cf_conf_path']}/backup");
1016
	if ($config['revision']['time'] == "") {
1017
		$baktime = 0;
1018
	} else {
1019
		$baktime = config_get_path('revision/time');
1020
	}
1021

    
1022
	if ($config['revision']['description'] == "") {
1023
		$bakdesc = "Unknown";
1024
	} else {
1025
		$bakdesc = config_get_path('revision/description');
1026
	}
1027

    
1028
	$bakver = ($config['version'] == "") ? "?" : $config['version'];
1029
	$bakfilename = $g['cf_conf_path'] . '/backup/config-' . $baktime . '.xml';
1030
	copy($g['cf_conf_path'] . '/config.xml', $bakfilename);
1031

    
1032
	if (file_exists($g['cf_conf_path'] . '/backup/backup.cache')) {
1033
		$backupcache = unserialize(file_get_contents($g['cf_conf_path'] . '/backup/backup.cache'));
1034
	} else {
1035
		$backupcache = array();
1036
	}
1037
	$backupcache[$baktime] = array('description' => $bakdesc, 'version' => $bakver, 'filesize' => filesize($bakfilename));
1038
	$bakout = fopen($g['cf_conf_path'] . '/backup/backup.cache', "w");
1039
	fwrite($bakout, serialize($backupcache));
1040
	fclose($bakout);
1041
	//pfSense_fsync("{$g['cf_conf_path']}/backup/backup.cache");
1042

    
1043

    
1044
	return true;
1045
}
1046

    
1047
function backup_info($backup_info, $number) {
1048
	if ($backup_info['time'] != 0) {
1049
		$date = date(gettext("n/j/y H:i:s"), $backup_info['time']);
1050
	} else {
1051
		$date = gettext("Unknown");
1052
	}
1053

    
1054
	list($page, $reason) = explode(": ", $backup_info['description'], 2);
1055
	if (empty($reason)) {
1056
		$reason = $page;
1057
		$page = gettext("Unknown Page");
1058
	}
1059

    
1060
	$backup_info = sprintf("%02d", $number) . ". {$date}\tv{$backup_info['version']}\t{$page}\n";
1061
	if ($reason) {
1062
		$backup_info .= "    {$reason}\n";
1063
	}
1064
	return $backup_info;
1065
}
1066

    
1067
function set_device_perms() {
1068
	$devices = array(
1069
		'pf' => array(
1070
			'user' => 'root',
1071
			'group' => 'proxy',
1072
			'mode' => 0660),
1073
		);
1074

    
1075
	foreach ($devices as $name => $attr) {
1076
		$path = "/dev/$name";
1077
		if (file_exists($path)) {
1078
			chown($path, $attr['user']);
1079
			chgrp($path, $attr['group']);
1080
			chmod($path, $attr['mode']);
1081
		}
1082
	}
1083
}
1084

    
1085
function get_config_user() {
1086
	if (empty($_SESSION["Username"])) {
1087
		$username = getenv("USER");
1088
		if (empty($conuser) || $conuser == "root") {
1089
			$username = "(system)";
1090
		}
1091
	} else {
1092
		$username = $_SESSION["Username"];
1093
	}
1094

    
1095
	if (!empty($_SERVER['REMOTE_ADDR'])) {
1096
		$username .= '@' . get_user_remote_address() . get_user_remote_authsource();
1097
	}
1098

    
1099
	return $username;
1100
}
1101

    
1102
function make_config_revision_entry($desc = null, $override_user = null) {
1103
	if (empty($override_user)) {
1104
		$username = get_config_user();
1105
	} else {
1106
		$username = $override_user;
1107
	}
1108

    
1109
	$revision = array();
1110

    
1111
	if (time() > mktime(0, 0, 0, 9, 1, 2004)) {     /* make sure the clock settings are plausible */
1112
		$revision['time'] = time();
1113
	}
1114

    
1115
	/* Log the running script so it's not entirely unlogged what changed */
1116
	if ($desc == "Unknown") {
1117
		$desc = sprintf(gettext("%s made unknown change"), $_SERVER['SCRIPT_NAME']);
1118
	}
1119
	if (!empty($desc)) {
1120
		$revision['description'] = "{$username}: " . $desc;
1121
	}
1122
	$revision['username'] = $username;
1123
	return $revision;
1124
}
1125

    
1126
function pfSense_clear_globals() {
1127
	global $config, $g, $FilterIfList, $GatewaysList, $filterdns, $aliases, $aliastable;
1128

    
1129
	$error = error_get_last();
1130

    
1131
	// Errors generated by user code (diag_commands.php) are identified by path and not added to notices
1132
	if ($error !== NULL && !preg_match('|^' . preg_quote($g['tmp_path_user_code']) . '/[^/]{1,16}$|', $error['file'])) {
1133
		if (in_array($error['type'], array(E_ERROR, E_COMPILE_ERROR, E_CORE_ERROR, E_RECOVERABLE_ERROR))) {
1134
			$errorstr = "PHP ERROR: Type: {$error['type']}, File: {$error['file']}, Line: {$error['line']}, Message: {$error['message']}";
1135
			print($errorstr);
1136
			log_error($errorstr);
1137
			file_notice("phperror", $errorstr, 'PHP errors');
1138
		} else if ($error['type'] != E_NOTICE) {
1139
			$errorstr = "PHP WARNING: Type: {$error['type']}, File: {$error['file']}, Line: {$error['line']}, Message: {$error['message']}";
1140
			// XXX: comment out for now, should re-enable post-2.2
1141
			//print($errorstr);
1142
			//log_error($errorstr);
1143
			//file_notice("phpwarning", $errorstr, 'PHP warning');
1144
		}
1145
	}
1146

    
1147
	if (isset($FilterIfList)) {
1148
		unset($FilterIfList);
1149
	}
1150

    
1151
	if (isset($GatewaysList)) {
1152
		unset($GatewaysList);
1153
	}
1154

    
1155
	/* Used for the hostname dns resolver */
1156
	if (isset($filterdns)) {
1157
		unset($filterdns);
1158
	}
1159

    
1160
	/* Used for aliases and interface macros */
1161
	if (isset($aliases)) {
1162
		unset($aliases);
1163
	}
1164
	if (isset($aliastable)) {
1165
		unset($aliastable);
1166
	}
1167

    
1168
	unset($config);
1169
}
1170

    
1171
/*
1172
 * Same semantics as init_config_arr(), but with the new
1173
 * path string interface.
1174
 */
1175
function config_init_path(string $path) {
1176
	global $config;
1177
	array_init_path($config, $path);
1178
}
1179

    
1180
/* 
1181
 * Notice: Use config_init_path() instead, if you must...
1182
 *
1183
 * This is retained for compatibility with older code
1184
 *
1185
 * Initialize a config array multiple levels deep only if unset
1186
 * Pass it an array of keys to test and create
1187
 * init_config_arr(array('virtualip', 'vip'));
1188
 */
1189
function init_config_arr($keys) {
1190
	// just translate the old signature to the new one
1191
	config_init_path(implode('/', $keys));
1192
}
1193

    
1194
/**
1195
 * Return a value specified by path in the config, if it exists.
1196
 * @param $path string path with '/' separators
1197
 * @param $default mixed value to return if the path is not found
1198
 * @returns mixed value at path or $default if the path does not exist or if the
1199
 *          path keys an empty string and $default is non-null
1200
 */
1201
function config_get_path(string $path, $default = null) {
1202
	global $config;
1203
	return(array_get_path($config, $path, $default));
1204
}
1205

    
1206
/**
1207
 * Set a value by path in the config, creating arrays for intermediary keys as
1208
 * necessary. If the path cannot be reached because an intermediary exists but
1209
 * is not empty or an array, return $default.
1210
 * @param $path string path with '/' separators
1211
 * @param $val mixed 
1212
 * @param $default mixed value to return if the path is not found
1213
 * @returns mixed $val or $default if the path prefix does not exist
1214
 */
1215
function config_set_path(string $path, $value, $default = null) {
1216
	global $config;
1217
	return (array_set_path($config, $path, $value, $default));
1218
}
1219

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

    
1236
/**
1237
 * Remove a key from the config by path
1238
 * @param $path string path with '/' separators
1239
 * @returns array copy of the removed value or null
1240
 */
1241
function config_del_path(string $path) {
1242
	global $config;
1243
	return (array_del_path($config, $path));
1244
}
1245
register_shutdown_function('pfSense_clear_globals');
1246

    
1247
?>
(12-12/61)