Project

General

Profile

Download (37.3 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-2023 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_get('conf_path') . "/config.xml")) {
43
		return;
44
	}
45

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

    
50
	$configtxt = file_get_contents(g_get('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_get('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_get('tmp_path') . '/config.cache')) {
112
			$config = unserialize(file_get_contents(g_get('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_get('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_get('conf_path') . '/config.xml', array(g_get('xml_rootobj'), 'pfsense'));
137
		if ($config == -1) {
138
			$last_backup = discover_last_backup();
139
			if ($last_backup) {
140
				restore_backup("/cf/conf/backup/{$last_backup}");
141
			} else {
142
				log_error(gettext("Could not restore config.xml."));
143
				unlock($lockkey);
144
				die("Config.xml is corrupted and is 0 bytes.  Could not restore a previous backup.");
145
			}
146
		}
147
		generate_config_cache($config);
148
	}
149

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

    
154
	$config_parsed = true;
155
	unlock($lockkey);
156

    
157
	return $config;
158
}
159

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

    
171
	$configcache = fopen(g_get('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_get('tmp_path') . '/config.extra.cache') && $config_extra) {
179
		$configcacheextra = fopen(g_get('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_get('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_get('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_get('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_get('product_label'), $file));
234
		file_notice("config.xml", sprintf(gettext('%1$s is restoring the configuration %2$s'), g_get('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/" . basename($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 " . escapeshellarg($xml_file) . ' ' . escapeshellarg($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/" . basename($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_get('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_get('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_get_path('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_get('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_get_path('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_get('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
				config_del_path("system/already_run_config_upgrade/{$migration_function}");
512
			} else {
513
				$migration_function();
514
			}
515
		}
516
		$config['version'] = sprintf('%.1f', $next / 10);
517
		if (platform_booting()) {
518
			echo ".";
519
		}
520
	}
521

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

    
526
		write_config(sprintf(gettext('Upgraded config version level from %1$s to %2$s'), $prev_version, $config['version']));
527
	}
528

    
529
	additional_config_upgrade();
530
}
531

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

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

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

    
580
	// Sync file before returning
581
	//return pfSense_fsync($file);
582
	return true;
583
}
584

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

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

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

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

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

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

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

    
653
	if ($backup) {
654
		backup_config();
655
	}
656

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

    
663
	$lockkey = lock('config', LOCK_EX);
664

    
665
	/* generate configuration XML */
666
	$xmlconfig = dump_xml_config($config, g_get('xml_rootobj'));
667

    
668
	/* write new configuration */
669
	if (!safe_write_file("{$g['cf_conf_path']}/config.xml", $xmlconfig)) {
670
		log_error(gettext("WARNING: Config contents could not be saved. Could not open file!"));
671
		unlock($lockkey);
672
		file_notice("config.xml", sprintf(gettext('Unable to open %1$s/config.xml for writing in write_config()%2$s'), g_get('cf_conf_path'), "\n"));
673
		return -1;
674
	}
675

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

    
680
	cleanup_backupcache(true);
681

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

    
703
	unlock($lockkey);
704

    
705
	if ($write_config_only) {
706
		return $config;
707
	}
708

    
709
	unlink_if_exists("/usr/local/pkg/pf/carp_sync_client.php");
710

    
711
	/* sync carp entries to other firewalls */
712
	carp_sync_client();
713

    
714
	if (is_dir("/usr/local/pkg/write_config")) {
715
		/* process packager manager custom rules */
716
		run_plugins("/usr/local/pkg/write_config/");
717
	}
718

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

    
727
		if (file_exists("/tmp/forceacb")) {
728
			unlink("/tmp/forceacb");
729
		}
730
	}
731

    
732
	return $config;
733
}
734

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

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

    
748
	if (!$lock) {
749
		$lockkey = lock('config', LOCK_EX);
750
	}
751

    
752
	/* create conf directory, if necessary */
753
	safe_mkdir(g_get('cf_conf_path'));
754

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

    
768
	/* copy default configuration */
769
	copy("{$g['conf_default_path']}/config.xml",
770
	    "{$g['cf_conf_path']}/config.xml");
771

    
772
	disable_security_checks();
773

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

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

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

    
799
function config_restore($conffile) {
800
	global $config, $g;
801

    
802
	if (!file_exists($conffile)) {
803
		return 1;
804
	}
805

    
806
	backup_config();
807

    
808

    
809
	$lockkey = lock('config', LOCK_EX);
810

    
811
	unlink_if_exists("{$g['tmp_path']}/config.cache");
812
	copy($conffile, "{$g['cf_conf_path']}/config.xml");
813

    
814
	disable_security_checks();
815

    
816
	unlock($lockkey);
817

    
818
	$config = parse_config(true);
819

    
820

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

    
823
	return 0;
824
}
825

    
826
function config_install($conffile) {
827
	global $config, $g;
828

    
829
	if (!file_exists($conffile)) {
830
		return 1;
831
	}
832

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

    
837
	if (platform_booting()) {
838
		echo gettext("Installing configuration...") . "\n";
839
	} else {
840
		log_error(gettext("Installing configuration ...."));
841
	}
842

    
843
	$lockkey = lock('config', LOCK_EX);
844

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

    
847
	disable_security_checks();
848

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

    
854
	unlock($lockkey);
855

    
856
	return 0;
857
}
858

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

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

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

    
884
function config_validate($conffile) {
885

    
886
	global $g, $xmlerr;
887

    
888
	$xml_parser = xml_parser_create();
889

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

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

    
905
	fclose($fp);
906

    
907
	return true;
908
}
909

    
910
function cleanup_backupcache($lock = false) {
911
	global $config, $g;
912
	$i = false;
913

    
914
	$revisions = intval(is_numericint($config['system']['backupcount']) ? $config['system']['backupcount'] : g_get('default_config_backup_count'));
915

    
916
	if (!$lock) {
917
		$lockkey = lock('config');
918
	}
919

    
920

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

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

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

    
998
	if (!$lock) {
999
		unlock($lockkey);
1000
	}
1001
}
1002

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

    
1021
function backup_config() {
1022
	global $config, $g;
1023

    
1024

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

    
1033
	if ($config['revision']['description'] == "") {
1034
		$bakdesc = "Unknown";
1035
	} else {
1036
		$bakdesc = config_get_path('revision/description');
1037
	}
1038

    
1039
	$bakver = ($config['version'] == "") ? "?" : $config['version'];
1040
	$bakfilename = g_get('cf_conf_path') . '/backup/config-' . $baktime . '.xml';
1041
	copy(g_get('cf_conf_path') . '/config.xml', $bakfilename);
1042

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

    
1054

    
1055
	return true;
1056
}
1057

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

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

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

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

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

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

    
1106
	if (!empty($_SERVER['REMOTE_ADDR'])) {
1107
		$username .= '@' . get_user_remote_address() . get_user_remote_authsource();
1108
	}
1109

    
1110
	return $username;
1111
}
1112

    
1113
function make_config_revision_entry($desc = null, $override_user = null) {
1114
	if (empty($override_user)) {
1115
		$username = get_config_user();
1116
	} else {
1117
		$username = $override_user;
1118
	}
1119

    
1120
	$revision = array();
1121

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

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

    
1137
function pfSense_clear_globals() {
1138
	global $config, $g, $FilterIfList, $GatewaysList, $filterdns, $aliases, $aliastable;
1139

    
1140
	$error = error_get_last();
1141

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

    
1158
	if (isset($FilterIfList)) {
1159
		unset($FilterIfList);
1160
	}
1161

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

    
1166
	/* Used for the hostname dns resolver */
1167
	if (isset($filterdns)) {
1168
		unset($filterdns);
1169
	}
1170

    
1171
	/* Used for aliases and interface macros */
1172
	if (isset($aliases)) {
1173
		unset($aliases);
1174
	}
1175
	if (isset($aliastable)) {
1176
		unset($aliastable);
1177
	}
1178

    
1179
	unset($config);
1180
}
1181

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

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

    
1205
/**
1206
 * Return a value specified by path in the config, if it exists.
1207
 * @param $path string path with '/' separators
1208
 * @param $default mixed value to return if the path is not found
1209
 * @returns mixed value at path or $default if the path does not exist or if the
1210
 *          path keys an empty string and $default is non-null
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, creating arrays for intermediary keys as
1219
 * necessary. If the path cannot be reached because an intermediary exists but
1220
 * is not empty or an array, return $default.
1221
 * @param $path string path with '/' separators
1222
 * @param $val mixed 
1223
 * @param $default mixed value to return if the path is not found
1224
 * @returns mixed $val or $default if the path prefix does not exist
1225
 */
1226
function config_set_path(string $path, $value, $default = null) {
1227
	global $config;
1228
	return (array_set_path($config, $path, $value, $default));
1229
}
1230

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

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

    
1258
?>
(11-11/61)