Project

General

Profile

Download (113 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * pfsense-utils.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-2025 Rubicon Communications, LLC (Netgate)
9
 * All rights reserved.
10
 *
11
 * Licensed under the Apache License, Version 2.0 (the "License");
12
 * you may not use this file except in compliance with the License.
13
 * You may obtain a copy of the License at
14
 *
15
 * http://www.apache.org/licenses/LICENSE-2.0
16
 *
17
 * Unless required by applicable law or agreed to in writing, software
18
 * distributed under the License is distributed on an "AS IS" BASIS,
19
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
 * See the License for the specific language governing permissions and
21
 * limitations under the License.
22
 */
23

    
24
require_once("config.inc");
25
require_once("config.lib.inc");
26

    
27
function dhcp_is_backend($dhcpbackend)
28
{
29
	return (dhcp_get_backend() === $dhcpbackend);
30
}
31

    
32
function dhcp_get_backend()
33
{
34
	$dhcpbackend = config_get_path('dhcpbackend');
35
	return (in_array($dhcpbackend, ['isc', 'kea']) ? $dhcpbackend : 'isc');
36
}
37

    
38
function display_isc_warning()
39
{
40
	if (!dhcp_is_backend('isc') ||
41
	    config_path_enabled('system', 'ignoreiscwarning')) {
42
		return;
43
	}
44

    
45
	print_info_box(sprintf(gettext('ISC DHCP has reached end-of-life and will be removed in a future version of %s. Visit %s to switch DHCP backend.'), g_get('product_label'), sprintf('<a href="%s">%s</a>', 'system_advanced_network.php', 'System > Advanced > Networking')), 'warning', false);
46
}
47

    
48
/****f* pfsense-utils/have_natpfruleint_access
49
 * NAME
50
 *   have_natpfruleint_access
51
 * INPUTS
52
 *	none
53
 * RESULT
54
 *   returns true if user has access to edit a specific firewall nat port forward interface
55
 ******/
56
function have_natpfruleint_access($if) {
57
	$security_url = "firewall_nat_edit.php?if=". strtolower($if);
58
	if (isAllowedPage($security_url)) {
59
		return true;
60
	}
61
	return false;
62
}
63

    
64
/****f* pfsense-utils/have_ruleint_access
65
 * NAME
66
 *   have_ruleint_access
67
 * INPUTS
68
 *	none
69
 * RESULT
70
 *   returns true if user has access to edit a specific firewall interface
71
 ******/
72
function have_ruleint_access($if) {
73
	$security_url = "firewall_rules.php?if=". strtolower($if);
74
	if (isAllowedPage($security_url)) {
75
		return true;
76
	}
77
	return false;
78
}
79

    
80
/****f* pfsense-utils/does_url_exist
81
 * NAME
82
 *   does_url_exist
83
 * INPUTS
84
 *	none
85
 * RESULT
86
 *   returns true if a url is available
87
 ******/
88
function does_url_exist($url) {
89
	$fd = fopen("$url", "r");
90
	if ($fd) {
91
		fclose($fd);
92
		return true;
93
	} else {
94
		return false;
95
	}
96
}
97

    
98
/****f* pfsense-utils/is_private_ip
99
 * NAME
100
 *   is_private_ip
101
 * INPUTS
102
 *	none
103
 * RESULT
104
 *   returns true if an ip address is in a private range
105
 ******/
106
function is_private_ip($iptocheck) {
107
	$isprivate = false;
108
	$ip_private_list = array(
109
		"10.0.0.0/8",
110
		"100.64.0.0/10",
111
		"172.16.0.0/12",
112
		"192.168.0.0/16",
113
	);
114
	foreach ($ip_private_list as $private) {
115
		if (ip_in_subnet($iptocheck, $private) == true) {
116
			$isprivate = true;
117
		}
118
	}
119
	return $isprivate;
120
}
121

    
122
/****f* pfsense-utils/get_tmp_file
123
 * NAME
124
 *   get_tmp_file
125
 * INPUTS
126
 *	none
127
 * RESULT
128
 *   returns a temporary filename
129
 ******/
130
function get_tmp_file() {
131
	global $g;
132
	return "{$g['tmp_path']}/tmp-" . time();
133
}
134

    
135
/* Stub for deprecated function
136
 * See https://redmine.pfsense.org/issues/10931 */
137
function get_dns_servers() {
138
	return get_dns_nameservers(false, true);
139
}
140

    
141
/****f* pfsense-utils/pfSenseHeader
142
 * NAME
143
 *   pfSenseHeader
144
 * INPUTS
145
 *   none
146
 * RESULT
147
 *   JavaScript header change or browser Location:
148
 ******/
149
function pfSenseHeader($text) {
150
	global $_SERVER;
151
	if (isAjax()) {
152
		if ($_SERVER['HTTPS'] == "on") {
153
			$protocol = "https";
154
		} else {
155
			$protocol = "http";
156
		}
157

    
158
		$port = ":{$_SERVER['SERVER_PORT']}";
159
		if ($_SERVER['SERVER_PORT'] == "80" && $protocol == "http") {
160
			$port = "";
161
		}
162
		if ($_SERVER['SERVER_PORT'] == "443" && $protocol == "https") {
163
			$port = "";
164
		}
165
		$complete_url = "{$protocol}://{$_SERVER['HTTP_HOST']}{$port}/{$text}";
166
		echo "\ndocument.location.href = '{$complete_url}';\n";
167
	} else {
168
		header("Location: $text");
169
	}
170
}
171

    
172
/****f* pfsense-utils/get_css_files
173
 * NAME
174
 *   get_css_files - get a list of the available CSS files (themes)
175
 * INPUTS
176
 *   none
177
 * RESULT
178
 *   $csslist - an array of the CSS files
179
 ******/
180
function get_css_files() {
181
	$csslist = array();
182

    
183
	// List pfSense files, then any BETA files followed by any user-contributed files
184
	$cssfiles = glob("/usr/local/www/css/*.css");
185

    
186
	if (is_array($cssfiles)) {
187
		arsort($cssfiles);
188
		$usrcss = $pfscss = $betacss = array();
189

    
190
		foreach ($cssfiles as $css) {
191
			// Don't display any login/logo page related CSS files
192
			if (strpos($css, "login") == 0 &&
193
			    strpos($css, "logo") == 0) {
194
				if (strpos($css, "BETA") != 0) {
195
					array_push($betacss, $css);
196
				} else if (strpos($css, "pfSense") != 0) {
197
					array_push($pfscss, $css);
198
				} else {
199
					array_push($usrcss, $css);
200
				}
201
			}
202
		}
203

    
204
		$css = array_merge($pfscss, $betacss, $usrcss);
205

    
206
		foreach ($css as $file) {
207
			$file = basename($file);
208
			$csslist[$file] = pathinfo($file, PATHINFO_FILENAME);
209
		}
210
	}
211
	return $csslist;
212
}
213

    
214
/****f* pfsense-utils/gen_webguicss_field
215
 * NAME
216
 *   gen_webguicss_field
217
 * INPUTS
218
 *   Pointer to section object
219
 *   Initial value for the field
220
 * RESULT
221
 *   no return value, section object is updated
222
 ******/
223
function gen_webguicss_field(&$section, $value) {
224

    
225
	$csslist = get_css_files();
226

    
227
	if (!isset($csslist[$value])) {
228
		$value = "pfSense.css";
229
	}
230

    
231
	$section->addInput(new Form_Select(
232
		'webguicss',
233
		'Theme',
234
		$value,
235
		$csslist
236
	))->setHelp('Choose an alternative css file (if installed) to change the appearance of the webConfigurator. css files are located in /usr/local/www/css/%s', '<span id="csstxt"></span>');
237
}
238
function validate_webguicss_field(&$input_errors, $value) {
239
	$csslist = get_css_files();
240
	if (!isset($csslist[$value])) {
241
		$input_errors[] = gettext("The submitted Theme could not be found. Pick a different theme.");
242
	}
243
}
244

    
245
/****f* pfsense-utils/gen_webguifixedmenu_field
246
 * NAME
247
 *   gen_webguifixedmenu_field
248
 * INPUTS
249
 *   Pointer to section object
250
 *   Initial value for the field
251
 * RESULT
252
 *   no return value, section object is updated
253
 ******/
254
function gen_webguifixedmenu_field(&$section, $value) {
255

    
256
	$section->addInput(new Form_Select(
257
		'webguifixedmenu',
258
		'Top Navigation',
259
		$value,
260
		["" => gettext("Scrolls with page"), "fixed" => gettext("Fixed (Remains visible at top of page)")]
261
	))->setHelp("The fixed option is intended for large screens only.");
262
}
263
function validate_webguifixedmenu_field(&$input_errors, $value) {
264
	$valid_values = array("", "fixed");
265
	if (!in_array($value, $valid_values)) {
266
		$input_errors[] = gettext("The submitted Top Navigation value is invalid.");
267
	}
268
}
269

    
270
/****f* pfsense-utils/gen_webguihostnamemenu_field
271
 * NAME
272
 *   gen_webguihostnamemenu_field
273
 * INPUTS
274
 *   Pointer to section object
275
 *   Initial value for the field
276
 * RESULT
277
 *   no return value, section object is updated
278
 ******/
279
function gen_webguihostnamemenu_field(&$section, $value) {
280

    
281
	$section->addInput(new Form_Select(
282
		'webguihostnamemenu',
283
		'Hostname in Menu',
284
		$value,
285
		["" => gettext("Default (No hostname)"), "hostonly" => gettext("Hostname only"), "fqdn" => gettext("Fully Qualified Domain Name")]
286
	))->setHelp("Replaces the Help menu title in the Navbar with the system hostname or FQDN.");
287
}
288
function validate_webguihostnamemenu_field(&$input_errors, $value) {
289
	$valid_values = array("", "hostonly", "fqdn");
290
	if (!in_array($value, $valid_values)) {
291
		$input_errors[] = gettext("The submitted Hostname in Menu value is invalid.");
292
	}
293
}
294

    
295
/****f* pfsense-utils/gen_dashboardcolumns_field
296
 * NAME
297
 *   gen_dashboardcolumns_field
298
 * INPUTS
299
 *   Pointer to section object
300
 *   Initial value for the field
301
 * RESULT
302
 *   no return value, section object is updated
303
 ******/
304
function gen_dashboardcolumns_field(&$section, $value) {
305

    
306
	if (((int) $value < 1) || ((int) $value > 6)) {
307
		$value = 2;
308
	}
309

    
310
	$section->addInput(new Form_Input(
311
		'dashboardcolumns',
312
		'Dashboard Columns',
313
		'number',
314
		$value,
315
		['min' => 1, 'max' => 6]
316
	));
317
}
318
function validate_dashboardcolumns_field(&$input_errors, $value) {
319
	if (!is_numericint($value) || ((int) $value < 1) || ((int) $value > 6)) {
320
		$input_errors[] = gettext("The submitted Dashboard Columns value is invalid.");
321
	}
322
}
323

    
324
/****f* pfsense-utils/gen_interfacessort_field
325
 * NAME
326
 *   gen_interfacessort_field
327
 * INPUTS
328
 *   Pointer to section object
329
 *   Initial value for the field
330
 * RESULT
331
 *   no return value, section object is updated
332
 ******/
333
function gen_interfacessort_field(&$section, $value) {
334

    
335
	$section->addInput(new Form_Checkbox(
336
		'interfacessort',
337
		'Interfaces Sort',
338
		'Sort Alphabetically',
339
		$value
340
	))->setHelp('If selected, lists of interfaces will be sorted by description, otherwise they are listed wan,lan,optn...');
341
}
342

    
343
/****f* pfsense-utils/gen_associatedpanels_fields
344
 * NAME
345
 *   gen_associatedpanels_fields
346
 * INPUTS
347
 *   Pointer to section object
348
 *   Initial value for each of the fields
349
 * RESULT
350
 *   no return value, section object is updated
351
 ******/
352
function gen_associatedpanels_fields(&$section, $value1, $value2, $value3, $value4) {
353

    
354
	$group = new Form_Group('Associated Panels Show/Hide');
355

    
356
	$group->add(new Form_Checkbox(
357
		'dashboardavailablewidgetspanel',
358
		null,
359
		'Available Widgets',
360
		$value1
361
		))->setHelp('Show the Available Widgets panel on the Dashboard.');
362

    
363
	$group->add(new Form_Checkbox(
364
		'systemlogsfilterpanel',
365
		null,
366
		'Log Filter',
367
		$value2
368
	))->setHelp('Show the Log Filter panel in System Logs.');
369

    
370
	$group->add(new Form_Checkbox(
371
		'systemlogsmanagelogpanel',
372
		null,
373
		'Manage Log',
374
		$value3
375
	))->setHelp('Show the Manage Log panel in System Logs.');
376

    
377
	$group->add(new Form_Checkbox(
378
		'statusmonitoringsettingspanel',
379
		null,
380
		'Monitoring Settings',
381
		$value4
382
	))->setHelp('Show the Settings panel in Status Monitoring.');
383

    
384
	$group->setHelp('These options allow certain panels to be automatically hidden on page load. A control is provided in the title bar to un-hide the panel.');
385

    
386
	$section->add($group);
387
}
388

    
389
/****f* pfsense-utils/gen_webguileftcolumnhyper_field
390
 * NAME
391
 *   gen_webguileftcolumnhyper_field
392
 * INPUTS
393
 *   Pointer to section object
394
 *   Initial value for the field
395
 * RESULT
396
 *   no return value, section object is updated
397
 ******/
398
function gen_webguileftcolumnhyper_field(&$section, $value) {
399

    
400
	$section->addInput(new Form_Checkbox(
401
		'webguileftcolumnhyper',
402
		'Left Column Labels',
403
		'Active',
404
		$value
405
	))->setHelp('If selected, clicking a label in the left column will select/toggle the first item of the group.');
406
}
407

    
408
/****f* pfsense-utils/gen_disablealiaspopupdetail_field
409
 * NAME
410
 *   gen_disablealiaspopupdetail_field
411
 * INPUTS
412
 *   Pointer to section object
413
 *   Initial value for the field
414
 * RESULT
415
 *   no return value, section object is updated
416
 ******/
417
function gen_disablealiaspopupdetail_field(&$section, $value) {
418

    
419
	$section->addInput(new Form_Checkbox(
420
		'disablealiaspopupdetail',
421
		'Alias Popups',
422
		'Disable details in alias popups',
423
		$value
424
	))->setHelp('If selected, the details in alias popups will not be shown, just the alias description (e.g. in Firewall Rules).');
425
}
426

    
427
/****f* pfsense-utils/gen_pagenamefirst_field
428
 * NAME
429
 *   gen_pagenamefirst_field
430
 * INPUTS
431
 *   Pointer to section object
432
 *   Initial value for the field
433
 * RESULT
434
 *   no return value, section object is updated
435
 ******/
436
function gen_pagenamefirst_field(&$section, $value) {
437

    
438
	$section->addInput(new Form_Checkbox(
439
		'pagenamefirst',
440
		'Browser tab text',
441
		'Display page name first in browser tab',
442
		$value
443
	))->setHelp('When this is unchecked, the browser tab shows the host name followed '.
444
		'by the current page. Check this box to display the current page followed by the '.
445
		'host name.');
446
}
447

    
448
/****f* pfsense-utils/gen_user_settings_fields
449
 * NAME
450
 *   gen_user_settings_fields
451
 * INPUTS
452
 *   Pointer to section object
453
 *   Array of initial values for the fields
454
 * RESULT
455
 *   no return value, section object is updated
456
 ******/
457
function gen_user_settings_fields(&$section, $pconfig) {
458

    
459
	gen_webguicss_field($section, $pconfig['webguicss']);
460
	gen_webguifixedmenu_field($section, $pconfig['webguifixedmenu']);
461
	gen_webguihostnamemenu_field($section, $pconfig['webguihostnamemenu']);
462
	gen_dashboardcolumns_field($section, $pconfig['dashboardcolumns']);
463
	gen_interfacessort_field($section, $pconfig['interfacessort']);
464
	gen_associatedpanels_fields(
465
		$section,
466
		$pconfig['dashboardavailablewidgetspanel'],
467
		$pconfig['systemlogsfilterpanel'],
468
		$pconfig['systemlogsmanagelogpanel'],
469
		$pconfig['statusmonitoringsettingspanel']);
470
	gen_webguileftcolumnhyper_field($section, $pconfig['webguileftcolumnhyper']);
471
	gen_disablealiaspopupdetail_field($section, $pconfig['disablealiaspopupdetail']);
472
	gen_pagenamefirst_field($section, $pconfig['pagenamefirst']);
473
}
474

    
475
/****f* pfsense-utils/gen_requirestatefilter_field
476
 * NAME
477
 *   gen_requirestatefilter_field
478
 * INPUTS
479
 *   Pointer to section object
480
 *   Initial value for the field
481
 * RESULT
482
 *   no return value, section object is updated
483
 ******/
484
function gen_requirestatefilter_field(&$section, $value) {
485
	$section->addInput(new Form_Checkbox(
486
		'requirestatefilter',
487
		'Require State Filter',
488
		'Do not display state table without a filter',
489
		$value
490
	))->setHelp('By default, the entire state table is displayed when entering '.
491
		'Diagnostics > States. This option requires a filter to be entered '.
492
		'before the states are displayed. Useful for systems with large state tables.');
493
}
494

    
495
/****f* pfsense-utils/gen_requirefirewallinterface_field
496
 * NAME
497
 *   gen_requirefirewallinterface_field
498
 * INPUTS
499
 *   Pointer to section object
500
 *   Initial value for the field
501
 * RESULT
502
 *   no return value, section object is updated
503
 ******/
504
function gen_requirefirewallinterface_field(&$section, $value) {
505
	$section->addInput(new Form_Checkbox(
506
		'requirefirewallinterface',
507
		'Require Firewall Interface',
508
		'Do not display firewall rules list without first selecting an interface',
509
		$value
510
	))->setHelp('By default, the GUI displays firewall rules for the first enabled interface '.
511
		'(e.g. WAN) when navigating to Firewall > Rules. This option requires the user to '.
512
		'explicitly select an interface before the GUI displays any rules. This is useful '.
513
		'for systems with a large number of rules on the first enabled interface, '.
514
		'causing slowness when loading that page.');
515
}
516

    
517
/****f* pfsense-utils/gen_created_updated_fields
518
 * NAME
519
 *   gen_created_updated_fields
520
 * INPUTS
521
 *   Pointer to form object
522
 *   Array of created time and username
523
 *   Array of updated time and username
524
 * RESULT
525
 *   no return value, section object is added to form if needed
526
 ******/
527
function gen_created_updated_fields(&$form, $created, $updated, $tracker = 0) {
528
	$has_created_time = (isset($created['time']) && isset($created['username']));
529
	$has_updated_time = (isset($updated['time']) && isset($updated['username']));
530

    
531
	if ($has_created_time || $has_updated_time) {
532
		$section = new Form_Section('Rule Information');
533

    
534
		if (!empty($tracker)) {
535
			$section->addInput(new Form_StaticText(
536
				'Tracking ID',
537
				htmlspecialchars($tracker)
538
			));
539
		}
540

    
541
		if ($has_created_time) {
542
			$section->addInput(new Form_StaticText(
543
				'Created',
544
				htmlspecialchars(sprintf(
545
					gettext('%1$s by %2$s'),
546
					date(gettext("n/j/y H:i:s"), $created['time']),
547
					$created['username']))
548
			));
549
		}
550

    
551
		if ($has_updated_time) {
552
			$section->addInput(new Form_StaticText(
553
				'Updated',
554
				htmlspecialchars(sprintf(
555
					gettext('%1$s by %2$s'),
556
					date(gettext("n/j/y H:i:s"), $updated['time']),
557
					$updated['username']))
558
			));
559
		}
560

    
561
		$form->add($section);
562
	}
563
}
564

    
565
function hardware_offloading_applyflags($iface) {
566
	$flags_on = 0;
567
	$flags_off = 0;
568
	$options = get_interface_addresses($iface);
569
	if (!isset($options)) {
570
		return;
571
	}
572

    
573
	/* disable hardware checksum offloading for VirtIO network drivers,
574
	 * see https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=165059 */
575
	if (config_path_enabled('system','disablechecksumoffloading') ||
576
	    stristr($iface, "vtnet") || stristr($iface, "ena")) {
577
		if (isset($options['encaps']['txcsum'])) {
578
			$flags_off |= IFCAP_TXCSUM;
579
		}
580
		if (isset($options['encaps']['rxcsum'])) {
581
			$flags_off |= IFCAP_RXCSUM;
582
		}
583
		if (isset($options['encaps']['txcsum6'])) {
584
			$flags_off |= IFCAP_TXCSUM_IPV6;
585
		}
586
		if (isset($options['encaps']['rxcsum6'])) {
587
			$flags_off |= IFCAP_RXCSUM_IPV6;
588
		}
589
	} else {
590
		if (isset($options['caps']['txcsum'])) {
591
			$flags_on |= IFCAP_TXCSUM;
592
		}
593
		if (isset($options['caps']['rxcsum'])) {
594
			$flags_on |= IFCAP_RXCSUM;
595
		}
596
		if (isset($options['caps']['txcsum6'])) {
597
			$flags_on |= IFCAP_TXCSUM_IPV6;
598
		}
599
		if (isset($options['caps']['rxcsum6'])) {
600
			$flags_on |= IFCAP_RXCSUM_IPV6;
601
		}
602
	}
603

    
604
	if (config_path_enabled('system','disablesegmentationoffloading')) {
605
		$flags_off |= IFCAP_TSO;
606
		$flags_off |= IFCAP_VLAN_HWTSO;
607
	} else if (isset($options['caps']['tso']) || isset($options['caps']['tso4']) || isset($options['caps']['tso6'])) {
608
		$flags_on |= IFCAP_TSO;
609
		$flags_on |= IFCAP_VLAN_HWTSO;
610
	}
611

    
612
	if (config_path_enabled('system','disablelargereceiveoffloading')) {
613
		$flags_off |= IFCAP_LRO;
614
	} else if (isset($options['caps']['lro'])) {
615
		$flags_on |= IFCAP_LRO;
616
	}
617

    
618
	pfSense_interface_capabilities($iface, -$flags_off);
619
	pfSense_interface_capabilities($iface, $flags_on);
620
}
621

    
622
/****f* pfsense-utils/enable_hardware_offloading
623
 * NAME
624
 *   enable_hardware_offloading - Enable a NIC's supported hardware features.
625
 * INPUTS
626
 *   $interface	- string containing the physical interface to work on.
627
 * RESULT
628
 *   null
629
 * NOTES
630
 *   This function only supports the fxp driver's loadable microcode.
631
 ******/
632
function enable_hardware_offloading($interface) {
633
	$int = get_real_interface($interface);
634
	if (empty($int)) {
635
		return;
636
	}
637

    
638
	if (!config_path_enabled('system','do_not_use_nic_microcode')) {
639
		/* translate wan, lan, opt -> real interface if needed */
640
		$int_family = preg_split("/[0-9]+/", $int);
641
		$supported_ints = array('fxp');
642
		if (in_array($int_family, $supported_ints)) {
643
			if (does_interface_exist($int)) {
644
				pfSense_interface_flags($int, IFF_LINK0);
645
			}
646
		}
647
	}
648

    
649
	/* This is mostly for vlans and ppp types */
650
	$realhwif = get_parent_interface($interface);
651
	if ($realhwif[0] == $int) {
652
		hardware_offloading_applyflags($int);
653
	} else {
654
		hardware_offloading_applyflags($realhwif[0]);
655
		hardware_offloading_applyflags($int);
656
	}
657
}
658

    
659
/****f* pfsense-utils/is_alias_inuse
660
 * NAME
661
 *   checks to see if an alias is currently in use by a rule
662
 * INPUTS
663
 *
664
 * RESULT
665
 *   true or false
666
 * NOTES
667
 *
668
 ******/
669
function is_alias_inuse($alias) {
670
	if ($alias == "") {
671
		return false;
672
	}
673
	/* loop through firewall rules looking for alias in use */
674
	foreach (config_get_path('filter/rule', []) as $rule) {
675
		foreach (['source', 'destination'] as $origin) {
676
			if (array_get_path($rule, "{$origin}/address") == $alias) {
677
				return true;
678
			}
679
		}
680
	}
681
	/* loop through nat rules looking for alias in use */
682
	foreach (config_get_path('nat/rule', []) as $rule) {
683
		foreach (['target', 'source/address', 'destination/address'] as $property) {
684
			if (array_get_path($rule, $property) == $alias) {
685
				return true;
686
			}
687
		}
688
	}
689
	/* loop through outbound nat rules looking for alias in use */
690
	foreach (config_get_path('nat/outbound/rule', []) as $rule) {
691
		foreach (['target', 'sourceport', 'dstport', 'source/network', 'destination/network'] as $property) {
692
			if (array_get_path($rule, $property) == $alias) {
693
				return true;
694
			}
695
		}
696
	}
697
	return false;
698
}
699

    
700
/****f* pfsense-utils/is_schedule_inuse
701
 * NAME
702
 *   checks to see if a schedule is currently in use by a rule
703
 * INPUTS
704
 *
705
 * RESULT
706
 *   true or false
707
 * NOTES
708
 *
709
 ******/
710
function is_schedule_inuse($schedule) {
711
	if ($schedule == "") {
712
		return false;
713
	}
714
	/* loop through firewall rules looking for schedule in use */
715
	foreach (config_get_path('filter/rule', []) as $rule) {
716
		if ($rule['sched'] == $schedule) {
717
			return true;
718
		}
719
	}
720
	return false;
721
}
722

    
723
/****f* pfsense-utils/setup_microcode
724
 * NAME
725
 *   enumerates all interfaces and calls enable_hardware_offloading which
726
 *   enables a NIC's supported hardware features.
727
 * INPUTS
728
 *
729
 * RESULT
730
 *   null
731
 * NOTES
732
 *   This function only supports the fxp driver's loadable microcode.
733
 ******/
734
function setup_microcode() {
735

    
736
	/* if list */
737
	$iflist = get_configured_interface_list(true);
738
	foreach (array_keys($iflist) as $if) {
739
		enable_hardware_offloading($if);
740
	}
741
	unset($iflist);
742
}
743

    
744
/****f* pfsense-utils/get_carp_status
745
 * NAME
746
 *   get_carp_status - Return whether CARP is enabled or disabled.
747
 * RESULT
748
 *   boolean	- true if CARP is enabled, false if otherwise.
749
 ******/
750
function get_carp_status() {
751
	/* grab the current status of carp */
752
	$status = get_single_sysctl('net.inet.carp.allow');
753
	return (intval($status) > 0);
754
}
755

    
756
/**
757
 * Enable or disable CARP by toggling net.inet.carp.allow if necessary, enabling
758
 * only if CARP VIPs are configured
759
 */
760
function enable_carp(bool $enable=true) {
761
	$carp_enabled = get_carp_status();
762
	$carplist = get_configured_vip_list('all', VIP_CARP);
763
	if (($enable != $carp_enabled) && (!$enable || count($carplist) > 0)) {
764
		set_single_sysctl("net.inet.carp.allow", $enable?"1":"0");
765
	}
766
}
767

    
768
/*
769
 * convert_ip_to_network_format($ip, $subnet): converts an ip address to network form
770
 */
771
function convert_ip_to_network_format($ip, $subnet) {
772
	$ipsplit = explode('.', $ip);
773
	$string = $ipsplit[0] . "." . $ipsplit[1] . "." . $ipsplit[2] . ".0/" . $subnet;
774
	return $string;
775
}
776

    
777
/*
778
 * get_carp_interface_status($carpid): returns the status of a carp uniqid
779
 */
780
function get_carp_interface_status($carpid) {
781

    
782
	$carpiface = get_configured_vip_interface($carpid);
783
	if ($carpiface == NULL)
784
		return "";
785
	$interface = get_real_interface($carpiface);
786
	if ($interface == NULL)
787
		return "";
788
	$vip = get_configured_vip($carpid);
789
	if ($vip == NULL || !isset($vip['vhid']))
790
		return "";
791

    
792
	$vhid = $vip['vhid'];
793
	$carp_query = [];
794
	exec("/sbin/ifconfig {$interface} | /usr/bin/grep \"carp:.* vhid {$vhid} \"", $carp_query);
795
	foreach ($carp_query as $int) {
796
		if (stripos($int, "MASTER"))
797
			return "MASTER";
798
		elseif (stripos($int, "BACKUP"))
799
			return "BACKUP";
800
		elseif (stripos($int, "INIT"))
801
			return "INIT";
802
	}
803

    
804
	return "";
805
}
806

    
807
function get_carp_bind_status($interface) {
808
	$carpstatus = get_carp_interface_status($interface);
809
	if (!empty($carpstatus)) {
810
		return $carpstatus;
811
	} else {
812
		foreach (config_get_path('virtualip/vip', []) as $vip) {
813
			if ($interface == "_vip{$vip['uniqid']}") {
814
				return get_carp_interface_status($vip['interface']);
815
			}
816
		}
817
	}
818
}
819

    
820
/*
821
 * Return true if the CARP status of at least one interface of a captive portal zone is in backup mode
822
 * This function return false if CARP is not enabled on any interface of the captive portal zone
823
 */
824
function captiveportal_ha_is_node_in_backup_mode($cpzone) {
825
	$cpinterfaces = explode(",", config_get_path("captiveportal/{$cpzone}/interface", ""));
826

    
827
	foreach ($cpinterfaces as $interface) {
828
		foreach (config_get_path('virtualip/vip', []) as $vip) {
829
			if (($vip['interface'] == $interface) && ($vip['mode'] == "carp")) {
830
				if (get_carp_interface_status("_vip{$vip['uniqid']}") != "MASTER") {
831
					return true;
832
				}
833
			}
834
		}
835
	}
836
	return false;
837
}
838

    
839
/****f* pfsense-utils/WakeOnLan
840
 * NAME
841
 *   WakeOnLan - Wake a machine up using the wake on lan format/protocol
842
 * RESULT
843
 *   true/false - true if the operation was successful
844
 ******/
845
function WakeOnLan($addr, $mac) {
846
	$addr_byte = explode(':', $mac);
847
	$hw_addr = '';
848

    
849
	for ($a = 0; $a < 6; $a++) {
850
		$hw_addr .= chr(hexdec($addr_byte[$a]));
851
	}
852

    
853
	$msg = chr(255).chr(255).chr(255).chr(255).chr(255).chr(255);
854

    
855
	for ($a = 1; $a <= 16; $a++) {
856
		$msg .= $hw_addr;
857
	}
858

    
859
	// send it to the broadcast address using UDP
860
	$s = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
861
	if ($s == false) {
862
		log_error(gettext("Error creating socket!"));
863
		log_error(sprintf(gettext("Error code is '%1\$s' - %2\$s"), socket_last_error(), socket_strerror(socket_last_error())));
864
	} else {
865
		// setting a broadcast option to socket:
866
		$opt_ret = socket_set_option($s, 1, 6, TRUE);
867
		if ($opt_ret < 0) {
868
			log_error(sprintf(gettext("setsockopt() failed, error: %s"),
869
							  socket_strerror(socket_last_error($s))));
870
		}
871
		$e = socket_sendto($s, $msg, strlen($msg), 0, $addr, 2050);
872
		socket_close($s);
873
		log_error(sprintf(gettext('Magic Packet sent (%1$s) to (%2$s) MAC=%3$s'), $e, $addr, $mac));
874
		return true;
875
	}
876

    
877
	return false;
878
}
879

    
880
/*
881
 * reverse_strrchr($haystack, $needle):  Return everything in $haystack up to the *last* instance of $needle.
882
 *					 Useful for finding paths and stripping file extensions.
883
 */
884
function reverse_strrchr($haystack, $needle) {
885
	if (!is_string($haystack)) {
886
		return;
887
	}
888
	return strrpos($haystack, $needle) ? substr($haystack, 0, strrpos($haystack, $needle) +1) : false;
889
}
890

    
891
/*
892
 *  backup_config_section($section): returns as an xml file string of
893
 *                                   the configuration section
894
 */
895
function backup_config_section($section_name) {
896
	$new_section = config_get_path($section_name, []);
897
	/* generate configuration XML */
898
	$xmlconfig = dump_xml_config($new_section, $section_name);
899
	$xmlconfig = str_replace("<?xml version=\"1.0\"?>", "", $xmlconfig);
900
	return $xmlconfig;
901
}
902

    
903
/*
904
 *  restore_config_section($section_name, new_contents): restore a configuration section,
905
 *                                                  and write the configuration out
906
 *                                                  to disk/cf.
907
 */
908
function restore_config_section($section_name, $new_contents) {
909
	global $g;
910
	$fout = fopen("{$g['tmp_path']}/tmpxml", "w");
911
	fwrite($fout, $new_contents);
912
	fclose($fout);
913

    
914
	$xml = parse_xml_config(g_get('tmp_path') . "/tmpxml", null);
915
	if ($xml['pfsense']) {
916
		$xml = $xml['pfsense'];
917
	}
918
	if ($xml[$section_name]) {
919
		$section_xml = $xml[$section_name];
920
	} else {
921
		$section_xml = -1;
922
	}
923

    
924
	@unlink(g_get('tmp_path') . "/tmpxml");
925
	if ($section_xml === -1) {
926
		return false;
927
	}
928

    
929
	/* Save current pkg repo to re-add on new config */
930
	unset($pkg_repo_conf_path);
931
	if ($section_name == "system" &&
932
	    config_get_path('system/pkg_repo_conf_path')) {
933
		$pkg_repo_conf_path = config_get_path('system/pkg_repo_conf_path');
934
	}
935

    
936
	config_set_path($section_name, $section_xml);
937
	if (file_exists("{$g['tmp_path']}/config.cache")) {
938
		unlink("{$g['tmp_path']}/config.cache");
939
	}
940

    
941
	/* Restore previously pkg repo configured */
942
	if ($section_name == "system") {
943
		if (isset($pkg_repo_conf_path)) {
944
			config_set_path('system/pkg_repo_conf_path', $pkg_repo_conf_path);
945
		} elseif (config_get_path('system/pkg_repo_conf_path')) {
946
			config_del_path(('system/pkg_repo_conf_path'));
947
		}
948
	}
949

    
950
	write_config(sprintf(gettext("Restored %s of config file (maybe from CARP partner)"), $section_name));
951
	disable_security_checks();
952
	return true;
953
}
954

    
955
/*
956
 *  merge_config_section($section_name, new_contents):   restore a configuration section,
957
 *                                                  and write the configuration out
958
 *                                                  to disk/cf.  But preserve the prior
959
 * 													structure if needed
960
 */
961
function merge_config_section($section_name, $new_contents) {
962
	$fname = get_tmp_file();
963
	$fout = fopen($fname, "w");
964
	fwrite($fout, $new_contents);
965
	fclose($fout);
966
	$section_xml = parse_xml_config($fname, $section_name);
967
	config_set_path($section_name, $section_xml);
968
	unlink($fname);
969
	write_config(sprintf(gettext("Restored %s of config file (maybe from CARP partner)"), $section_name));
970
	disable_security_checks();
971
	return;
972
}
973

    
974
/*
975
 * rmdir_recursive($path, $follow_links=false)
976
 * Recursively remove a directory tree (rm -rf path)
977
 * This is for directories _only_
978
 */
979
function rmdir_recursive($path, $follow_links=false) {
980
	$to_do = glob($path);
981
	if (!is_array($to_do)) {
982
		$to_do = array($to_do);
983
	}
984
	foreach ($to_do as $workingdir) { // Handle wildcards by foreaching.
985
		if (file_exists($workingdir)) {
986
			if (is_dir($workingdir)) {
987
				$dir = opendir($workingdir);
988
				while ($entry = readdir($dir)) {
989
					if (is_file("{$workingdir}/{$entry}") || ((!$follow_links) && is_link("{$workingdir}/{$entry}"))) {
990
						unlink("{$workingdir}/{$entry}");
991
					} elseif (is_dir("{$workingdir}/{$entry}") && $entry != '.' && $entry != '..') {
992
						rmdir_recursive("{$workingdir}/{$entry}");
993
					}
994
				}
995
				closedir($dir);
996
				rmdir($workingdir);
997
			} elseif (is_file($workingdir)) {
998
				unlink($workingdir);
999
			}
1000
		}
1001
	}
1002
	return;
1003
}
1004

    
1005
/*
1006
 * host_firmware_version(): Return the versions used in this install
1007
 */
1008
function host_firmware_version() {
1009
	global $g;
1010

    
1011
	$os_version = trim(substr(php_uname("r"), 0, strpos(php_uname("r"), '-')));
1012

    
1013
	return array(
1014
		"firmware" => array("version" => g_get('product_version')),
1015
		"kernel"   => array("version" => $os_version),
1016
		"base"     => array("version" => $os_version),
1017
		"platform" => g_get('product_label'),
1018
		"config_version" => config_get_path('version')
1019
	);
1020
}
1021

    
1022
function get_disk_info() {
1023
	$diskout = "";
1024
	exec("/bin/df -h | /usr/bin/grep -w '/' | /usr/bin/awk '{ print $2, $3, $4, $5 }'", $diskout);
1025
	return explode(' ', $diskout[0]);
1026
}
1027

    
1028
/****f* pfsense-utils/strncpy
1029
 * NAME
1030
 *   strncpy - copy strings
1031
 * INPUTS
1032
 *   &$dst, $src, $length
1033
 * RESULT
1034
 *   none
1035
 ******/
1036
function strncpy(&$dst, $src, $length) {
1037
	if (strlen($src) > $length) {
1038
		$dst = substr($src, 0, $length);
1039
	} else {
1040
		$dst = $src;
1041
	}
1042
}
1043

    
1044
/****f* pfsense-utils/reload_interfaces_sync
1045
 * NAME
1046
 *   reload_interfaces - reload all interfaces
1047
 * INPUTS
1048
 *   none
1049
 * RESULT
1050
 *   none
1051
 ******/
1052
function reload_interfaces_sync() {
1053
	global $g;
1054

    
1055
	if (g_get('debug')) {
1056
		log_error(gettext("reload_interfaces_sync() is starting."));
1057
	}
1058

    
1059
	/* parse config.xml */
1060
	config_read_file(true, true);
1061

    
1062
	/* enable routing */
1063
	system_routing_enable();
1064
	if (g_get('debug')) {
1065
		log_error(gettext("Enabling system routing"));
1066
	}
1067

    
1068
	if (g_get('debug')) {
1069
		log_error(gettext("Cleaning up Interfaces"));
1070
	}
1071

    
1072
	/* set up interfaces */
1073
	interfaces_configure();
1074
}
1075

    
1076
/****f* pfsense-utils/reload_all
1077
 * NAME
1078
 *   reload_all - triggers a reload of all settings
1079
 *   * INPUTS
1080
 *   none
1081
 * RESULT
1082
 *   none
1083
 ******/
1084
function reload_all() {
1085
	send_event("service reload all");
1086
}
1087

    
1088
/****f* pfsense-utils/reload_interfaces
1089
 * NAME
1090
 *   reload_interfaces - triggers a reload of all interfaces
1091
 * INPUTS
1092
 *   none
1093
 * RESULT
1094
 *   none
1095
 ******/
1096
function reload_interfaces() {
1097
	send_event("interface all reload");
1098
}
1099

    
1100
/****f* pfsense-utils/reload_all_sync
1101
 * NAME
1102
 *   reload_all - reload all settings
1103
 *   * INPUTS
1104
 *   none
1105
 * RESULT
1106
 *   none
1107
 ******/
1108
function reload_all_sync() {
1109
	/* parse config.xml */
1110
	config_read_file(true, true);
1111

    
1112
	/* set up our timezone */
1113
	system_timezone_configure();
1114

    
1115
	/* set up our hostname */
1116
	system_hostname_configure();
1117

    
1118
	/* make hosts file */
1119
	system_hosts_generate();
1120

    
1121
	/* generate resolv.conf */
1122
	system_resolvconf_generate();
1123

    
1124
	/* enable routing */
1125
	system_routing_enable();
1126

    
1127
	/* set up interfaces */
1128
	interfaces_configure();
1129

    
1130
	/* start dyndns service */
1131
	services_dyndns_configure();
1132

    
1133
	/* configure cron service */
1134
	configure_cron();
1135

    
1136
	/* start the NTP client */
1137
	system_ntp_configure();
1138

    
1139
	/* sync pw database */
1140
	unlink_if_exists("/etc/spwd.db.tmp");
1141
	mwexec("/usr/sbin/pwd_mkdb -d /etc/ /etc/master.passwd");
1142

    
1143
	/* restart sshd */
1144
	send_event("service restart sshd");
1145

    
1146
	/* restart webConfigurator if needed */
1147
	send_event("service restart webgui");
1148
}
1149

    
1150
function load_loader_conf($loader_conf = NULL, $local = false) {
1151

    
1152
	if ($loader_conf == NULL) {
1153
		return (NULL);
1154
	}
1155
	if (file_exists($loader_conf)) {
1156
		$input = file_get_contents($loader_conf);
1157
	} else {
1158
		$input = "";
1159
	}
1160

    
1161
	$input_split = explode("\n", $input);
1162

    
1163
	/*
1164
	 * Loop through and only add lines that are not empty and not
1165
	 * managed by us.
1166
	 */
1167
	$data = array();
1168
	/* These values should be removed from loader.conf and loader.conf.local
1169
	 * As they will be replaced when necessary. */
1170
	$remove = array(
1171
	    "hint.cordbuc.0",
1172
	    "hint.e6000sw.0",
1173
	    "hint.gpioled",
1174
	    "hint.mdio.0.at",
1175
	    "hint-model.",
1176
	    "hw.e6000sw.default_disabled",
1177
	    "hw.hn.vf_transparent",
1178
	    "hw.hn.use_if_start",
1179
	    "hw.usb.no_pf",
1180
	    "net.pf.request_maxcount",
1181
	    "vm.pmap.pti",
1182
	);
1183
	if (!$local) {
1184
		/* These values should only be filtered in loader.conf, not .local */
1185
		$remove = array_merge($remove, array(
1186
		    "autoboot_delay",
1187
		    "boot_multicons",
1188
		    "boot_serial",
1189
		    "comconsole_speed",
1190
		    "comconsole_port",
1191
		    "console",
1192
		    "debug.ddb.capture.bufsize",
1193
		    "hint.uart.0.flags",
1194
		    "hint.uart.1.flags",
1195
		    "net.link.ifqmaxlen",
1196
		    "hint.hwpstate_intel.0.disabled",
1197
		    "loader_conf_files",
1198
		    "machdep.hwpstate_pkg_ctrl",
1199
		    "net.pf.states_hashsize"
1200
		));
1201
	}
1202
	foreach ($input_split as $line) {
1203
		if (empty($line)) {
1204
			continue;
1205
		}
1206
		$skip = false;
1207
		$name = explode('=', $line, 2)[0];
1208
		foreach($remove as $rkey) {
1209
			if (strncasecmp(trim($name), $rkey, strlen($rkey)) == 0) {
1210
				$skip = true;
1211
				break;
1212
			}
1213
		}
1214
		if (!$skip) {
1215
			$data[] = $line;
1216
		}
1217
	}
1218

    
1219
	return ($data);
1220
}
1221

    
1222
function setup_loader_settings($path = "", $upgrade = false) {
1223
	global $g;
1224

    
1225
	$boot_config_file = "{$path}/boot.config";
1226
	$loader_conf_file = "{$path}/boot/loader.conf";
1227

    
1228
	$serialspeed = (config_get_path('system/serialspeed', 115200));
1229

    
1230
	$vga_only = false;
1231
	$serial_only = false;
1232
	$specific_platform = system_identify_specific_platform();
1233
	$video_console_type = (get_single_sysctl("machdep.bootmethod") == "UEFI") ? "efi" : "vidconsole";
1234
	if ($specific_platform['name'] == '1540' ||
1235
	    $specific_platform['name'] == '1541') {
1236
		$vga_only = true;
1237
	} elseif ($specific_platform['name'] == 'Turbot Dual-E') {
1238
		$g['primaryconsole_force'] = "video";
1239
	} elseif (
1240
	    $specific_platform['name'] == 'RCC-VE' ||
1241
	    $specific_platform['name'] == 'RCC' ||
1242
	    $specific_platform['name'] == 'SG-2220' ||
1243
	    $specific_platform['name'] == 'apu2') {
1244
		$serial_only = true;
1245
	}
1246

    
1247
	/* Serial console - write out /boot.config */
1248
	if (file_exists($boot_config_file)) {
1249
		$boot_config = file_get_contents($boot_config_file);
1250
	} else {
1251
		$boot_config = "";
1252
	}
1253
	$boot_config_split = explode("\n", $boot_config);
1254
	$data = array();
1255
	foreach ($boot_config_split as $bcs) {
1256
		/* Ignore -S, -D and -h lines now */
1257
		if (!empty($bcs) && !strstr($bcs, "-S") &&
1258
		    !strstr($bcs, "-D") && !strstr($bcs, "-h")) {
1259
			$data[] = $bcs;
1260
		}
1261
	}
1262
	if ($serial_only === true) {
1263
		$data[] = "-S{$serialspeed} -h";
1264
	} elseif (is_serial_enabled()) {
1265
		$data[] = "-S{$serialspeed} -D";
1266
	}
1267

    
1268
	if (empty($data)) {
1269
		@unlink($boot_config_file);
1270
	} else {
1271
		safe_write_file($boot_config_file, $data);
1272
	}
1273
	unset($data, $boot_config, $boot_config_file, $boot_config_split);
1274

    
1275
	/* Serial console - write out /boot/loader.conf */
1276
	if ($upgrade) {
1277
		system("echo \"Reading {$loader_conf_file}...\" >> /conf/upgrade_log.txt");
1278
	}
1279

    
1280
	$data = load_loader_conf($loader_conf_file, false);
1281

    
1282
	/* The armv7 port do not use the lua loader. */
1283
	if (!is_arch('arm', true)) {
1284
		$data[] = 'loader_conf_files="/boot/loader.conf.lua"';
1285
	}
1286

    
1287
	if ($serial_only === true) {
1288
		$data[] = 'boot_serial="YES"';
1289
		$data[] = 'console="comconsole"';
1290
		$data[] = 'comconsole_speed="' . $serialspeed . '"';
1291
	} elseif ($vga_only === true) {
1292
		if ($video_console_type == 'efi') {
1293
			$data[] = 'boot_serial="NO"';
1294
		}
1295
		$data[] = "console=\"{$video_console_type}\"";
1296
	} elseif (is_serial_enabled()) {
1297
		$data[] = 'boot_multicons="YES"';
1298
		$primaryconsole = isset($g['primaryconsole_force']) ?
1299
		    g_get('primaryconsole_force') :
1300
		    config_get_path('system/primaryconsole');
1301
		switch ($primaryconsole) {
1302
			case "video":
1303
				if ($video_console_type == 'efi') {
1304
					$data[] = 'boot_serial="NO"';
1305
				}
1306
				$data[] = "console=\"{$video_console_type},comconsole\"";
1307
				break;
1308
			case "serial":
1309
			default:
1310
				$data[] = 'boot_serial="YES"';
1311
				$data[] = "console=\"comconsole,{$video_console_type}\"";
1312
		}
1313
		$data[] = 'comconsole_speed="' . $serialspeed . '"';
1314
	} elseif ($video_console_type == 'efi') {
1315
		$data[] = 'boot_serial="NO"';
1316
	}
1317

    
1318
	if ($specific_platform['name'] == 'RCC-VE' ||
1319
	    $specific_platform['name'] == 'RCC' ||
1320
	    $specific_platform['name'] == 'SG-2220') {
1321
		$data[] = 'comconsole_port="0x2F8"';
1322
		$data[] = 'hint.uart.0.flags="0x00"';
1323
		$data[] = 'hint.uart.1.flags="0x10"';
1324
	}
1325
	$data[] = 'autoboot_delay="3"';
1326
	if (config_path_enabled('system','pti_disabled')) {
1327
		$data[] = 'vm.pmap.pti="0"';
1328
	}
1329

    
1330
	/* Enable ALTQ support for hnX NICs. */
1331
	if (config_path_enabled('system','hn_altq_enable')) {
1332
		$data[] = 'hw.hn.vf_transparent="0"';
1333
		$data[] = 'hw.hn.use_if_start="1"';
1334
	}
1335

    
1336
	/* Set maximum send queue length. */
1337
	$data[] = 'net.link.ifqmaxlen="128"';
1338

    
1339
	/* Speed Shift / hwpstate */
1340
	if (config_get_path('system/hwpstate', 'enabled') == 'disabled') {
1341
		$data[] = 'hint.hwpstate_intel.0.disabled="1"';
1342
	}
1343
	$data[] = 'machdep.hwpstate_pkg_ctrl="' . config_get_path('system/hwpstate_control_level', get_single_sysctl('machdep.hwpstate_pkg_ctrl')) . '"';
1344

    
1345
	// calculate the size of hash tables that store states - should be power of 2
1346
	$pf_maximumstates = (config_get_path('system/maximumstates', 0) > 0) ? config_get_path('system/maximumstates') : pfsense_default_state_size();
1347
	$pf_hashtablesize = pow(2, ceil(log(floor(intval($pf_maximumstates) / 2), 2)));
1348
	$data[] = "net.pf.states_hashsize=\"{$pf_hashtablesize}\"";
1349

    
1350
	safe_write_file($loader_conf_file, $data);
1351

    
1352
	/* Filter loader.conf.local to avoid duplicate settings. */
1353
	$loader_conf_file = "{$path}/boot/loader.conf.local";
1354
	$data = load_loader_conf($loader_conf_file, true);
1355
	if (empty($data)) {
1356
		@unlink($loader_conf_file);
1357
	} else {
1358
		safe_write_file($loader_conf_file, $data);
1359
	}
1360

    
1361
}
1362

    
1363
function console_configure($when = "save", $path = "") {
1364
	$ttys_file = "{$path}/etc/ttys";
1365

    
1366
	/* Update the loader settings. */
1367
	setup_loader_settings($path, ($when == "upgrade"));
1368

    
1369
	$ttys = file_get_contents($ttys_file);
1370
	$ttys_split = explode("\n", $ttys);
1371

    
1372
	$data = array();
1373

    
1374
	$on_off = (is_serial_enabled() ? 'onifconsole' : 'off');
1375

    
1376
	if (config_path_enabled('system','disableconsolemenu')) {
1377
		$console_type = 'Pc';
1378
		$serial_type = '3wire';
1379
	} else {
1380
		$console_type = 'al.Pc';
1381
		$serial_type = 'al.3wire';
1382
	}
1383

    
1384
	$console_line = "console\tnone\t\t\t\tunknown\toff\tsecure";
1385
	$virt_line =
1386
	    "\"/usr/libexec/getty {$console_type}\"\txterm\tonifexists secure";
1387
	$ttyu_line =
1388
	    "\"/usr/libexec/getty {$serial_type}\"\tvt100\t{$on_off}\tsecure";
1389

    
1390
	$found = array();
1391

    
1392
	foreach ($ttys_split as $tty) {
1393
		/* Ignore blank lines */
1394
		if (empty($tty)) {
1395
			continue;
1396
		}
1397

    
1398
		if (stristr($tty, "ttyv0")) {
1399
			$found['ttyv0'] = 1;
1400
			$data[] = "ttyv0\t{$virt_line}";
1401
		} elseif (stristr($tty, "xc0")) {
1402
			$found['xc0'] = 1;
1403
			$data[] = "xc0\t{$virt_line}";
1404
		} elseif (stristr($tty, "ttyu")) {
1405
			$ttyn = substr($tty, 0, 5);
1406
			$found[$ttyn] = 1;
1407
			$data[] = "{$ttyn}\t{$ttyu_line}";
1408
		} elseif (substr($tty, 0, 7) == 'console') {
1409
			$found['console'] = 1;
1410
			$data[] = $tty;
1411
		} else {
1412
			$data[] = $tty;
1413
		}
1414
	}
1415
	unset($on_off, $console_type, $serial_type);
1416

    
1417
	/* Detect missing main lines on original file and try to rebuild it */
1418
	$items = array(
1419
		'console',
1420
		'ttyv0',
1421
		'ttyu0',
1422
		'ttyu1',
1423
		'ttyu2',
1424
		'ttyu3',
1425
		'xc0'
1426
	);
1427

    
1428
	foreach ($items as $item) {
1429
		if (isset($found[$item])) {
1430
			continue;
1431
		}
1432

    
1433
		if ($item == 'console') {
1434
			$data[] = $console_line;
1435
		} elseif (($item == 'ttyv0') || ($item == 'xc0')) {
1436
			/* xc0 - Xen console, see https://redmine.pfsense.org/issues/11402 */
1437
			$data[] = "{$item}\t{$virt_line}";
1438
		} else {
1439
			$data[] = "{$item}\t{$ttyu_line}";
1440
		}
1441
	}
1442

    
1443
	safe_write_file($ttys_file, $data);
1444

    
1445
	unset($ttys, $ttys_file, $ttys_split, $data);
1446

    
1447
	if ($when != "upgrade") {
1448
		reload_ttys();
1449
	}
1450

    
1451
	return;
1452
}
1453

    
1454
function is_serial_enabled() {
1455
	global $g;
1456

    
1457
	if (!isset($g['enableserial_force']) &&
1458
	    !config_path_enabled('system','enableserial')) {
1459
		return false;
1460
	}
1461

    
1462
	return true;
1463
}
1464

    
1465
function reload_ttys() {
1466
	// Send a HUP signal to init will make it reload /etc/ttys
1467
	posix_kill(1, SIGHUP);
1468
}
1469

    
1470
function print_value_list($list, $count = 10, $separator = ",") {
1471
	$ret = implode($separator, array_slice($list, 0, $count));
1472
	if (count($list) < $count) {
1473
		$ret .= ".";
1474
	} else {
1475
		$ret .= "...";
1476
	}
1477
	return $ret;
1478
}
1479

    
1480
/* DHCP enabled on any interfaces? */
1481
function is_dhcp_server_enabled() {
1482
	foreach (config_get_path('dhcpd', []) as $dhcpif => $dhcpifconf) {
1483
		if (empty($dhcpifconf)) {
1484
			continue;
1485
		}
1486
		if (isset($dhcpifconf['enable']) &&
1487
			!empty(config_get_path("interfaces/{$dhcpif}"))) {
1488
			return true;
1489
		}
1490
	}
1491

    
1492
	return false;
1493
}
1494

    
1495
/* DHCP enabled on any interfaces? */
1496
function is_dhcpv6_server_enabled() {
1497
	foreach (config_get_path('dhcpdv6', []) as $dhcpv6if => $dhcpv6ifconf) {
1498
		if (empty($dhcpv6ifconf)) {
1499
			continue;
1500
		}
1501
		if (isset($dhcpv6ifconf['enable']) &&
1502
			!empty(config_get_path("interfaces/{$dhcpv6if}"))) {
1503
			return true;
1504
		}
1505
	}
1506

    
1507
	return false;
1508
}
1509

    
1510
/* radvd enabled on any interfaces? */
1511
function is_radvd_enabled() {
1512
	$dhcpdv6cfg = config_get_path('dhcpdv6', []);
1513
	$Iflist = get_configured_interface_list();
1514

    
1515
	/* handle manually configured DHCP6 server settings first */
1516
	foreach ($dhcpdv6cfg as $dhcpv6if => $dhcpv6ifconf) {
1517
		if (empty($dhcpv6ifconf)) {
1518
			continue;
1519
		}
1520
		if (!config_path_enabled("interfaces/{$dhcpv6if}")) {
1521
			continue;
1522
		}
1523

    
1524
		if (!isset($dhcpv6ifconf['ramode'])) {
1525
			$dhcpv6ifconf['ramode'] = $dhcpv6ifconf['mode'];
1526
		}
1527

    
1528
		if ($dhcpv6ifconf['ramode'] == "disabled") {
1529
			continue;
1530
		}
1531

    
1532
		$ifcfgipv6 = get_interface_ipv6($dhcpv6if);
1533
		if (!is_ipaddrv6($ifcfgipv6)) {
1534
			continue;
1535
		}
1536

    
1537
		return true;
1538
	}
1539

    
1540
	/* handle DHCP-PD prefixes and 6RD dynamic interfaces */
1541
	foreach (array_keys($Iflist) as $if) {
1542
		if (!config_path_enabled("interfaces/{$if}", 'track6-interface')) {
1543
			continue;
1544
		}
1545
		if (!config_path_enabled("interfaces/{$if}")) {
1546
			continue;
1547
		}
1548

    
1549
		$ifcfgipv6 = get_interface_ipv6($if);
1550
		if (!is_ipaddrv6($ifcfgipv6)) {
1551
			continue;
1552
		}
1553

    
1554
		$ifcfgsnv6 = get_interface_subnetv6($if);
1555
		$subnetv6 = gen_subnetv6($ifcfgipv6, $ifcfgsnv6);
1556

    
1557
		if (!is_ipaddrv6($subnetv6)) {
1558
			continue;
1559
		}
1560

    
1561
		if (config_get_path("dhcpdv6/{$if}/ramode", "disabled") == "disabled") {
1562
			continue;
1563
		}
1564

    
1565
		return true;
1566
	}
1567

    
1568
	return false;
1569
}
1570

    
1571
/* Any PPPoE servers enabled? */
1572
function is_pppoe_server_enabled() {
1573
	$pppoeenable = false;
1574

    
1575
	foreach (config_get_path('pppoes/pppoe', []) as $pppoes) {
1576
		if ($pppoes['mode'] == 'server') {
1577
			$pppoeenable = true;
1578
		}
1579
	}
1580

    
1581
	return $pppoeenable;
1582
}
1583

    
1584
/* Optional arg forces hh:mm:ss without days */
1585
function convert_seconds_to_dhms($sec, $showhoursonly = false) {
1586
	if (!is_numericint($sec)) {
1587
		return '-';
1588
	}
1589
	// FIXME: When we move to PHP 7 we can use "intdiv($sec % X, Y)" etc
1590
	list($d, $h, $m, $s) = array(	(int)($showhoursonly ? 0 : $sec/86400),
1591
					(int)(($showhoursonly ? $sec : $sec % 86400)/3600),
1592
					(int)(($sec % 3600)/60),
1593
					$sec % 60
1594
				);
1595
	return ($d > 0 ? $d . 'd ' : '') . sprintf('%02d:%02d:%02d', $h, $m, $s);
1596
}
1597

    
1598
/* Compute the total uptime from the ppp uptime log file in the conf directory */
1599

    
1600
function get_ppp_uptime($port) {
1601
	if (file_exists("/conf/{$port}.log")) {
1602
		$saved_time = file_get_contents("/conf/{$port}.log");
1603
		$uptime_data = explode("\n", $saved_time);
1604
		$sec = 0;
1605
		foreach ($uptime_data as $upt) {
1606
			/* Skip blank lines, trim, and cast to int to ensure type matches.
1607
			 * https://redmine.pfsense.org/issues/14117 */
1608
			if (!empty($upt)) {
1609
				$sec += (int) trim(substr($upt, 1 + strpos($upt, " ")));
1610
			}
1611
		}
1612
		return convert_seconds_to_dhms($sec);
1613
	} else {
1614
		$total_time = gettext("No history data found!");
1615
		return $total_time;
1616
	}
1617
}
1618

    
1619
//returns interface information
1620
function get_interface_info($ifdescr) {
1621
	global $g;
1622

    
1623
	$ifinfo = array();
1624
	if (empty(config_get_path("interfaces/{$ifdescr}"))) {
1625
		return;
1626
	}
1627
	$ifinfo['hwif'] = config_get_path("interfaces/{$ifdescr}/if");
1628
	$ifinfo['enable'] = config_path_enabled("interfaces/{$ifdescr}");
1629
	$ifinfo['if'] = get_real_interface($ifdescr);
1630

    
1631
	$chkif = $ifinfo['if'];
1632
	$ifinfotmp = get_interface_addresses($chkif) ?? [];
1633
	$ifinfo['status'] = $ifinfotmp['status'];
1634
	if (empty($ifinfo['status'])) {
1635
		$ifinfo['status'] = "down";
1636
	}
1637
	$ifinfo['macaddr'] = $ifinfotmp['macaddr'];
1638
	$ifinfo['mtu'] = $ifinfotmp['mtu'];
1639
	$ifinfo['ipaddr'] = $ifinfotmp['ipaddr'];
1640
	$ifinfo['subnet'] = $ifinfotmp['subnet'];
1641
	$ifinfo['linklocal'] = get_interface_linklocal($ifdescr);
1642
	$ifinfo['ipaddrv6'] = get_interface_ipv6($ifdescr);
1643
	$ifinfo['subnetv6'] = get_interface_subnetv6($ifdescr);
1644
	if (isset($ifinfotmp['link0'])) {
1645
		$link0 = "down";
1646
	}
1647
	$ifinfotmp = pfSense_get_interface_stats($chkif);
1648
	// $ifinfo['inpkts'] = $ifinfotmp['inpkts'];
1649
	// $ifinfo['outpkts'] = $ifinfotmp['outpkts'];
1650
	$ifinfo['inerrs'] = $ifinfotmp['inerrs'];
1651
	$ifinfo['outerrs'] = $ifinfotmp['outerrs'];
1652
	$ifinfo['collisions'] = $ifinfotmp['collisions'];
1653

    
1654
	/* Use pfctl for non wrapping 64 bit counters */
1655
	/* Pass */
1656
	exec("/sbin/pfctl -vvsI -i {$chkif}", $pfctlstats);
1657
	$pf_in4_pass = preg_split("/ +/ ", $pfctlstats[3]);
1658
	$pf_out4_pass = preg_split("/ +/", $pfctlstats[5]);
1659
	$pf_in6_pass = preg_split("/ +/ ", $pfctlstats[7]);
1660
	$pf_out6_pass = preg_split("/ +/", $pfctlstats[9]);
1661
	$in4_pass = $pf_in4_pass[5];
1662
	$out4_pass = $pf_out4_pass[5];
1663
	$in4_pass_packets = $pf_in4_pass[3];
1664
	$out4_pass_packets = $pf_out4_pass[3];
1665
	$in6_pass = $pf_in6_pass[5];
1666
	$out6_pass = $pf_out6_pass[5];
1667
	$in6_pass_packets = $pf_in6_pass[3];
1668
	$out6_pass_packets = $pf_out6_pass[3];
1669
	$ifinfo['inbytespass'] = $in4_pass + $in6_pass;
1670
	$ifinfo['outbytespass'] = $out4_pass + $out6_pass;
1671
	$ifinfo['inpktspass'] = $in4_pass_packets + $in6_pass_packets;
1672
	$ifinfo['outpktspass'] = $out4_pass_packets + $out6_pass_packets;
1673

    
1674
	/* Block */
1675
	$pf_in4_block = preg_split("/ +/", $pfctlstats[4]);
1676
	$pf_out4_block = preg_split("/ +/", $pfctlstats[6]);
1677
	$pf_in6_block = preg_split("/ +/", $pfctlstats[8]);
1678
	$pf_out6_block = preg_split("/ +/", $pfctlstats[10]);
1679
	$in4_block = $pf_in4_block[5];
1680
	$out4_block = $pf_out4_block[5];
1681
	$in4_block_packets = $pf_in4_block[3];
1682
	$out4_block_packets = $pf_out4_block[3];
1683
	$in6_block = $pf_in6_block[5];
1684
	$out6_block = $pf_out6_block[5];
1685
	$in6_block_packets = $pf_in6_block[3];
1686
	$out6_block_packets = $pf_out6_block[3];
1687
	$ifinfo['inbytesblock'] = $in4_block + $in6_block;
1688
	$ifinfo['outbytesblock'] = $out4_block + $out6_block;
1689
	$ifinfo['inpktsblock'] = $in4_block_packets + $in6_block_packets;
1690
	$ifinfo['outpktsblock'] = $out4_block_packets + $out6_block_packets;
1691

    
1692
	$ifinfo['inbytes'] = $in4_pass + $in6_pass;
1693
	$ifinfo['outbytes'] = $out4_pass + $out6_pass;
1694
	$ifinfo['inpkts'] = $in4_pass_packets + $in6_pass_packets;
1695
	$ifinfo['outpkts'] = $out4_pass_packets + $out6_pass_packets;
1696

    
1697
	$link_type = config_get_path("interfaces/{$ifdescr}/ipaddr");
1698
	switch ($link_type) {
1699
		/* DHCP? -> see if dhclient is up */
1700
		case "dhcp":
1701
			/* see if dhclient is up */
1702
			if (find_dhclient_process($ifinfo['if']) != 0) {
1703
				$ifinfo['dhcplink'] = "up";
1704
			} else {
1705
				$ifinfo['dhcplink'] = "down";
1706
			}
1707

    
1708
			break;
1709
		/* PPPoE/PPTP/L2TP interface? -> get status from virtual interface */
1710
		case "pppoe":
1711
		case "pptp":
1712
		case "l2tp":
1713
			if ($ifinfo['status'] == "up" && !isset($link0)) {
1714
				/* get PPPoE link status for dial on demand */
1715
				$ifinfo["{$link_type}link"] = "up";
1716
			} else {
1717
				$ifinfo["{$link_type}link"] = "down";
1718
			}
1719

    
1720
			break;
1721
		/* PPP interface? -> get uptime for this session and cumulative uptime from the persistent log file in conf */
1722
		case "ppp":
1723
			if ($ifinfo['status'] == "up") {
1724
				$ifinfo['ppplink'] = "up";
1725
			} else {
1726
				$ifinfo['ppplink'] = "down" ;
1727
			}
1728

    
1729
			if (empty($ifinfo['status'])) {
1730
				$ifinfo['status'] = "down";
1731
			}
1732

    
1733
			foreach (config_get_path('ppps/ppp', []) as $ppp) {
1734
				if (config_get_path("interfaces/{$ifdescr}/if") == $ppp['if']) {
1735
					break;
1736
				}
1737
			}
1738
			$dev = $ppp['ports'];
1739
			if (config_get_path("interfaces/{$ifdescr}/if") != $ppp['if'] || empty($dev)) {
1740
				break;
1741
			}
1742
			if (!file_exists($dev)) {
1743
				$ifinfo['nodevice'] = 1;
1744
				$ifinfo['pppinfo'] = $dev . " " . gettext("device not present! Is the modem attached to the system?");
1745
			}
1746

    
1747
			$usbmodemoutput = array();
1748
			exec("/usr/sbin/usbconfig", $usbmodemoutput);
1749
			$mondev = "{$g['tmp_path']}/3gstats.{$ifdescr}";
1750
			if (file_exists($mondev)) {
1751
				$cellstats = file($mondev);
1752
				/* skip header */
1753
				$a_cellstats = explode(",", $cellstats[1]);
1754
				if (preg_match("/huawei/i", implode("\n", $usbmodemoutput))) {
1755
					$ifinfo['cell_rssi'] = huawei_rssi_to_string($a_cellstats[1]);
1756
					$ifinfo['cell_mode'] = huawei_mode_to_string($a_cellstats[2], $a_cellstats[3]);
1757
					$ifinfo['cell_simstate'] = huawei_simstate_to_string($a_cellstats[10]);
1758
					$ifinfo['cell_service'] = huawei_service_to_string(trim($a_cellstats[11]));
1759
				}
1760
				if (preg_match("/zte/i", implode("\n", $usbmodemoutput))) {
1761
					$ifinfo['cell_rssi'] = zte_rssi_to_string($a_cellstats[1]);
1762
					$ifinfo['cell_mode'] = zte_mode_to_string($a_cellstats[2], $a_cellstats[3]);
1763
					$ifinfo['cell_simstate'] = zte_simstate_to_string($a_cellstats[10]);
1764
					$ifinfo['cell_service'] = zte_service_to_string(trim($a_cellstats[11]));
1765
				}
1766
				$ifinfo['cell_upstream'] = $a_cellstats[4];
1767
				$ifinfo['cell_downstream'] = trim($a_cellstats[5]);
1768
				$ifinfo['cell_sent'] = $a_cellstats[6];
1769
				$ifinfo['cell_received'] = trim($a_cellstats[7]);
1770
				$ifinfo['cell_bwupstream'] = $a_cellstats[8];
1771
				$ifinfo['cell_bwdownstream'] = trim($a_cellstats[9]);
1772
			}
1773
			// Calculate cumulative uptime for PPP link. Useful for connections that have per minute/hour contracts so you don't go over!
1774
			if (isset($ppp['uptime'])) {
1775
				$ifinfo['ppp_uptime_accumulated'] = "(".get_ppp_uptime($ifinfo['if']).")";
1776
			}
1777
			break;
1778
		default:
1779
			break;
1780
	}
1781

    
1782
	if (config_path_enabled('system', 'use_if_pppoe')) {
1783
		$ppp_if_file = "/tmp/{$ifinfo['if']}up";
1784
	} else {
1785
		$ppp_if_file = "{$g['varrun_path']}/{$link_type}_{$ifdescr}.pid";
1786
	}
1787
	if (file_exists($ppp_if_file)) {
1788
		$sec = trim(`/usr/local/sbin/ppp-uptime.sh {$ifinfo['if']}`);
1789
		$ifinfo['ppp_uptime'] = convert_seconds_to_dhms($sec);
1790
	}
1791

    
1792
	if ($ifinfo['status'] == "up") {
1793
		/* try to determine media with ifconfig */
1794
		$ifconfiginfo = [];
1795
		exec("/sbin/ifconfig -v " . $ifinfo['if'], $ifconfiginfo);
1796
		$wifconfiginfo = [];
1797
		if (is_interface_wireless($ifdescr)) {
1798
			exec("/sbin/ifconfig {$ifinfo['if']} list sta", $wifconfiginfo);
1799
			array_shift($wifconfiginfo);
1800
		}
1801
		$matches = "";
1802
		foreach ($ifconfiginfo as $ici) {
1803

    
1804
			/* don't list media/speed for wireless cards, as it always
1805
			   displays 2 Mbps even though clients can connect at 11 Mbps */
1806
			if (preg_match("/media: .*? \((.*?)\)/", $ici, $matches)) {
1807
				$ifinfo['media'] = $matches[1];
1808
			} else if (preg_match("/media: Ethernet (.*)/", $ici, $matches)) {
1809
				$ifinfo['media'] = $matches[1];
1810
			} else if (preg_match("/media: IEEE 802.11 Wireless Ethernet (.*)/", $ici, $matches)) {
1811
				$ifinfo['media'] = $matches[1];
1812
			}
1813

    
1814
			if (preg_match("/status: (.*)$/", $ici, $matches)) {
1815
				if ($matches[1] != "active") {
1816
					$ifinfo['status'] = $matches[1];
1817
				}
1818
				if ($ifinfo['status'] == gettext("running")) {
1819
					$ifinfo['status'] = gettext("up");
1820
				}
1821
			}
1822
			if (preg_match("/channel (\S*)/", $ici, $matches)) {
1823
				$ifinfo['channel'] = $matches[1];
1824
			}
1825
			if (preg_match("/ssid (\".*?\"|\S*)/", $ici, $matches)) {
1826
				if ($matches[1][0] == '"') {
1827
					$ifinfo['ssid'] = substr($matches[1], 1, -1);
1828
				}
1829
				else {
1830
					$ifinfo['ssid'] = $matches[1];
1831
				}
1832
			}
1833
			if (preg_match("/laggproto (.*)$/", $ici, $matches)) {
1834
				$ifinfo['laggproto'] = $matches[1];
1835
			}
1836
			if (preg_match("/laggport: (.*)$/", $ici, $matches)) {
1837
				$ifinfo['laggport'][] = $matches[1];
1838
			}
1839
			if (preg_match("/plugged: (.*)$/", $ici, $matches)) {
1840
				$ifinfo['plugged'] = $matches[1];
1841
			}
1842
			if (preg_match("/vendor: (.*)$/", $ici, $matches)) {
1843
				$ifinfo['vendor'] = $matches[1];
1844
			}
1845
			if (preg_match("/module temperature: (.*) voltage: (.*)$/", $ici, $matches)) {
1846
				$ifinfo['temperature'] = $matches[1];
1847
				$ifinfo['voltage'] = $matches[2];
1848
			}
1849
			if (preg_match("/RX power: (.*) TX bias: (.*)$/", $ici, $matches)) {
1850
				$ifinfo['rx'] = $matches[1];
1851
				$ifinfo['tx'] = $matches[2];
1852
			}
1853
		}
1854
		foreach ($wifconfiginfo as $ici) {
1855
			$elements = preg_split("/[ ]+/i", $ici);
1856
			if ($elements[0] != "") {
1857
				$ifinfo['bssid'] = $elements[0];
1858
			}
1859
			if ($elements[3] != "") {
1860
				$ifinfo['rate'] = $elements[3];
1861
			}
1862
			if ($elements[4] != "") {
1863
				$ifinfo['rssi'] = $elements[4];
1864
			}
1865
		}
1866
		/* lookup the gateway */
1867
		if (interface_has_gateway($ifdescr)) {
1868
			$ifinfo['gateway'] = get_interface_gateway($ifdescr);
1869
		}
1870
		if (interface_has_gatewayv6($ifdescr)) {
1871
			$ifinfo['gatewayv6'] = get_interface_gateway_v6($ifdescr);
1872
		}
1873
	}
1874

    
1875
	$bridge = "";
1876
	$bridge = link_interface_to_bridge($ifdescr);
1877
	if ($bridge) {
1878
		$bridge_text = `/sbin/ifconfig {$bridge}`;
1879
		if (stristr($bridge_text, "blocking") <> false) {
1880
			$ifinfo['bridge'] = "<b><font color='red'>" . gettext("blocking") . "</font></b> - " . gettext("check for ethernet loops");
1881
			$ifinfo['bridgeint'] = $bridge;
1882
		} else if (stristr($bridge_text, "learning") <> false) {
1883
			$ifinfo['bridge'] = gettext("learning");
1884
			$ifinfo['bridgeint'] = $bridge;
1885
		} else if (stristr($bridge_text, "forwarding") <> false) {
1886
			$ifinfo['bridge'] = gettext("forwarding");
1887
			$ifinfo['bridgeint'] = $bridge;
1888
		}
1889
	}
1890

    
1891
	return $ifinfo;
1892
}
1893

    
1894
//returns cpu speed of processor. Good for determining capabilities of machine
1895
function get_cpu_speed() {
1896
	return get_single_sysctl("hw.clockrate");
1897
}
1898

    
1899
function get_uptime_sec() {
1900
	$boottime = "";
1901
	$matches = "";
1902
	$boottime = get_single_sysctl("kern.boottime");
1903
	preg_match("/sec = (\d+)/", $boottime, $matches);
1904
	$boottime = $matches[1];
1905
	if (intval($boottime) == 0) {
1906
		return 0;
1907
	}
1908

    
1909
	$uptime = time() - $boottime;
1910
	return $uptime;
1911
}
1912

    
1913
function resolve_host_addresses($host, $recordtypes = array(DNS_A, DNS_AAAA, DNS_CNAME), $index = true) {
1914
	$dnsresult = array();
1915
	$resolved = array();
1916
	$errreporting = error_reporting();
1917
	error_reporting($errreporting & ~E_WARNING);// dns_get_record throws a warning if nothing is resolved..
1918
	foreach ($recordtypes as $recordtype) {
1919
		$tmp = dns_get_record($host, $recordtype);
1920
		if (is_array($tmp)) {
1921
			$dnsresult = array_merge($dnsresult, $tmp);
1922
		}
1923
	}
1924
	error_reporting($errreporting);// restore original php warning/error settings.
1925
	foreach ($dnsresult as $item) {
1926
		$newitem = array();
1927
		$newitem['type'] = $item['type'];
1928
		switch ($item['type']) {
1929
			case 'CNAME':
1930
				$newitem['data'] = $item['target'];
1931
				$resolved[] = $newitem;
1932
				break;
1933
			case 'A':
1934
				$newitem['data'] = $item['ip'];
1935
				$resolved[] = $newitem;
1936
				break;
1937
			case 'AAAA':
1938
				$newitem['data'] = $item['ipv6'];
1939
				$resolved[] = $newitem;
1940
				break;
1941
		}
1942
	}
1943
	if ($index == false) {
1944
		$noind = [];
1945
		foreach ($resolved as $res) {
1946
			$noind[] = $res['data'];
1947
		}
1948
		$resolved = $noind;
1949
	}
1950
	return $resolved;
1951
}
1952

    
1953
function add_hostname_to_watch($hostname) {
1954
	if (!is_dir("/var/db/dnscache")) {
1955
		mkdir("/var/db/dnscache");
1956
	}
1957
	$result = array();
1958
	if ((is_fqdn($hostname)) && (!is_ipaddr($hostname))) {
1959
		$contents = "";
1960
		$domips = resolve_host_addresses($hostname, array(DNS_A), false);
1961
		if (!empty($domips)) {
1962
			foreach ($domips as $ip) {
1963
				$contents .= "$ip\n";
1964
			}
1965
		}
1966
		file_put_contents("/var/db/dnscache/{$hostname}", $contents);
1967
		/* Remove empty elements */
1968
		$result = array_filter(explode("\n", $contents), 'strlen');
1969
	}
1970
	return $result;
1971
}
1972

    
1973
function is_fqdn($fqdn) {
1974
	return (bool) preg_match('/^(?:(?!-)[-_a-z0-9]+(?<!-)\.)*(?!-)[a-z0-9\-]+(?<!-)\.(?:xn--)?[a-z]+[\.]?$/i', $fqdn);
1975
}
1976

    
1977
function pfsense_default_state_size() {
1978
	/* get system memory amount */
1979
	$memory = get_memory();
1980
	$physmem = $memory[0];
1981

    
1982
	if ((int) $physmem > 0) {
1983
		/* Be cautious and only allocate 10% of system memory to the state table */
1984
		$max_states = (int) ($physmem/10)*1000;
1985
	} else {
1986
		/* If the memory check result is invalid, use a low but still
1987
		 * somewhat sane default (Equivalent to ~256MB RAM) */
1988
		$max_states = 25600;
1989
	}
1990

    
1991
	return $max_states;
1992
}
1993

    
1994
function pfsense_current_tables_size() {
1995
	$current = `pfctl -sm | grep ^tables | awk '{print $4};'`;
1996
	return $current;
1997
}
1998

    
1999
function pfsense_current_table_entries_size() {
2000
	$current = `pfctl -sm | grep table-entries | awk '{print $4};'`;
2001
	return (trim($current));
2002
}
2003

    
2004
/* Compare the current hostname DNS to the DNS cache we made
2005
 * if it has changed we return the old records
2006
 * if no change we return false */
2007
function compare_hostname_to_dnscache($hostname) {
2008
	global $g;
2009
	if (!is_dir("/var/db/dnscache")) {
2010
		mkdir("/var/db/dnscache");
2011
	}
2012
	$hostname = trim($hostname);
2013
	if (is_readable("/var/db/dnscache/{$hostname}")) {
2014
		$oldcontents = file_get_contents("/var/db/dnscache/{$hostname}");
2015
	} else {
2016
		$oldcontents = "";
2017
	}
2018
	if ((is_fqdn($hostname)) && (!is_ipaddr($hostname))) {
2019
		$contents = "";
2020
		$domips = resolve_host_addresses($hostname, array(DNS_A), false);
2021
		if (!empty($domips)) {
2022
			foreach ($domips as $ip) {
2023
				$contents .= "$ip\n";
2024
			}
2025
		}
2026
	}
2027

    
2028
	if (trim($oldcontents) != trim($contents)) {
2029
		if (g_get('debug')) {
2030
			log_error(sprintf(gettext('DNSCACHE: Found old IP %1$s and new IP %2$s'), $oldcontents, $contents));
2031
		}
2032
		return ($oldcontents);
2033
	} else {
2034
		return false;
2035
	}
2036
}
2037

    
2038
/*
2039
 * load_crypto() - Load crypto modules if enabled in config.
2040
 */
2041
function load_crypto() {
2042
	$crypto_modules = [
2043
		'aesni',
2044
		'cryptodev'
2045
	];
2046

    
2047
	$enabled_modules = explode('_', config_get_path('system/crypto_hardware'));
2048

    
2049
	foreach ($enabled_modules as $enmod) {
2050
		if (empty($enmod) || !in_array($enmod, $crypto_modules)) {
2051
			continue;
2052
		}
2053
		if (!is_module_loaded($enmod)) {
2054
			log_error(sprintf(gettext("Loading %s cryptographic accelerator module."), $enmod));
2055
			mute_kernel_msgs();
2056
			mwexec("/sbin/kldload " . escapeshellarg($enmod));
2057
			unmute_kernel_msgs();
2058
		}
2059
	}
2060
}
2061

    
2062
/*
2063
 * load_thermal_hardware() - Load temperature monitor kernel module
2064
 */
2065
function load_thermal_hardware() {
2066
	$thermal_hardware_modules = array('coretemp', 'amdtemp');
2067
	$thermal_hardware = config_get_path('system/thermal_hardware');
2068

    
2069
	if (!in_array($thermal_hardware, $thermal_hardware_modules)) {
2070
		return false;
2071
	}
2072

    
2073
	if (!empty($thermal_hardware) && !is_module_loaded($thermal_hardware)) {
2074
		log_error(sprintf(gettext("Loading %s thermal monitor module."), $thermal_hardware));
2075
		mute_kernel_msgs();
2076
		mwexec("/sbin/kldload {$thermal_hardware}");
2077
		unmute_kernel_msgs();
2078
	}
2079
}
2080

    
2081
/****f* pfsense-utils/isvm
2082
 * NAME
2083
 *   isvm
2084
 * INPUTS
2085
 *	none
2086
 * RESULT
2087
 *   returns true if machine is running under a virtual environment
2088
 ******/
2089
function isvm() {
2090
	$virtualenvs = array("vmware", "parallels", "qemu", "bochs", "plex86", "VirtualBox");
2091
	exec('/bin/kenv -q smbios.system.product 2>/dev/null', $output, $rc);
2092

    
2093
	if ($rc != 0 || !isset($output[0])) {
2094
		return false;
2095
	}
2096

    
2097
	foreach ($virtualenvs as $virtualenv) {
2098
		if (stripos($output[0], $virtualenv) !== false) {
2099
			return true;
2100
		}
2101
	}
2102

    
2103
	return false;
2104
}
2105

    
2106
function get_freebsd_version() {
2107
	$version = explode(".", php_uname("r"));
2108
	return $version[0];
2109
}
2110

    
2111
function download_file($url, $destination, $verify_ssl = true, $connect_timeout = 5, $timeout = 0) {
2112
	global $g;
2113

    
2114
	$fp = fopen($destination, "wb");
2115

    
2116
	if (!$fp) {
2117
		return false;
2118
	}
2119

    
2120
	$ch = curl_init();
2121
	curl_setopt($ch, CURLOPT_URL, $url);
2122
	if ($verify_ssl) {
2123
		curl_setopt($ch, CURLOPT_CAPATH, "/etc/ssl/certs/");
2124
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
2125
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
2126
	} else {
2127
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
2128
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
2129
		curl_setopt($ch, CURLOPT_SSL_VERIFYSTATUS, false);
2130
	}
2131
	curl_setopt($ch, CURLOPT_FILE, $fp);
2132
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connect_timeout);
2133
	curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
2134
	curl_setopt($ch, CURLOPT_HEADER, false);
2135
	curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
2136
	if (!config_path_enabled('system','do_not_send_uniqueid')) {
2137
		curl_setopt($ch, CURLOPT_USERAGENT, g_get('product_label') . '/' . g_get('product_version') . ':' . system_get_uniqueid());
2138
	} else {
2139
		curl_setopt($ch, CURLOPT_USERAGENT, g_get('product_label') . '/' . g_get('product_version'));
2140
	}
2141

    
2142
	set_curlproxy($ch);
2143

    
2144
	@curl_exec($ch);
2145
	$http_code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
2146
	fclose($fp);
2147
	curl_close($ch);
2148
	if ($http_code == 200) {
2149
		return true;
2150
	} else {
2151
		log_error(sprintf(gettext('Download file failed with status code %1$s. URL: %2$s'), $http_code, $url));
2152
		unlink_if_exists($destination);
2153
		return false;
2154
	}
2155
}
2156

    
2157
function download_file_with_progress_bar($url, $destination, $verify_ssl = true, $readbody = 'read_body', $connect_timeout = 5, $timeout = 0) {
2158
	global $g, $ch, $fout, $file_size, $downloaded, $first_progress_update;
2159
	$file_size = 1;
2160
	$downloaded = 1;
2161
	$first_progress_update = TRUE;
2162
	/* open destination file */
2163
	$fout = fopen($destination, "wb");
2164

    
2165
	if (!$fout) {
2166
		return false;
2167
	}
2168
	/*
2169
	 *      Originally by Author: Keyvan Minoukadeh
2170
	 *      Modified by Scott Ullrich to return Content-Length size
2171
	 */
2172
	$ch = curl_init();
2173
	curl_setopt($ch, CURLOPT_URL, $url);
2174
	if ($verify_ssl) {
2175
		curl_setopt($ch, CURLOPT_CAPATH, "/etc/ssl/certs/");
2176
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
2177
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
2178
	} else {
2179
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
2180
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
2181
		curl_setopt($ch, CURLOPT_SSL_VERIFYSTATUS, false);
2182
	}
2183
	curl_setopt($ch, CURLOPT_HEADERFUNCTION, 'read_header');
2184
	curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
2185
	curl_setopt($ch, CURLOPT_WRITEFUNCTION, $readbody);
2186
	curl_setopt($ch, CURLOPT_NOPROGRESS, '1');
2187
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $connect_timeout);
2188
	curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
2189
	if (!config_path_enabled('system','do_not_send_uniqueid')) {
2190
		curl_setopt($ch, CURLOPT_USERAGENT, g_get('product_label') . '/' . g_get('product_version') . ':' . system_get_uniqueid());
2191
	} else {
2192
		curl_setopt($ch, CURLOPT_USERAGENT, g_get('product_label') . '/' . g_get('product_version'));
2193
	}
2194

    
2195
	set_curlproxy($ch);
2196

    
2197
	@curl_exec($ch);
2198
	$http_code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
2199
	fclose($fout);
2200
	curl_close($ch);
2201
	if ($http_code == 200) {
2202
		return true;
2203
	} else {
2204
		log_error(sprintf(gettext('Download file failed with status code %1$s. URL: %2$s'), $http_code, $url));
2205
		unlink_if_exists($destination);
2206
		return false;
2207
	}
2208
}
2209

    
2210
function read_header($ch, $string) {
2211
	global $file_size;
2212
	$length = strlen($string);
2213
	$regs = "";
2214
	preg_match("/(Content-Length:) (.*)/", $string, $regs);
2215
	if ($regs[2] <> "") {
2216
		$file_size = intval($regs[2]);
2217
	}
2218
	ob_flush();
2219
	return $length;
2220
}
2221

    
2222
function read_body($ch, $string) {
2223
	global $fout, $file_size, $downloaded, $sendto, $static_status, $static_output, $lastseen, $first_progress_update;
2224
	global $pkg_interface;
2225
	$length = strlen($string);
2226
	$downloaded += intval($length);
2227
	if ($file_size > 0) {
2228
		$downloadProgress = round(100 * (1 - $downloaded / $file_size), 0);
2229
		$downloadProgress = 100 - $downloadProgress;
2230
	} else {
2231
		$downloadProgress = 0;
2232
	}
2233
	if ($lastseen <> $downloadProgress and $downloadProgress < 101) {
2234
		if ($sendto == "status") {
2235
			if ($pkg_interface == "console") {
2236
				if (($downloadProgress % 10) == 0 || $downloadProgress < 10) {
2237
					$tostatus = $static_status . $downloadProgress . "%";
2238
					if ($downloadProgress == 100) {
2239
						$tostatus = $tostatus . "\r";
2240
					}
2241
					update_status($tostatus);
2242
				}
2243
			} else {
2244
				$tostatus = $static_status . $downloadProgress . "%";
2245
				update_status($tostatus);
2246
			}
2247
		} else {
2248
			if ($pkg_interface == "console") {
2249
				if (($downloadProgress % 10) == 0 || $downloadProgress < 10) {
2250
					$tooutput = $static_output . $downloadProgress . "%";
2251
					if ($downloadProgress == 100) {
2252
						$tooutput = $tooutput . "\r";
2253
					}
2254
					update_output_window($tooutput);
2255
				}
2256
			} else {
2257
				$tooutput = $static_output . $downloadProgress . "%";
2258
				update_output_window($tooutput);
2259
			}
2260
		}
2261
		if (($pkg_interface != "console") || (($downloadProgress % 10) == 0) || ($downloadProgress < 10)) {
2262
			update_progress_bar($downloadProgress, $first_progress_update);
2263
			$first_progress_update = FALSE;
2264
		}
2265
		$lastseen = $downloadProgress;
2266
	}
2267
	if ($fout) {
2268
		fwrite($fout, $string);
2269
	}
2270
	ob_flush();
2271
	return $length;
2272
}
2273

    
2274
/*
2275
 *   update_output_window: update bottom textarea dynamically.
2276
 */
2277
function update_output_window($text) {
2278
	global $pkg_interface;
2279
	$log = preg_replace("/\n/", "\\n", $text);
2280
	if ($pkg_interface != "console") {
2281
?>
2282
<script type="text/javascript">
2283
//<![CDATA[
2284
	document.getElementById("output").textContent="<?=htmlspecialchars($log)?>";
2285
	document.getElementById("output").scrollTop = document.getElementById("output").scrollHeight;
2286
//]]>
2287
</script>
2288
<?php
2289
	}
2290
	/* ensure that contents are written out */
2291
	ob_flush();
2292
}
2293

    
2294
/*
2295
 *   update_status: update top textarea dynamically.
2296
 */
2297
function update_status($status) {
2298
	global $pkg_interface;
2299

    
2300
	if ($pkg_interface == "console") {
2301
		print ("{$status}");
2302
	}
2303

    
2304
	/* ensure that contents are written out */
2305
	ob_flush();
2306
}
2307

    
2308
/*
2309
 * update_progress_bar($percent, $first_time): updates the javascript driven progress bar.
2310
 */
2311
function update_progress_bar($percent, $first_time) {
2312
	global $pkg_interface;
2313
	if ($percent > 100) {
2314
		$percent = 1;
2315
	}
2316
	if ($pkg_interface <> "console") {
2317
		echo '<script type="text/javascript">';
2318
		echo "\n//<![CDATA[\n";
2319
		echo 'document.getElementById("progressbar").style.width="'. $percent.'%"';
2320
		echo "\n//]]>\n";
2321
		echo '</script>';
2322
	} else {
2323
		if (!($first_time)) {
2324
			echo "\x08\x08\x08\x08\x08";
2325
		}
2326
		echo sprintf("%4d%%", $percent);
2327
	}
2328
}
2329

    
2330
function update_alias_name($new_alias_name, $orig_alias_name) {
2331
	if (!$orig_alias_name) {
2332
		return;
2333
	}
2334

    
2335
	// Firewall rules
2336
	update_alias_names_upon_change(array('filter', 'rule'), array('source', 'address'), $new_alias_name, $orig_alias_name);
2337
	update_alias_names_upon_change(array('filter', 'rule'), array('destination', 'address'), $new_alias_name, $orig_alias_name);
2338
	update_alias_names_upon_change(array('filter', 'rule'), array('source', 'port'), $new_alias_name, $orig_alias_name);
2339
	update_alias_names_upon_change(array('filter', 'rule'), array('destination', 'port'), $new_alias_name, $orig_alias_name);
2340
	// NAT Rules
2341
	update_alias_names_upon_change(array('nat', 'rule'), array('source', 'address'), $new_alias_name, $orig_alias_name);
2342
	update_alias_names_upon_change(array('nat', 'rule'), array('source', 'port'), $new_alias_name, $orig_alias_name);
2343
	update_alias_names_upon_change(array('nat', 'rule'), array('destination', 'address'), $new_alias_name, $orig_alias_name);
2344
	update_alias_names_upon_change(array('nat', 'rule'), array('destination', 'port'), $new_alias_name, $orig_alias_name);
2345
	update_alias_names_upon_change(array('nat', 'rule'), array('target'), $new_alias_name, $orig_alias_name);
2346
	update_alias_names_upon_change(array('nat', 'rule'), array('local-port'), $new_alias_name, $orig_alias_name);
2347
	// NAT 1:1 Rules
2348
	//update_alias_names_upon_change(array('nat', 'onetoone'), array('external'), $new_alias_name, $orig_alias_name);
2349
	//update_alias_names_upon_change(array('nat', 'onetoone'), array('source', 'address'), $new_alias_name, $orig_alias_name);
2350
	update_alias_names_upon_change(array('nat', 'onetoone'), array('destination', 'address'), $new_alias_name, $orig_alias_name);
2351
	// NAT Outbound Rules
2352
	update_alias_names_upon_change(array('nat', 'outbound', 'rule'), array('source', 'network'), $new_alias_name, $orig_alias_name);
2353
	update_alias_names_upon_change(array('nat', 'outbound', 'rule'), array('sourceport'), $new_alias_name, $orig_alias_name);
2354
	update_alias_names_upon_change(array('nat', 'outbound', 'rule'), array('destination', 'network'), $new_alias_name, $orig_alias_name);
2355
	update_alias_names_upon_change(array('nat', 'outbound', 'rule'), array('dstport'), $new_alias_name, $orig_alias_name);
2356
	update_alias_names_upon_change(array('nat', 'outbound', 'rule'), array('target'), $new_alias_name, $orig_alias_name);
2357
	// Alias in an alias
2358
	update_alias_names_upon_change(array('aliases', 'alias'), array('address'), $new_alias_name, $orig_alias_name, ' ');
2359
	// Static routes
2360
	update_alias_names_upon_change(array('staticroutes', 'route'), array('network'), $new_alias_name, $orig_alias_name);
2361
	// OpenVPN
2362
	update_alias_names_upon_change(array('openvpn', 'openvpn-server'), array('tunnel_network'), $new_alias_name, $orig_alias_name);
2363
	update_alias_names_upon_change(array('openvpn', 'openvpn-server'), array('tunnel_networkv6'), $new_alias_name, $orig_alias_name);
2364
	update_alias_names_upon_change(array('openvpn', 'openvpn-server'), array('local_network'), $new_alias_name, $orig_alias_name);
2365
	update_alias_names_upon_change(array('openvpn', 'openvpn-server'), array('local_networkv6'), $new_alias_name, $orig_alias_name);
2366
	update_alias_names_upon_change(array('openvpn', 'openvpn-server'), array('remote_network'), $new_alias_name, $orig_alias_name);
2367
	update_alias_names_upon_change(array('openvpn', 'openvpn-server'), array('remote_networkv6'), $new_alias_name, $orig_alias_name);
2368
	update_alias_names_upon_change(array('openvpn', 'openvpn-client'), array('tunnel_network'), $new_alias_name, $orig_alias_name);
2369
	update_alias_names_upon_change(array('openvpn', 'openvpn-client'), array('tunnel_networkv6'), $new_alias_name, $orig_alias_name);
2370
	update_alias_names_upon_change(array('openvpn', 'openvpn-client'), array('remote_network'), $new_alias_name, $orig_alias_name);
2371
	update_alias_names_upon_change(array('openvpn', 'openvpn-client'), array('remote_networkv6'), $new_alias_name, $orig_alias_name);
2372
	update_alias_names_upon_change(array('openvpn', 'openvpn-csc'), array('tunnel_network'), $new_alias_name, $orig_alias_name);
2373
	update_alias_names_upon_change(array('openvpn', 'openvpn-csc'), array('tunnel_networkv6'), $new_alias_name, $orig_alias_name);
2374
	update_alias_names_upon_change(array('openvpn', 'openvpn-csc'), array('local_network'), $new_alias_name, $orig_alias_name);
2375
	update_alias_names_upon_change(array('openvpn', 'openvpn-csc'), array('local_networkv6'), $new_alias_name, $orig_alias_name);
2376
	update_alias_names_upon_change(array('openvpn', 'openvpn-csc'), array('remote_network'), $new_alias_name, $orig_alias_name);
2377
	update_alias_names_upon_change(array('openvpn', 'openvpn-csc'), array('remote_networkv6'), $new_alias_name, $orig_alias_name);
2378
}
2379

    
2380
function update_alias_names_upon_change($section, $field, $new_alias_name, $origname, string $separator = '') {
2381
	global $g, $pconfig, $debug;
2382
	if (!$origname) {
2383
		return;
2384
	}
2385

    
2386
	$config = config_get_path('');
2387
	$sectionref = &$config;
2388
	foreach ($section as $sectionname) {
2389
		if (is_array($sectionref) && isset($sectionref[$sectionname])) {
2390
			$sectionref = &$sectionref[$sectionname];
2391
		} else {
2392
			return;
2393
		}
2394
	}
2395

    
2396
	if ($debug) {
2397
		$fd = fopen("{$g['tmp_path']}/print_r", "a");
2398
		fwrite($fd, print_r($pconfig, true));
2399
	}
2400

    
2401
	if (is_array($sectionref)) {
2402
		foreach (array_keys($sectionref) as $itemkey) {
2403
			if ($debug) {
2404
				fwrite($fd, "$itemkey\n");
2405
			}
2406

    
2407
			$fieldfound = true;
2408
			$fieldref = &$sectionref[$itemkey];
2409
			foreach ($field as $fieldname) {
2410
				if (is_array($fieldref) && isset($fieldref[$fieldname])) {
2411
					$fieldref = &$fieldref[$fieldname];
2412
				} else {
2413
					$fieldfound = false;
2414
					break;
2415
				}
2416
			}
2417
			if ($fieldfound) {
2418
				$replaced = false;
2419
				if ((mb_strlen($separator) > 0) && !empty($fieldref)) {
2420
					$fieldref_new = [];
2421
					foreach (array_filter(explode($separator, $fieldref)) as $temp_item) {
2422
						if ($temp_item == $origname) {
2423
							$fieldref_new[] = $new_alias_name;
2424
							$replaced = true;
2425
						} else {
2426
							$fieldref_new[] = $temp_item;
2427
						}
2428
					}
2429
					if ($replaced) {
2430
						$fieldref = implode($separator, $fieldref_new);
2431
					}
2432
				} elseif ($fieldref == $origname) {
2433
					$fieldref = $new_alias_name;
2434
					$replaced = true;
2435
				}
2436
				if ($replaced && $debug) {
2437
					fwrite($fd, "Setting old alias value $origname to $new_alias_name\n");
2438
				}
2439
			}
2440
		}
2441
		config_set_path('', $config);
2442
	}
2443

    
2444
	if ($debug) {
2445
		fclose($fd);
2446
	}
2447

    
2448
}
2449

    
2450
function parse_aliases_file($filename, $type = "url", $max_items = -1, $kflc = false) {
2451
	/*
2452
	 * $filename = file to process for example blocklist like DROP:  http://www.spamhaus.org/drop/drop.txt
2453
	 * $type = if set to 'url' then subnets and ips will be returned,
2454
	 *         if set to 'url_ports' port-ranges and ports will be returned
2455
	 * $max_items = sets the maximum amount of valid items to load, -1 the default defines there is no limit.
2456
	 *
2457
	 * RETURNS an array of ip subnets and ip's or ports and port-ranges, returns NULL upon a error conditions (file not found)
2458
	 */
2459

    
2460
	if (!file_exists($filename)) {
2461
		log_error(sprintf(gettext("Could not process non-existent file from alias: %s"), $filename));
2462
		return null;
2463
	}
2464

    
2465
	if (filesize($filename) == 0) {
2466
		log_error(sprintf(gettext("Could not process empty file from alias: %s"), $filename));
2467
		return null;
2468
	}
2469
	$fd = @fopen($filename, 'r');
2470
	if (!$fd) {
2471
		log_error(sprintf(gettext("Could not process aliases from alias: %s"), $filename));
2472
		return null;
2473
	}
2474
	$items = array();
2475
	$comments = array();
2476
	while (($fc = fgets($fd)) !== FALSE) {
2477
		$fc = strip_tags($fc);
2478
		$tmp = alias_idn_to_ascii(trim($fc, " \t\n\r"));
2479
		if (empty($tmp)) {
2480
			continue;
2481
		}
2482
		if (($kflc) && (strpos($tmp, '#') === 0)) {	// Keep Full Line Comments (lines beginning with #).
2483
			$comments[] = $tmp;
2484
		} else {
2485
			$tmp_str = strstr($tmp, '#', true);
2486
			if (!empty($tmp_str)) {
2487
				$tmp = $tmp_str;
2488
			}
2489
			$tmp_str = strstr($tmp, ' ', true);
2490
			if (!empty($tmp_str)) {
2491
				$tmp = $tmp_str;
2492
			}
2493
			switch ($type) {
2494
				case "url":
2495
				case "urltable":
2496
					if (is_ipaddr($tmp) || is_subnet($tmp)) {
2497
						$items[] = $tmp;
2498
						break;
2499
					}
2500
					if (is_fqdn($tmp)) {
2501
						$results = resolve_host_addresses($tmp, array(DNS_A, DNS_AAAA), false);
2502
						if (!empty($results)) {
2503
							foreach ($results as $ip) {
2504
								if (is_ipaddr($ip)) {
2505
									$items[] = $ip;
2506
								}
2507
							}
2508
						}
2509
					}
2510
					break;
2511
				case "url_ports":
2512
				case "urltable_ports":
2513
					if (is_port_or_range($tmp)) {
2514
						$items[] = $tmp;
2515
					}
2516
					break;
2517
				default:
2518
					/* unknown type */
2519
					break;
2520
				}
2521
			if (count($items) == $max_items) {
2522
				break;
2523
			}
2524
		}
2525
	}
2526
	fclose($fd);
2527
	return array_merge($comments, $items);
2528
}
2529

    
2530
function update_alias_url_data() {
2531
	global $g, $aliastable;
2532

    
2533
	$updated = false;
2534

    
2535
	/* item is a url type */
2536
	$lockkey = lock('aliasurl');
2537
	$aliases = array();
2538
	$aliases_nested = array();
2539

    
2540
	foreach (config_get_path('aliases/alias', []) as $x => $alias) {
2541
		if (empty($alias['aliasurl'])) {
2542
			continue;
2543
		}
2544
		foreach ($alias['aliasurl'] as $alias_url) {
2545
			if (is_alias($alias_url)) {
2546
				// process nested URL aliases after URL-only aliases
2547
				$aliases_nested[] = $x;
2548
				continue 2;
2549
			}
2550
		}
2551
		$aliases[] = $x;
2552
	}
2553

    
2554
	foreach (array_merge($aliases, $aliases_nested) as $x) {
2555

    
2556
		$address = array();
2557
		$type = config_get_path("aliases/alias/{$x}/type");
2558
		foreach (config_get_path("aliases/alias/{$x}/aliasurl", []) as $alias_url) {
2559
			/* fetch down and add in */
2560
			if (is_URL($alias_url)) {
2561
				$temp_filename = tempnam("{$g['tmp_path']}/", "alias_import");
2562
				rmdir_recursive($temp_filename);
2563
				$verify_ssl = config_path_enabled('system','checkaliasesurlcert');
2564
				mkdir($temp_filename);
2565
				if (!download_file($alias_url, $temp_filename . "/aliases", $verify_ssl)) {
2566
					log_error(sprintf(gettext("Failed to download alias %s"), $alias_url));
2567
					rmdir_recursive($temp_filename);
2568
					continue;
2569
				}
2570

    
2571
				/* if the item is tar gzipped then extract */
2572
				if (stripos($alias_url, '.tgz') && !process_alias_tgz($temp_filename)) {
2573
					log_error(sprintf(gettext("Could not unpack tgz from the URL '%s'."), $alias_url));
2574
					rmdir_recursive($temp_filename);
2575
					continue;
2576
				}
2577
				if (file_exists("{$temp_filename}/aliases")) {
2578
					$t_address = parse_aliases_file("{$temp_filename}/aliases", $type, 5000);
2579
					if ($t_address == null) {
2580
						/* nothing was found */
2581
						log_error(sprintf(gettext("Could not fetch usable data from '%s'."), $alias_url));
2582
						//rmdir_recursive($temp_filename);
2583
						continue;
2584
					} else {
2585
						array_push($address, ...$t_address);
2586
					}
2587
					unset($t_address);
2588
				}
2589
				rmdir_recursive($temp_filename);
2590
			} elseif (is_alias($alias_url)) {
2591
				/* nested URL alias, see https://redmine.pfsense.org/issues/11863 */
2592
				if (!$aliastable) {
2593
					alias_make_table();
2594
				}
2595
				$t_address = explode(" ", $aliastable[$alias_url]);
2596
				if ($t_address == null) {
2597
					log_error(sprintf(gettext("Could not get usable data from '%s' URL alias."), $alias_url));
2598
					continue;
2599
				}
2600
				array_push($address, ...$t_address);
2601
			}
2602
			if (!empty($address)) {
2603
				config_set_path("aliases/alias/{$x}/address", implode(" ", $address));
2604
				$updated = true;
2605
			}
2606
		}
2607
	}
2608

    
2609
	unlock($lockkey);
2610

    
2611
	/* Report status to callers as well */
2612
	return $updated;
2613
}
2614

    
2615
function process_alias_tgz($temp_filename) {
2616
	if (!file_exists('/usr/bin/tar')) {
2617
		log_error(gettext("Alias archive is a .tar/tgz file which cannot be decompressed because utility is missing!"));
2618
		return false;
2619
	}
2620
	rename("{$temp_filename}/aliases", "{$temp_filename}/aliases.tgz");
2621
	mwexec("/usr/bin/tar xzf {$temp_filename}/aliases.tgz -C {$temp_filename}/aliases/");
2622
	unlink("{$temp_filename}/aliases.tgz");
2623
	$files_to_process = return_dir_as_array("{$temp_filename}/");
2624
	/* foreach through all extracted files and build up aliases file */
2625
	$fd = @fopen("{$temp_filename}/aliases", "w");
2626
	if (!$fd) {
2627
		log_error(sprintf(gettext("Could not open %s/aliases for writing!"), $temp_filename));
2628
		return false;
2629
	}
2630
	foreach ($files_to_process as $f2p) {
2631
		$tmpfd = @fopen($f2p, 'r');
2632
		if (!$tmpfd) {
2633
			log_error(sprintf(gettext('The following file could not be read %1$s from %2$s'), $f2p, $temp_filename));
2634
			continue;
2635
		}
2636
		while (($tmpbuf = fread($tmpfd, 65536)) !== FALSE) {
2637
			fwrite($fd, $tmpbuf);
2638
		}
2639
		fclose($tmpfd);
2640
		unlink($f2p);
2641
	}
2642
	fclose($fd);
2643
	unset($tmpbuf);
2644

    
2645
	return true;
2646
}
2647

    
2648
function version_compare_dates($a, $b) {
2649
	$a_time = strtotime($a);
2650
	$b_time = strtotime($b);
2651

    
2652
	if ((!$a_time) || (!$b_time)) {
2653
		return FALSE;
2654
	} else {
2655
		if ($a_time < $b_time) {
2656
			return -1;
2657
		} elseif ($a_time == $b_time) {
2658
			return 0;
2659
		} else {
2660
			return 1;
2661
		}
2662
	}
2663
}
2664
function version_get_string_value($a) {
2665
	$strs = array(
2666
		0 => "ALPHA-ALPHA",
2667
		2 => "ALPHA",
2668
		3 => "BETA",
2669
		4 => "B",
2670
		5 => "C",
2671
		6 => "D",
2672
		7 => "RC",
2673
		8 => "RELEASE",
2674
		9 => "*"			// Matches all release levels
2675
	);
2676
	$major = 0;
2677
	$minor = 0;
2678
	foreach ($strs as $num => $str) {
2679
		if (substr($a, 0, strlen($str)) == $str) {
2680
			$major = $num;
2681
			$n = substr($a, strlen($str));
2682
			if (is_numeric($n)) {
2683
				$minor = $n;
2684
			}
2685
			break;
2686
		}
2687
	}
2688
	return "{$major}.{$minor}";
2689
}
2690
function version_compare_string($a, $b) {
2691
	// Only compare string parts if both versions give a specific release
2692
	// (If either version lacks a string part, assume intended to match all release levels)
2693
	if (isset($a) && isset($b)) {
2694
		return version_compare_numeric(version_get_string_value($a), version_get_string_value($b));
2695
	} else {
2696
		return 0;
2697
	}
2698
}
2699
function version_compare_numeric($a, $b) {
2700
	$a_arr = explode('.', rtrim($a, '.'));
2701
	$b_arr = explode('.', rtrim($b, '.'));
2702

    
2703
	foreach ($a_arr as $n => $val) {
2704
		if (array_key_exists($n, $b_arr)) {
2705
			// So far so good, both have values at this minor version level. Compare.
2706
			if ($val > $b_arr[$n]) {
2707
				return 1;
2708
			} elseif ($val < $b_arr[$n]) {
2709
				return -1;
2710
			}
2711
		} else {
2712
			// a is greater, since b doesn't have any minor version here.
2713
			return 1;
2714
		}
2715
	}
2716
	if (count($b_arr) > count($a_arr)) {
2717
		// b is longer than a, so it must be greater.
2718
		return -1;
2719
	} else {
2720
		// Both a and b are of equal length and value.
2721
		return 0;
2722
	}
2723
}
2724
function pfs_version_compare($cur_time, $cur_text, $remote) {
2725
	// First try date compare
2726
	$v = version_compare_dates($cur_time, $remote);
2727
	if ($v === FALSE) {
2728
		// If that fails, try to compare by string
2729
		// Before anything else, simply test if the strings are equal
2730
		if (($cur_text == $remote) || ($cur_time == $remote)) {
2731
			return 0;
2732
		}
2733
		list($cur_num, $cur_str) = explode('-', $cur_text);
2734
		list($rem_num, $rem_str) = explode('-', $remote);
2735

    
2736
		// First try to compare the numeric parts of the version string.
2737
		$v = version_compare_numeric($cur_num, $rem_num);
2738

    
2739
		// If the numeric parts are the same, compare the string parts.
2740
		if ($v == 0) {
2741
			return version_compare_string($cur_str, $rem_str);
2742
		}
2743
	}
2744
	return $v;
2745
}
2746
function process_alias_urltable($name, $type, $url, $freq, $forceupdate=false, $validateonly=false) {
2747
	global $g;
2748

    
2749
	if (!is_validaliasname($name) || !filter_var($url, FILTER_VALIDATE_URL)) {
2750
		return false;
2751
	}
2752

    
2753
	$urltable_prefix = "/var/db/aliastables/";
2754
	$urltable_filename = $urltable_prefix . basename($name) . ".txt";
2755
	$tmp_urltable_filename = $urltable_filename . ".tmp";
2756

    
2757
	// Make the aliases directory if it doesn't exist
2758
	if (!file_exists($urltable_prefix)) {
2759
		mkdir($urltable_prefix);
2760
	} elseif (!is_dir($urltable_prefix)) {
2761
		unlink($urltable_prefix);
2762
		mkdir($urltable_prefix);
2763
	}
2764

    
2765
	// If the file doesn't exist or is older than update_freq days, fetch a new copy.
2766
	if (!file_exists($urltable_filename) || (filesize($urltable_filename) == "0") ||
2767
	    ((time() - filemtime($urltable_filename)) > ($freq * 86400 - 90)) ||
2768
	    $forceupdate) {
2769

    
2770
		// Try to fetch the URL supplied
2771
		unlink_if_exists($tmp_urltable_filename);
2772
		$verify_ssl = config_path_enabled('system','checkaliasesurlcert');
2773
		if (download_file($url, $tmp_urltable_filename, $verify_ssl)) {
2774
			// Convert lines that begin with '$' or ';' to comments '#' instead of deleting them.
2775
			mwexec("/usr/bin/sed -i \"\" -E 's/^[[:space:]]*($|#|;)/#/g; /^#/!s/\;.*//g;' ". escapeshellarg($tmp_urltable_filename));
2776

    
2777
			$type = ($type) ? $type : alias_get_type($name);	// If empty type passed, try to get it from config.
2778

    
2779
			$parsed_contents = parse_aliases_file($tmp_urltable_filename, $type, "-1", true);
2780
			if ($type == "urltable_ports") {
2781
				$parsed_contents = group_ports($parsed_contents, true);
2782
			}
2783
			if (is_array($parsed_contents)) {
2784
				file_put_contents($urltable_filename, implode("\n", $parsed_contents));
2785
			} else {
2786
				touch($urltable_filename);
2787
			}
2788

    
2789
			/* Remove existing archive and create an up to date archive if RAM disk is enabled. */
2790
			unlink_if_exists("{$g['cf_conf_path']}/RAM_Disk_Store/{$name}.txt.tgz");
2791
			if (config_path_enabled('system','use_mfs_tmpvar') && !file_exists("/conf/ram_disks_failed")) {
2792
				if (!file_exists("{$g['cf_conf_path']}/RAM_Disk_Store")) {
2793
					mkdir("{$g['cf_conf_path']}/RAM_Disk_Store");
2794
				}
2795
				mwexec("/usr/bin/tar -czf " . escapeshellarg("{$g['cf_conf_path']}/RAM_Disk_Store/{$name}.txt.tgz") . " -C / " . escapeshellarg($urltable_filename));
2796
			}
2797

    
2798
			unlink_if_exists($tmp_urltable_filename);
2799
		} else {
2800
			if (!$validateonly) {
2801
				touch($urltable_filename);
2802
			}
2803
			return false;
2804
		}
2805
		return true;
2806
	} else {
2807
		// File exists, and it doesn't need to be updated.
2808
		return -1;
2809
	}
2810
}
2811

    
2812
function get_include_contents($filename) {
2813
	if (is_file($filename)) {
2814
		ob_start();
2815
		include $filename;
2816
		$contents = ob_get_contents();
2817
		ob_end_clean();
2818
		return $contents;
2819
	}
2820
	return false;
2821
}
2822

    
2823
/* This xml 2 array function is courtesy of the php.net comment section on xml_parse.
2824
 * it is roughly 4 times faster then our existing pfSense parser but due to the large
2825
 * size of the RRD xml dumps this is required.
2826
 * The reason we do not use it for pfSense is that it does not know about array fields
2827
 * which causes it to fail on array fields with single items. Possible Todo?
2828
 */
2829
function xml2array($contents, $get_attributes = 1, $priority = 'tag') {
2830
	if (!function_exists('xml_parser_create')) {
2831
		return array ();
2832
	}
2833
	$parser = xml_parser_create('');
2834
	xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, "UTF-8");
2835
	xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
2836
	xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
2837
	xml_parse_into_struct($parser, trim($contents), $xml_values);
2838
	if (!$xml_values) {
2839
		return; //Hmm...
2840
	}
2841
	$xml_array = array ();
2842
	$parents = array ();
2843
	$opened_tags = array ();
2844
	$arr = array ();
2845
	$current = & $xml_array;
2846
	$repeated_tag_index = array ();
2847
	foreach ($xml_values as $data) {
2848
		unset ($attributes, $value);
2849
		extract($data);
2850
		$result = array ();
2851
		$attributes_data = array ();
2852
		if (isset ($value)) {
2853
			if ($priority == 'tag') {
2854
				$result = $value;
2855
			} else {
2856
				$result['value'] = $value;
2857
			}
2858
		}
2859
		if (isset ($attributes) and $get_attributes) {
2860
			foreach ($attributes as $attr => $val) {
2861
				if ($priority == 'tag') {
2862
					$attributes_data[$attr] = $val;
2863
				} else {
2864
					$result['attr'][$attr] = $val; //Set all the attributes in a array called 'attr'
2865
				}
2866
			}
2867
		}
2868
		if ($type == "open") {
2869
			$parent[$level -1] = & $current;
2870
			if (!is_array($current) or (!in_array($tag, array_keys($current)))) {
2871
				$current[$tag] = $result;
2872
				if ($attributes_data) {
2873
					$current[$tag . '_attr'] = $attributes_data;
2874
				}
2875
				$repeated_tag_index[$tag . '_' . $level] = 1;
2876
				$current = &$current[$tag];
2877
			} else {
2878
				if (isset ($current[$tag][0])) {
2879
					$current[$tag][$repeated_tag_index[$tag . '_' . $level]] = $result;
2880
					$repeated_tag_index[$tag . '_' . $level]++;
2881
				} else {
2882
					$current[$tag] = array (
2883
						$current[$tag],
2884
						$result
2885
						);
2886
					$repeated_tag_index[$tag . '_' . $level] = 2;
2887
					if (isset ($current[$tag . '_attr'])) {
2888
						$current[$tag]['0_attr'] = $current[$tag . '_attr'];
2889
						unset ($current[$tag . '_attr']);
2890
					}
2891
				}
2892
				$last_item_index = $repeated_tag_index[$tag . '_' . $level] - 1;
2893
				$current = &$current[$tag][$last_item_index];
2894
			}
2895
		} elseif ($type == "complete") {
2896
			if (!isset ($current[$tag])) {
2897
				$current[$tag] = $result;
2898
				$repeated_tag_index[$tag . '_' . $level] = 1;
2899
				if ($priority == 'tag' and $attributes_data) {
2900
					$current[$tag . '_attr'] = $attributes_data;
2901
				}
2902
			} else {
2903
				if (isset ($current[$tag][0]) and is_array($current[$tag])) {
2904
					$current[$tag][$repeated_tag_index[$tag . '_' . $level]] = $result;
2905
					if ($priority == 'tag' and $get_attributes and $attributes_data) {
2906
						$current[$tag][$repeated_tag_index[$tag . '_' . $level] . '_attr'] = $attributes_data;
2907
					}
2908
					$repeated_tag_index[$tag . '_' . $level]++;
2909
				} else {
2910
					$current[$tag] = array (
2911
						$current[$tag],
2912
						$result
2913
						);
2914
					$repeated_tag_index[$tag . '_' . $level] = 1;
2915
					if ($priority == 'tag' and $get_attributes) {
2916
						if (isset ($current[$tag . '_attr'])) {
2917
							$current[$tag]['0_attr'] = $current[$tag . '_attr'];
2918
							unset ($current[$tag . '_attr']);
2919
						}
2920
						if ($attributes_data) {
2921
							$current[$tag][$repeated_tag_index[$tag . '_' . $level] . '_attr'] = $attributes_data;
2922
						}
2923
					}
2924
					$repeated_tag_index[$tag . '_' . $level]++; //0 and 1 index is already taken
2925
				}
2926
			}
2927
		} elseif ($type == 'close') {
2928
			$current = &$parent[$level -1];
2929
		}
2930
	}
2931
	return ($xml_array);
2932
}
2933

    
2934
function get_country_name($country_code = "ALL") {
2935
	if ($country_code != "ALL" && strlen($country_code) != 2) {
2936
		return "";
2937
	}
2938

    
2939
	$country_names_xml = "/usr/local/share/pfSense/iso_3166-1_list_en.xml";
2940
	$country_names_contents = file_get_contents($country_names_xml);
2941
	$country_names = xml2array($country_names_contents);
2942

    
2943
	if ($country_code == "ALL") {
2944
		$country_list = array();
2945
		foreach ($country_names['ISO_3166-1_List_en']['ISO_3166-1_Entry'] as $country) {
2946
			$country_list[] = array(
2947
				"code" => $country['ISO_3166-1_Alpha-2_Code_element'],
2948
				"name" => ucwords(strtolower($country['ISO_3166-1_Country_name'])));
2949
		}
2950
		return $country_list;
2951
	}
2952

    
2953
	foreach ($country_names['ISO_3166-1_List_en']['ISO_3166-1_Entry'] as $country) {
2954
		if ($country['ISO_3166-1_Alpha-2_Code_element'] == strtoupper($country_code)) {
2955
			return ucwords(strtolower($country['ISO_3166-1_Country_name']));
2956
		}
2957
	}
2958
	return "";
2959
}
2960

    
2961
/* Return the list of country codes to be used on CAs and certs */
2962
function get_cert_country_codes() {
2963
	$countries = get_country_name();
2964

    
2965
	$country_codes = array();
2966
	foreach ($countries as $country) {
2967
		$country_codes[$country['code']] = $country['code'];
2968
	}
2969
	ksort($country_codes);
2970

    
2971
	/* Preserve historical order: None, US, CA, other countries */
2972
	$first_items[''] = gettext("None");
2973
	$first_items['US'] = $country_codes['US'];
2974
	$first_items['CA'] = $country_codes['CA'];
2975
	unset($country_codes['US']);
2976
	unset($country_codes['CA']);
2977

    
2978
	return array_merge($first_items, $country_codes);
2979
}
2980

    
2981
/* sort by interface only, retain the original order of rules that apply to
2982
   the same interface */
2983
function filter_rules_sort() {
2984
	$rules = config_get_path('filter/rule', []);
2985

    
2986
	/* mark each rule with the sequence number (to retain the order while sorting) */
2987
	$i = 0;
2988
	foreach ($rules as &$rule) {
2989
		$rule['seq'] = $i++;
2990
	}
2991
	unset($rule);
2992
	usort($rules, "filter_rules_compare");
2993

    
2994
	/* strip the sequence numbers again */
2995
	foreach ($rules as &$rule) {
2996
		unset($rule['seq']);
2997
	}
2998
	unset($rule);
2999
	/* commit changes */
3000
	config_set_path('filter/rule', $rules);
3001
}
3002
function filter_rules_compare($a, $b) {
3003
	if (isset($a['floating'])) {
3004
		// Floating rules are placed before Interface rules.
3005
		if (!isset($b['floating'])) {
3006
			return -1;
3007
		}
3008
	} elseif (isset($b['floating'])) {
3009
		// Interface rules are placed after Floating rules.
3010
		return 1;
3011
	} elseif ($a['interface'] != $b['interface']) {
3012
		// Interface rules for the same interface are grouped together.
3013
		return compare_interface_friendly_names($a['interface'], $b['interface']);
3014
	}
3015

    
3016
	// Use the sequence number as a last resort.
3017
	if (isset($a['seq']) && isset($b['seq'])) {
3018
		return ($a['seq'] <=> $b['seq']);
3019
	}
3020

    
3021
	// Nothing to compare.
3022
	return 0;
3023
}
3024

    
3025
function generate_ipv6_from_mac($mac) {
3026
	$elements = explode(":", $mac);
3027
	if (count($elements) <> 6) {
3028
		return false;
3029
	}
3030

    
3031
	$i = 0;
3032
	$ipv6 = "fe80::";
3033
	foreach ($elements as $byte) {
3034
		if ($i == 0) {
3035
			$hexadecimal = substr($byte, 1, 2);
3036
			$bitmap = base_convert($hexadecimal, 16, 2);
3037
			$bitmap = str_pad($bitmap, 4, "0", STR_PAD_LEFT);
3038
			$bitmap = substr($bitmap, 0, 2) ."1". substr($bitmap, 3, 4);
3039
			$byte = substr($byte, 0, 1) . base_convert($bitmap, 2, 16);
3040
		}
3041
		$ipv6 .= $byte;
3042
		if ($i == 1) {
3043
			$ipv6 .= ":";
3044
		}
3045
		if ($i == 3) {
3046
			$ipv6 .= ":";
3047
		}
3048
		if ($i == 2) {
3049
			$ipv6 .= "ff:fe";
3050
		}
3051

    
3052
		$i++;
3053
	}
3054
	return $ipv6;
3055
}
3056

    
3057
/****f* pfsense-utils/load_mac_manufacturer_table
3058
 * NAME
3059
 *   load_mac_manufacturer_table
3060
 * INPUTS
3061
 *   none
3062
 * RESULT
3063
 *   returns associative array with MAC-Manufacturer pairs
3064
 ******/
3065
function load_mac_manufacturer_table() {
3066
	/* load MAC-Manufacture data from the file */
3067
	$macs = false;
3068
	if (file_exists("/usr/local/share/nmap/nmap-mac-prefixes")) {
3069
		$macs=file("/usr/local/share/nmap/nmap-mac-prefixes");
3070
	}
3071
	if ($macs) {
3072
		foreach ($macs as $line) {
3073
			if (preg_match('/([0-9A-Fa-f]{6}) (.*)$/', $line, $matches)) {
3074
				/* store values like this $mac_man['000C29']='VMware' */
3075
				$mac_man["$matches[1]"] = $matches[2];
3076
			}
3077
		}
3078
		return $mac_man;
3079
	} else {
3080
		return -1;
3081
	}
3082

    
3083
}
3084

    
3085
/****f* pfsense-utils/is_ipaddr_configured
3086
 * NAME
3087
 *   is_ipaddr_configured
3088
 * INPUTS
3089
 *   IP Address to check.
3090
 *   If ignore_if is a VIP (not carp), vip array index is passed after string _virtualip
3091
 *   check_localip - if true then also check for matches with PPTP and L2TP addresses
3092
 *   check_subnets - if true then check if the given ipaddr is contained anywhere in the subnet of any other configured IP address
3093
 *   cidrprefix - the CIDR prefix (16, 20, 24, 64...) of ipaddr.
3094
 *     If check_subnets is true and cidrprefix is specified,
3095
 *     then check if the ipaddr/cidrprefix subnet overlaps the subnet of any other configured IP address
3096
 * RESULT
3097
 *   returns true if the IP Address is configured and present on this device or overlaps a configured subnet.
3098
*/
3099
function is_ipaddr_configured($ipaddr, $ignore_if = "", $check_localip = false, $check_subnets = false, $cidrprefix = "") {
3100
	if (count(where_is_ipaddr_configured($ipaddr, $ignore_if, $check_localip, $check_subnets, $cidrprefix))) {
3101
		return true;
3102
	}
3103
	return false;
3104
}
3105

    
3106
/****f* pfsense-utils/where_is_ipaddr_configured
3107
 * NAME
3108
 *   where_is_ipaddr_configured
3109
 * INPUTS
3110
 *   IP Address to check.
3111
 *   If ignore_if is a VIP (not carp), vip array index is passed after string _virtualip
3112
 *   check_localip - if true then also check for matches with PPTP and L2TP addresses
3113
 *   check_subnets - if true then check if the given ipaddr is contained anywhere in the subnet of any other configured IP address
3114
 *   cidrprefix - the CIDR prefix (16, 20, 24, 64...) of ipaddr.
3115
 *     If check_subnets is true and cidrprefix is specified,
3116
 *     then check if the ipaddr/cidrprefix subnet overlaps the subnet of any other configured IP address
3117
 * RESULT
3118
 *   Returns an array of the interfaces 'if' plus IP address or subnet 'ip_or_subnet' that match or overlap the IP address to check.
3119
 *   If there are no matches then an empty array is returned.
3120
*/
3121
function where_is_ipaddr_configured($ipaddr, $ignore_if = "", $check_localip = false, $check_subnets = false, $cidrprefix = "") {
3122
	$where_configured = array();
3123

    
3124
	$pos = strpos($ignore_if, '_virtualip');
3125
	if ($pos !== false) {
3126
		$ignore_vip_id = substr($ignore_if, $pos+10);
3127
		$ignore_vip_if = substr($ignore_if, 0, $pos);
3128
	} else {
3129
		$ignore_vip_id = -1;
3130
		$ignore_vip_if = $ignore_if;
3131
	}
3132

    
3133
	$isipv6 = is_ipaddrv6($ipaddr);
3134

    
3135
	if ($isipv6) {
3136
		$ipaddr = text_to_compressed_ip6($ipaddr);
3137
	}
3138

    
3139
	if ($check_subnets) {
3140
		$cidrprefix = intval($cidrprefix);
3141
		if ($isipv6) {
3142
			if (($cidrprefix < 1) || ($cidrprefix > 128)) {
3143
				$cidrprefix = 128;
3144
			}
3145
		} else {
3146
			if (($cidrprefix < 1) || ($cidrprefix > 32)) {
3147
				$cidrprefix = 32;
3148
			}
3149
		}
3150
		$iflist = get_configured_interface_list();
3151
		foreach ($iflist as $if => $ifname) {
3152
			if ($ignore_if == $if) {
3153
				continue;
3154
			}
3155

    
3156
			if ($isipv6) {
3157
				$if_ipv6 = get_interface_ipv6($if);
3158
				$if_snbitsv6 = get_interface_subnetv6($if);
3159
				/* do not check subnet overlapping on 6rd interfaces,
3160
				 * see https://redmine.pfsense.org/issues/12371 */
3161
				if ($if_ipv6 && $if_snbitsv6 &&
3162
				    ((config_get_path("interfaces/{$if}/ipaddrv6") != '6rd') || ($cidrprefix > $if_snbitsv6)) &&
3163
				    check_subnetsv6_overlap($ipaddr, $cidrprefix, $if_ipv6, $if_snbitsv6)) {
3164
					$where_entry = array();
3165
					$where_entry['if'] = $if;
3166
					$where_entry['ip_or_subnet'] = get_interface_ipv6($if) . "/" . get_interface_subnetv6($if);
3167
					$where_configured[] = $where_entry;
3168
				}
3169
			} else {
3170
				$if_ipv4 = get_interface_ip($if);
3171
				$if_snbitsv4 = get_interface_subnet($if);
3172
				if ($if_ipv4 && $if_snbitsv4 && check_subnets_overlap($ipaddr, $cidrprefix, $if_ipv4, $if_snbitsv4)) {
3173
					$where_entry = array();
3174
					$where_entry['if'] = $if;
3175
					$where_entry['ip_or_subnet'] = get_interface_ip($if) . "/" . get_interface_subnet($if);
3176
					$where_configured[] = $where_entry;
3177
				}
3178
			}
3179
		}
3180
	} else {
3181
		if ($isipv6) {
3182
			$interface_list_ips = get_configured_ipv6_addresses();
3183
		} else {
3184
			$interface_list_ips = get_configured_ip_addresses();
3185
		}
3186

    
3187
		foreach ($interface_list_ips as $if => $ilips) {
3188
			if ($ignore_if == $if) {
3189
				continue;
3190
			}
3191
			if (strcasecmp($ipaddr, $ilips) == 0) {
3192
				$where_entry = array();
3193
				$where_entry['if'] = $if;
3194
				$where_entry['ip_or_subnet'] = $ilips;
3195
				$where_configured[] = $where_entry;
3196
			}
3197
		}
3198
	}
3199

    
3200
	if ($check_localip) {
3201
		if (strcasecmp($ipaddr, text_to_compressed_ip6(config_get_path('l2tp/localip', ""))) == 0) {
3202
			$where_entry = array();
3203
			$where_entry['if'] = 'l2tp';
3204
			$where_entry['ip_or_subnet'] = config_get_path('l2tp/localip');
3205
			$where_configured[] = $where_entry;
3206
		}
3207
	}
3208

    
3209
	return $where_configured;
3210
}
3211

    
3212
/****f* pfsense-utils/pfSense_handle_custom_code
3213
 * NAME
3214
 *   pfSense_handle_custom_code
3215
 * INPUTS
3216
 *   directory name to process
3217
 * RESULT
3218
 *   globs the directory and includes the files
3219
 */
3220
function pfSense_handle_custom_code($src_dir) {
3221
	// Allow extending of the nat edit page and include custom input validation
3222
	if (is_dir("$src_dir")) {
3223
		$cf = glob($src_dir . "/*.inc");
3224
		foreach ($cf as $nf) {
3225
			if ($nf == "." || $nf == "..") {
3226
				continue;
3227
			}
3228
			// Include the extra handler
3229
			include_once("$nf");
3230
		}
3231
	}
3232
}
3233

    
3234
function set_language() {
3235
	global $g;
3236

    
3237
	$lang = "";
3238
	if (!empty(config_get_path('system/language'))) {
3239
		$lang = config_get_path('system/language');
3240
	} elseif (!empty(g_get('language'))) {
3241
		$lang = g_get('language');
3242
	}
3243
	$lang .= ".UTF-8";
3244

    
3245
	putenv("LANG={$lang}");
3246
	setlocale(LC_ALL, $lang);
3247
	textdomain("pfSense");
3248
	bindtextdomain("pfSense", "/usr/local/share/locale");
3249
	bind_textdomain_codeset("pfSense", $lang);
3250
}
3251

    
3252
function get_locale_list() {
3253
	$locales = array(
3254
		"bs" => gettext("Bosnian"),
3255
		"zh_CN" => gettext("Chinese"),
3256
		"zh_Hans_CN" => gettext("Chinese (Simplified, China)"),
3257
		"zh_Hans_HK" => gettext("Chinese (Simplified, Hong Kong SAR China)"),
3258
		"zh_Hant_TW" => gettext("Chinese (Traditional, Taiwan)"),
3259
		"nl_NL" => gettext("Dutch"),
3260
		"en_US" => gettext("English"),
3261
		"fr_FR" => gettext("French"),
3262
		"de_DE" => gettext("German (Germany)"),
3263
		"it_IT" => gettext("Italian"),
3264
		"ko_FR" => gettext("Korean"),
3265
		"nb_NO" => gettext("Norwegian Bokmål"),
3266
		"pl_PL" => gettext("Polish"),
3267
		"pt_PT" => gettext("Portuguese"),
3268
		"pt_BR" => gettext("Portuguese (Brazil)"),
3269
		"ru_RU" => gettext("Russian"),
3270
		"es_ES" => gettext("Spanish"),
3271
		"es_AR" => gettext("Spanish (Argentina)"),
3272
	);
3273

    
3274
	// If the locales are sorted, the order changes depending on the language selected. If the user accidentally
3275
	// selects the wrong language, this makes it very difficult to guess the intended language. NOT sorting
3276
	// allows the user to remember that English (say) is the seventh on the list and to get back to it more easily
3277

    
3278
	//asort($locales);
3279

    
3280
	return $locales;
3281
}
3282

    
3283
function return_hex_ipv4($ipv4) {
3284
	if (!is_ipaddrv4($ipv4)) {
3285
		return(false);
3286
	}
3287

    
3288
	/* we need the hex form of the interface IPv4 address */
3289
	$ip4arr = explode(".", $ipv4);
3290
	return (sprintf("%02x%02x%02x%02x", $ip4arr[0], $ip4arr[1], $ip4arr[2], $ip4arr[3]));
3291
}
3292

    
3293
function convert_ipv6_to_128bit($ipv6) {
3294
	if (!is_ipaddrv6($ipv6)) {
3295
		return(false);
3296
	}
3297

    
3298
	$ip6arr = array();
3299
	$ip6prefix = Net_IPv6::uncompress($ipv6);
3300
	$ip6arr = explode(":", $ip6prefix);
3301
	/* binary presentation of the prefix for all 128 bits. */
3302
	$ip6prefixbin = "";
3303
	foreach ($ip6arr as $element) {
3304
		$ip6prefixbin .= sprintf("%016b", hexdec($element));
3305
	}
3306
	return($ip6prefixbin);
3307
}
3308

    
3309
function convert_128bit_to_ipv6($ip6bin) {
3310
	if (strlen($ip6bin) <> 128) {
3311
		return(false);
3312
	}
3313

    
3314
	$ip6arr = array();
3315
	$ip6binarr = array();
3316
	$ip6binarr = str_split($ip6bin, 16);
3317
	foreach ($ip6binarr as $binpart) {
3318
		$ip6arr[] = dechex(bindec($binpart));
3319
	}
3320
	$ip6addr = text_to_compressed_ip6(implode(":", $ip6arr));
3321

    
3322
	return($ip6addr);
3323
}
3324

    
3325

    
3326
/* Returns the calculated bit length of the prefix delegation from the WAN interface */
3327
/* DHCP-PD is variable, calculate from the prefix-len on the WAN interface */
3328
/* 6rd is variable, calculate from 64 - (v6 prefixlen - (32 - v4 prefixlen)) */
3329
/* 6to4 is 16 bits, e.g. 65535 */
3330
function calculate_ipv6_delegation_length($if) {
3331
	$cfg = config_get_path("interfaces/{$if}");
3332
	if (!is_array($cfg)) {
3333
		return false;
3334
	}
3335

    
3336
	switch ($cfg['ipaddrv6']) {
3337
		case "6to4":
3338
			$pdlen = 16;
3339
			break;
3340
		case "6rd":
3341
			$rd6plen = explode("/", $cfg['prefix-6rd']);
3342
			$pdlen = (64 - ((int) $rd6plen[1] + (32 - (int) $cfg['prefix-6rd-v4plen'])));
3343
			break;
3344
		case "dhcp6":
3345
			$pdlen = $cfg['dhcp6-ia-pd-len'];
3346
			break;
3347
		default:
3348
			$pdlen = 0;
3349
			break;
3350
	}
3351
	return($pdlen);
3352
}
3353

    
3354
function merge_ipv6_delegated_prefix($prefix, $suffix, $len = 64) {
3355
	/* convert zero-value prefix IPv6 addresses with IPv4 mapping to hex
3356
	 * see https://redmine.pfsense.org/issues/12440 */
3357
	$suffix_list = explode(':', $suffix);
3358
	if (is_ipaddrv4($suffix_list[count($suffix_list) - 1])) {
3359
		$hexsuffix = dechex(ip2long($suffix_list[count($suffix_list) - 1]));
3360
		$suffix_list[count($suffix_list) - 1] = substr($hexsuffix, 0, 4);
3361
		$suffix_list[] = substr($hexsuffix, 4, 8);
3362
		$suffix = implode(':', $suffix_list);
3363
	}
3364
	$prefix = Net_IPv6::uncompress($prefix, true);
3365
	$suffix = Net_IPv6::uncompress($suffix, true);
3366

    
3367
	/*
3368
	 * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
3369
	 *                ^^^^ ^
3370
	 *                |||| \-> 64
3371
	 *                |||\---> 63, 62, 61, 60
3372
	 *                ||\----> 56
3373
	 *                |\-----> 52
3374
	 *                \------> 48
3375
	 */
3376

    
3377
	switch ($len) {
3378
	case 48:
3379
		$prefix_len = 15;
3380
		break;
3381
	case 52:
3382
		$prefix_len = 16;
3383
		break;
3384
	case 56:
3385
		$prefix_len = 17;
3386
		break;
3387
	case 59:
3388
	case 60:
3389
		$prefix_len = 18;
3390
		break;
3391
	/*
3392
	 * XXX 63, 62 and 61 should use 18 but PD can change and if
3393
	 * we let user chose this bit it can end up out of PD network
3394
	 *
3395
	 * Leave this with 20 for now until we find a way to let user
3396
	 * chose it. The side-effect is users with PD with one of these
3397
	 * lengths will not be able to setup DHCP server range for full
3398
	 * PD size, only for last /64 network
3399
	 */
3400
	case 63:
3401
	case 62:
3402
	case 61:
3403
	default:
3404
		$prefix_len = 20;
3405
		break;
3406
	}
3407

    
3408
	return text_to_compressed_ip6(substr($prefix, 0, $prefix_len) .
3409
	    substr($suffix, $prefix_len));
3410
}
3411

    
3412
function dhcpv6_pd_str_help($pdlen) {
3413
	$result = '';
3414

    
3415
	switch ($pdlen) {
3416
	case 48:
3417
		$result = '::xxxx:xxxx:xxxx:xxxx:xxxx';
3418
		break;
3419
	case 52:
3420
		$result = '::xxx:xxxx:xxxx:xxxx:xxxx';
3421
		break;
3422
	case 56:
3423
		$result = '::xx:xxxx:xxxx:xxxx:xxxx';
3424
		break;
3425
	case 59:
3426
	case 60:
3427
		$result = '::x:xxxx:xxxx:xxxx:xxxx';
3428
		break;
3429
	/*
3430
	 * XXX 63, 62 and 61 should use same mask as 60 but if
3431
	 * we let the user choose this bit it can end up out of PD network
3432
	 *
3433
	 * Leave this with the same as 64 for now until we find a way to
3434
	 * let the user choose it. The side-effect is users with PD with one
3435
	 * of these lengths will not be able to setup DHCP server ranges
3436
	 * for full PD size, only for last /64 network
3437
	 */
3438
	case 61:
3439
	case 62:
3440
	case 63:
3441
	case 64:
3442
	default:
3443
		$result = '::xxxx:xxxx:xxxx:xxxx';
3444
		break;
3445
	}
3446

    
3447
	return $result;
3448
}
3449

    
3450
function huawei_rssi_to_string($rssi) {
3451
	$dbm = array();
3452
	$i = 0;
3453
	$dbstart = -113;
3454
	while ($i < 32) {
3455
		$dbm[$i] = $dbstart + ($i * 2);
3456
		$i++;
3457
	}
3458
	$percent = round(($rssi / 31) * 100);
3459
	$string = "rssi:{$rssi} level:{$dbm[$rssi]}dBm percent:{$percent}%";
3460
	return $string;
3461
}
3462

    
3463
function huawei_mode_to_string($mode, $submode) {
3464
	$modes[0] = gettext("None");
3465
	$modes[1] = "AMPS";
3466
	$modes[2] = "CDMA";
3467
	$modes[3] = "GSM/GPRS";
3468
	$modes[4] = "HDR";
3469
	$modes[5] = "WCDMA";
3470
	$modes[6] = "GPS";
3471

    
3472
	$submodes[0] = gettext("No Service");
3473
	$submodes[1] = "GSM";
3474
	$submodes[2] = "GPRS";
3475
	$submodes[3] = "EDGE";
3476
	$submodes[4] = "WCDMA";
3477
	$submodes[5] = "HSDPA";
3478
	$submodes[6] = "HSUPA";
3479
	$submodes[7] = "HSDPA+HSUPA";
3480
	$submodes[8] = "TD-SCDMA";
3481
	$submodes[9] = "HSPA+";
3482
	$string = "{$modes[$mode]}, {$submodes[$submode]} " . gettext("Mode");
3483
	return $string;
3484
}
3485

    
3486
function huawei_service_to_string($state) {
3487
	$modes[0] = gettext("No Service");
3488
	$modes[1] = gettext("Restricted Service");
3489
	$modes[2] = gettext("Valid Service");
3490
	$modes[3] = gettext("Restricted Regional Service");
3491
	$modes[4] = gettext("Powersaving Service");
3492
	$modes[255] = gettext("Unknown Service");
3493
	$string = $modes[$state];
3494
	return $string;
3495
}
3496

    
3497
function huawei_simstate_to_string($state) {
3498
	$modes[0] = gettext("Invalid SIM/locked State");
3499
	$modes[1] = gettext("Valid SIM State");
3500
	$modes[2] = gettext("Invalid SIM CS State");
3501
	$modes[3] = gettext("Invalid SIM PS State");
3502
	$modes[4] = gettext("Invalid SIM CS/PS State");
3503
	$modes[255] = gettext("Missing SIM State");
3504
	$string = $modes[$state];
3505
	return $string;
3506
}
3507

    
3508
function zte_rssi_to_string($rssi) {
3509
	return huawei_rssi_to_string($rssi);
3510
}
3511

    
3512
function zte_mode_to_string($mode, $submode) {
3513
	$modes[0] = gettext("No Service");
3514
	$modes[1] = gettext("Limited Service");
3515
	$modes[2] = "GPRS";
3516
	$modes[3] = "GSM";
3517
	$modes[4] = "UMTS";
3518
	$modes[5] = "EDGE";
3519
	$modes[6] = "HSDPA";
3520

    
3521
	$submodes[0] = "CS_ONLY";
3522
	$submodes[1] = "PS_ONLY";
3523
	$submodes[2] = "CS_PS";
3524
	$submodes[3] = "CAMPED";
3525
	$string = "{$modes[$mode]}, {$submodes[$submode]} " . gettext("Mode");
3526
	return $string;
3527
}
3528

    
3529
function zte_service_to_string($service) {
3530
	$modes[0] = gettext("Initializing Service");
3531
	$modes[1] = gettext("Network Lock error Service");
3532
	$modes[2] = gettext("Network Locked Service");
3533
	$modes[3] = gettext("Unlocked or correct MCC/MNC Service");
3534
	$string = $modes[$service];
3535
	return $string;
3536
}
3537

    
3538
function zte_simstate_to_string($state) {
3539
	$modes[0] = gettext("No action State");
3540
	$modes[1] = gettext("Network lock State");
3541
	$modes[2] = gettext("(U)SIM card lock State");
3542
	$modes[3] = gettext("Network Lock and (U)SIM card Lock State");
3543
	$string = $modes[$state];
3544
	return $string;
3545
}
3546

    
3547
function get_configured_pppoe_server_interfaces() {
3548
	$iflist = array();
3549
	foreach (config_get_path('pppoes/pppoe', []) as $pppoe) {
3550
		if ($pppoe['mode'] == "server") {
3551
			$int = "poes". $pppoe['pppoeid'];
3552
			$iflist[$int] = strtoupper($int);
3553
		}
3554
	}
3555
	return $iflist;
3556
}
3557

    
3558
function get_pppoes_child_interfaces($ifpattern) {
3559
	$if_arr = array();
3560
	if ($ifpattern == "") {
3561
		return;
3562
	}
3563

    
3564
	exec("/sbin/ifconfig", $out, $ret);
3565
	foreach ($out as $line) {
3566
		if (preg_match("/^({$ifpattern}-[0-9]+):/i", $line, $match)) {
3567
			$if_arr[] = $match[1];
3568
		}
3569
	}
3570
	return $if_arr;
3571

    
3572
}
3573

    
3574
/****f* pfsense-utils/pkg_call_plugins
3575
 * NAME
3576
 *   pkg_call_plugins
3577
 * INPUTS
3578
 *   $plugin_type value used to search in package configuration if the plugin is used, also used to create the function name
3579
 *   $plugin_params parameters to pass to the plugin function for passing multiple parameters a array can be used.
3580
 * RESULT
3581
 *   returns associative array results from the plugin calls for each package
3582
 * NOTES
3583
 *   This generic function can be used to notify or retrieve results from functions that are defined in packages.
3584
 ******/
3585
function pkg_call_plugins($plugin_type, $plugin_params) {
3586
	global $g;
3587
	$results = array();
3588
	foreach (config_get_path('installedpackages/package', []) as $package) {
3589
		foreach (array_get_path($package, 'plugins/item', []) as $plugin) {
3590
			if (!is_array($plugin) || empty($plugin)) {
3591
				continue;
3592
			}
3593
			if ($plugin['type'] == $plugin_type) {
3594
				if (file_exists($package['include_file'])) {
3595
					require_once($package['include_file']);
3596
				} else {
3597
					continue;
3598
				}
3599
				$pkgname = substr(reverse_strrchr($package['configurationfile'], "."), 0, -1);
3600
				$plugin_function = $pkgname . '_'. $plugin_type;
3601
				$results[$pkgname] = call_user_func($plugin_function, $plugin_params);
3602
			}
3603
		}
3604
	}
3605
	return $results;
3606
}
3607

    
3608
// Convert IPv6 addresses to lower case
3609
function addrtolower($ip) {
3610
	if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
3611
		return(strtolower($ip));
3612
	} else {
3613
		return($ip);
3614
	}
3615
}
3616

    
3617
function compare_by_name($a, $b) {
3618
	return strcasecmp($a['name'], $b['name']);
3619
}
3620

    
3621
/****f* pfsense-utils/getarraybyref
3622
 * NAME
3623
 *   getarraybyref
3624
 * INPUTS
3625
 *   $array the array of which a items array needs to be found.
3626
 *   $args.. the sub-items to be retrieved/created
3627
 * RESULT
3628
 *   returns the array that was retrieved from configuration, its created if it does not exist
3629
 * NOTES
3630
 *   Used by haproxy / acme / others.?. .
3631
 *   can be used like this:  $a_certificates = getarraybyref($config, 'installedpackages', 'acme', 'certificates', 'item');
3632
 ******/
3633
function &getarraybyref(&$array) {
3634
	if (!isset($array)) {
3635
		return false;
3636
	}
3637
	if (!is_array($array)) {
3638
		$array = array();
3639
	}
3640
	$item = &$array;
3641
	$arg = func_get_args();
3642
	for($i = 1; $i < count($arg); $i++) {
3643
		$itemindex = $arg[$i];
3644
		if (!is_array($item[$itemindex])) {
3645
			$item[$itemindex] = array();
3646
		}
3647
		$item = &$item[$itemindex];
3648
	}
3649
	return $item;
3650
}
3651

    
3652
/****f* pfsense-utils/send_download_data
3653
 * NAME
3654
 *   send_download_data - Send content to a user's browser as a file to download
3655
 * INPUTS
3656
 *   $type        : The type of download, either 'data' to send the contents of
3657
 *                    a variable or 'file' to send the contents of a file on the
3658
 *                    filesystem.
3659
 *   $content     : For 'data' type, the content to send the user. For 'file'
3660
 *                    type, the full path to the file to send.
3661
 *   $userfilename: The filename presented to the user when downloading. For
3662
 *                    'file' type, this may be omitted and the basename of
3663
 *                    $content will be used instead.
3664
 *   $contenttype : MIME content type of the data. Default "application/octet-stream"
3665
 *   $download    : Boolean. Send the file as a download (true, default) or
3666
 *                    inline (false).
3667
 * RESULT
3668
 *   Sends the data to the browser as a file.
3669
 ******/
3670

    
3671
function send_user_download($type, $content, $userfilename = "", $contenttype = "application/octet-stream", $download = true) {
3672
	/* If the type is 'file', then use the file size, otherwise use the size of the data to send */
3673
	$size = ($type == 'file') ? filesize($content) : strlen($content);
3674

    
3675
	/* Send the file as a download (attachment) or inline content */
3676
	$disposition = ($download) ? "attachment" : "inline";
3677

    
3678
	/* If the filename to pass to the user is empty, assume it to be the filename being read. */
3679
	$name = basename((($type == 'file') && empty($userfilename)) ? $content : $userfilename);
3680

    
3681
	/* Cannot determine the filename, so bail. */
3682
	if (empty($name)) {
3683
		exit;
3684
	}
3685

    
3686
	/* Send basic download headers */
3687
	header("Content-Type: {$contenttype}");
3688
	header("Content-Length: {$size}");
3689
	header("Content-Disposition: {$disposition}; filename=" . urlencode($name));
3690

    
3691
	/* Send cache headers */
3692
	if (isset($_SERVER['HTTPS'])) {
3693
		header('Cache-Control: ');
3694
	} else {
3695
		header("Cache-Control: private, must-revalidate");
3696
	}
3697

    
3698
	/* Ensure output buffering is off so PHP does not consume
3699
	 * memory in readfile(). https://redmine.pfsense.org/issues/9239 */
3700
	while (ob_get_level()) {
3701
		@ob_end_clean();
3702
	}
3703

    
3704
	/* Send the data to the user */
3705
	if ($type == 'file') {
3706
		readfile($content);
3707
	} else {
3708
		echo $content;
3709
	}
3710

    
3711
	/* Flush any remaining output buffer */
3712
	@ob_end_flush();
3713
	exit;
3714
}
3715

    
3716
function get_pf_reserved(?string $name = '', bool $check_ifname = true):bool|array {
3717
	global $pf_reserved_keywords, $reserved_table_names, $FilterIflist;
3718

    
3719
	// get relevant interface names
3720
	$iflist = $FilterIflist;
3721
	if (empty($iflist)) {
3722
		// if list
3723
		$iflist = config_get_path('interfaces', []);
3724
		// l2tp
3725
		if (config_get_path('l2tp/mode') == "server") {
3726
			$iflist['l2tp'] = 'L2TP';
3727
		}
3728
		// pppoe
3729
		foreach (config_get_path('pppoes/pppoe', []) as $ifgen) {
3730
			if (array_key_exists('mode', $ifgen) && $ifgen['mode'] == 'server') {
3731
				$iflist['pppoe'] = 'pppoe';
3732
				break;
3733
			}
3734
		}
3735
		// add interface groups
3736
		foreach (config_get_path('ifgroups/ifgroupentry', []) as $ifgen) {
3737
			$iflist[$ifgen['ifname']] = $ifgen['ifname'];
3738
		}
3739
	}
3740
	/**
3741
	 * pf table names are limited to a max of 31 characters.
3742
	 * Interface group names are limited to a max of 15 characters.
3743
	 * Hence, a combination of the interface name (optX) + the suffix should be safe.
3744
	 */ 
3745
	$reserved = [];
3746
	foreach (array_keys($iflist) as $if) {
3747
		if ($check_ifname) {
3748
			$reserved[] = strtoupper($if);
3749
		}
3750
		$reserved[] = strtoupper("{$if}__NETWORK");
3751
	}
3752
	$reserved = array_merge($pf_reserved_keywords, array_keys($reserved_table_names), $reserved);
3753

    
3754
	return (empty($name) ? $reserved : in_array(strtoupper($name), $reserved));
3755
}
3756

    
3757
function get_pf_timeouts () {
3758
	$pftimeout = array();
3759
	exec("/sbin/pfctl -st", $pfctlst, $retval);
3760
	if ($retval == 0) {
3761
		foreach ($pfctlst as $pfst) {
3762
			preg_match('/([a-z]+)\.([a-z]+)\s+([0-9]+)/', $pfst, $timeout);
3763
			if ($timeout[1] == "other") {
3764
				$proto = "Other";
3765
			} else {
3766
				$proto = strtoupper($timeout[1]);
3767
			}
3768
			if ($timeout[2] == "finwait") {
3769
				$type = "FIN Wait";
3770
			} else {
3771
				$type = ucfirst($timeout[2]);
3772
			}
3773
			$pftimeout[$proto][$type]['name'] = $proto . " " . $type;
3774
			$pftimeout[$proto][$type]['keyname'] = $timeout[1] . $timeout[2] . "timeout";
3775
			$pftimeout[$proto][$type]['value'] = $timeout[3];
3776
		}
3777
	}
3778
	return $pftimeout;
3779
}
3780

    
3781
function set_curlproxy(&$ch) {
3782
	if (!empty(config_get_path('system/proxyurl'))) {
3783
		curl_setopt($ch, CURLOPT_PROXY, config_get_path('system/proxyurl'));
3784
		if (!empty(config_get_path('system/proxyport'))) {
3785
			curl_setopt($ch, CURLOPT_PROXYPORT, config_get_path('system/proxyport'));
3786
		}
3787
		$proxyuser = config_get_path('system/proxyuser');
3788
		$proxypass = config_get_path('system/proxypass');
3789
		if (!empty($proxyuser) && !empty($proxypass)) {
3790
			$proxyuser = rawurlencode($proxyuser);
3791
			$proxypass = rawurlencode($proxypass);
3792
			@curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_ANY | CURLAUTH_ANYSAFE);
3793
			curl_setopt($ch, CURLOPT_PROXYUSERPWD, "{$proxyuser}:{$proxypass}");
3794
		}
3795
	}
3796
}
3797

    
3798
/*
3799
 * return the time difference in seconds.microseconds(6 digits) format between 2 DateTime objects
3800
 * @param DateTime $date1
3801
 * @param DateTime $date2
3802
 */
3803

    
3804
function date_mdiff($date1, $date2){
3805
	return number_format(abs((float)$date1->format("U.u") - (float)$date2->format("U.u")), 6);
3806
}
3807

    
3808
/**
3809
 * Parse user-configured aliases and rename any that conflict with reserved names.
3810
 * @return void
3811
 */
3812
function rename_conflicting_aliases() {
3813
	$aliases = config_get_path('aliases/alias', []);
3814
	$renamed_aliases = [];
3815

    
3816
	// Normalize to lower case, filter out duplicates, and use array keys for searching.
3817
	$reserved_alias_names = array_merge(array_keys(get_reserved_table_names()), get_pf_reserved());
3818
	$reserved_alias_names = array_map('strtolower', $reserved_alias_names);
3819
	$reserved_alias_names = array_flip($reserved_alias_names);
3820

    
3821
	foreach ($aliases as & $alias) {
3822
		// Skip if there's no conflict.
3823
		if (empty($alias['name']) || !is_string($alias['name']) ||
3824
		    !array_key_exists(strtolower($alias['name']), $reserved_alias_names)) {
3825
			continue;
3826
		}
3827

    
3828
		// There's a name conflict, try to rename it.
3829
		$try_count = 0;
3830
		$try_successful = false;
3831
		$current_alias_names = array_flip(array_column($aliases, 'name'));
3832
		do {
3833
			$try_count++;
3834

    
3835
			$new_name = mb_str_pad($alias['name'], 31, bin2hex(random_bytes(15)));
3836
			if (array_key_exists($new_name, $current_alias_names)) {
3837
				continue;
3838
			}
3839

    
3840
			$renamed_aliases[] = [
3841
				'old' => $alias['name'],
3842
				'new' => $new_name
3843
			];
3844
			$alias['name'] = $new_name;
3845

    
3846
			$try_successful = true;
3847
			break;
3848
		} while ($try_count < 10);
3849

    
3850
		// The rename attempts failed - log and warn the user.
3851
		if (!$try_successful) {
3852
			file_notice('Alias', gettext('The following alias conflicts with a reserved keyword and must' .
3853
				' be manually renamed: ' . $alias['name'])
3854
			);
3855
		}
3856
	}
3857
	if (isset($alias)) {
3858
		unset($alias);
3859
	}
3860
	
3861
	// Abort if there's nothing to do.
3862
	if (empty($renamed_aliases)) {
3863
		return;
3864
	}
3865

    
3866
	// Commit alias changes and update references to their old name.
3867
	config_set_path('aliases/alias', $aliases);
3868
	foreach ($renamed_aliases as $alias) {
3869
		update_alias_name($alias['new'], $alias['old']);
3870
	}
3871

    
3872
	// Log and warn the user of old alias names.
3873
	file_notice('Alias', gettext('The following aliases conflict with a reserved keyword and have been renamed: ') .
3874
		implode(', ', array_column($renamed_aliases, 'old'))
3875
	);
3876
}
3877

    
3878
function remove_pppoe_cron_items(string $interface = '') {
3879
	$cron_config = config_get_path('cron/item', []);
3880
	$cron_pppoe_file_paths = [];
3881
	foreach (config_get_path('ppps/ppp') as $ppp_config) {
3882
		if ($ppp_config['type'] != 'pppoe') {
3883
			continue;
3884
		}
3885
		if (empty($interface) || ($ppp_config['if'] == $interface)) {
3886
			$cron_pppoe_file_paths[] = g_get('varetc_path') . "/pppoe_restart_{$ppp_config['if']}";
3887
		}
3888
	}
3889
	if (!empty($cron_pppoe_file_paths)) {
3890
		foreach ($cron_pppoe_file_paths as $file_path) {
3891
			unlink_if_exists($file_path);
3892
		}
3893

    
3894
		$config_changed = false;
3895
		foreach ($cron_config as $idx => $cron_item) {
3896
			if (in_array($cron_item['command'], $cron_pppoe_file_paths)) {
3897
				unset($cron_config[$idx]);
3898
				$config_changed = true;
3899
			}
3900
		}
3901
		if ($config_changed) {
3902
			config_set_path('cron/item', $cron_config);
3903
		}
3904
	}
3905
}
(39-39/61)