diff --git a/.gitignore b/.gitignore index 9c66d65e7..b63ebf708 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +*/__pycache__/ *.DS_Store .phplint-cache diff --git a/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc b/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc index 38cb96c0a..441a7b461 100644 --- a/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc +++ b/pfSense-pkg-API/files/etc/inc/api/framework/APIResponse.inc @@ -272,13 +272,13 @@ function get($id, $data=[], $all=false) { "status" => "bad request", "code" => 400, "return" => $id, - "message" => "Unbound host override alias already exists" + "message" => "Unbound host override alias already exists with this IP address type" ], 2010 => [ "status" => "bad request", "code" => 400, "return" => $id, - "message" => "Unbound host override already exists" + "message" => "Unbound host override already exists with this IP address type" ], 2011 => [ "status" => "bad request", @@ -490,6 +490,18 @@ function get($id, $data=[], $all=false) { "return" => $id, "message" => "DHCPd static mapping ID does not exist" ], + 2046 => [ + "status" => "bad request", + "code" => 400, + "return" => $id, + "message" => "Invalid unbound host value" + ], + 2047 => [ + "status" => "bad request", + "code" => 400, + "return" => $id, + "message" => "Invalid unbound domain value" + ], // 3000-3999 reserved for /interfaces API calls 3000 => [ diff --git a/pfSense-pkg-API/files/etc/inc/api/framework/APITools.inc b/pfSense-pkg-API/files/etc/inc/api/framework/APITools.inc index 8e681c8a4..1eea4a4e9 100644 --- a/pfSense-pkg-API/files/etc/inc/api/framework/APITools.inc +++ b/pfSense-pkg-API/files/etc/inc/api/framework/APITools.inc @@ -119,7 +119,7 @@ function create_jwt_server_key($rotate=false) { # Create a new server key if one is not set if (empty($api_config["server_key"]) or $rotate === true) { $config["installedpackages"]["package"][$pkg_index]["conf"]["server_key"] = bin2hex(random_bytes(32)); - write_config(); + write_config("API server key created"); } } @@ -345,10 +345,11 @@ function get_pfsense_if_id($interface) { } } -// Check if input is valid for rule source and destination +# Check if input is valid for rule source and destination +# TODO: this function is messy, clean it up function is_valid_rule_addr($addr, $direction) { // Variables - $addr_types = array("any", "pppoe", "l2tp"); // Array of special src/dst types + $addr_types = array("any", "pppoe", "l2tp", "(self)"); // Array of special src/dst types $ret_val = array("valid" => true, "data" => array()); // Check if our source values are valid if (is_string($addr)) { @@ -357,14 +358,17 @@ function is_valid_rule_addr($addr, $direction) { $addr_not = true; $addr = str_replace("!", "", $addr); } - // Check if our source data is valid - $addr_if = str_replace("ip", "", $addr); // Save seperate variable to check for interface sourcees + + // Check if our data is valid + $addr_if = str_replace("ip", "", $addr); + if (is_ipaddr($addr) or is_subnet($addr)) { $ret_val["data"] = array($direction => array("address" => $addr)); } elseif (is_alias($addr)) { $ret_val["data"] = array($direction => array("address" => $addr)); } elseif (get_pfsense_if_id($addr_if)) { $addr_pfif = get_pfsense_if_id($addr_if); // Save our interface pfid + // If source was interface address (ending in ip), otherwise assume entire subnet if (str_replace($addr_if, "", $addr) === "ip") { $ret_val["data"] = array($direction => array("network" => $addr_pfif . "ip")); @@ -372,9 +376,16 @@ function is_valid_rule_addr($addr, $direction) { $ret_val["data"] = array($direction => array("network" => $addr_pfif)); } } elseif (in_array($addr, $addr_types)) { + # Format config for any address if ($addr === "any") { $ret_val["data"] = array($direction => array("any" => "")); - } else { + } + # Do not allow (self) address if direction is source + elseif ($addr === "(self)" and $direction === "source") { + $ret_val["valid"] = false; + } + # Otherwise, Format config as network + else { $ret_val["data"] = array($direction => array("network" => $addr)); } } else { @@ -690,36 +701,6 @@ function unbound_reload_config() { } } -// Check if a DNS Resolver (Unbound) host override already exists -function is_unbound_fqdn($hostname, $domain, $instance_id=null) { - # Local variables - global $config; - $curr_hosts = (array_key_exists("hosts", $config["unbound"])) ? $config["unbound"]["hosts"] : []; - $host_exists = false; - $index = 0; - - # Loop through each host override and check if the FQDN already exists - foreach ($curr_hosts as $host_ent) { - # Check the FQDN matches this entry - if ($host_ent["host"] === $hostname and $host_ent["domain"] === $domain) { - # If we are working with an existing instance, allow existing FQDN if ID matches - if ($index !== $instance_id) { - return true; - } - } - - # Check FQDN within host override aliases as well - if (is_array($host_ent["aliases"])) { - foreach ($host_ent["aliases"]["item"] as $alias_ent) { - if ($alias_ent["host"] === $hostname and $alias_ent["domain"] === $domain) { - return true; - } - } - } - $index++; - } - return $host_exists; -} // Get a complete config list of ALL interfaces. Based off interfaces_assign.php function get_all_avail_interfaces() { diff --git a/pfSense-pkg-API/files/etc/inc/api/framework/overrides/system.inc b/pfSense-pkg-API/files/etc/inc/api/framework/overrides/2.4.4-RELEASE/system.inc similarity index 100% rename from pfSense-pkg-API/files/etc/inc/api/framework/overrides/system.inc rename to pfSense-pkg-API/files/etc/inc/api/framework/overrides/2.4.4-RELEASE/system.inc diff --git a/pfSense-pkg-API/files/etc/inc/api/framework/overrides/2.4.5-RELEASE/system.inc b/pfSense-pkg-API/files/etc/inc/api/framework/overrides/2.4.5-RELEASE/system.inc new file mode 100644 index 000000000..4f1fc8c99 --- /dev/null +++ b/pfSense-pkg-API/files/etc/inc/api/framework/overrides/2.4.5-RELEASE/system.inc @@ -0,0 +1,2563 @@ +. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function activate_powerd() { + global $config, $g; + + if (is_process_running("powerd")) { + exec("/usr/bin/killall powerd"); + } + if (isset($config['system']['powerd_enable'])) { + $ac_mode = "hadp"; + if (!empty($config['system']['powerd_ac_mode'])) { + $ac_mode = $config['system']['powerd_ac_mode']; + } + + $battery_mode = "hadp"; + if (!empty($config['system']['powerd_battery_mode'])) { + $battery_mode = $config['system']['powerd_battery_mode']; + } + + $normal_mode = "hadp"; + if (!empty($config['system']['powerd_normal_mode'])) { + $normal_mode = $config['system']['powerd_normal_mode']; + } + + mwexec("/usr/sbin/powerd" . + " -b " . escapeshellarg($battery_mode) . + " -a " . escapeshellarg($ac_mode) . + " -n " . escapeshellarg($normal_mode)); + } +} + +function get_default_sysctl_value($id) { + global $sysctls; + + if (isset($sysctls[$id])) { + return $sysctls[$id]; + } +} + +function get_sysctl_descr($sysctl) { + unset($output); + $_gb = exec("/sbin/sysctl -qnd {$sysctl}", $output); + + return $output[0]; +} + +function system_get_sysctls() { + global $config, $sysctls; + + $disp_sysctl = array(); + $disp_cache = array(); + if (is_array($config['sysctl']) && is_array($config['sysctl']['item'])) { + foreach ($config['sysctl']['item'] as $id => $tunable) { + if ($tunable['value'] == "default") { + $value = get_default_sysctl_value($tunable['tunable']); + } else { + $value = $tunable['value']; + } + + $disp_sysctl[$id] = $tunable; + $disp_sysctl[$id]['modified'] = true; + $disp_cache[$tunable['tunable']] = 'set'; + } + } + + foreach ($sysctls as $sysctl => $value) { + if (isset($disp_cache[$sysctl])) { + continue; + } + + $disp_sysctl[$sysctl] = array('tunable' => $sysctl, 'value' => $value, 'descr' => get_sysctl_descr($sysctl)); + } + unset($disp_cache); + return $disp_sysctl; +} + +function activate_sysctls() { + global $config, $g, $sysctls; + + if (is_array($config['sysctl']) && is_array($config['sysctl']['item'])) { + foreach ($config['sysctl']['item'] as $tunable) { + if ($tunable['value'] == "default") { + $value = get_default_sysctl_value($tunable['tunable']); + } else { + $value = $tunable['value']; + } + + $sysctls[$tunable['tunable']] = $value; + } + } + + set_sysctl($sysctls); +} + +function system_resolvconf_generate($dynupdate = false) { + global $config, $g; + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_resolvconf_generate() being called $mt\n"; + } + + $syscfg = $config['system']; + + foreach(get_dns_nameservers() as $dns_ns) { + $resolvconf .= "nameserver $dns_ns\n"; + } + + $ns = array(); + if (isset($syscfg['dnsallowoverride'])) { + /* get dynamically assigned DNS servers (if any) */ + $ns = array_unique(get_searchdomains()); + foreach ($ns as $searchserver) { + if ($searchserver) { + $resolvconf .= "search {$searchserver}\n"; + } + } + } + if (empty($ns)) { + // Do not create blank search/domain lines, it can break tools like dig. + if ($syscfg['domain']) { + $resolvconf .= "search {$syscfg['domain']}\n"; + } + } + + // Add EDNS support + if (isset($config['unbound']['enable']) && isset($config['unbound']['edns'])) { + $resolvconf .= "options edns0\n"; + } + + $dnslock = lock('resolvconf', LOCK_EX); + + $fd = fopen("{$g['etc_path']}/resolv.conf", "w"); + if (!$fd) { + printf("Error: cannot open resolv.conf in system_resolvconf_generate().\n"); + unlock($dnslock); + return 1; + } + + fwrite($fd, $resolvconf); + fclose($fd); + + // Prevent resolvconf(8) from rewriting our resolv.conf + $fd = fopen("{$g['etc_path']}/resolvconf.conf", "w"); + if (!$fd) { + printf("Error: cannot open resolvconf.conf in system_resolvconf_generate().\n"); + return 1; + } + fwrite($fd, "resolv_conf=\"/dev/null\"\n"); + fclose($fd); + + if (!platform_booting()) { + /* restart dhcpd (nameservers may have changed) */ + if (!$dynupdate) { + services_dhcpd_configure(); + } + } + + /* setup static routes for DNS servers. */ + $dnscounter = 1; + $dnsgw = "dns{$dnscounter}gw"; + while (isset($config['system'][$dnsgw])) { + /* setup static routes for dns servers */ + if (!(empty($config['system'][$dnsgw]) || + $config['system'][$dnsgw] == "none")) { + $gwname = $config['system'][$dnsgw]; + $gatewayip = lookup_gateway_ip_by_name($gwname); + $inet6 = is_ipaddrv6($gatewayip) ? '-inet6 ' : ''; + /* dns server array starts at 0 */ + $dnsserver = $syscfg['dnsserver'][$dnscounter - 1]; + + if (is_ipaddr($gatewayip)) { + route_add_or_change("-host {$inet6}{$dnsserver} {$gatewayip}"); + } else { + /* Remove old route when disable gw */ + mwexec("/sbin/route delete -host {$inet6}{$dnsserver}"); + if (isset($config['system']['route-debug'])) { + $mt = microtime(); + log_error("ROUTING debug: $mt - route delete -host {$inet6}{$dnsserver}"); + } + } + } + $dnscounter++; + $dnsgw = "dns{$dnscounter}gw"; + } + + unlock($dnslock); + + return 0; +} + +function get_searchdomains() { + global $config, $g; + + $master_list = array(); + + // Read in dhclient nameservers + $search_list = glob("/var/etc/searchdomain_*"); + if (is_array($search_list)) { + foreach ($search_list as $fdns) { + $contents = file($fdns, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($contents)) { + continue; + } + foreach ($contents as $dns) { + if (is_hostname($dns)) { + $master_list[] = $dns; + } + } + } + } + + return $master_list; +} + +function get_nameservers() { + global $config, $g; + $master_list = array(); + + // Read in dhclient nameservers + $dns_lists = glob("/var/etc/nameserver_*"); + if (is_array($dns_lists)) { + foreach ($dns_lists as $fdns) { + $contents = file($fdns, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($contents)) { + continue; + } + foreach ($contents as $dns) { + if (is_ipaddr($dns)) { + $master_list[] = $dns; + } + } + } + } + + // Read in any extra nameservers + if (file_exists("/var/etc/nameservers.conf")) { + $dns_s = file("/var/etc/nameservers.conf", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (is_array($dns_s)) { + foreach ($dns_s as $dns) { + if (is_ipaddr($dns)) { + $master_list[] = $dns; + } + } + } + } + + return $master_list; +} + +/* Create localhost + local interfaces entries for /etc/hosts */ +function system_hosts_local_entries() { + global $config; + + $syscfg = $config['system']; + + $hosts = array(); + $hosts[] = array( + 'ipaddr' => '127.0.0.1', + 'fqdn' => 'localhost.' . $syscfg['domain'], + 'name' => 'localhost', + 'domain' => $syscfg['domain'] + ); + $hosts[] = array( + 'ipaddr' => '::1', + 'fqdn' => 'localhost.' . $syscfg['domain'], + 'name' => 'localhost', + 'domain' => $syscfg['domain'] + ); + + if ($config['interfaces']['lan']) { + $sysiflist = array('lan' => "lan"); + } else { + $sysiflist = get_configured_interface_list(); + } + + $hosts_if_found = false; + $local_fqdn = "{$syscfg['hostname']}.{$syscfg['domain']}"; + foreach ($sysiflist as $sysif) { + if ($sysif != 'lan' && interface_has_gateway($sysif)) { + continue; + } + $cfgip = get_interface_ip($sysif); + if (is_ipaddrv4($cfgip)) { + $hosts[] = array( + 'ipaddr' => $cfgip, + 'fqdn' => $local_fqdn, + 'name' => $syscfg['hostname'], + 'domain' => $syscfg['domain'] + ); + $hosts_if_found = true; + } + if (!isset($syscfg['ipv6dontcreatelocaldns'])) { + $cfgipv6 = get_interface_ipv6($sysif); + if (is_ipaddrv6($cfgipv6)) { + $hosts[] = array( + 'ipaddr' => $cfgipv6, + 'fqdn' => $local_fqdn, + 'name' => $syscfg['hostname'], + 'domain' => $syscfg['domain'] + ); + $hosts_if_found = true; + } + } + if ($hosts_if_found == true) { + break; + } + } + + return $hosts; +} + +/* Read host override entries from dnsmasq or unbound */ +function system_hosts_override_entries($dnscfg) { + $hosts = array(); + + if (!is_array($dnscfg) || + !is_array($dnscfg['hosts']) || + !isset($dnscfg['enable'])) { + return $hosts; + } + + foreach ($dnscfg['hosts'] as $host) { + $fqdn = ''; + if ($host['host'] || $host['host'] == "0") { + $fqdn .= "{$host['host']}."; + } + $fqdn .= $host['domain']; + + $hosts[] = array( + 'ipaddr' => $host['ip'], + 'fqdn' => $fqdn, + 'name' => $host['host'], + 'domain' => $host['domain'] + ); + + if (!is_array($host['aliases']) || + !is_array($host['aliases']['item'])) { + continue; + } + + foreach ($host['aliases']['item'] as $alias) { + $fqdn = ''; + if ($alias['host'] || $alias['host'] == "0") { + $fqdn .= "{$alias['host']}."; + } + $fqdn .= $alias['domain']; + + $hosts[] = array( + 'ipaddr' => $host['ip'], + 'fqdn' => $fqdn, + 'name' => $alias['host'], + 'domain' => $alias['domain'] + ); + } + } + + return $hosts; +} + +/* Read all dhcpd/dhcpdv6 staticmap entries */ +function system_hosts_dhcpd_entries() { + global $config; + + $hosts = array(); + $syscfg = $config['system']; + + if (is_array($config['dhcpd'])) { + $conf_dhcpd = $config['dhcpd']; + } else { + $conf_dhcpd = array(); + } + + foreach ($conf_dhcpd as $dhcpif => $dhcpifconf) { + if (!is_array($dhcpifconf['staticmap']) || + !isset($dhcpifconf['enable'])) { + continue; + } + foreach ($dhcpifconf['staticmap'] as $host) { + if (!$host['ipaddr'] || + !$host['hostname']) { + continue; + } + + $fqdn = $host['hostname'] . "."; + $domain = ""; + if ($host['domain']) { + $domain = $host['domain']; + } elseif ($dhcpifconf['domain']) { + $domain = $dhcpifconf['domain']; + } else { + $domain = $syscfg['domain']; + } + + $hosts[] = array( + 'ipaddr' => $host['ipaddr'], + 'fqdn' => $fqdn . $domain, + 'name' => $host['hostname'], + 'domain' => $domain + ); + } + } + unset($conf_dhcpd); + + if (is_array($config['dhcpdv6'])) { + $conf_dhcpdv6 = $config['dhcpdv6']; + } else { + $conf_dhcpdv6 = array(); + } + + foreach ($conf_dhcpdv6 as $dhcpif => $dhcpifconf) { + if (!is_array($dhcpifconf['staticmap']) || + !isset($dhcpifconf['enable'])) { + continue; + } + + if (isset($config['interfaces'][$dhcpif]['ipaddrv6']) && + $config['interfaces'][$dhcpif]['ipaddrv6'] == + 'track6') { + $isdelegated = true; + } else { + $isdelegated = false; + } + + foreach ($dhcpifconf['staticmap'] as $host) { + $ipaddrv6 = $host['ipaddrv6']; + + if (!$ipaddrv6 || !$host['hostname']) { + continue; + } + + if ($isdelegated) { + /* + * We are always in an "end-user" subnet + * here, which all are /64 for IPv6. + */ + $ipaddrv6 = merge_ipv6_delegated_prefix( + get_interface_ipv6($dhcpif), + $ipaddrv6, 64); + } + + $fqdn = $host['hostname'] . "."; + $domain = ""; + if ($host['domain']) { + $domain = $host['domain']; + } elseif ($dhcpifconf['domain']) { + $domain = $dhcpifconf['domain']; + } else { + $domain = $syscfg['domain']; + } + + $hosts[] = array( + 'ipaddr' => $ipaddrv6, + 'fqdn' => $fqdn . $domain, + 'name' => $host['hostname'], + 'domain' => $domain + ); + } + } + unset($conf_dhcpdv6); + + return $hosts; +} + +/* Concatenate local, dnsmasq/unbound and dhcpd/dhcpdv6 hosts entries */ +function system_hosts_entries($dnscfg) { + $local = array(); + if (!isset($dnscfg['disable_auto_added_host_entries'])) { + $local = system_hosts_local_entries(); + } + + $dns = array(); + $dhcpd = array(); + if (isset($dnscfg['enable'])) { + $dns = system_hosts_override_entries($dnscfg); + if (isset($dnscfg['regdhcpstatic'])) { + $dhcpd = system_hosts_dhcpd_entries(); + } + } + + if (isset($dnscfg['dhcpfirst'])) { + return array_merge($local, $dns, $dhcpd); + } else { + return array_merge($local, $dhcpd, $dns); + } +} + +function system_hosts_generate() { + global $config, $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_hosts_generate() being called $mt\n"; + } + + // prefer dnsmasq for hosts generation where it's enabled. It relies + // on hosts for name resolution of its overrides, unbound does not. + if (isset($config['dnsmasq']) && isset($config['dnsmasq']['enable'])) { + $dnsmasqcfg = $config['dnsmasq']; + } else { + $dnsmasqcfg = $config['unbound']; + } + + $syscfg = $config['system']; + $hosts = ""; + $lhosts = ""; + $dhosts = ""; + + $hosts_array = system_hosts_entries($dnsmasqcfg); + foreach ($hosts_array as $host) { + $hosts .= "{$host['ipaddr']}\t"; + if ($host['name'] == "localhost") { + $hosts .= "{$host['name']} {$host['fqdn']}"; + } else { + $hosts .= "{$host['fqdn']} {$host['name']}"; + } + $hosts .= "\n"; + } + unset($hosts_array); + + $fd = fopen("{$g['etc_path']}/hosts", "w"); + if (!$fd) { + log_error(gettext( + "Error: cannot open hosts file in system_hosts_generate()." + )); + return 1; + } + + /* + * Do not remove this because dhcpleases monitors with kqueue it needs + * to be killed before writing to hosts files. + */ + if (file_exists("{$g['varrun_path']}/dhcpleases.pid")) { + sigkillbypid("{$g['varrun_path']}/dhcpleases.pid", "TERM"); + @unlink("{$g['varrun_path']}/dhcpleases.pid"); + } + + fwrite($fd, $hosts); + fclose($fd); + + if (isset($config['unbound']['enable'])) { + require_once("unbound.inc"); + unbound_hosts_generate(); + } + + /* restart dhcpleases */ + if (!platform_booting()) { + system_dhcpleases_configure(); + } + + return 0; +} + +function system_dhcpleases_configure() { + global $config, $g; + if (!function_exists('is_dhcp_server_enabled')) { + require_once('pfsense-utils.inc'); + } + $pidfile = "{$g['varrun_path']}/dhcpleases.pid"; + + /* Start the monitoring process for dynamic dhcpclients. */ + if (((isset($config['dnsmasq']['enable']) && isset($config['dnsmasq']['regdhcp'])) || + (isset($config['unbound']['enable']) && isset($config['unbound']['regdhcp']))) && + (is_dhcp_server_enabled())) { + /* Make sure we do not error out */ + mwexec("/bin/mkdir -p {$g['dhcpd_chroot_path']}/var/db"); + if (!file_exists("{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases")) { + @touch("{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases"); + } + + if (isset($config['unbound']['enable'])) { + $dns_pid = "unbound.pid"; + $unbound_conf = "-u {$g['unbound_chroot_path']}/dhcpleases_entries.conf"; + } else { + $dns_pid = "dnsmasq.pid"; + $unbound_conf = ""; + } + + if (isvalidpid($pidfile)) { + /* Make sure dhcpleases is using correct unbound or dnsmasq */ + $_gb = exec("/bin/pgrep -F {$pidfile} -f {$dns_pid}", $output, $retval); + if (intval($retval) == 0) { + sigkillbypid($pidfile, "HUP"); + return; + } else { + sigkillbypid($pidfile, "TERM"); + } + } + + /* To ensure we do not start multiple instances of dhcpleases, perform some clean-up first. */ + if (is_process_running("dhcpleases")) { + sigkillbyname('dhcpleases', "TERM"); + } + @unlink($pidfile); + mwexec("/usr/local/sbin/dhcpleases -l {$g['dhcpd_chroot_path']}/var/db/dhcpd.leases -d {$config['system']['domain']} -p {$g['varrun_path']}/{$dns_pid} {$unbound_conf} -h {$g['etc_path']}/hosts"); + } elseif (isvalidpid($pidfile)) { + sigkillbypid($pidfile, "TERM"); + @unlink($pidfile); + } +} + +function system_hostname_configure() { + global $config, $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_hostname_configure() being called $mt\n"; + } + + $syscfg = $config['system']; + + /* set hostname */ + $status = mwexec("/bin/hostname " . + escapeshellarg("{$syscfg['hostname']}.{$syscfg['domain']}")); + + /* Setup host GUID ID. This is used by ZFS. */ + mwexec("/etc/rc.d/hostid start"); + + return $status; +} + +function system_routing_configure($interface = "") { + global $config, $g; + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_routing_configure() being called $mt\n"; + } + + $gateways_arr = return_gateways_array(false, true); + foreach ($gateways_arr as $gateway) { + // setup static interface routes for nonlocal gateways + if (isset($gateway["nonlocalgateway"])) { + $srgatewayip = $gateway['gateway']; + $srinterfacegw = $gateway['interface']; + if (is_ipaddr($srgatewayip) && !empty($srinterfacegw)) { + $inet = (!is_ipaddrv4($srgatewayip) ? "-inet6" : "-inet"); + route_add_or_change("{$inet} {$srgatewayip} " . + "-iface {$srinterfacegw}"); + } + } + } + + $gateways_status = return_gateways_status(true); + fixup_default_gateway("inet", $gateways_status, $gateways_arr); + fixup_default_gateway("inet6", $gateways_status, $gateways_arr); + + system_staticroutes_configure($interface, false); + + return 0; +} + +function system_staticroutes_configure($interface = "", $update_dns = false) { + global $config, $g, $aliastable; + + $filterdns_list = array(); + + $static_routes = get_staticroutes(false, true); + if (count($static_routes)) { + $gateways_arr = return_gateways_array(false, true); + + foreach ($static_routes as $rtent) { + if (empty($gateways_arr[$rtent['gateway']])) { + log_error(sprintf(gettext("Static Routes: Gateway IP could not be found for %s"), $rtent['network'])); + continue; + } + $gateway = $gateways_arr[$rtent['gateway']]; + if (!empty($interface) && $interface != $gateway['friendlyiface']) { + continue; + } + + $gatewayip = $gateway['gateway']; + $interfacegw = $gateway['interface']; + + $blackhole = ""; + if (!strcasecmp("Null", substr($rtent['gateway'], 0, 4))) { + $blackhole = "-blackhole"; + } + + if (!is_fqdn($rtent['network']) && !is_subnet($rtent['network'])) { + continue; + } + + $dnscache = array(); + if ($update_dns === true) { + if (is_subnet($rtent['network'])) { + continue; + } + $dnscache = explode("\n", trim(compare_hostname_to_dnscache($rtent['network']))); + if (empty($dnscache)) { + continue; + } + } + + if (is_subnet($rtent['network'])) { + $ips = array($rtent['network']); + } else { + if (!isset($rtent['disabled'])) { + $filterdns_list[] = $rtent['network']; + } + $ips = add_hostname_to_watch($rtent['network']); + } + + foreach ($dnscache as $ip) { + if (in_array($ip, $ips)) { + continue; + } + mwexec("/sbin/route delete " . escapeshellarg($ip), true); + if (isset($config['system']['route-debug'])) { + $mt = microtime(); + log_error("ROUTING debug: $mt - route delete $ip "); + } + } + + if (isset($rtent['disabled'])) { + /* XXX: This can break things by deleting routes that shouldn't be deleted - OpenVPN, dynamic routing scenarios, etc. redmine #3709 */ + foreach ($ips as $ip) { + mwexec("/sbin/route delete " . escapeshellarg($ip), true); + if (isset($config['system']['route-debug'])) { + $mt = microtime(); + log_error("ROUTING debug: $mt - route delete $ip "); + } + } + continue; + } + + foreach ($ips as $ip) { + if (is_ipaddrv4($ip)) { + $ip .= "/32"; + } + // do NOT do the same check here on v6, is_ipaddrv6 returns true when including the CIDR mask. doing so breaks v6 routes + + $inet = (is_subnetv6($ip) ? "-inet6" : "-inet"); + + $cmd = "{$inet} {$blackhole} {$ip} "; + + if (is_subnet($ip)) { + if (is_ipaddr($gatewayip)) { + if (is_linklocal($gatewayip) == "6" && !strpos($gatewayip, '%')) { + // add interface scope for link local v6 routes + $gatewayip .= "%$interfacegw"; + } + route_add_or_change($cmd . $gatewayip); + } else if (!empty($interfacegw)) { + route_add_or_change($cmd . "-iface {$interfacegw}"); + } + } + } + } + unset($gateways_arr); + } + unset($static_routes); + + if ($update_dns === false) { + if (count($filterdns_list)) { + $interval = 60; + $hostnames = ""; + array_unique($filterdns_list); + foreach ($filterdns_list as $hostname) { + $hostnames .= "cmd {$hostname} '/usr/local/sbin/pfSctl -c \"service reload routedns\"'\n"; + } + file_put_contents("{$g['varetc_path']}/filterdns-route.hosts", $hostnames); + unset($hostnames); + + if (isvalidpid("{$g['varrun_path']}/filterdns-route.pid")) { + sigkillbypid("{$g['varrun_path']}/filterdns-route.pid", "HUP"); + } else { + mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-route.pid -i {$interval} -c {$g['varetc_path']}/filterdns-route.hosts -d 1"); + } + } else { + killbypid("{$g['varrun_path']}/filterdns-route.pid"); + @unlink("{$g['varrun_path']}/filterdns-route.pid"); + } + } + unset($filterdns_list); + + return 0; +} + +function system_routing_enable() { + global $config, $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_routing_enable() being called $mt\n"; + } + + set_sysctl(array( + "net.inet.ip.forwarding" => "1", + "net.inet6.ip6.forwarding" => "1" + )); + + return; +} + +function system_syslogd_fixup_server($server) { + /* If it's an IPv6 IP alone, encase it in brackets */ + if (is_ipaddrv6($server)) { + return "[$server]"; + } else { + return $server; + } +} + +function system_syslogd_get_remote_servers($syslogcfg, $facility = "*.*") { + // Rather than repeatedly use the same code, use this function to build a list of remote servers. + $facility .= " ". + $remote_servers = ""; + $pad_to = max(strlen($facility), 56); + $padding = ceil(($pad_to - strlen($facility))/8)+1; + if (isset($syslogcfg['enable'])) { + if ($syslogcfg['remoteserver']) { + $remote_servers .= "{$facility}" . str_repeat("\t", $padding) . "@" . system_syslogd_fixup_server($syslogcfg['remoteserver']) . "\n"; + } + if ($syslogcfg['remoteserver2']) { + $remote_servers .= "{$facility}" . str_repeat("\t", $padding) . "@" . system_syslogd_fixup_server($syslogcfg['remoteserver2']) . "\n"; + } + if ($syslogcfg['remoteserver3']) { + $remote_servers .= "{$facility}" . str_repeat("\t", $padding) . "@" . system_syslogd_fixup_server($syslogcfg['remoteserver3']) . "\n"; + } + } + return $remote_servers; +} + +function clear_log_file($logfile = "/var/log/system.log", $restart_syslogd = true) { + global $config, $g; + + if ($restart_syslogd) { + /* syslogd does not react well to clog rewriting the file while it is running. */ + if (isvalidpid("{$g['varrun_path']}/syslog.pid")) { + sigkillbypid("{$g['varrun_path']}/syslog.pid", "KILL"); + } + } + if (isset($config['system']['disablesyslogclog'])) { + unlink($logfile); + touch($logfile); + } else { + $log_size = isset($config['syslog']['logfilesize']) ? $config['syslog']['logfilesize'] : "511488"; + $log_size = ($log_size < (2**32)/2) ? $log_size : "511488"; + $log_size = isset($config['syslog'][basename($logfile, '.log') . '_settings']['logfilesize']) ? $config['syslog'][basename($logfile, '.log') . '_settings']['logfilesize'] : $log_size; + exec("/usr/local/sbin/clog -i -s {$log_size} " . escapeshellarg($logfile)); + } + if ($restart_syslogd) { + system_syslogd_start(); + } + // Bug #6915 + if ($logfile == "/var/log/resolver.log") { + services_unbound_configure(true); + } +} + +function clear_all_log_files($restart = false) { + global $g; + if ($restart) { + /* syslogd does not react well to clog rewriting the file while it is running. */ + if (isvalidpid("{$g['varrun_path']}/syslog.pid")) { + sigkillbypid("{$g['varrun_path']}/syslog.pid", "KILL"); + } + } + + $log_files = array("system", "filter", "dhcpd", "vpn", "poes", "l2tps", "openvpn", "portalauth", "ipsec", "ppp", "relayd", "wireless", "nginx", "ntpd", "gateways", "resolver", "routing"); + foreach ($log_files as $lfile) { + clear_log_file("{$g['varlog_path']}/{$lfile}.log", false); + } + + if ($restart) { + system_syslogd_start(); + killbyname("dhcpd"); + if (!function_exists('services_dhcpd_configure')) { + require_once('services.inc'); + } + services_dhcpd_configure(); + // Bug #6915 + services_unbound_configure(false); + } + return; +} + +function system_syslogd_start($sighup = false) { + global $config, $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_syslogd_start() being called $mt\n"; + } + + mwexec("/etc/rc.d/hostid start"); + + $syslogcfg = $config['syslog']; + + if (platform_booting()) { + echo gettext("Starting syslog..."); + } + + // Which logging type are we using this week?? + if (isset($config['system']['disablesyslogclog'])) { + $log_directive = ""; + $log_create_directive = "/usr/bin/touch "; + $log_size = ""; + } else { // Defaults to CLOG + $log_directive = "%"; + $log_size = isset($config['syslog']['logfilesize']) ? $config['syslog']['logfilesize'] : "10240"; + $log_create_directive = "/usr/local/sbin/clog -i -s "; + } + + $syslogd_extra = ""; + if (isset($syslogcfg)) { + $separatelogfacilities = array('ntp', 'ntpd', 'ntpdate', 'charon', 'ipsec_starter', 'openvpn', 'poes', 'l2tps', 'relayd', 'hostapd', 'dnsmasq', 'named', 'filterdns', 'unbound', 'dhcpd', 'dhcrelay', 'dhclient', 'dhcp6c', 'dpinger', 'radvd', 'routed', 'zebra', 'ospfd', 'ospf6d', 'bgpd', 'miniupnpd', 'igmpproxy', 'filterlog'); + $syslogconf = ""; + if ($config['installedpackages']['package']) { + foreach ($config['installedpackages']['package'] as $package) { + if (isset($package['logging']['facilityname']) && isset($package['logging']['logfilename'])) { + array_push($separatelogfacilities, $package['logging']['facilityname']); + if (!is_file($g['varlog_path'].'/'.$package['logging']['logfilename'])) { + mwexec("{$log_create_directive} {$log_size} {$g['varlog_path']}/{$package['logging']['logfilename']}"); + } + $syslogconf .= "!{$package['logging']['facilityname']}\n*.*\t\t\t\t\t\t {$log_directive}{$g['varlog_path']}/{$package['logging']['logfilename']}\n"; + } + } + } + $facilitylist = implode(',', array_unique($separatelogfacilities)); + $syslogconf .= "!radvd,routed,zebra,ospfd,ospf6d,bgpd,miniupnpd,igmpproxy\n"; + if (!isset($syslogcfg['disablelocallogging'])) { + $syslogconf .= "*.* {$log_directive}{$g['varlog_path']}/routing.log\n"; + } + if (isset($syslogcfg['routing'])) { + $syslogconf .= system_syslogd_get_remote_servers($syslogcfg, "*.*"); + } + + $syslogconf .= "!ntp,ntpd,ntpdate\n"; + if (!isset($syslogcfg['disablelocallogging'])) { + $syslogconf .= "*.* {$log_directive}{$g['varlog_path']}/ntpd.log\n"; + } + if (isset($syslogcfg['ntpd'])) { + $syslogconf .= system_syslogd_get_remote_servers($syslogcfg, "*.*"); + } + + $syslogconf .= "!ppp\n"; + if (!isset($syslogcfg['disablelocallogging'])) { + $syslogconf .= "*.* {$log_directive}{$g['varlog_path']}/ppp.log\n"; + } + if (isset($syslogcfg['ppp'])) { + $syslogconf .= system_syslogd_get_remote_servers($syslogcfg, "*.*"); + } + + $syslogconf .= "!poes\n"; + if (!isset($syslogcfg['disablelocallogging'])) { + $syslogconf .= "*.* {$log_directive}{$g['varlog_path']}/poes.log\n"; + } + if (isset($syslogcfg['vpn'])) { + $syslogconf .= system_syslogd_get_remote_servers($syslogcfg, "*.*"); + } + + $syslogconf .= "!l2tps\n"; + if (!isset($syslogcfg['disablelocallogging'])) { + $syslogconf .= "*.* {$log_directive}{$g['varlog_path']}/l2tps.log\n"; + } + if (isset($syslogcfg['vpn'])) { + $syslogconf .= system_syslogd_get_remote_servers($syslogcfg, "*.*"); + } + + $syslogconf .= "!charon,ipsec_starter\n"; + if (!isset($syslogcfg['disablelocallogging'])) { + $syslogconf .= "*.* {$log_directive}{$g['varlog_path']}/ipsec.log\n"; + } + if (isset($syslogcfg['vpn'])) { + $syslogconf .= system_syslogd_get_remote_servers($syslogcfg, "*.*"); + } + + $syslogconf .= "!openvpn\n"; + if (!isset($syslogcfg['disablelocallogging'])) { + $syslogconf .= "*.* {$log_directive}{$g['varlog_path']}/openvpn.log\n"; + } + if (isset($syslogcfg['vpn'])) { + $syslogconf .= system_syslogd_get_remote_servers($syslogcfg, "*.*"); + } + + $syslogconf .= "!dpinger\n"; + if (!isset($syslogcfg['disablelocallogging'])) { + $syslogconf .= "*.* {$log_directive}{$g['varlog_path']}/gateways.log\n"; + } + if (isset($syslogcfg['dpinger'])) { + $syslogconf .= system_syslogd_get_remote_servers($syslogcfg, "*.*"); + } + + $syslogconf .= "!dnsmasq,named,filterdns,unbound\n"; + if (!isset($syslogcfg['disablelocallogging'])) { + $syslogconf .= "*.* {$log_directive}{$g['varlog_path']}/resolver.log\n"; + } + if (isset($syslogcfg['resolver'])) { + $syslogconf .= system_syslogd_get_remote_servers($syslogcfg, "*.*"); + } + + $syslogconf .= "!dhcpd,dhcrelay,dhclient,dhcp6c,dhcpleases,dhcpleases6\n"; + if (!isset($syslogcfg['disablelocallogging'])) { + $syslogconf .= "*.* {$log_directive}{$g['varlog_path']}/dhcpd.log\n"; + } + if (isset($syslogcfg['dhcp'])) { + $syslogconf .= system_syslogd_get_remote_servers($syslogcfg, "*.*"); + } + + $syslogconf .= "!relayd\n"; + if (!isset($syslogcfg['disablelocallogging'])) { + $syslogconf .= "*.* {$log_directive}{$g['varlog_path']}/relayd.log\n"; + } + if (isset($syslogcfg['relayd'])) { + $syslogconf .= system_syslogd_get_remote_servers($syslogcfg, "*.*"); + } + + $syslogconf .= "!hostapd\n"; + if (!isset($syslogcfg['disablelocallogging'])) { + $syslogconf .= "*.* {$log_directive}{$g['varlog_path']}/wireless.log\n"; + } + if (isset($syslogcfg['hostapd'])) { + $syslogconf .= system_syslogd_get_remote_servers($syslogcfg, "*.*"); + } + + $syslogconf .= "!filterlog\n"; + if (!isset($syslogcfg['disablelocallogging'])) { + $syslogconf .= "*.* {$log_directive}{$g['varlog_path']}/filter.log\n"; + } + if (isset($syslogcfg['filter'])) { + $syslogconf .= system_syslogd_get_remote_servers($syslogcfg, "*.*"); + } + + $syslogconf .= "!-{$facilitylist}\n"; + if (!isset($syslogcfg['disablelocallogging'])) { + $syslogconf .= << "{$g['product_name']} webConfigurator Self-Signed Certificate", + 'commonName' => $cert_hostname, + 'subjectAltName' => "DNS:{$cert_hostname}"); + $old_err_level = error_reporting(0); /* otherwise openssl_ functions throw warnings directly to a page screwing menu tab */ + if (!cert_create($cert, null, 2048, 398, $dn, "self-signed", "sha256")) { + while ($ssl_err = openssl_error_string()) { + log_error(sprintf(gettext("Error creating WebGUI Certificate: openssl library returns: %s"), $ssl_err)); + } + error_reporting($old_err_level); + return null; + } + error_reporting($old_err_level); + + $a_cert[] = $cert; + $config['system']['webgui']['ssl-certref'] = $cert['refid']; + write_config(sprintf(gettext("Generated new self-signed SSL/TLS certificate for HTTPS (%s)"), $cert['refid'])); + return $cert; +} + +function system_webgui_start() { + global $config, $g; + + if (platform_booting()) { + echo gettext("Starting webConfigurator..."); + } + + chdir($g['www_path']); + + /* defaults */ + $portarg = "80"; + $crt = ""; + $key = ""; + $ca = ""; + + /* non-standard port? */ + if (isset($config['system']['webgui']['port']) && $config['system']['webgui']['port'] <> "") { + $portarg = "{$config['system']['webgui']['port']}"; + } + + if ($config['system']['webgui']['protocol'] == "https") { + // Ensure that we have a webConfigurator CERT + $cert =& lookup_cert($config['system']['webgui']['ssl-certref']); + if (!is_array($cert) || !$cert['crt'] || !$cert['prv']) { + $cert = system_webgui_create_certificate(); + } + $crt = base64_decode($cert['crt']); + $key = base64_decode($cert['prv']); + + if (!$config['system']['webgui']['port']) { + $portarg = "443"; + } + $ca = ca_chain($cert); + $hsts = isset($config['system']['webgui']['disablehsts']) ? false : true; + } + + /* generate nginx configuration */ + system_generate_nginx_config("{$g['varetc_path']}/nginx-webConfigurator.conf", + $crt, $key, $ca, "nginx-webConfigurator.pid", $portarg, "/usr/local/www/", + "cert.crt", "cert.key", false, $hsts); + + /* kill any running nginx */ + killbypid("{$g['varrun_path']}/nginx-webConfigurator.pid"); + + sleep(1); + + @unlink("{$g['varrun_path']}/nginx-webConfigurator.pid"); + + /* start nginx */ + $res = mwexec("/usr/local/sbin/nginx -c {$g['varetc_path']}/nginx-webConfigurator.conf"); + + if (platform_booting()) { + if ($res == 0) { + echo gettext("done.") . "\n"; + } else { + echo gettext("failed!") . "\n"; + } + } + + return $res; +} + +function get_dns_nameservers($add_v6_brackets = false) { + global $config; + + $dns_nameservers = array(); + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "get_dns_nameservers() being called $mt\n"; + } + + $syscfg = $config['system']; + if ((((isset($config['dnsmasq']['enable'])) && + (empty($config['dnsmasq']['port']) || $config['dnsmasq']['port'] == "53") && + (empty($config['dnsmasq']['interface']) || + in_array("lo0", explode(",", $config['dnsmasq']['interface'])))) || + ((isset($config['unbound']['enable'])) && + (empty($config['unbound']['port']) || $config['unbound']['port'] == "53") && + (empty($config['unbound']['active_interface']) || + in_array("lo0", explode(",", $config['unbound']['active_interface'])) || + in_array("all", explode(",", $config['unbound']['active_interface']), true)))) && + (!isset($config['system']['dnslocalhost']))) { + $dns_nameservers[] = "127.0.0.1"; + } + /* get dynamically assigned DNS servers (if any) */ + $ns = array_unique(get_nameservers()); + if (isset($syscfg['dnsallowoverride'])) { + if(!is_array($ns)) { + $ns = array(); + } + foreach ($ns as $nameserver) { + if ($nameserver) { + if ($add_v6_brackets && is_ipaddrv6($nameserver)) { + $nameserver = "[{$nameserver}]"; + } + $dns_nameservers[] = $nameserver; + } + } + } else { + /* If override is disallowed, ignore the dynamic NS entries + * https://redmine.pfsense.org/issues/9963 */ + $ns = array(); + } + if (is_array($syscfg['dnsserver'])) { + foreach ($syscfg['dnsserver'] as $sys_dnsserver) { + if ($sys_dnsserver && (!in_array($sys_dnsserver, $ns))) { + if ($add_v6_brackets && is_ipaddrv6($sys_dnsserver)) { + $sys_dnsserver = "[{$sys_dnsserver}]"; + } + $dns_nameservers[] = $sys_dnsserver; + } + } + } + return array_unique($dns_nameservers); +} + +function system_generate_nginx_config($filename, + $cert, + $key, + $ca, + $pid_file, + $port = 80, + $document_root = "/usr/local/www/", + $cert_location = "cert.crt", + $key_location = "cert.key", + $captive_portal = false, + $hsts = true) { + + global $config, $g; + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_generate_nginx_config() being called $mt\n"; + } + + if ($captive_portal !== false) { + $cp_interfaces = explode(",", $config['captiveportal'][$captive_portal]['interface']); + $cp_hostcheck = ""; + foreach ($cp_interfaces as $cpint) { + $cpint_ip = get_interface_ip($cpint); + if (is_ipaddr($cpint_ip)) { + $cp_hostcheck .= "\t\tif (\$http_host ~* $cpint_ip) {\n"; + $cp_hostcheck .= "\t\t\tset \$cp_redirect no;\n"; + $cp_hostcheck .= "\t\t}\n"; + } + } + if (isset($config['captiveportal'][$captive_portal]['httpsname']) && + is_domain($config['captiveportal'][$captive_portal]['httpsname'])) { + $cp_hostcheck .= "\t\tif (\$http_host ~* {$config['captiveportal'][$captive_portal]['httpsname']}) {\n"; + $cp_hostcheck .= "\t\t\tset \$cp_redirect no;\n"; + $cp_hostcheck .= "\t\t}\n"; + } + $cp_rewrite = "\t\tif (\$cp_redirect = '') {\n"; + $cp_rewrite .= "\t\t\trewrite ^ /index.php?zone=$captive_portal&redirurl=\$request_uri break;\n"; + $cp_rewrite .= "\t\t}\n"; + + $maxprocperip = $config['captiveportal'][$captive_portal]['maxprocperip']; + if (empty($maxprocperip)) { + $maxprocperip = 10; + } + $captive_portal_maxprocperip = "\t\tlimit_conn addr $maxprocperip;\n"; + } + + if (empty($port)) { + $nginx_port = "80"; + } else { + $nginx_port = $port; + } + + $memory = get_memory(); + $realmem = $memory[1]; + + // Determine web GUI process settings and take into account low memory systems + if ($realmem < 255) { + $max_procs = 1; + } else { + $max_procs = ($config['system']['webgui']['max_procs']) ? $config['system']['webgui']['max_procs'] : 2; + } + + // Ramp up captive portal max procs, assuming each PHP process can consume up to 64MB RAM + if ($captive_portal !== false) { + if ($realmem > 135 and $realmem < 256) { + $max_procs += 1; // 2 worker processes + } else if ($realmem > 255 and $realmem < 513) { + $max_procs += 2; // 3 worker processes + } else if ($realmem > 512) { + $max_procs += 4; // 6 worker processes + } + } + + $nginx_config = << "" and $key <> "") { + $nginx_config .= "\n"; + $nginx_config .= "\tserver {\n"; + $nginx_config .= "\t\tlisten {$nginx_port} ssl http2;\n"; + $nginx_config .= "\t\tlisten [::]:{$nginx_port} ssl http2;\n"; + $nginx_config .= "\n"; + $nginx_config .= "\t\tssl_certificate {$g['varetc_path']}/{$cert_location};\n"; + $nginx_config .= "\t\tssl_certificate_key {$g['varetc_path']}/{$key_location};\n"; + $nginx_config .= "\t\tssl_session_timeout 10m;\n"; + $nginx_config .= "\t\tkeepalive_timeout 70;\n"; + $nginx_config .= "\t\tssl_session_cache shared:SSL:10m;\n"; + if ($captive_portal !== false) { + // leave TLSv1.0 for CP for now for compatibility + $nginx_config .= "\t\tssl_protocols TLSv1 TLSv1.1 TLSv1.2;\n"; + } else { + $nginx_config .= "\t\tssl_protocols TLSv1.1 TLSv1.2;\n"; + } + $nginx_config .= "\t\tssl_ciphers \"EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH\";\n"; + $nginx_config .= "\t\tssl_prefer_server_ciphers on;\n"; + if ($captive_portal === false && $hsts !== false) { + $nginx_config .= "\t\tadd_header Strict-Transport-Security \"max-age=31536000\";\n"; + } + $nginx_config .= "\t\tadd_header X-Content-Type-Options nosniff;\n"; + $nginx_config .= "\t\tssl_session_tickets off;\n"; + $nginx_config .= "\t\tssl_dhparam /etc/dh-parameters.4096;\n"; + $cert_temp = lookup_cert($config['system']['webgui']['ssl-certref']); + if (($config['system']['webgui']['ocsp-staple'] == true) or + (cert_get_ocspstaple($cert_temp['crt']) == true)) { + $nginx_config .= "\t\tssl_stapling on;\n"; + $nginx_config .= "\t\tssl_stapling_verify on;\n"; + $nginx_config .= "\t\tresolver " . implode(" ", get_dns_nameservers(true)) . " valid=300s;\n"; + $nginx_config .= "\t\tresolver_timeout 5s;\n"; + } + } else { + $nginx_config .= "\n"; + $nginx_config .= "\tserver {\n"; + $nginx_config .= "\t\tlisten {$nginx_port};\n"; + $nginx_config .= "\t\tlisten [::]:{$nginx_port};\n"; + } + + $nginx_config .= << "" and $key <> "") { + $fd = fopen("{$g['varetc_path']}/{$cert_location}", "w"); + if (!$fd) { + printf(gettext("Error: cannot open certificate file in system_webgui_start().%s"), "\n"); + return 1; + } + chmod("{$g['varetc_path']}/{$cert_location}", 0644); + if ($ca <> "") { + $cert_chain = $cert . "\n" . $ca; + } else { + $cert_chain = $cert; + } + fwrite($fd, $cert_chain); + fclose($fd); + $fd = fopen("{$g['varetc_path']}/{$key_location}", "w"); + if (!$fd) { + printf(gettext("Error: cannot open certificate key file in system_webgui_start().%s"), "\n"); + return 1; + } + chmod("{$g['varetc_path']}/{$key_location}", 0600); + fwrite($fd, $key); + fclose($fd); + } + + // Add HTTP to HTTPS redirect + if ($captive_portal === false && $config['system']['webgui']['protocol'] == "https" && !isset($config['system']['webgui']['disablehttpredirect'])) { + if ($nginx_port != "443") { + $redirectport = ":{$nginx_port}"; + } + $nginx_config .= << {$serialport}"); + + /* Add /etc/remote entry in case we need to read from the GPS with tip */ + if (intval(`grep -c '^gps0' /etc/remote`) == 0) { + @file_put_contents("/etc/remote", "gps0:dv={$serialport}:br#{$gpsbaud}:pa=none:\n", FILE_APPEND); + } + + + return true; +} + +function system_ntp_setup_pps($serialport) { + global $config, $g; + + $pps_device = '/dev/pps0'; + $serialport = '/dev/'.$serialport; + + if (!file_exists($serialport)) { + return false; + } + + // Create symlink that ntpd requires + unlink_if_exists($pps_device); + @symlink($serialport, $pps_device); + + + return true; +} + + +function system_ntp_configure() { + global $config, $g; + + $driftfile = "/var/db/ntpd.drift"; + $statsdir = "/var/log/ntp"; + $gps_device = '/dev/gps0'; + + safe_mkdir($statsdir); + + if (!is_array($config['ntpd'])) { + $config['ntpd'] = array(); + } + + $ntpcfg = "# \n"; + $ntpcfg .= "# pfSense ntp configuration file \n"; + $ntpcfg .= "# \n\n"; + $ntpcfg .= "tinker panic 0 \n"; + + /* Add Orphan mode */ + $ntpcfg .= "# Orphan mode stratum\n"; + $ntpcfg .= 'tos orphan '; + if (!empty($config['ntpd']['orphan'])) { + $ntpcfg .= $config['ntpd']['orphan']; + } else { + $ntpcfg .= '12'; + } + $ntpcfg .= "\n"; + + /* Add PPS configuration */ + if (is_array($config['ntpd']['pps']) && !empty($config['ntpd']['pps']['port']) && + file_exists('/dev/'.$config['ntpd']['pps']['port']) && + system_ntp_setup_pps($config['ntpd']['pps']['port'])) { + $ntpcfg .= "\n"; + $ntpcfg .= "# PPS Setup\n"; + $ntpcfg .= 'server 127.127.22.0'; + $ntpcfg .= ' minpoll 4 maxpoll 4'; + if (empty($config['ntpd']['pps']['prefer'])) { /*note: this one works backwards */ + $ntpcfg .= ' prefer'; + } + if (!empty($config['ntpd']['pps']['noselect'])) { + $ntpcfg .= ' noselect '; + } + $ntpcfg .= "\n"; + $ntpcfg .= 'fudge 127.127.22.0'; + if (!empty($config['ntpd']['pps']['fudge1'])) { + $ntpcfg .= ' time1 '; + $ntpcfg .= $config['ntpd']['pps']['fudge1']; + } + if (!empty($config['ntpd']['pps']['flag2'])) { + $ntpcfg .= ' flag2 1'; + } + if (!empty($config['ntpd']['pps']['flag3'])) { + $ntpcfg .= ' flag3 1'; + } else { + $ntpcfg .= ' flag3 0'; + } + if (!empty($config['ntpd']['pps']['flag4'])) { + $ntpcfg .= ' flag4 1'; + } + if (!empty($config['ntpd']['pps']['refid'])) { + $ntpcfg .= ' refid '; + $ntpcfg .= $config['ntpd']['pps']['refid']; + } + $ntpcfg .= "\n"; + } + /* End PPS configuration */ + + /* Add GPS configuration */ + if (is_array($config['ntpd']['gps']) && !empty($config['ntpd']['gps']['port']) && + file_exists('/dev/'.$config['ntpd']['gps']['port']) && + system_ntp_setup_gps($config['ntpd']['gps']['port'])) { + $ntpcfg .= "\n"; + $ntpcfg .= "# GPS Setup\n"; + $ntpcfg .= 'server 127.127.20.0 mode '; + if (!empty($config['ntpd']['gps']['nmea']) || !empty($config['ntpd']['gps']['speed']) || !empty($config['ntpd']['gps']['subsec']) || !empty($config['ntpd']['gps']['processpgrmf'])) { + if (!empty($config['ntpd']['gps']['nmea'])) { + $ntpmode = (int) $config['ntpd']['gps']['nmea']; + } + if (!empty($config['ntpd']['gps']['speed'])) { + $ntpmode += (int) $config['ntpd']['gps']['speed']; + } + if (!empty($config['ntpd']['gps']['subsec'])) { + $ntpmode += 128; + } + if (!empty($config['ntpd']['gps']['processpgrmf'])) { + $ntpmode += 256; + } + $ntpcfg .= (string) $ntpmode; + } else { + $ntpcfg .= '0'; + } + $ntpcfg .= ' minpoll 4 maxpoll 4'; + if (empty($config['ntpd']['gps']['prefer'])) { /*note: this one works backwards */ + $ntpcfg .= ' prefer'; + } + if (!empty($config['ntpd']['gps']['noselect'])) { + $ntpcfg .= ' noselect '; + } + $ntpcfg .= "\n"; + $ntpcfg .= 'fudge 127.127.20.0'; + if (!empty($config['ntpd']['gps']['fudge1'])) { + $ntpcfg .= ' time1 '; + $ntpcfg .= $config['ntpd']['gps']['fudge1']; + } + if (!empty($config['ntpd']['gps']['fudge2'])) { + $ntpcfg .= ' time2 '; + $ntpcfg .= $config['ntpd']['gps']['fudge2']; + } + if (!empty($config['ntpd']['gps']['flag1'])) { + $ntpcfg .= ' flag1 1'; + } else { + $ntpcfg .= ' flag1 0'; + } + if (!empty($config['ntpd']['gps']['flag2'])) { + $ntpcfg .= ' flag2 1'; + } + if (!empty($config['ntpd']['gps']['flag3'])) { + $ntpcfg .= ' flag3 1'; + } else { + $ntpcfg .= ' flag3 0'; + } + if (!empty($config['ntpd']['gps']['flag4'])) { + $ntpcfg .= ' flag4 1'; + } + if (!empty($config['ntpd']['gps']['refid'])) { + $ntpcfg .= ' refid '; + $ntpcfg .= $config['ntpd']['gps']['refid']; + } + if (!empty($config['ntpd']['gps']['stratum'])) { + $ntpcfg .= ' stratum '; + $ntpcfg .= $config['ntpd']['gps']['stratum']; + } + $ntpcfg .= "\n"; + } elseif (is_array($config['ntpd']) && !empty($config['ntpd']['gpsport']) && + file_exists('/dev/'.$config['ntpd']['gpsport']) && + system_ntp_setup_gps($config['ntpd']['gpsport'])) { + /* This handles a 2.1 and earlier config */ + $ntpcfg .= "# GPS Setup\n"; + $ntpcfg .= "server 127.127.20.0 mode 0 minpoll 4 maxpoll 4 prefer\n"; + $ntpcfg .= "fudge 127.127.20.0 time1 0.155 time2 0.000 flag1 1 flag2 0 flag3 1\n"; + // Fall back to local clock if GPS is out of sync? + $ntpcfg .= "server 127.127.1.0\n"; + $ntpcfg .= "fudge 127.127.1.0 stratum 12\n"; + } + /* End GPS configuration */ + $auto_pool_suffix = "pool.ntp.org"; + $have_pools = false; + $ntpcfg .= "\n\n# Upstream Servers\n"; + /* foreach through ntp servers and write out to ntpd.conf */ + foreach (explode(' ', $config['system']['timeservers']) as $ts) { + if ((substr_compare($ts, $auto_pool_suffix, strlen($ts) - strlen($auto_pool_suffix), strlen($auto_pool_suffix)) === 0) + || substr_count($config['ntpd']['ispool'], $ts)) { + $ntpcfg .= 'pool '; + $have_pools = true; + } else { + $ntpcfg .= 'server '; + } + + $ntpcfg .= "{$ts} iburst maxpoll 9"; + if (substr_count($config['ntpd']['prefer'], $ts)) { + $ntpcfg .= ' prefer'; + } + if (substr_count($config['ntpd']['noselect'], $ts)) { + $ntpcfg .= ' noselect'; + } + $ntpcfg .= "\n"; + } + unset($ts); + + $ntpcfg .= "\n\n"; + if (!empty($config['ntpd']['clockstats']) || !empty($config['ntpd']['loopstats']) || !empty($config['ntpd']['peerstats'])) { + $ntpcfg .= "enable stats\n"; + $ntpcfg .= 'statistics'; + if (!empty($config['ntpd']['clockstats'])) { + $ntpcfg .= ' clockstats'; + } + if (!empty($config['ntpd']['loopstats'])) { + $ntpcfg .= ' loopstats'; + } + if (!empty($config['ntpd']['peerstats'])) { + $ntpcfg .= ' peerstats'; + } + $ntpcfg .= "\n"; + } + $ntpcfg .= "statsdir {$statsdir}\n"; + $ntpcfg .= 'logconfig =syncall +clockall'; + if (!empty($config['ntpd']['logpeer'])) { + $ntpcfg .= ' +peerall'; + } + if (!empty($config['ntpd']['logsys'])) { + $ntpcfg .= ' +sysall'; + } + $ntpcfg .= "\n"; + $ntpcfg .= "driftfile {$driftfile}\n"; + + /* Default Access restrictions */ + $ntpcfg .= 'restrict default'; + if (empty($config['ntpd']['kod'])) { /*note: this one works backwards */ + $ntpcfg .= ' kod limited'; + } + if (empty($config['ntpd']['nomodify'])) { /*note: this one works backwards */ + $ntpcfg .= ' nomodify'; + } + if (!empty($config['ntpd']['noquery'])) { + $ntpcfg .= ' noquery'; + } + if (empty($config['ntpd']['nopeer'])) { /*note: this one works backwards */ + $ntpcfg .= ' nopeer'; + } + if (empty($config['ntpd']['notrap'])) { /*note: this one works backwards */ + $ntpcfg .= ' notrap'; + } + if (!empty($config['ntpd']['noserve'])) { + $ntpcfg .= ' noserve'; + } + $ntpcfg .= "\nrestrict -6 default"; + if (empty($config['ntpd']['kod'])) { /*note: this one works backwards */ + $ntpcfg .= ' kod limited'; + } + if (empty($config['ntpd']['nomodify'])) { /*note: this one works backwards */ + $ntpcfg .= ' nomodify'; + } + if (!empty($config['ntpd']['noquery'])) { + $ntpcfg .= ' noquery'; + } + if (empty($config['ntpd']['nopeer'])) { /*note: this one works backwards */ + $ntpcfg .= ' nopeer'; + } + if (!empty($config['ntpd']['noserve'])) { + $ntpcfg .= ' noserve'; + } + if (empty($config['ntpd']['notrap'])) { /*note: this one works backwards */ + $ntpcfg .= ' notrap'; + } + + /* Pools require "restrict source" and cannot contain "nopeer". */ + if ($have_pools) { + $ntpcfg .= "\nrestrict source"; + if (empty($config['ntpd']['kod'])) { /*note: this one works backwards */ + $ntpcfg .= ' kod limited'; + } + if (empty($config['ntpd']['nomodify'])) { /*note: this one works backwards */ + $ntpcfg .= ' nomodify'; + } + if (!empty($config['ntpd']['noquery'])) { + $ntpcfg .= ' noquery'; + } + if (!empty($config['ntpd']['noserve'])) { + $ntpcfg .= ' noserve'; + } + if (empty($config['ntpd']['notrap'])) { /*note: this one works backwards */ + $ntpcfg .= ' notrap'; + } + } + + /* Custom Access Restrictions */ + if (is_array($config['ntpd']['restrictions']) && is_array($config['ntpd']['restrictions']['row'])) { + $networkacl = $config['ntpd']['restrictions']['row']; + foreach ($networkacl as $acl) { + $restrict = ""; + if (is_ipaddrv6($acl['acl_network'])) { + $restrict .= "{$acl['acl_network']} mask " . gen_subnet_mask_v6($acl['mask']) . " "; + } elseif (is_ipaddrv4($acl['acl_network'])) { + $restrict .= "{$acl['acl_network']} mask " . gen_subnet_mask($acl['mask']) . " "; + } else { + continue; + } + if (!empty($acl['kod'])) { + $restrict .= ' kod limited'; + } + if (!empty($acl['nomodify'])) { + $restrict .= ' nomodify'; + } + if (!empty($acl['noquery'])) { + $restrict .= ' noquery'; + } + if (!empty($acl['nopeer'])) { + $restrict .= ' nopeer'; + } + if (!empty($acl['noserve'])) { + $restrict .= ' noserve'; + } + if (!empty($acl['notrap'])) { + $restrict .= ' notrap'; + } + if (!empty($restrict)) { + $ntpcfg .= "\nrestrict {$restrict} "; + } + } + } + /* End Custom Access Restrictions */ + + /* A leapseconds file is really only useful if this clock is stratum 1 */ + $ntpcfg .= "\n"; + if (!empty($config['ntpd']['leapsec'])) { + $leapsec .= base64_decode($config['ntpd']['leapsec']); + file_put_contents('/var/db/leap-seconds', $leapsec); + $ntpcfg .= "leapfile /var/db/leap-seconds\n"; + } + + + if (empty($config['ntpd']['interface'])) { + if (is_array($config['installedpackages']['openntpd']) && !empty($config['installedpackages']['openntpd']['config'][0]['interface'])) { + $interfaces = explode(",", $config['installedpackages']['openntpd']['config'][0]['interface']); + } else { + $interfaces = array(); + } + } else { + $interfaces = explode(",", $config['ntpd']['interface']); + } + + if (is_array($interfaces) && count($interfaces)) { + $finterfaces = array(); + $ntpcfg .= "interface ignore all\n"; + $ntpcfg .= "interface ignore wildcard\n"; + foreach ($interfaces as $interface) { + $interface = get_real_interface($interface); + if (!empty($interface)) { + $finterfaces[] = $interface; + } + } + foreach ($finterfaces as $interface) { + $ntpcfg .= "interface listen {$interface}\n"; + } + } + + /* open configuration for writing or bail */ + if (!@file_put_contents("{$g['varetc_path']}/ntpd.conf", $ntpcfg)) { + log_error(sprintf(gettext("Could not open %s/ntpd.conf for writing"), $g['varetc_path'])); + return; + } + + /* if ntpd is running, kill it */ + while (isvalidpid("{$g['varrun_path']}/ntpd.pid")) { + killbypid("{$g['varrun_path']}/ntpd.pid"); + } + @unlink("{$g['varrun_path']}/ntpd.pid"); + + /* if /var/empty does not exist, create it */ + if (!is_dir("/var/empty")) { + mkdir("/var/empty", 0555, true); + } + + /* start opentpd, set time now and use /var/etc/ntpd.conf */ + mwexec("/usr/local/sbin/ntpd -g -c {$g['varetc_path']}/ntpd.conf -p {$g['varrun_path']}/ntpd.pid", false, true); + + // Note that we are starting up + log_error("NTPD is starting up."); + return; +} + +function system_halt() { + global $g; + + system_reboot_cleanup(); + + mwexec("/usr/bin/nohup /etc/rc.halt > /dev/null 2>&1 &"); +} + +function system_reboot() { + global $g; + + system_reboot_cleanup(); + + mwexec("nohup /etc/rc.reboot > /dev/null 2>&1 &"); +} + +function system_reboot_sync($reroot=false) { + global $g; + + if ($reroot) { + $args = " -r "; + } + + system_reboot_cleanup(); + + mwexec("/etc/rc.reboot {$args} > /dev/null 2>&1"); +} + +function system_reboot_cleanup() { + global $config, $cpzone, $cpzoneid; + + mwexec("/usr/local/bin/beep.sh stop"); + require_once("captiveportal.inc"); + if (is_array($config['captiveportal'])) { + foreach ($config['captiveportal'] as $cpzone=>$cp) { + /* send Accounting-Stop packet for all clients, termination cause 'Admin-Reboot' */ + $cpzoneid = $cp['zoneid']; + captiveportal_radius_stop_all(7); // Admin-Reboot + /* Send Accounting-Off packet to the RADIUS server */ + captiveportal_send_server_accounting('off'); + } + } + require_once("voucher.inc"); + voucher_save_db_to_config(); + require_once("pkg-utils.inc"); + stop_packages(); +} + +function system_do_shell_commands($early = 0) { + global $config, $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_do_shell_commands() being called $mt\n"; + } + + if ($early) { + $cmdn = "earlyshellcmd"; + } else { + $cmdn = "shellcmd"; + } + + if (is_array($config['system'][$cmdn])) { + + /* *cmd is an array, loop through */ + foreach ($config['system'][$cmdn] as $cmd) { + exec($cmd); + } + + } elseif ($config['system'][$cmdn] <> "") { + + /* execute single item */ + exec($config['system'][$cmdn]); + + } +} + +function system_dmesg_save() { + global $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_dmesg_save() being called $mt\n"; + } + + $dmesg = ""; + $_gb = exec("/sbin/dmesg", $dmesg); + + /* find last copyright line (output from previous boots may be present) */ + $lastcpline = 0; + + for ($i = 0; $i < count($dmesg); $i++) { + if (strstr($dmesg[$i], "Copyright (c) 1992-")) { + $lastcpline = $i; + } + } + + $fd = fopen("{$g['varlog_path']}/dmesg.boot", "w"); + if (!$fd) { + printf(gettext("Error: cannot open dmesg.boot in system_dmesg_save().%s"), "\n"); + return 1; + } + + for ($i = $lastcpline; $i < count($dmesg); $i++) { + fwrite($fd, $dmesg[$i] . "\n"); + } + + fclose($fd); + unset($dmesg); + + // vm-bhyve expects dmesg.boot at the standard location + @symlink("{$g['varlog_path']}/dmesg.boot", "{$g['varrun_path']}/dmesg.boot"); + + return 0; +} + +function system_set_harddisk_standby() { + global $g, $config; + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_set_harddisk_standby() being called $mt\n"; + } + + if (isset($config['system']['harddiskstandby'])) { + if (platform_booting()) { + echo gettext('Setting hard disk standby... '); + } + + $standby = $config['system']['harddiskstandby']; + // Check for a numeric value + if (is_numeric($standby)) { + // Get only suitable candidates for standby; using get_smart_drive_list() + // from utils.inc to get the list of drives. + $harddisks = get_smart_drive_list(); + + // Since get_smart_drive_list() only matches ad|da|ada; lets put the check below + // just in case of some weird pfSense platform installs. + if (count($harddisks) > 0) { + // Iterate disks and run the camcontrol command for each + foreach ($harddisks as $harddisk) { + mwexec("/sbin/camcontrol standby {$harddisk} -t {$standby}"); + } + if (platform_booting()) { + echo gettext("done.") . "\n"; + } + } else if (platform_booting()) { + echo gettext("failed!") . "\n"; + } + } else if (platform_booting()) { + echo gettext("failed!") . "\n"; + } + } +} + +function system_setup_sysctl() { + global $config; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_setup_sysctl() being called $mt\n"; + } + + activate_sysctls(); + + if (isset($config['system']['sharednet'])) { + system_disable_arp_wrong_if(); + } +} + +function system_disable_arp_wrong_if() { + global $config; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_disable_arp_wrong_if() being called $mt\n"; + } + set_sysctl(array( + "net.link.ether.inet.log_arp_wrong_iface" => "0", + "net.link.ether.inet.log_arp_movements" => "0" + )); +} + +function system_enable_arp_wrong_if() { + global $config; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_enable_arp_wrong_if() being called $mt\n"; + } + set_sysctl(array( + "net.link.ether.inet.log_arp_wrong_iface" => "1", + "net.link.ether.inet.log_arp_movements" => "1" + )); +} + +function enable_watchdog() { + global $config; + return; + $install_watchdog = false; + $supported_watchdogs = array("Geode"); + $file = file_get_contents("/var/log/dmesg.boot"); + foreach ($supported_watchdogs as $sd) { + if (stristr($file, "Geode")) { + $install_watchdog = true; + } + } + if ($install_watchdog == true) { + if (is_process_running("watchdogd")) { + mwexec("/usr/bin/killall watchdogd", true); + } + exec("/usr/sbin/watchdogd"); + } +} + +function system_check_reset_button() { + global $g; + + $specplatform = system_identify_specific_platform(); + + switch ($specplatform['name']) { + case 'SG-2220': + $binprefix = "RCC-DFF"; + break; + case 'alix': + case 'wrap': + case 'FW7541': + case 'APU': + case 'RCC-VE': + case 'RCC': + $binprefix = $specplatform['name']; + break; + default: + return 0; + } + + $retval = mwexec("/usr/local/sbin/" . $binprefix . "resetbtn"); + + if ($retval == 99) { + /* user has pressed reset button for 2 seconds - + reset to factory defaults */ + echo <<= 10 && strlen($serial) <= 16 && + $vm_guest == 'none') { + return $serial; + } + + return ""; +} + +function system_get_uniqueid() { + global $g; + + $uniqueid_file="{$g['vardb_path']}/uniqueid"; + + if (empty($g['uniqueid'])) { + if (!file_exists($uniqueid_file)) { + mwexec("/usr/sbin/gnid > {$g['vardb_path']}/uniqueid " . + "2>/dev/null"); + } + if (file_exists($uniqueid_file)) { + $g['uniqueid'] = @file_get_contents($uniqueid_file); + } + } + + return ($g['uniqueid'] ?: ''); +} + +/* + * attempt to identify the specific platform (for embedded systems) + * Returns an array with two elements: + * name => platform string (e.g. 'wrap', 'alix' etc.) + * descr => human-readable description (e.g. "PC Engines WRAP") + */ +function system_identify_specific_platform() { + global $g; + + $hw_model = get_single_sysctl('hw.model'); + $hw_ncpu = get_single_sysctl('hw.ncpu'); + + /* Try to guess from smbios strings */ + unset($product); + unset($maker); + $_gb = exec('/bin/kenv -q smbios.system.product 2>/dev/null', $product); + $_gb = exec('/bin/kenv -q smbios.system.maker 2>/dev/null', $maker); + switch ($product[0]) { + case 'FW7541': + return (array('name' => 'FW7541', 'descr' => 'Netgate FW7541')); + break; + case 'APU': + return (array('name' => 'APU', 'descr' => 'Netgate APU')); + break; + case 'RCC-VE': + $result = array(); + $result['name'] = 'RCC-VE'; + + /* Detect specific models */ + if (!function_exists('does_interface_exist')) { + require_once("interfaces.inc"); + } + if (!does_interface_exist('igb4')) { + $result['model'] = 'SG-2440'; + } elseif (strpos($hw_model, "C2558") !== false) { + $result['model'] = 'SG-4860'; + } elseif (strpos($hw_model, "C2758") !== false) { + $result['model'] = 'SG-8860'; + } else { + $result['model'] = 'RCC-VE'; + } + $result['descr'] = 'Netgate ' . $result['model']; + return $result; + break; + case 'DFFv2': + return (array('name' => 'SG-2220', 'descr' => 'Netgate SG-2220')); + break; + case 'RCC': + return (array('name' => 'RCC', 'descr' => 'Netgate XG-2758')); + break; + case 'SG-5100': + return (array('name' => 'SG-5100', 'descr' => 'Netgate SG-5100')); + break; + case 'Minnowboard Turbot D0 PLATFORM': + $result = array(); + $result['name'] = 'Turbot Dual-E'; + /* Detect specific model */ + switch ($hw_ncpu) { + case '4': + $result['model'] = 'MBT-4220'; + break; + case '2': + $result['model'] = 'MBT-2220'; + break; + default: + $result['model'] = $result['name']; + break; + } + $result['descr'] = 'Netgate ' . $result['model']; + return $result; + break; + case 'SYS-5018A-FTN4': + case 'A1SAi': + if (strpos($hw_model, "C2558") !== false) { + return (array( + 'name' => 'C2558', + 'descr' => 'Super Micro C2558')); + } elseif (strpos($hw_model, "C2758") !== false) { + return (array( + 'name' => 'C2758', + 'descr' => 'Super Micro C2758')); + } + break; + case 'SYS-5018D-FN4T': + if (strpos($hw_model, "D-1541") !== false) { + return (array('name' => 'XG-1541', 'descr' => 'Super Micro XG-1541')); + } else { + return (array('name' => 'XG-1540', 'descr' => 'Super Micro XG-1540')); + } + break; + case 'apu2': + case 'APU2': + return (array('name' => 'apu2', 'descr' => 'PC Engines APU2')); + break; + case 'VirtualBox': + return (array('name' => 'VirtualBox', 'descr' => 'VirtualBox Virtual Machine')); + break; + case 'Virtual Machine': + if ($maker[0] == "Microsoft Corporation") { + return (array('name' => 'Hyper-V', 'descr' => 'Hyper-V Virtual Machine')); + } + break; + case 'VMware Virtual Platform': + if ($maker[0] == "VMware, Inc.") { + return (array('name' => 'VMware', 'descr' => 'VMware Virtual Machine')); + } + break; + } + + $_gb = exec('/bin/kenv -q smbios.planar.product 2>/dev/null', + $planar_product); + if (isset($planar_product[0]) && + $planar_product[0] == 'X10SDV-8C-TLN4F+') { + return array('name' => 'XG-1537', 'descr' => 'Super Micro XG-1537'); + } + + if (strpos($hw_model, "PC Engines WRAP") !== false) { + return array('name' => 'wrap', 'descr' => gettext('PC Engines WRAP')); + } + + if (strpos($hw_model, "PC Engines ALIX") !== false) { + return array('name' => 'alix', 'descr' => gettext('PC Engines ALIX')); + } + + if (preg_match("/Soekris net45../", $hw_model, $matches)) { + return array('name' => 'net45xx', 'descr' => $matches[0]); + } + + if (preg_match("/Soekris net48../", $hw_model, $matches)) { + return array('name' => 'net48xx', 'descr' => $matches[0]); + } + + if (preg_match("/Soekris net55../", $hw_model, $matches)) { + return array('name' => 'net55xx', 'descr' => $matches[0]); + } + + unset($hw_model); + + $dmesg_boot = system_get_dmesg_boot(); + if (strpos($dmesg_boot, "PC Engines ALIX") !== false) { + return array('name' => 'alix', 'descr' => gettext('PC Engines ALIX')); + } + unset($dmesg_boot); + + return array('name' => $g['platform'], 'descr' => $g['platform']); +} + +function system_get_dmesg_boot() { + global $g; + + return file_get_contents("{$g['varlog_path']}/dmesg.boot"); +} + +?> diff --git a/pfSense-pkg-API/files/etc/inc/api/framework/overrides/2.5.0-RELEASE/system.inc b/pfSense-pkg-API/files/etc/inc/api/framework/overrides/2.5.0-RELEASE/system.inc new file mode 100644 index 000000000..86907a45a --- /dev/null +++ b/pfSense-pkg-API/files/etc/inc/api/framework/overrides/2.5.0-RELEASE/system.inc @@ -0,0 +1,2670 @@ +. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +require_once('syslog.inc'); + +function activate_powerd() { + global $config, $g; + + if (is_process_running("powerd")) { + exec("/usr/bin/killall powerd"); + } + if (isset($config['system']['powerd_enable'])) { + $ac_mode = "hadp"; + if (!empty($config['system']['powerd_ac_mode'])) { + $ac_mode = $config['system']['powerd_ac_mode']; + } + + $battery_mode = "hadp"; + if (!empty($config['system']['powerd_battery_mode'])) { + $battery_mode = $config['system']['powerd_battery_mode']; + } + + $normal_mode = "hadp"; + if (!empty($config['system']['powerd_normal_mode'])) { + $normal_mode = $config['system']['powerd_normal_mode']; + } + + mwexec("/usr/sbin/powerd" . + " -b " . escapeshellarg($battery_mode) . + " -a " . escapeshellarg($ac_mode) . + " -n " . escapeshellarg($normal_mode)); + } +} + +function get_default_sysctl_value($id) { + global $sysctls; + + if (isset($sysctls[$id])) { + return $sysctls[$id]; + } +} + +function get_sysctl_descr($sysctl) { + unset($output); + $_gb = exec("/sbin/sysctl -qnd {$sysctl}", $output); + + return $output[0]; +} + +function system_get_sysctls() { + global $config, $sysctls; + + $disp_sysctl = array(); + $disp_cache = array(); + if (is_array($config['sysctl']) && is_array($config['sysctl']['item'])) { + foreach ($config['sysctl']['item'] as $id => $tunable) { + if ($tunable['value'] == "default") { + $value = get_default_sysctl_value($tunable['tunable']); + } else { + $value = $tunable['value']; + } + + $disp_sysctl[$id] = $tunable; + $disp_sysctl[$id]['modified'] = true; + $disp_cache[$tunable['tunable']] = 'set'; + } + } + + foreach ($sysctls as $sysctl => $value) { + if (isset($disp_cache[$sysctl])) { + continue; + } + + $disp_sysctl[$sysctl] = array('tunable' => $sysctl, 'value' => $value, 'descr' => get_sysctl_descr($sysctl)); + } + unset($disp_cache); + return $disp_sysctl; +} + +function activate_sysctls() { + global $config, $g, $sysctls; + + if (is_array($config['sysctl']) && is_array($config['sysctl']['item'])) { + foreach ($config['sysctl']['item'] as $tunable) { + if ($tunable['value'] == "default") { + $value = get_default_sysctl_value($tunable['tunable']); + } else { + $value = $tunable['value']; + } + + $sysctls[$tunable['tunable']] = $value; + } + } + + /* Set net.pf.request_maxcount via sysctl since it is no longer a loader + * tunable. See https://redmine.pfsense.org/issues/10861 + * Set the value dynamically since its default is not static, yet this + * still could be overridden by a user tunable. */ + if (isset($config['system']['maximumtableentries'])) { + $maximumtableentries = $config['system']['maximumtableentries']; + } else { + $maximumtableentries = pfsense_default_table_entries_size(); + } + /* Set the default when there is no tunable or when the tunable is set + * too low. */ + if (empty($sysctls['net.pf.request_maxcount']) || + ($sysctls['net.pf.request_maxcount'] < $maximumtableentries)) { + $sysctls['net.pf.request_maxcount'] = $maximumtableentries; + } + + set_sysctl($sysctls); +} + +function system_resolvconf_generate($dynupdate = false) { + global $config, $g; + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_resolvconf_generate() being called $mt\n"; + } + + $syscfg = $config['system']; + + foreach(get_dns_nameservers(false, false) as $dns_ns) { + $resolvconf .= "nameserver $dns_ns\n"; + } + + $ns = array(); + if (isset($syscfg['dnsallowoverride'])) { + /* get dynamically assigned DNS servers (if any) */ + $ns = array_unique(get_searchdomains()); + foreach ($ns as $searchserver) { + if ($searchserver) { + $resolvconf .= "search {$searchserver}\n"; + } + } + } + if (empty($ns)) { + // Do not create blank search/domain lines, it can break tools like dig. + if ($syscfg['domain']) { + $resolvconf .= "search {$syscfg['domain']}\n"; + } + } + + // Add EDNS support + if (isset($config['unbound']['enable']) && isset($config['unbound']['edns'])) { + $resolvconf .= "options edns0\n"; + } + + $dnslock = lock('resolvconf', LOCK_EX); + + $fd = fopen("{$g['etc_path']}/resolv.conf", "w"); + if (!$fd) { + printf("Error: cannot open resolv.conf in system_resolvconf_generate().\n"); + unlock($dnslock); + return 1; + } + + fwrite($fd, $resolvconf); + fclose($fd); + + // Prevent resolvconf(8) from rewriting our resolv.conf + $fd = fopen("{$g['etc_path']}/resolvconf.conf", "w"); + if (!$fd) { + printf("Error: cannot open resolvconf.conf in system_resolvconf_generate().\n"); + return 1; + } + fwrite($fd, "resolv_conf=\"/dev/null\"\n"); + fclose($fd); + + if (!platform_booting()) { + /* restart dhcpd (nameservers may have changed) */ + if (!$dynupdate) { + services_dhcpd_configure(); + } + } + + // set up or tear down static routes for DNS servers + $dnscounter = 1; + $dnsgw = "dns{$dnscounter}gw"; + while (isset($config['system'][$dnsgw])) { + /* setup static routes for dns servers */ + $gwname = $config['system'][$dnsgw]; + unset($gatewayip); + unset($inet6); + if ((!empty($gwname)) && ($gwname != "none")) { + $gatewayip = lookup_gateway_ip_by_name($gwname); + $inet6 = is_ipaddrv6($gatewayip) ? '-inet6 ' : ''; + } + /* dns server array starts at 0 */ + $dnsserver = $syscfg['dnsserver'][$dnscounter - 1]; + if (!empty($dnsserver)) { + if (is_ipaddr($gatewayip)) { + route_add_or_change($dnsserver, $gatewayip); + } else { + /* Remove old route when disable gw */ + route_del($dnsserver); + } + } + $dnscounter++; + $dnsgw = "dns{$dnscounter}gw"; + } + + unlock($dnslock); + + return 0; +} + +function get_searchdomains() { + global $config, $g; + + $master_list = array(); + + // Read in dhclient nameservers + $search_list = glob("/var/etc/searchdomain_*"); + if (is_array($search_list)) { + foreach ($search_list as $fdns) { + $contents = file($fdns, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($contents)) { + continue; + } + foreach ($contents as $dns) { + if (is_hostname($dns)) { + $master_list[] = $dns; + } + } + } + } + + return $master_list; +} + +/* Stub for deprecated function name + * See https://redmine.pfsense.org/issues/10931 */ +function get_nameservers() { + return get_dynamic_nameservers(); +} + +/****f* system.inc/get_dynamic_nameservers + * NAME + * get_dynamic_nameservers - Get DNS servers from dynamic sources (DHCP, PPP, etc) + * INPUTS + * $iface: Interface name used to filter results. + * RESULT + * $master_list - Array containing DNS servers + ******/ +function get_dynamic_nameservers($iface = '') { + global $config, $g; + $master_list = array(); + + if (!empty($iface)) { + $realif = get_real_interface($iface); + } + + // Read in dynamic nameservers + $dns_lists = array_merge(glob("/var/etc/nameserver_{$realif}*"), glob("/var/etc/nameserver_v6{$iface}*")); + if (is_array($dns_lists)) { + foreach ($dns_lists as $fdns) { + $contents = file($fdns, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($contents)) { + continue; + } + foreach ($contents as $dns) { + if (is_ipaddr($dns)) { + $master_list[] = $dns; + } + } + } + } + + return $master_list; +} + +/* Create localhost + local interfaces entries for /etc/hosts */ +function system_hosts_local_entries() { + global $config; + + $syscfg = $config['system']; + + $hosts = array(); + $hosts[] = array( + 'ipaddr' => '127.0.0.1', + 'fqdn' => 'localhost.' . $syscfg['domain'], + 'name' => 'localhost', + 'domain' => $syscfg['domain'] + ); + $hosts[] = array( + 'ipaddr' => '::1', + 'fqdn' => 'localhost.' . $syscfg['domain'], + 'name' => 'localhost', + 'domain' => $syscfg['domain'] + ); + + if ($config['interfaces']['lan']) { + $sysiflist = array('lan' => "lan"); + } else { + $sysiflist = get_configured_interface_list(); + } + + $hosts_if_found = false; + $local_fqdn = "{$syscfg['hostname']}.{$syscfg['domain']}"; + foreach ($sysiflist as $sysif) { + if ($sysif != 'lan' && interface_has_gateway($sysif)) { + continue; + } + $cfgip = get_interface_ip($sysif); + if (is_ipaddrv4($cfgip)) { + $hosts[] = array( + 'ipaddr' => $cfgip, + 'fqdn' => $local_fqdn, + 'name' => $syscfg['hostname'], + 'domain' => $syscfg['domain'] + ); + $hosts_if_found = true; + } + if (!isset($syscfg['ipv6dontcreatelocaldns'])) { + $cfgipv6 = get_interface_ipv6($sysif); + if (is_ipaddrv6($cfgipv6)) { + $hosts[] = array( + 'ipaddr' => $cfgipv6, + 'fqdn' => $local_fqdn, + 'name' => $syscfg['hostname'], + 'domain' => $syscfg['domain'] + ); + $hosts_if_found = true; + } + } + if ($hosts_if_found == true) { + break; + } + } + + return $hosts; +} + +/* Read host override entries from dnsmasq or unbound */ +function system_hosts_override_entries($dnscfg) { + $hosts = array(); + + if (!is_array($dnscfg) || + !is_array($dnscfg['hosts']) || + !isset($dnscfg['enable'])) { + return $hosts; + } + + foreach ($dnscfg['hosts'] as $host) { + $fqdn = ''; + if ($host['host'] || $host['host'] == "0") { + $fqdn .= "{$host['host']}."; + } + $fqdn .= $host['domain']; + + foreach (explode(',', $host['ip']) as $ip) { + $hosts[] = array( + 'ipaddr' => $ip, + 'fqdn' => $fqdn, + 'name' => $host['host'], + 'domain' => $host['domain'] + ); + } + + if (!is_array($host['aliases']) || + !is_array($host['aliases']['item'])) { + continue; + } + + foreach ($host['aliases']['item'] as $alias) { + $fqdn = ''; + if ($alias['host'] || $alias['host'] == "0") { + $fqdn .= "{$alias['host']}."; + } + $fqdn .= $alias['domain']; + + foreach (explode(',', $host['ip']) as $ip) { + $hosts[] = array( + 'ipaddr' => $ip, + 'fqdn' => $fqdn, + 'name' => $alias['host'], + 'domain' => $alias['domain'] + ); + } + } + } + + return $hosts; +} + +/* Read all dhcpd/dhcpdv6 staticmap entries */ +function system_hosts_dhcpd_entries() { + global $config; + + $hosts = array(); + $syscfg = $config['system']; + + if (is_array($config['dhcpd'])) { + $conf_dhcpd = $config['dhcpd']; + } else { + $conf_dhcpd = array(); + } + + foreach ($conf_dhcpd as $dhcpif => $dhcpifconf) { + if (!is_array($dhcpifconf['staticmap']) || + !isset($dhcpifconf['enable'])) { + continue; + } + foreach ($dhcpifconf['staticmap'] as $host) { + if (!$host['ipaddr'] || + !$host['hostname']) { + continue; + } + + $fqdn = $host['hostname'] . "."; + $domain = ""; + if ($host['domain']) { + $domain = $host['domain']; + } elseif ($dhcpifconf['domain']) { + $domain = $dhcpifconf['domain']; + } else { + $domain = $syscfg['domain']; + } + + $hosts[] = array( + 'ipaddr' => $host['ipaddr'], + 'fqdn' => $fqdn . $domain, + 'name' => $host['hostname'], + 'domain' => $domain + ); + } + } + unset($conf_dhcpd); + + if (is_array($config['dhcpdv6'])) { + $conf_dhcpdv6 = $config['dhcpdv6']; + } else { + $conf_dhcpdv6 = array(); + } + + foreach ($conf_dhcpdv6 as $dhcpif => $dhcpifconf) { + if (!is_array($dhcpifconf['staticmap']) || + !isset($dhcpifconf['enable'])) { + continue; + } + + if (isset($config['interfaces'][$dhcpif]['ipaddrv6']) && + $config['interfaces'][$dhcpif]['ipaddrv6'] == + 'track6') { + $isdelegated = true; + } else { + $isdelegated = false; + } + + foreach ($dhcpifconf['staticmap'] as $host) { + $ipaddrv6 = $host['ipaddrv6']; + + if (!$ipaddrv6 || !$host['hostname']) { + continue; + } + + if ($isdelegated) { + /* + * We are always in an "end-user" subnet + * here, which all are /64 for IPv6. + */ + $prefix6 = 64; + } else { + $prefix6 = get_interface_subnetv6($dhcpif); + } + $ipaddrv6 = merge_ipv6_delegated_prefix(get_interface_ipv6($dhcpif), $ipaddrv6, $prefix6); + + $fqdn = $host['hostname'] . "."; + $domain = ""; + if ($host['domain']) { + $domain = $host['domain']; + } elseif ($dhcpifconf['domain']) { + $domain = $dhcpifconf['domain']; + } else { + $domain = $syscfg['domain']; + } + + $hosts[] = array( + 'ipaddr' => $ipaddrv6, + 'fqdn' => $fqdn . $domain, + 'name' => $host['hostname'], + 'domain' => $domain + ); + } + } + unset($conf_dhcpdv6); + + return $hosts; +} + +/* Concatenate local, dnsmasq/unbound and dhcpd/dhcpdv6 hosts entries */ +function system_hosts_entries($dnscfg) { + $local = array(); + if (!isset($dnscfg['disable_auto_added_host_entries'])) { + $local = system_hosts_local_entries(); + } + + $dns = array(); + $dhcpd = array(); + if (isset($dnscfg['enable'])) { + $dns = system_hosts_override_entries($dnscfg); + if (isset($dnscfg['regdhcpstatic'])) { + $dhcpd = system_hosts_dhcpd_entries(); + } + } + + if (isset($dnscfg['dhcpfirst'])) { + return array_merge($local, $dns, $dhcpd); + } else { + return array_merge($local, $dhcpd, $dns); + } +} + +function system_hosts_generate() { + global $config, $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_hosts_generate() being called $mt\n"; + } + + // prefer dnsmasq for hosts generation where it's enabled. It relies + // on hosts for name resolution of its overrides, unbound does not. + if (isset($config['dnsmasq']) && isset($config['dnsmasq']['enable'])) { + $dnsmasqcfg = $config['dnsmasq']; + } else { + $dnsmasqcfg = $config['unbound']; + } + + $syscfg = $config['system']; + $hosts = ""; + $lhosts = ""; + $dhosts = ""; + + $hosts_array = system_hosts_entries($dnsmasqcfg); + foreach ($hosts_array as $host) { + $hosts .= "{$host['ipaddr']}\t"; + if ($host['name'] == "localhost") { + $hosts .= "{$host['name']} {$host['fqdn']}"; + } else { + $hosts .= "{$host['fqdn']} {$host['name']}"; + } + $hosts .= "\n"; + } + unset($hosts_array); + + /* + * Do not remove this because dhcpleases monitors with kqueue it needs + * to be killed before writing to hosts files. + */ + if (file_exists("{$g['varrun_path']}/dhcpleases.pid")) { + sigkillbypid("{$g['varrun_path']}/dhcpleases.pid", "TERM"); + @unlink("{$g['varrun_path']}/dhcpleases.pid"); + } + + $fd = fopen("{$g['etc_path']}/hosts", "w"); + if (!$fd) { + log_error(gettext( + "Error: cannot open hosts file in system_hosts_generate()." + )); + return 1; + } + + fwrite($fd, $hosts); + fclose($fd); + + if (isset($config['unbound']['enable'])) { + require_once("unbound.inc"); + unbound_hosts_generate(); + } + + /* restart dhcpleases */ + if (!platform_booting()) { + system_dhcpleases_configure(); + } + + return 0; +} + +function system_dhcpleases_configure() { + global $config, $g; + if (!function_exists('is_dhcp_server_enabled')) { + require_once('pfsense-utils.inc'); + } + $pidfile = "{$g['varrun_path']}/dhcpleases.pid"; + + /* Start the monitoring process for dynamic dhcpclients. */ + if (((isset($config['dnsmasq']['enable']) && isset($config['dnsmasq']['regdhcp'])) || + (isset($config['unbound']['enable']) && isset($config['unbound']['regdhcp']))) && + (is_dhcp_server_enabled())) { + /* Make sure we do not error out */ + mwexec("/bin/mkdir -p {$g['dhcpd_chroot_path']}/var/db"); + if (!file_exists("{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases")) { + @touch("{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases"); + } + + if (isset($config['unbound']['enable'])) { + $dns_pid = "unbound.pid"; + $unbound_conf = "-u {$g['unbound_chroot_path']}/dhcpleases_entries.conf"; + } else { + $dns_pid = "dnsmasq.pid"; + $unbound_conf = ""; + } + + if (isvalidpid($pidfile)) { + /* Make sure dhcpleases is using correct unbound or dnsmasq */ + $_gb = exec("/bin/pgrep -F {$pidfile} -f {$dns_pid}", $output, $retval); + if (intval($retval) == 0) { + sigkillbypid($pidfile, "HUP"); + return; + } else { + sigkillbypid($pidfile, "TERM"); + } + } + + /* To ensure we do not start multiple instances of dhcpleases, perform some clean-up first. */ + if (is_process_running("dhcpleases")) { + sigkillbyname('dhcpleases', "TERM"); + } + @unlink($pidfile); + mwexec("/usr/local/sbin/dhcpleases -l {$g['dhcpd_chroot_path']}/var/db/dhcpd.leases -d {$config['system']['domain']} -p {$g['varrun_path']}/{$dns_pid} {$unbound_conf} -h {$g['etc_path']}/hosts"); + } else { + if (isvalidpid($pidfile)) { + sigkillbypid($pidfile, "TERM"); + @unlink($pidfile); + } + if (file_exists("{$g['unbound_chroot_path']}/dhcpleases_entries.conf")) { + $dhcpleases = fopen("{$g['unbound_chroot_path']}/dhcpleases_entries.conf", "w"); + ftruncate($dhcpleases, 0); + fclose($dhcpleases); + } + } +} + +function system_get_dhcpleases() { + global $config, $g; + + $leases = array(); + $leases['lease'] = array(); + $leases['failover'] = array(); + + $leases_file = "{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases"; + + if (!file_exists($leases_file)) { + return $leases; + } + + $leases_content = file($leases_file, FILE_IGNORE_NEW_LINES | + FILE_IGNORE_NEW_LINES); + + if ($leases_content === FALSE) { + return $leases; + } + + $arp_table = system_get_arp_table(); + + $arpdata_ip = array(); + $arpdata_mac = array(); + foreach ($arp_table as $arp_entry) { + if (isset($arpentry['incomplete'])) { + continue; + } + $arpdata_ip[] = $arp_entry['ip-address']; + $arpdata_mac[] = $arp_entry['mac-address']; + } + unset($arp_table); + + /* + * Translate these once so we don't do it over and over in the loops + * below. + */ + $online_string = gettext("online"); + $offline_string = gettext("offline"); + $active_string = gettext("active"); + $expired_string = gettext("expired"); + $reserved_string = gettext("reserved"); + $dynamic_string = gettext("dynamic"); + $static_string = gettext("static"); + + $lease_regex = '/^lease\s+([^\s]+)\s+{$/'; + $starts_regex = '/^\s*(starts|ends)\s+\d+\s+([\d\/]+|never)\s*(|[\d:]*);$/'; + $binding_regex = '/^\s*binding\s+state\s+(.+);$/'; + $mac_regex = '/^\s*hardware\s+ethernet\s+(.+);$/'; + $hostname_regex = '/^\s*client-hostname\s+"(.+)";$/'; + + $failover_regex = '/^failover\s+peer\s+"(.+)"\s+state\s+{$/'; + $state_regex = '/\s*(my|partner)\s+state\s+(.+)\s+at\s+\d+\s+([\d\/]+)\s+([\d:]+);$/'; + + $lease = false; + $failover = false; + $dedup_lease = false; + $dedup_failover = false; + foreach ($leases_content as $line) { + /* Skip comments */ + if (preg_match('/^\s*(|#.*)$/', $line)) { + continue; + } + + if (preg_match('/}$/', $line)) { + if ($lease) { + if (empty($item['hostname'])) { + $hostname = gethostbyaddr($item['ip']); + if (!empty($hostname)) { + $item['hostname'] = $hostname; + } + } + $leases['lease'][] = $item; + $lease = false; + $dedup_lease = true; + } else if ($failover) { + $leases['failover'][] = $item; + $failover = false; + $dedup_failover = true; + } + continue; + } + + if (preg_match($lease_regex, $line, $m)) { + $lease = true; + $item = array(); + $item['ip'] = $m[1]; + $item['type'] = $dynamic_string; + continue; + } + + if ($lease) { + if (preg_match($starts_regex, $line, $m)) { + /* + * Quote from dhcpd.leases(5) man page: + * If a lease will never expire, date is never + * instead of an actual date + */ + if ($m[2] == "never") { + $item[$m[1]] = gettext("Never"); + } else { + $item[$m[1]] = dhcpd_date_adjust_gmt( + $m[2] . ' ' . $m[3]); + } + continue; + } + + if (preg_match($binding_regex, $line, $m)) { + switch ($m[1]) { + case "active": + $item['act'] = $active_string; + break; + case "free": + $item['act'] = $expired_string; + $item['online'] = + $offline_string; + break; + case "backup": + $item['act'] = $reserved_string; + $item['online'] = + $offline_string; + break; + } + continue; + } + + if (preg_match($mac_regex, $line, $m) && + is_macaddr($m[1])) { + $item['mac'] = $m[1]; + + if (in_array($item['ip'], $arpdata_ip)) { + $item['online'] = $online_string; + } else { + $item['online'] = $offline_string; + } + continue; + } + + if (preg_match($hostname_regex, $line, $m)) { + $item['hostname'] = $m[1]; + } + } + + if (preg_match($failover_regex, $line, $m)) { + $failover = true; + $item = array(); + $item['name'] = $m[1] . ' (' . + convert_friendly_interface_to_friendly_descr( + substr($m[1],5)) . ')'; + continue; + } + + if ($failover && preg_match($state_regex, $line, $m)) { + $item[$m[1] . 'state'] = $m[2]; + $item[$m[1] . 'date'] = dhcpd_date_adjust_gmt($m[3] . + ' ' . $m[4]); + continue; + } + } + + foreach ($config['interfaces'] as $ifname => $ifarr) { + if (!is_array($config['dhcpd'][$ifname]) || + !is_array($config['dhcpd'][$ifname]['staticmap'])) { + continue; + } + + foreach ($config['dhcpd'][$ifname]['staticmap'] as $idx => + $static) { + if (empty($static['mac']) && empty($static['cid'])) { + continue; + } + + $slease = array(); + $slease['ip'] = $static['ipaddr']; + $slease['type'] = $static_string; + if (!empty($static['cid'])) { + $slease['cid'] = $static['cid']; + } + $slease['mac'] = $static['mac']; + $slease['if'] = $ifname; + $slease['starts'] = ""; + $slease['ends'] = ""; + $slease['hostname'] = $static['hostname']; + $slease['descr'] = $static['descr']; + $slease['act'] = $static_string; + $slease['online'] = in_array(strtolower($slease['mac']), + $arpdata_mac) ? $online_string : $offline_string; + $slease['staticmap_array_index'] = $idx; + $leases['lease'][] = $slease; + $dedup_lease = true; + } + } + + if ($dedup_lease) { + $leases['lease'] = array_remove_duplicate($leases['lease'], + 'ip'); + } + if ($dedup_failover) { + $leases['failover'] = array_remove_duplicate( + $leases['failover'], 'name'); + asort($leases['failover']); + } + + return $leases; +} + +function system_hostname_configure() { + global $config, $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_hostname_configure() being called $mt\n"; + } + + $syscfg = $config['system']; + + /* set hostname */ + $status = mwexec("/bin/hostname " . + escapeshellarg("{$syscfg['hostname']}.{$syscfg['domain']}")); + + /* Setup host GUID ID. This is used by ZFS. */ + mwexec("/etc/rc.d/hostid start"); + + return $status; +} + +function system_routing_configure($interface = "") { + global $config, $g; + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_routing_configure() being called $mt\n"; + } + + $gateways_arr = return_gateways_array(false, true); + foreach ($gateways_arr as $gateway) { + // setup static interface routes for nonlocal gateways + if (isset($gateway["nonlocalgateway"])) { + $srgatewayip = $gateway['gateway']; + $srinterfacegw = $gateway['interface']; + if (is_ipaddr($srgatewayip) && !empty($srinterfacegw)) { + route_add_or_change($srgatewayip, '', + $srinterfacegw); + } + } + } + + $gateways_status = return_gateways_status(true); + fixup_default_gateway("inet", $gateways_status, $gateways_arr); + fixup_default_gateway("inet6", $gateways_status, $gateways_arr); + + system_staticroutes_configure($interface, false); + + return 0; +} + +function system_staticroutes_configure($interface = "", $update_dns = false) { + global $config, $g, $aliastable; + + $filterdns_list = array(); + + $static_routes = get_staticroutes(false, true); + if (count($static_routes)) { + $gateways_arr = return_gateways_array(false, true); + + foreach ($static_routes as $rtent) { + if (empty($gateways_arr[$rtent['gateway']])) { + log_error(sprintf(gettext("Static Routes: Gateway IP could not be found for %s"), $rtent['network'])); + continue; + } + $gateway = $gateways_arr[$rtent['gateway']]; + if (!empty($interface) && $interface != $gateway['friendlyiface']) { + continue; + } + + $gatewayip = $gateway['gateway']; + $interfacegw = $gateway['interface']; + + $blackhole = ""; + if (!strcasecmp("Null", substr($rtent['gateway'], 0, 4))) { + $blackhole = "-blackhole"; + } + + if (!is_fqdn($rtent['network']) && !is_subnet($rtent['network'])) { + continue; + } + + $dnscache = array(); + if ($update_dns === true) { + if (is_subnet($rtent['network'])) { + continue; + } + $dnscache = explode("\n", trim(compare_hostname_to_dnscache($rtent['network']))); + if (empty($dnscache)) { + continue; + } + } + + if (is_subnet($rtent['network'])) { + $ips = array($rtent['network']); + } else { + if (!isset($rtent['disabled'])) { + $filterdns_list[] = $rtent['network']; + } + $ips = add_hostname_to_watch($rtent['network']); + } + + foreach ($dnscache as $ip) { + if (in_array($ip, $ips)) { + continue; + } + route_del($ip); + } + + if (isset($rtent['disabled'])) { + /* + * XXX: This can break things by deleting + * routes that shouldn't be deleted - OpenVPN, + * dynamic routing scenarios, etc. + * redmine #3709 + */ + foreach ($ips as $ip) { + route_del($ip); + } + continue; + } + + foreach ($ips as $ip) { + if (is_ipaddrv4($ip)) { + $ip .= "/32"; + } + /* + * do NOT do the same check here on v6, + * is_ipaddrv6 returns true when including + * the CIDR mask. doing so breaks v6 routes + */ + if (is_subnet($ip)) { + if (is_ipaddr($gatewayip)) { + if (is_linklocal($gatewayip) == "6" && + !strpos($gatewayip, '%')) { + /* + * add interface scope + * for link local v6 + * routes + */ + $gatewayip .= "%$interfacegw"; + } + route_add_or_change($ip, + $gatewayip, '', $blackhole); + } else if (!empty($interfacegw)) { + route_add_or_change($ip, + '', $interfacegw, $blackhole); + } + } + } + } + unset($gateways_arr); + } + unset($static_routes); + + if ($update_dns === false) { + if (count($filterdns_list)) { + $interval = 60; + $hostnames = ""; + array_unique($filterdns_list); + foreach ($filterdns_list as $hostname) { + $hostnames .= "cmd {$hostname} '/usr/local/sbin/pfSctl -c \"service reload routedns\"'\n"; + } + file_put_contents("{$g['varetc_path']}/filterdns-route.hosts", $hostnames); + unset($hostnames); + + if (isvalidpid("{$g['varrun_path']}/filterdns-route.pid")) { + sigkillbypid("{$g['varrun_path']}/filterdns-route.pid", "HUP"); + } else { + mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-route.pid -i {$interval} -c {$g['varetc_path']}/filterdns-route.hosts -d 1"); + } + } else { + killbypid("{$g['varrun_path']}/filterdns-route.pid"); + @unlink("{$g['varrun_path']}/filterdns-route.pid"); + } + } + unset($filterdns_list); + + return 0; +} + +function system_routing_enable() { + global $config, $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_routing_enable() being called $mt\n"; + } + + set_sysctl(array( + "net.inet.ip.forwarding" => "1", + "net.inet6.ip6.forwarding" => "1" + )); + + return; +} + +function system_webgui_create_certificate() { + global $config, $g, $cert_strict_values; + + init_config_arr(array('ca')); + $a_ca = &$config['ca']; + init_config_arr(array('cert')); + $a_cert = &$config['cert']; + log_error(gettext("Creating SSL/TLS Certificate for this host")); + + $cert = array(); + $cert['refid'] = uniqid(); + $cert['descr'] = sprintf(gettext("webConfigurator default (%s)"), $cert['refid']); + $cert_hostname = "{$config['system']['hostname']}-{$cert['refid']}"; + + $dn = array( + 'organizationName' => "{$g['product_label']} webConfigurator Self-Signed Certificate", + 'commonName' => $cert_hostname, + 'subjectAltName' => "DNS:{$cert_hostname}"); + $old_err_level = error_reporting(0); /* otherwise openssl_ functions throw warnings directly to a page screwing menu tab */ + if (!cert_create($cert, null, 2048, $cert_strict_values['max_server_cert_lifetime'], $dn, "self-signed", "sha256")) { + while ($ssl_err = openssl_error_string()) { + log_error(sprintf(gettext("Error creating WebGUI Certificate: openssl library returns: %s"), $ssl_err)); + } + error_reporting($old_err_level); + return null; + } + error_reporting($old_err_level); + + $a_cert[] = $cert; + $config['system']['webgui']['ssl-certref'] = $cert['refid']; + write_config(sprintf(gettext("Generated new self-signed SSL/TLS certificate for HTTPS (%s)"), $cert['refid'])); + return $cert; +} + +function system_webgui_start() { + global $config, $g; + + if (platform_booting()) { + echo gettext("Starting webConfigurator..."); + } + + chdir($g['www_path']); + + /* defaults */ + $portarg = "80"; + $crt = ""; + $key = ""; + $ca = ""; + + /* non-standard port? */ + if (isset($config['system']['webgui']['port']) && $config['system']['webgui']['port'] <> "") { + $portarg = "{$config['system']['webgui']['port']}"; + } + + if ($config['system']['webgui']['protocol'] == "https") { + // Ensure that we have a webConfigurator CERT + $cert =& lookup_cert($config['system']['webgui']['ssl-certref']); + if (!is_array($cert) || !$cert['crt'] || !$cert['prv']) { + $cert = system_webgui_create_certificate(); + } + $crt = base64_decode($cert['crt']); + $key = base64_decode($cert['prv']); + + if (!$config['system']['webgui']['port']) { + $portarg = "443"; + } + $ca = ca_chain($cert); + $hsts = isset($config['system']['webgui']['disablehsts']) ? false : true; + } + + /* generate nginx configuration */ + system_generate_nginx_config("{$g['varetc_path']}/nginx-webConfigurator.conf", + $crt, $key, $ca, "nginx-webConfigurator.pid", $portarg, "/usr/local/www/", + "cert.crt", "cert.key", false, $hsts); + + /* kill any running nginx */ + killbypid("{$g['varrun_path']}/nginx-webConfigurator.pid"); + + sleep(1); + + @unlink("{$g['varrun_path']}/nginx-webConfigurator.pid"); + + /* start nginx */ + $res = mwexec("/usr/local/sbin/nginx -c {$g['varetc_path']}/nginx-webConfigurator.conf"); + + if (platform_booting()) { + if ($res == 0) { + echo gettext("done.") . "\n"; + } else { + echo gettext("failed!") . "\n"; + } + } + + return $res; +} + +/****f* system.inc/get_dns_nameservers + * NAME + * get_dns_nameservers - Get system DNS servers + * INPUTS + * $add_v6_brackets: (boolean, false) + * Add brackets around IPv6 DNS servers, as expected by some + * daemons such as nginx. + * $hostns : (boolean, true) + * true : Return only DNS servers used by the firewall + * itself as upstream forwarding servers + * false: Return all DNS servers from the configuration and + * overrides (if allowed). + * RESULT + * $dns_nameservers - An array of the requested DNS servers + ******/ +function get_dns_nameservers($add_v6_brackets = false, $hostns=true) { + global $config; + + $dns_nameservers = array(); + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "get_dns_nameservers() being called $mt\n"; + } + + $syscfg = $config['system']; + if ((((isset($config['dnsmasq']['enable'])) && + (empty($config['dnsmasq']['port']) || $config['dnsmasq']['port'] == "53") && + (empty($config['dnsmasq']['interface']) || + in_array("lo0", explode(",", $config['dnsmasq']['interface'])))) || + ((isset($config['unbound']['enable'])) && + (empty($config['unbound']['port']) || $config['unbound']['port'] == "53") && + (empty($config['unbound']['active_interface']) || + in_array("lo0", explode(",", $config['unbound']['active_interface'])) || + in_array("all", explode(",", $config['unbound']['active_interface']), true)))) && + ($config['system']['dnslocalhost'] != 'remote')) { + $dns_nameservers[] = "127.0.0.1"; + } + + if ($hostns || ($config['system']['dnslocalhost'] != 'local')) { + if (isset($syscfg['dnsallowoverride'])) { + /* get dynamically assigned DNS servers (if any) */ + foreach (array_unique(get_dynamic_nameservers()) as $nameserver) { + if ($nameserver) { + if ($add_v6_brackets && is_ipaddrv6($nameserver)) { + $nameserver = "[{$nameserver}]"; + } + $dns_nameservers[] = $nameserver; + } + } + } + if (is_array($syscfg['dnsserver'])) { + foreach ($syscfg['dnsserver'] as $sys_dnsserver) { + if ($sys_dnsserver && (!in_array($sys_dnsserver, $dns_nameservers))) { + if ($add_v6_brackets && is_ipaddrv6($sys_dnsserver)) { + $sys_dnsserver = "[{$sys_dnsserver}]"; + } + $dns_nameservers[] = $sys_dnsserver; + } + } + } + } + return array_unique($dns_nameservers); +} + +function system_generate_nginx_config($filename, + $cert, + $key, + $ca, + $pid_file, + $port = 80, + $document_root = "/usr/local/www/", + $cert_location = "cert.crt", + $key_location = "cert.key", + $captive_portal = false, + $hsts = true) { + + global $config, $g; + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_generate_nginx_config() being called $mt\n"; + } + + if ($captive_portal !== false) { + $cp_interfaces = explode(",", $config['captiveportal'][$captive_portal]['interface']); + $cp_hostcheck = ""; + foreach ($cp_interfaces as $cpint) { + $cpint_ip = get_interface_ip($cpint); + if (is_ipaddr($cpint_ip)) { + $cp_hostcheck .= "\t\tif (\$http_host ~* $cpint_ip) {\n"; + $cp_hostcheck .= "\t\t\tset \$cp_redirect no;\n"; + $cp_hostcheck .= "\t\t}\n"; + } + } + if (isset($config['captiveportal'][$captive_portal]['httpsname']) && + is_domain($config['captiveportal'][$captive_portal]['httpsname'])) { + $cp_hostcheck .= "\t\tif (\$http_host ~* {$config['captiveportal'][$captive_portal]['httpsname']}) {\n"; + $cp_hostcheck .= "\t\t\tset \$cp_redirect no;\n"; + $cp_hostcheck .= "\t\t}\n"; + } + $cp_rewrite = "\t\tif (\$cp_redirect = '') {\n"; + $cp_rewrite .= "\t\t\trewrite ^ /index.php?zone=$captive_portal&redirurl=\$request_uri break;\n"; + $cp_rewrite .= "\t\t}\n"; + + $maxprocperip = $config['captiveportal'][$captive_portal]['maxprocperip']; + if (empty($maxprocperip)) { + $maxprocperip = 10; + } + $captive_portal_maxprocperip = "\t\tlimit_conn addr $maxprocperip;\n"; + } + + if (empty($port)) { + $nginx_port = "80"; + } else { + $nginx_port = $port; + } + + $memory = get_memory(); + $realmem = $memory[1]; + + // Determine web GUI process settings and take into account low memory systems + if ($realmem < 255) { + $max_procs = 1; + } else { + $max_procs = ($config['system']['webgui']['max_procs']) ? $config['system']['webgui']['max_procs'] : 2; + } + + // Ramp up captive portal max procs, assuming each PHP process can consume up to 64MB RAM + if ($captive_portal !== false) { + if ($realmem > 135 and $realmem < 256) { + $max_procs += 1; // 2 worker processes + } else if ($realmem > 255 and $realmem < 513) { + $max_procs += 2; // 3 worker processes + } else if ($realmem > 512) { + $max_procs += 4; // 6 worker processes + } + } + + $nginx_config = << "" and $key <> "") { + $nginx_config .= "\n"; + $nginx_config .= "\tserver {\n"; + $nginx_config .= "\t\tlisten {$nginx_port} ssl http2;\n"; + $nginx_config .= "\t\tlisten [::]:{$nginx_port} ssl http2;\n"; + $nginx_config .= "\n"; + $nginx_config .= "\t\tssl_certificate {$g['varetc_path']}/{$cert_location};\n"; + $nginx_config .= "\t\tssl_certificate_key {$g['varetc_path']}/{$key_location};\n"; + $nginx_config .= "\t\tssl_session_timeout 10m;\n"; + $nginx_config .= "\t\tkeepalive_timeout 70;\n"; + $nginx_config .= "\t\tssl_session_cache shared:SSL:10m;\n"; + if ($captive_portal !== false) { + // leave TLSv1.1 for CP for now for compatibility + $nginx_config .= "\t\tssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;\n"; + } else { + $nginx_config .= "\t\tssl_protocols TLSv1.2 TLSv1.3;\n"; + } + $nginx_config .= "\t\tssl_ciphers \"EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305\";\n"; + $nginx_config .= "\t\tssl_prefer_server_ciphers on;\n"; + if ($captive_portal === false && $hsts !== false) { + $nginx_config .= "\t\tadd_header Strict-Transport-Security \"max-age=31536000\";\n"; + } + $nginx_config .= "\t\tadd_header X-Content-Type-Options nosniff;\n"; + $nginx_config .= "\t\tssl_session_tickets off;\n"; + $nginx_config .= "\t\tssl_dhparam /etc/dh-parameters.4096;\n"; + $cert_temp = lookup_cert($config['system']['webgui']['ssl-certref']); + if (($config['system']['webgui']['ocsp-staple'] == true) or + (cert_get_ocspstaple($cert_temp['crt']) == true)) { + $nginx_config .= "\t\tssl_stapling on;\n"; + $nginx_config .= "\t\tssl_stapling_verify on;\n"; + $nginx_config .= "\t\tresolver " . implode(" ", get_dns_nameservers(true)) . " valid=300s;\n"; + $nginx_config .= "\t\tresolver_timeout 5s;\n"; + } + } else { + $nginx_config .= "\n"; + $nginx_config .= "\tserver {\n"; + $nginx_config .= "\t\tlisten {$nginx_port};\n"; + $nginx_config .= "\t\tlisten [::]:{$nginx_port};\n"; + } + + $nginx_config .= << "" and $key <> "") { + $fd = fopen("{$g['varetc_path']}/{$cert_location}", "w"); + if (!$fd) { + printf(gettext("Error: cannot open certificate file in system_webgui_start().%s"), "\n"); + return 1; + } + chmod("{$g['varetc_path']}/{$cert_location}", 0644); + if ($ca <> "") { + $cert_chain = $cert . "\n" . $ca; + } else { + $cert_chain = $cert; + } + fwrite($fd, $cert_chain); + fclose($fd); + $fd = fopen("{$g['varetc_path']}/{$key_location}", "w"); + if (!$fd) { + printf(gettext("Error: cannot open certificate key file in system_webgui_start().%s"), "\n"); + return 1; + } + chmod("{$g['varetc_path']}/{$key_location}", 0600); + fwrite($fd, $key); + fclose($fd); + } + + // Add HTTP to HTTPS redirect + if ($captive_portal === false && $config['system']['webgui']['protocol'] == "https" && !isset($config['system']['webgui']['disablehttpredirect'])) { + if ($nginx_port != "443") { + $redirectport = ":{$nginx_port}"; + } + $nginx_config .= << 0) { + return true; + } + } + if (strpos($contents, '0') > 0) { + $filters = ['`', '?', '/', '~']; + foreach ($filters as $filter) { + if (strpos($contents, $filter) !== false) { + return false; + } + } + return true; + } + } + return false; +} + +/* Generate list of possible NTP poll values + * https://redmine.pfsense.org/issues/9439 */ +global $ntp_poll_min_value, $ntp_poll_max_value; +global $ntp_poll_min_default_gps, $ntp_poll_max_default_gps; +global $ntp_poll_min_default_pps, $ntp_poll_max_default_pps; +global $ntp_poll_min_default, $ntp_poll_max_default; +global $ntp_auth_halgos; +$ntp_poll_min_value = 4; +$ntp_poll_max_value = 17; +$ntp_poll_min_default_gps = 4; +$ntp_poll_max_default_gps = 4; +$ntp_poll_min_default_pps = 4; +$ntp_poll_max_default_pps = 4; +$ntp_poll_min_default = 'omit'; +$ntp_poll_max_default = 9; +$ntp_auth_halgos = array( + 'md5' => 'MD5', + 'sha1' => 'SHA1' +); + +function system_ntp_poll_values() { + global $ntp_poll_min_value, $ntp_poll_max_value; + $poll_values = array("" => gettext('Default')); + + for ($i = $ntp_poll_min_value; $i <= $ntp_poll_max_value; $i++) { + $sec = 2 ** $i; + $poll_values[$i] = $i . ': ' . number_format($sec) . ' ' . gettext('seconds') . + ' (' . convert_seconds_to_dhms($sec) . ')'; + } + + $poll_values['omit'] = gettext('Omit (Do not set)'); + return $poll_values; +} + +function system_ntp_fixup_poll_value($type, $configvalue, $default) { + $pollstring = ""; + + if (empty($configvalue)) { + $configvalue = $default; + } + + if ($configvalue != 'omit') { + $pollstring = " {$type} {$configvalue}"; + } + + return $pollstring; +} + +function system_ntp_setup_gps($serialport) { + global $config, $g; + + if (is_array($config['ntpd']) && ($config['ntpd']['enable'] == 'disabled')) { + return false; + } + + init_config_arr(array('ntpd', 'gps')); + + $gps_device = '/dev/gps0'; + $serialport = '/dev/'.$serialport; + + if (!file_exists($serialport)) { + return false; + } + + // Create symlink that ntpd requires + unlink_if_exists($gps_device); + @symlink($serialport, $gps_device); + + $gpsbaud = '4800'; + $speeds = array( + 0 => '4800', + 16 => '9600', + 32 => '19200', + 48 => '38400', + 64 => '57600', + 80 => '115200' + ); + if (!empty($config['ntpd']['gps']['speed']) && array_key_exists($config['ntpd']['gps']['speed'], $speeds)) { + $gpsbaud = $speeds[$config['ntpd']['gps']['speed']]; + } + + system_ntp_setup_rawspeed($serialport, $gpsbaud); + + $autospeed = ($config['ntpd']['gps']['speed'] == 'autoalways' || $config['ntpd']['gps']['speed'] == 'autoset'); + if ($autospeed || ($config['ntpd']['gps']['autobaudinit'] && !check_gps_speed($gps_device))) { + $found = false; + foreach ($speeds as $baud) { + system_ntp_setup_rawspeed($serialport, $baud); + if ($found = check_gps_speed($gps_device)) { + if ($autospeed) { + $saveconfig = ($config['ntpd']['gps']['speed'] == 'autoset'); + $config['ntpd']['gps']['speed'] = array_search($baud, $speeds); + $gpsbaud = $baud; + if ($saveconfig) { + write_config(sprintf(gettext('Autoset GPS baud rate to %s'), $baud)); + } + } + break; + } + } + if ($found === false) { + log_error(gettext("Could not find correct GPS baud rate.")); + return false; + } + } + + /* Send the following to the GPS port to initialize the GPS */ + if (is_array($config['ntpd']) && is_array($config['ntpd']['gps']) && !empty($config['ntpd']['gps']['type'])) { + $gps_init = base64_decode($config['ntpd']['gps']['initcmd']); + } else { + $gps_init = base64_decode('JFBVQlgsNDAsR1NWLDAsMCwwLDAqNTkNCiRQVUJYLDQwLEdMTCwwLDAsMCwwKjVDDQokUFVCWCw0MCxaREEsMCwwLDAsMCo0NA0KJFBVQlgsNDAsVlRHLDAsMCwwLDAqNUUNCiRQVUJYLDQwLEdTViwwLDAsMCwwKjU5DQokUFVCWCw0MCxHU0EsMCwwLDAsMCo0RQ0KJFBVQlgsNDAsR0dBLDAsMCwwLDANCiRQVUJYLDQwLFRYVCwwLDAsMCwwDQokUFVCWCw0MCxSTUMsMCwwLDAsMCo0Ng0KJFBVQlgsNDEsMSwwMDA3LDAwMDMsNDgwMCwwDQokUFVCWCw0MCxaREEsMSwxLDEsMQ=='); + } + + /* XXX: Why not file_put_contents to the device */ + @file_put_contents('/tmp/gps.init', $gps_init); + mwexec("cat /tmp/gps.init > {$serialport}"); + + if ($found && $config['ntpd']['gps']['autobaudinit']) { + system_ntp_setup_rawspeed($serialport, $gpsbaud); + } + + /* Remove old /etc/remote entry if it exists */ + if (mwexec("/usr/bin/grep -c '^gps0' /etc/remote") == 0) { + mwexec("/usr/bin/sed -i '' -n '/gps0/!p' /etc/remote"); + } + + /* Add /etc/remote entry in case we need to read from the GPS with tip */ + if (mwexec("/usr/bin/grep -c '^gps0' /etc/remote") != 0) { + @file_put_contents("/etc/remote", "gps0:dv={$serialport}:br#{$gpsbaud}:pa=none:\n", FILE_APPEND); + } + + return true; +} + +// Configure the serial port for raw IO and set the speed +function system_ntp_setup_rawspeed($serialport, $baud) { + mwexec("/bin/stty -f " . escapeshellarg($serialport) . " raw speed " . escapeshellarg($baud)); + mwexec("/bin/stty -f " . escapeshellarg($serialport) . ".init raw speed " . escapeshellarg($baud)); +} + +function system_ntp_setup_pps($serialport) { + global $config, $g; + + $pps_device = '/dev/pps0'; + $serialport = '/dev/'.$serialport; + + if (!file_exists($serialport)) { + return false; + } + // If ntpd is disabled, just return + if (is_array($config['ntpd']) && ($config['ntpd']['enable'] == 'disabled')) { + return false; + } + + // Create symlink that ntpd requires + unlink_if_exists($pps_device); + @symlink($serialport, $pps_device); + + + return true; +} + +function system_ntp_configure() { + global $config, $g; + global $ntp_poll_min_default_gps, $ntp_poll_max_default_gps; + global $ntp_poll_min_default_pps, $ntp_poll_max_default_pps; + global $ntp_poll_min_default, $ntp_poll_max_default; + + $driftfile = "/var/db/ntpd.drift"; + $statsdir = "/var/log/ntp"; + $gps_device = '/dev/gps0'; + + safe_mkdir($statsdir); + + if (!is_array($config['ntpd'])) { + $config['ntpd'] = array(); + } + // ntpd is disabled, just stop it and return + if ($config['ntpd']['enable'] == 'disabled') { + while (isvalidpid("{$g['varrun_path']}/ntpd.pid")) { + killbypid("{$g['varrun_path']}/ntpd.pid"); + } + @unlink("{$g['varrun_path']}/ntpd.pid"); + @unlink("{$g['varetc_path']}/ntpd.conf"); + @unlink("{$g['varetc_path']}/ntp.keys"); + log_error("NTPD is disabled."); + return; + } + + if (platform_booting()) { + echo gettext("Starting NTP Server..."); + } + + /* if ntpd is running, kill it */ + while (isvalidpid("{$g['varrun_path']}/ntpd.pid")) { + killbypid("{$g['varrun_path']}/ntpd.pid"); + } + @unlink("{$g['varrun_path']}/ntpd.pid"); + + /* set NTP server authentication key */ + if ($config['ntpd']['serverauth'] == 'yes') { + $ntpkeyscfg = "1 " . strtoupper($config['ntpd']['serverauthalgo']) . " " . base64_decode($config['ntpd']['serverauthkey']) . "\n"; + if (!@file_put_contents("{$g['varetc_path']}/ntp.keys", $ntpkeyscfg)) { + log_error(sprintf(gettext("Could not open %s/ntp.keys for writing"), $g['varetc_path'])); + return; + } + } else { + unlink_if_exists("{$g['varetc_path']}/ntp.keys"); + } + + $ntpcfg = "# \n"; + $ntpcfg .= "# pfSense ntp configuration file \n"; + $ntpcfg .= "# \n\n"; + $ntpcfg .= "tinker panic 0 \n\n"; + + if ($config['ntpd']['serverauth'] == 'yes') { + $ntpcfg .= "# Authentication settings \n"; + $ntpcfg .= "keys /var/etc/ntp.keys \n"; + $ntpcfg .= "trustedkey 1 \n"; + $ntpcfg .= "requestkey 1 \n"; + $ntpcfg .= "controlkey 1 \n"; + $ntpcfg .= "\n"; + } + + /* Add Orphan mode */ + $ntpcfg .= "# Orphan mode stratum and Maximum candidate NTP peers\n"; + $ntpcfg .= 'tos orphan '; + if (!empty($config['ntpd']['orphan'])) { + $ntpcfg .= $config['ntpd']['orphan']; + } else { + $ntpcfg .= '12'; + } + /* Add Maximum candidate NTP peers */ + $ntpcfg .= ' maxclock '; + if (!empty($config['ntpd']['ntpmaxpeers'])) { + $ntpcfg .= $config['ntpd']['ntpmaxpeers']; + } else { + $ntpcfg .= '5'; + } + $ntpcfg .= "\n"; + + /* Add PPS configuration */ + if (is_array($config['ntpd']['pps']) && !empty($config['ntpd']['pps']['port']) && + file_exists('/dev/'.$config['ntpd']['pps']['port']) && + system_ntp_setup_pps($config['ntpd']['pps']['port'])) { + $ntpcfg .= "\n"; + $ntpcfg .= "# PPS Setup\n"; + $ntpcfg .= 'server 127.127.22.0'; + $ntpcfg .= system_ntp_fixup_poll_value('minpoll', $config['ntpd']['pps']['ppsminpoll'], $ntp_poll_min_default_pps); + $ntpcfg .= system_ntp_fixup_poll_value('maxpoll', $config['ntpd']['pps']['ppsmaxpoll'], $ntp_poll_max_default_pps); + if (empty($config['ntpd']['pps']['prefer'])) { /*note: this one works backwards */ + $ntpcfg .= ' prefer'; + } + if (!empty($config['ntpd']['pps']['noselect'])) { + $ntpcfg .= ' noselect '; + } + $ntpcfg .= "\n"; + $ntpcfg .= 'fudge 127.127.22.0'; + if (!empty($config['ntpd']['pps']['fudge1'])) { + $ntpcfg .= ' time1 '; + $ntpcfg .= $config['ntpd']['pps']['fudge1']; + } + if (!empty($config['ntpd']['pps']['flag2'])) { + $ntpcfg .= ' flag2 1'; + } + if (!empty($config['ntpd']['pps']['flag3'])) { + $ntpcfg .= ' flag3 1'; + } else { + $ntpcfg .= ' flag3 0'; + } + if (!empty($config['ntpd']['pps']['flag4'])) { + $ntpcfg .= ' flag4 1'; + } + if (!empty($config['ntpd']['pps']['refid'])) { + $ntpcfg .= ' refid '; + $ntpcfg .= $config['ntpd']['pps']['refid']; + } + $ntpcfg .= "\n"; + } + /* End PPS configuration */ + + /* Add GPS configuration */ + if (is_array($config['ntpd']['gps']) && !empty($config['ntpd']['gps']['port']) && + system_ntp_setup_gps($config['ntpd']['gps']['port'])) { + $ntpcfg .= "\n"; + $ntpcfg .= "# GPS Setup\n"; + $ntpcfg .= 'server 127.127.20.0 mode '; + if (!empty($config['ntpd']['gps']['nmea']) || !empty($config['ntpd']['gps']['speed']) || !empty($config['ntpd']['gps']['subsec']) || !empty($config['ntpd']['gps']['processpgrmf'])) { + if (!empty($config['ntpd']['gps']['nmea'])) { + $ntpmode = (int) $config['ntpd']['gps']['nmea']; + } + if (!empty($config['ntpd']['gps']['speed'])) { + $ntpmode += (int) $config['ntpd']['gps']['speed']; + } + if (!empty($config['ntpd']['gps']['subsec'])) { + $ntpmode += 128; + } + if (!empty($config['ntpd']['gps']['processpgrmf'])) { + $ntpmode += 256; + } + $ntpcfg .= (string) $ntpmode; + } else { + $ntpcfg .= '0'; + } + $ntpcfg .= system_ntp_fixup_poll_value('minpoll', $config['ntpd']['gps']['gpsminpoll'], $ntp_poll_min_default_gps); + $ntpcfg .= system_ntp_fixup_poll_value('maxpoll', $config['ntpd']['gps']['gpsmaxpoll'], $ntp_poll_max_default_gps); + + if (empty($config['ntpd']['gps']['prefer'])) { /*note: this one works backwards */ + $ntpcfg .= ' prefer'; + } + if (!empty($config['ntpd']['gps']['noselect'])) { + $ntpcfg .= ' noselect '; + } + $ntpcfg .= "\n"; + $ntpcfg .= 'fudge 127.127.20.0'; + if (!empty($config['ntpd']['gps']['fudge1'])) { + $ntpcfg .= ' time1 '; + $ntpcfg .= $config['ntpd']['gps']['fudge1']; + } + if (!empty($config['ntpd']['gps']['fudge2'])) { + $ntpcfg .= ' time2 '; + $ntpcfg .= $config['ntpd']['gps']['fudge2']; + } + if (!empty($config['ntpd']['gps']['flag1'])) { + $ntpcfg .= ' flag1 1'; + } else { + $ntpcfg .= ' flag1 0'; + } + if (!empty($config['ntpd']['gps']['flag2'])) { + $ntpcfg .= ' flag2 1'; + } + if (!empty($config['ntpd']['gps']['flag3'])) { + $ntpcfg .= ' flag3 1'; + } else { + $ntpcfg .= ' flag3 0'; + } + if (!empty($config['ntpd']['gps']['flag4'])) { + $ntpcfg .= ' flag4 1'; + } + if (!empty($config['ntpd']['gps']['refid'])) { + $ntpcfg .= ' refid '; + $ntpcfg .= $config['ntpd']['gps']['refid']; + } + if (!empty($config['ntpd']['gps']['stratum'])) { + $ntpcfg .= ' stratum '; + $ntpcfg .= $config['ntpd']['gps']['stratum']; + } + $ntpcfg .= "\n"; + } elseif (is_array($config['ntpd']) && !empty($config['ntpd']['gpsport']) && + system_ntp_setup_gps($config['ntpd']['gpsport'])) { + /* This handles a 2.1 and earlier config */ + $ntpcfg .= "# GPS Setup\n"; + $ntpcfg .= "server 127.127.20.0 mode 0 minpoll 4 maxpoll 4 prefer\n"; + $ntpcfg .= "fudge 127.127.20.0 time1 0.155 time2 0.000 flag1 1 flag2 0 flag3 1\n"; + // Fall back to local clock if GPS is out of sync? + $ntpcfg .= "server 127.127.1.0\n"; + $ntpcfg .= "fudge 127.127.1.0 stratum 12\n"; + } + /* End GPS configuration */ + $auto_pool_suffix = "pool.ntp.org"; + $have_pools = false; + $ntpcfg .= "\n\n# Upstream Servers\n"; + /* foreach through ntp servers and write out to ntpd.conf */ + foreach (explode(' ', $config['system']['timeservers']) as $ts) { + if ((substr_compare($ts, $auto_pool_suffix, strlen($ts) - strlen($auto_pool_suffix), strlen($auto_pool_suffix)) === 0) + || substr_count($config['ntpd']['ispool'], $ts)) { + $ntpcfg .= 'pool '; + $have_pools = true; + } else { + $ntpcfg .= 'server '; + if ($config['ntpd']['dnsresolv'] == 'inet') { + $ntpcfg .= '-4 '; + } elseif ($config['ntpd']['dnsresolv'] == 'inet6') { + $ntpcfg .= '-6 '; + } + } + + $ntpcfg .= "{$ts} iburst"; + + $ntpcfg .= system_ntp_fixup_poll_value('minpoll', $config['ntpd']['ntpminpoll'], $ntp_poll_min_default); + $ntpcfg .= system_ntp_fixup_poll_value('maxpoll', $config['ntpd']['ntpmaxpoll'], $ntp_poll_max_default); + + if (substr_count($config['ntpd']['prefer'], $ts)) { + $ntpcfg .= ' prefer'; + } + if (substr_count($config['ntpd']['noselect'], $ts)) { + $ntpcfg .= ' noselect'; + } + $ntpcfg .= "\n"; + } + unset($ts); + + $ntpcfg .= "\n\n"; + if (!empty($config['ntpd']['clockstats']) || !empty($config['ntpd']['loopstats']) || !empty($config['ntpd']['peerstats'])) { + $ntpcfg .= "enable stats\n"; + $ntpcfg .= 'statistics'; + if (!empty($config['ntpd']['clockstats'])) { + $ntpcfg .= ' clockstats'; + } + if (!empty($config['ntpd']['loopstats'])) { + $ntpcfg .= ' loopstats'; + } + if (!empty($config['ntpd']['peerstats'])) { + $ntpcfg .= ' peerstats'; + } + $ntpcfg .= "\n"; + } + $ntpcfg .= "statsdir {$statsdir}\n"; + $ntpcfg .= 'logconfig =syncall +clockall'; + if (!empty($config['ntpd']['logpeer'])) { + $ntpcfg .= ' +peerall'; + } + if (!empty($config['ntpd']['logsys'])) { + $ntpcfg .= ' +sysall'; + } + $ntpcfg .= "\n"; + $ntpcfg .= "driftfile {$driftfile}\n"; + + /* Default Access restrictions */ + $ntpcfg .= 'restrict default'; + if (empty($config['ntpd']['kod'])) { /*note: this one works backwards */ + $ntpcfg .= ' kod limited'; + } + if (empty($config['ntpd']['nomodify'])) { /*note: this one works backwards */ + $ntpcfg .= ' nomodify'; + } + if (!empty($config['ntpd']['noquery'])) { + $ntpcfg .= ' noquery'; + } + if (empty($config['ntpd']['nopeer'])) { /*note: this one works backwards */ + $ntpcfg .= ' nopeer'; + } + if (empty($config['ntpd']['notrap'])) { /*note: this one works backwards */ + $ntpcfg .= ' notrap'; + } + if (!empty($config['ntpd']['noserve'])) { + $ntpcfg .= ' noserve'; + } + $ntpcfg .= "\nrestrict -6 default"; + if (empty($config['ntpd']['kod'])) { /*note: this one works backwards */ + $ntpcfg .= ' kod limited'; + } + if (empty($config['ntpd']['nomodify'])) { /*note: this one works backwards */ + $ntpcfg .= ' nomodify'; + } + if (!empty($config['ntpd']['noquery'])) { + $ntpcfg .= ' noquery'; + } + if (empty($config['ntpd']['nopeer'])) { /*note: this one works backwards */ + $ntpcfg .= ' nopeer'; + } + if (!empty($config['ntpd']['noserve'])) { + $ntpcfg .= ' noserve'; + } + if (empty($config['ntpd']['notrap'])) { /*note: this one works backwards */ + $ntpcfg .= ' notrap'; + } + + /* Pools require "restrict source" and cannot contain "nopeer" and "noserve". */ + if ($have_pools) { + $ntpcfg .= "\nrestrict source"; + if (empty($config['ntpd']['kod'])) { /*note: this one works backwards */ + $ntpcfg .= ' kod limited'; + } + if (empty($config['ntpd']['nomodify'])) { /*note: this one works backwards */ + $ntpcfg .= ' nomodify'; + } + if (!empty($config['ntpd']['noquery'])) { + $ntpcfg .= ' noquery'; + } + if (empty($config['ntpd']['notrap'])) { /*note: this one works backwards */ + $ntpcfg .= ' notrap'; + } + } + + /* Custom Access Restrictions */ + if (is_array($config['ntpd']['restrictions']) && is_array($config['ntpd']['restrictions']['row'])) { + $networkacl = $config['ntpd']['restrictions']['row']; + foreach ($networkacl as $acl) { + $restrict = ""; + if (is_ipaddrv6($acl['acl_network'])) { + $restrict .= "{$acl['acl_network']} mask " . gen_subnet_mask_v6($acl['mask']) . " "; + } elseif (is_ipaddrv4($acl['acl_network'])) { + $restrict .= "{$acl['acl_network']} mask " . gen_subnet_mask($acl['mask']) . " "; + } else { + continue; + } + if (!empty($acl['kod'])) { + $restrict .= ' kod limited'; + } + if (!empty($acl['nomodify'])) { + $restrict .= ' nomodify'; + } + if (!empty($acl['noquery'])) { + $restrict .= ' noquery'; + } + if (!empty($acl['nopeer'])) { + $restrict .= ' nopeer'; + } + if (!empty($acl['noserve'])) { + $restrict .= ' noserve'; + } + if (!empty($acl['notrap'])) { + $restrict .= ' notrap'; + } + if (!empty($restrict)) { + $ntpcfg .= "\nrestrict {$restrict} "; + } + } + } + /* End Custom Access Restrictions */ + + /* A leapseconds file is really only useful if this clock is stratum 1 */ + $ntpcfg .= "\n"; + if (!empty($config['ntpd']['leapsec'])) { + $leapsec .= base64_decode($config['ntpd']['leapsec']); + file_put_contents('/var/db/leap-seconds', $leapsec); + $ntpcfg .= "leapfile /var/db/leap-seconds\n"; + } + + + if (empty($config['ntpd']['interface'])) { + if (is_array($config['installedpackages']['openntpd']) && !empty($config['installedpackages']['openntpd']['config'][0]['interface'])) { + $interfaces = explode(",", $config['installedpackages']['openntpd']['config'][0]['interface']); + } else { + $interfaces = array(); + } + } else { + $interfaces = explode(",", $config['ntpd']['interface']); + } + + if (is_array($interfaces) && count($interfaces)) { + $finterfaces = array(); + $ntpcfg .= "interface ignore all\n"; + $ntpcfg .= "interface ignore wildcard\n"; + foreach ($interfaces as $interface) { + $interface = get_real_interface($interface); + if (!empty($interface)) { + $finterfaces[] = $interface; + } + } + foreach ($finterfaces as $interface) { + $ntpcfg .= "interface listen {$interface}\n"; + } + } + + /* open configuration for writing or bail */ + if (!@file_put_contents("{$g['varetc_path']}/ntpd.conf", $ntpcfg)) { + log_error(sprintf(gettext("Could not open %s/ntpd.conf for writing"), $g['varetc_path'])); + return; + } + + /* if /var/empty does not exist, create it */ + if (!is_dir("/var/empty")) { + mkdir("/var/empty", 0555, true); + } + + /* start ntpd, set time now and use /var/etc/ntpd.conf */ + mwexec("/usr/local/sbin/ntpd -g -c {$g['varetc_path']}/ntpd.conf -p {$g['varrun_path']}/ntpd.pid", false, true); + + // Note that we are starting up + log_error("NTPD is starting up."); + + if (platform_booting()) { + echo gettext("done.") . "\n"; + } + + return; +} + +function system_halt() { + global $g; + + system_reboot_cleanup(); + + mwexec("/usr/bin/nohup /etc/rc.halt > /dev/null 2>&1 &"); +} + +function system_reboot() { + global $g; + + system_reboot_cleanup(); + + mwexec("nohup /etc/rc.reboot > /dev/null 2>&1 &"); +} + +function system_reboot_sync($reroot=false) { + global $g; + + if ($reroot) { + $args = " -r "; + } + + system_reboot_cleanup(); + + mwexec("/etc/rc.reboot {$args} > /dev/null 2>&1"); +} + +function system_reboot_cleanup() { + global $config, $g, $cpzone; + + mwexec("/usr/local/bin/beep.sh stop"); + require_once("captiveportal.inc"); + if (is_array($config['captiveportal'])) { + foreach ($config['captiveportal'] as $cpzone=>$cp) { + if (!isset($cp['preservedb'])) { + /* send Accounting-Stop packet for all clients, termination cause 'Admin-Reboot' */ + captiveportal_radius_stop_all(7); // Admin-Reboot + unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db"); + captiveportal_free_dnrules(); + } + /* Send Accounting-Off packet to the RADIUS server */ + captiveportal_send_server_accounting('off'); + } + /* Remove the pipe database */ + unlink_if_exists("{$g['vardb_path']}/captiveportaldn.rules"); + } + require_once("voucher.inc"); + voucher_save_db_to_config(); + require_once("pkg-utils.inc"); + stop_packages(); +} + +function system_do_shell_commands($early = 0) { + global $config, $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_do_shell_commands() being called $mt\n"; + } + + if ($early) { + $cmdn = "earlyshellcmd"; + } else { + $cmdn = "shellcmd"; + } + + if (is_array($config['system'][$cmdn])) { + + /* *cmd is an array, loop through */ + foreach ($config['system'][$cmdn] as $cmd) { + exec($cmd); + } + + } elseif ($config['system'][$cmdn] <> "") { + + /* execute single item */ + exec($config['system'][$cmdn]); + + } +} + +function system_dmesg_save() { + global $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_dmesg_save() being called $mt\n"; + } + + $dmesg = ""; + $_gb = exec("/sbin/dmesg", $dmesg); + + /* find last copyright line (output from previous boots may be present) */ + $lastcpline = 0; + + for ($i = 0; $i < count($dmesg); $i++) { + if (strstr($dmesg[$i], "Copyright (c) 1992-")) { + $lastcpline = $i; + } + } + + $fd = fopen("{$g['varlog_path']}/dmesg.boot", "w"); + if (!$fd) { + printf(gettext("Error: cannot open dmesg.boot in system_dmesg_save().%s"), "\n"); + return 1; + } + + for ($i = $lastcpline; $i < count($dmesg); $i++) { + fwrite($fd, $dmesg[$i] . "\n"); + } + + fclose($fd); + unset($dmesg); + + // vm-bhyve expects dmesg.boot at the standard location + @symlink("{$g['varlog_path']}/dmesg.boot", "{$g['varrun_path']}/dmesg.boot"); + + return 0; +} + +function system_set_harddisk_standby() { + global $g, $config; + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_set_harddisk_standby() being called $mt\n"; + } + + if (isset($config['system']['harddiskstandby'])) { + if (platform_booting()) { + echo gettext('Setting hard disk standby... '); + } + + $standby = $config['system']['harddiskstandby']; + // Check for a numeric value + if (is_numeric($standby)) { + // Get only suitable candidates for standby; using get_smart_drive_list() + // from utils.inc to get the list of drives. + $harddisks = get_smart_drive_list(); + + // Since get_smart_drive_list() only matches ad|da|ada; lets put the check below + // just in case of some weird pfSense platform installs. + if (count($harddisks) > 0) { + // Iterate disks and run the camcontrol command for each + foreach ($harddisks as $harddisk) { + mwexec("/sbin/camcontrol standby {$harddisk} -t {$standby}"); + } + if (platform_booting()) { + echo gettext("done.") . "\n"; + } + } else if (platform_booting()) { + echo gettext("failed!") . "\n"; + } + } else if (platform_booting()) { + echo gettext("failed!") . "\n"; + } + } +} + +function system_setup_sysctl() { + global $config; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_setup_sysctl() being called $mt\n"; + } + + activate_sysctls(); + + if (isset($config['system']['sharednet'])) { + system_disable_arp_wrong_if(); + } +} + +function system_disable_arp_wrong_if() { + global $config; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_disable_arp_wrong_if() being called $mt\n"; + } + set_sysctl(array( + "net.link.ether.inet.log_arp_wrong_iface" => "0", + "net.link.ether.inet.log_arp_movements" => "0" + )); +} + +function system_enable_arp_wrong_if() { + global $config; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_enable_arp_wrong_if() being called $mt\n"; + } + set_sysctl(array( + "net.link.ether.inet.log_arp_wrong_iface" => "1", + "net.link.ether.inet.log_arp_movements" => "1" + )); +} + +function enable_watchdog() { + global $config; + return; + $install_watchdog = false; + $supported_watchdogs = array("Geode"); + $file = file_get_contents("/var/log/dmesg.boot"); + foreach ($supported_watchdogs as $sd) { + if (stristr($file, "Geode")) { + $install_watchdog = true; + } + } + if ($install_watchdog == true) { + if (is_process_running("watchdogd")) { + mwexec("/usr/bin/killall watchdogd", true); + } + exec("/usr/sbin/watchdogd"); + } +} + +function system_check_reset_button() { + global $g; + + $specplatform = system_identify_specific_platform(); + + switch ($specplatform['name']) { + case 'SG-2220': + $binprefix = "RCC-DFF"; + break; + case 'alix': + case 'wrap': + case 'FW7541': + case 'APU': + case 'RCC-VE': + case 'RCC': + $binprefix = $specplatform['name']; + break; + default: + return 0; + } + + $retval = mwexec("/usr/local/sbin/" . $binprefix . "resetbtn"); + + if ($retval == 99) { + /* user has pressed reset button for 2 seconds - + reset to factory defaults */ + echo <<= 10 && strlen($serial) <= 16 && + $vm_guest == 'none') { + return $serial; + } + + return ""; +} + +function system_get_uniqueid() { + global $g; + + $uniqueid_file="{$g['vardb_path']}/uniqueid"; + + if (empty($g['uniqueid'])) { + if (!file_exists($uniqueid_file)) { + mwexec("/usr/sbin/gnid > {$g['vardb_path']}/uniqueid " . + "2>/dev/null"); + } + if (file_exists($uniqueid_file)) { + $g['uniqueid'] = @file_get_contents($uniqueid_file); + } + } + + return ($g['uniqueid'] ?: ''); +} + +/* + * attempt to identify the specific platform (for embedded systems) + * Returns an array with two elements: + * name => platform string (e.g. 'wrap', 'alix' etc.) + * descr => human-readable description (e.g. "PC Engines WRAP") + */ +function system_identify_specific_platform() { + global $g; + + $hw_model = get_single_sysctl('hw.model'); + $hw_ncpu = get_single_sysctl('hw.ncpu'); + + /* Try to guess from smbios strings */ + unset($product); + unset($maker); + unset($bios); + $_gb = exec('/bin/kenv -q smbios.system.product 2>/dev/null', $product); + $_gb = exec('/bin/kenv -q smbios.system.maker 2>/dev/null', $maker); + $_gb = exec('/bin/kenv -q smbios.bios.version 2>/dev/null', $bios); + + // AWS can only be identified via the bios version + if (stripos($bios[0], "amazon") !== false) { + return (array('name' => 'AWS', 'descr' => 'Amazon Web Services')); + } else if (stripos($bios[0], "Google") !== false) { + return (array('name' => 'Google', 'descr' => 'Google Cloud Platform')); + } + + switch ($product[0]) { + case 'FW7541': + return (array('name' => 'FW7541', 'descr' => 'Netgate FW7541')); + break; + case 'APU': + return (array('name' => 'APU', 'descr' => 'Netgate APU')); + break; + case 'RCC-VE': + $result = array(); + $result['name'] = 'RCC-VE'; + + /* Detect specific models */ + if (!function_exists('does_interface_exist')) { + require_once("interfaces.inc"); + } + if (!does_interface_exist('igb4')) { + $result['model'] = 'SG-2440'; + } elseif (strpos($hw_model, "C2558") !== false) { + $result['model'] = 'SG-4860'; + } elseif (strpos($hw_model, "C2758") !== false) { + $result['model'] = 'SG-8860'; + } else { + $result['model'] = 'RCC-VE'; + } + $result['descr'] = 'Netgate ' . $result['model']; + return $result; + break; + case 'DFFv2': + return (array('name' => 'SG-2220', 'descr' => 'Netgate SG-2220')); + break; + case 'RCC': + return (array('name' => 'RCC', 'descr' => 'Netgate XG-2758')); + break; + case 'SG-5100': + return (array('name' => 'SG-5100', 'descr' => 'Netgate SG-5100')); + break; + case 'Minnowboard Turbot D0 PLATFORM': + case 'Minnowboard Turbot D0/D1 PLATFORM': + $result = array(); + $result['name'] = 'Turbot Dual-E'; + /* Detect specific model */ + switch ($hw_ncpu) { + case '4': + $result['model'] = 'MBT-4220'; + break; + case '2': + $result['model'] = 'MBT-2220'; + break; + default: + $result['model'] = $result['name']; + break; + } + $result['descr'] = 'Netgate ' . $result['model']; + return $result; + break; + case 'SYS-5018A-FTN4': + case 'A1SAi': + if (strpos($hw_model, "C2558") !== false) { + return (array( + 'name' => 'C2558', + 'descr' => 'Super Micro C2558')); + } elseif (strpos($hw_model, "C2758") !== false) { + return (array( + 'name' => 'C2758', + 'descr' => 'Super Micro C2758')); + } + break; + case 'SYS-5018D-FN4T': + if (strpos($hw_model, "D-1541") !== false) { + return (array('name' => 'XG-1541', 'descr' => 'Super Micro XG-1541')); + } else { + return (array('name' => 'XG-1540', 'descr' => 'Super Micro XG-1540')); + } + break; + case 'apu2': + case 'APU2': + return (array('name' => 'apu2', 'descr' => 'PC Engines APU2')); + break; + case 'VirtualBox': + return (array('name' => 'VirtualBox', 'descr' => 'VirtualBox Virtual Machine')); + break; + case 'Virtual Machine': + if ($maker[0] == "Microsoft Corporation") { + if (stripos($bios[0], "Hyper") !== false) { + return (array('name' => 'Hyper-V', 'descr' => 'Hyper-V Virtual Machine')); + } else { + return (array('name' => 'Azure', 'descr' => 'Microsoft Azure')); + } + } + break; + case 'VMware Virtual Platform': + if ($maker[0] == "VMware, Inc.") { + return (array('name' => 'VMware', 'descr' => 'VMware Virtual Machine')); + } + break; + } + + $_gb = exec('/bin/kenv -q smbios.planar.product 2>/dev/null', + $planar_product); + if (isset($planar_product[0]) && + $planar_product[0] == 'X10SDV-8C-TLN4F+') { + return array('name' => 'XG-1537', 'descr' => 'Super Micro XG-1537'); + } + + if (strpos($hw_model, "PC Engines WRAP") !== false) { + return array('name' => 'wrap', 'descr' => gettext('PC Engines WRAP')); + } + + if (strpos($hw_model, "PC Engines ALIX") !== false) { + return array('name' => 'alix', 'descr' => gettext('PC Engines ALIX')); + } + + if (preg_match("/Soekris net45../", $hw_model, $matches)) { + return array('name' => 'net45xx', 'descr' => $matches[0]); + } + + if (preg_match("/Soekris net48../", $hw_model, $matches)) { + return array('name' => 'net48xx', 'descr' => $matches[0]); + } + + if (preg_match("/Soekris net55../", $hw_model, $matches)) { + return array('name' => 'net55xx', 'descr' => $matches[0]); + } + + unset($hw_model); + + $dmesg_boot = system_get_dmesg_boot(); + if (strpos($dmesg_boot, "PC Engines ALIX") !== false) { + return array('name' => 'alix', 'descr' => gettext('PC Engines ALIX')); + } + unset($dmesg_boot); + + return array('name' => $g['product_name'], 'descr' => $g['product_label']); +} + +function system_get_dmesg_boot() { + global $g; + + return file_get_contents("{$g['varlog_path']}/dmesg.boot"); +} + +function system_get_arp_table($resolve_hostnames = false) { + $params="-a"; + if (!$resolve_hostnames) { + $params .= "n"; + } + + $arp_table = array(); + $_gb = exec("/usr/sbin/arp --libxo json {$params}", $rawdata, $rc); + if ($rc == 0) { + $arp_table = json_decode(implode(" ", $rawdata), + JSON_OBJECT_AS_ARRAY); + if ($rc == 0) { + $arp_table = $arp_table['arp']['arp-cache']; + } + } + + return $arp_table; +} + +?> \ No newline at end of file diff --git a/pfSense-pkg-API/files/etc/inc/api/framework/overrides/README.md b/pfSense-pkg-API/files/etc/inc/api/framework/overrides/README.md new file mode 100644 index 000000000..247621d13 --- /dev/null +++ b/pfSense-pkg-API/files/etc/inc/api/framework/overrides/README.md @@ -0,0 +1,14 @@ +File Overrides +============== +This directory contains files that intend to override an existing pfSense file. In specific cases, the existing pfSense +code must be changed to enable the API to work as intended. A file override is used to do this. File overrides are the +same exact file that exists on pfSense originally with the extended code as required. File overrides are specific to +the version of pfSense running. In the case that a file override does not exist for your version of pfSense, the +default file override will be used (typically the override from the newest pfSense version). A warning will be displayed +when the API is installed using a default override. Uninstalling the API package will restore the files to their original +state. Below are a description of file overrides required by the package: + +#### SYSTEM.INC +This file will override the existing `/etc/inc/system.inc` file to extend the capabilities of pfSense's NGINX web server. +This is required for the API to support endpoint URLs without a trailing slash as well as alternate request methods like +`PUT` and `DELETE`. \ No newline at end of file diff --git a/pfSense-pkg-API/files/etc/inc/api/framework/overrides/default/system.inc b/pfSense-pkg-API/files/etc/inc/api/framework/overrides/default/system.inc new file mode 100644 index 000000000..86907a45a --- /dev/null +++ b/pfSense-pkg-API/files/etc/inc/api/framework/overrides/default/system.inc @@ -0,0 +1,2670 @@ +. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +require_once('syslog.inc'); + +function activate_powerd() { + global $config, $g; + + if (is_process_running("powerd")) { + exec("/usr/bin/killall powerd"); + } + if (isset($config['system']['powerd_enable'])) { + $ac_mode = "hadp"; + if (!empty($config['system']['powerd_ac_mode'])) { + $ac_mode = $config['system']['powerd_ac_mode']; + } + + $battery_mode = "hadp"; + if (!empty($config['system']['powerd_battery_mode'])) { + $battery_mode = $config['system']['powerd_battery_mode']; + } + + $normal_mode = "hadp"; + if (!empty($config['system']['powerd_normal_mode'])) { + $normal_mode = $config['system']['powerd_normal_mode']; + } + + mwexec("/usr/sbin/powerd" . + " -b " . escapeshellarg($battery_mode) . + " -a " . escapeshellarg($ac_mode) . + " -n " . escapeshellarg($normal_mode)); + } +} + +function get_default_sysctl_value($id) { + global $sysctls; + + if (isset($sysctls[$id])) { + return $sysctls[$id]; + } +} + +function get_sysctl_descr($sysctl) { + unset($output); + $_gb = exec("/sbin/sysctl -qnd {$sysctl}", $output); + + return $output[0]; +} + +function system_get_sysctls() { + global $config, $sysctls; + + $disp_sysctl = array(); + $disp_cache = array(); + if (is_array($config['sysctl']) && is_array($config['sysctl']['item'])) { + foreach ($config['sysctl']['item'] as $id => $tunable) { + if ($tunable['value'] == "default") { + $value = get_default_sysctl_value($tunable['tunable']); + } else { + $value = $tunable['value']; + } + + $disp_sysctl[$id] = $tunable; + $disp_sysctl[$id]['modified'] = true; + $disp_cache[$tunable['tunable']] = 'set'; + } + } + + foreach ($sysctls as $sysctl => $value) { + if (isset($disp_cache[$sysctl])) { + continue; + } + + $disp_sysctl[$sysctl] = array('tunable' => $sysctl, 'value' => $value, 'descr' => get_sysctl_descr($sysctl)); + } + unset($disp_cache); + return $disp_sysctl; +} + +function activate_sysctls() { + global $config, $g, $sysctls; + + if (is_array($config['sysctl']) && is_array($config['sysctl']['item'])) { + foreach ($config['sysctl']['item'] as $tunable) { + if ($tunable['value'] == "default") { + $value = get_default_sysctl_value($tunable['tunable']); + } else { + $value = $tunable['value']; + } + + $sysctls[$tunable['tunable']] = $value; + } + } + + /* Set net.pf.request_maxcount via sysctl since it is no longer a loader + * tunable. See https://redmine.pfsense.org/issues/10861 + * Set the value dynamically since its default is not static, yet this + * still could be overridden by a user tunable. */ + if (isset($config['system']['maximumtableentries'])) { + $maximumtableentries = $config['system']['maximumtableentries']; + } else { + $maximumtableentries = pfsense_default_table_entries_size(); + } + /* Set the default when there is no tunable or when the tunable is set + * too low. */ + if (empty($sysctls['net.pf.request_maxcount']) || + ($sysctls['net.pf.request_maxcount'] < $maximumtableentries)) { + $sysctls['net.pf.request_maxcount'] = $maximumtableentries; + } + + set_sysctl($sysctls); +} + +function system_resolvconf_generate($dynupdate = false) { + global $config, $g; + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_resolvconf_generate() being called $mt\n"; + } + + $syscfg = $config['system']; + + foreach(get_dns_nameservers(false, false) as $dns_ns) { + $resolvconf .= "nameserver $dns_ns\n"; + } + + $ns = array(); + if (isset($syscfg['dnsallowoverride'])) { + /* get dynamically assigned DNS servers (if any) */ + $ns = array_unique(get_searchdomains()); + foreach ($ns as $searchserver) { + if ($searchserver) { + $resolvconf .= "search {$searchserver}\n"; + } + } + } + if (empty($ns)) { + // Do not create blank search/domain lines, it can break tools like dig. + if ($syscfg['domain']) { + $resolvconf .= "search {$syscfg['domain']}\n"; + } + } + + // Add EDNS support + if (isset($config['unbound']['enable']) && isset($config['unbound']['edns'])) { + $resolvconf .= "options edns0\n"; + } + + $dnslock = lock('resolvconf', LOCK_EX); + + $fd = fopen("{$g['etc_path']}/resolv.conf", "w"); + if (!$fd) { + printf("Error: cannot open resolv.conf in system_resolvconf_generate().\n"); + unlock($dnslock); + return 1; + } + + fwrite($fd, $resolvconf); + fclose($fd); + + // Prevent resolvconf(8) from rewriting our resolv.conf + $fd = fopen("{$g['etc_path']}/resolvconf.conf", "w"); + if (!$fd) { + printf("Error: cannot open resolvconf.conf in system_resolvconf_generate().\n"); + return 1; + } + fwrite($fd, "resolv_conf=\"/dev/null\"\n"); + fclose($fd); + + if (!platform_booting()) { + /* restart dhcpd (nameservers may have changed) */ + if (!$dynupdate) { + services_dhcpd_configure(); + } + } + + // set up or tear down static routes for DNS servers + $dnscounter = 1; + $dnsgw = "dns{$dnscounter}gw"; + while (isset($config['system'][$dnsgw])) { + /* setup static routes for dns servers */ + $gwname = $config['system'][$dnsgw]; + unset($gatewayip); + unset($inet6); + if ((!empty($gwname)) && ($gwname != "none")) { + $gatewayip = lookup_gateway_ip_by_name($gwname); + $inet6 = is_ipaddrv6($gatewayip) ? '-inet6 ' : ''; + } + /* dns server array starts at 0 */ + $dnsserver = $syscfg['dnsserver'][$dnscounter - 1]; + if (!empty($dnsserver)) { + if (is_ipaddr($gatewayip)) { + route_add_or_change($dnsserver, $gatewayip); + } else { + /* Remove old route when disable gw */ + route_del($dnsserver); + } + } + $dnscounter++; + $dnsgw = "dns{$dnscounter}gw"; + } + + unlock($dnslock); + + return 0; +} + +function get_searchdomains() { + global $config, $g; + + $master_list = array(); + + // Read in dhclient nameservers + $search_list = glob("/var/etc/searchdomain_*"); + if (is_array($search_list)) { + foreach ($search_list as $fdns) { + $contents = file($fdns, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($contents)) { + continue; + } + foreach ($contents as $dns) { + if (is_hostname($dns)) { + $master_list[] = $dns; + } + } + } + } + + return $master_list; +} + +/* Stub for deprecated function name + * See https://redmine.pfsense.org/issues/10931 */ +function get_nameservers() { + return get_dynamic_nameservers(); +} + +/****f* system.inc/get_dynamic_nameservers + * NAME + * get_dynamic_nameservers - Get DNS servers from dynamic sources (DHCP, PPP, etc) + * INPUTS + * $iface: Interface name used to filter results. + * RESULT + * $master_list - Array containing DNS servers + ******/ +function get_dynamic_nameservers($iface = '') { + global $config, $g; + $master_list = array(); + + if (!empty($iface)) { + $realif = get_real_interface($iface); + } + + // Read in dynamic nameservers + $dns_lists = array_merge(glob("/var/etc/nameserver_{$realif}*"), glob("/var/etc/nameserver_v6{$iface}*")); + if (is_array($dns_lists)) { + foreach ($dns_lists as $fdns) { + $contents = file($fdns, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($contents)) { + continue; + } + foreach ($contents as $dns) { + if (is_ipaddr($dns)) { + $master_list[] = $dns; + } + } + } + } + + return $master_list; +} + +/* Create localhost + local interfaces entries for /etc/hosts */ +function system_hosts_local_entries() { + global $config; + + $syscfg = $config['system']; + + $hosts = array(); + $hosts[] = array( + 'ipaddr' => '127.0.0.1', + 'fqdn' => 'localhost.' . $syscfg['domain'], + 'name' => 'localhost', + 'domain' => $syscfg['domain'] + ); + $hosts[] = array( + 'ipaddr' => '::1', + 'fqdn' => 'localhost.' . $syscfg['domain'], + 'name' => 'localhost', + 'domain' => $syscfg['domain'] + ); + + if ($config['interfaces']['lan']) { + $sysiflist = array('lan' => "lan"); + } else { + $sysiflist = get_configured_interface_list(); + } + + $hosts_if_found = false; + $local_fqdn = "{$syscfg['hostname']}.{$syscfg['domain']}"; + foreach ($sysiflist as $sysif) { + if ($sysif != 'lan' && interface_has_gateway($sysif)) { + continue; + } + $cfgip = get_interface_ip($sysif); + if (is_ipaddrv4($cfgip)) { + $hosts[] = array( + 'ipaddr' => $cfgip, + 'fqdn' => $local_fqdn, + 'name' => $syscfg['hostname'], + 'domain' => $syscfg['domain'] + ); + $hosts_if_found = true; + } + if (!isset($syscfg['ipv6dontcreatelocaldns'])) { + $cfgipv6 = get_interface_ipv6($sysif); + if (is_ipaddrv6($cfgipv6)) { + $hosts[] = array( + 'ipaddr' => $cfgipv6, + 'fqdn' => $local_fqdn, + 'name' => $syscfg['hostname'], + 'domain' => $syscfg['domain'] + ); + $hosts_if_found = true; + } + } + if ($hosts_if_found == true) { + break; + } + } + + return $hosts; +} + +/* Read host override entries from dnsmasq or unbound */ +function system_hosts_override_entries($dnscfg) { + $hosts = array(); + + if (!is_array($dnscfg) || + !is_array($dnscfg['hosts']) || + !isset($dnscfg['enable'])) { + return $hosts; + } + + foreach ($dnscfg['hosts'] as $host) { + $fqdn = ''; + if ($host['host'] || $host['host'] == "0") { + $fqdn .= "{$host['host']}."; + } + $fqdn .= $host['domain']; + + foreach (explode(',', $host['ip']) as $ip) { + $hosts[] = array( + 'ipaddr' => $ip, + 'fqdn' => $fqdn, + 'name' => $host['host'], + 'domain' => $host['domain'] + ); + } + + if (!is_array($host['aliases']) || + !is_array($host['aliases']['item'])) { + continue; + } + + foreach ($host['aliases']['item'] as $alias) { + $fqdn = ''; + if ($alias['host'] || $alias['host'] == "0") { + $fqdn .= "{$alias['host']}."; + } + $fqdn .= $alias['domain']; + + foreach (explode(',', $host['ip']) as $ip) { + $hosts[] = array( + 'ipaddr' => $ip, + 'fqdn' => $fqdn, + 'name' => $alias['host'], + 'domain' => $alias['domain'] + ); + } + } + } + + return $hosts; +} + +/* Read all dhcpd/dhcpdv6 staticmap entries */ +function system_hosts_dhcpd_entries() { + global $config; + + $hosts = array(); + $syscfg = $config['system']; + + if (is_array($config['dhcpd'])) { + $conf_dhcpd = $config['dhcpd']; + } else { + $conf_dhcpd = array(); + } + + foreach ($conf_dhcpd as $dhcpif => $dhcpifconf) { + if (!is_array($dhcpifconf['staticmap']) || + !isset($dhcpifconf['enable'])) { + continue; + } + foreach ($dhcpifconf['staticmap'] as $host) { + if (!$host['ipaddr'] || + !$host['hostname']) { + continue; + } + + $fqdn = $host['hostname'] . "."; + $domain = ""; + if ($host['domain']) { + $domain = $host['domain']; + } elseif ($dhcpifconf['domain']) { + $domain = $dhcpifconf['domain']; + } else { + $domain = $syscfg['domain']; + } + + $hosts[] = array( + 'ipaddr' => $host['ipaddr'], + 'fqdn' => $fqdn . $domain, + 'name' => $host['hostname'], + 'domain' => $domain + ); + } + } + unset($conf_dhcpd); + + if (is_array($config['dhcpdv6'])) { + $conf_dhcpdv6 = $config['dhcpdv6']; + } else { + $conf_dhcpdv6 = array(); + } + + foreach ($conf_dhcpdv6 as $dhcpif => $dhcpifconf) { + if (!is_array($dhcpifconf['staticmap']) || + !isset($dhcpifconf['enable'])) { + continue; + } + + if (isset($config['interfaces'][$dhcpif]['ipaddrv6']) && + $config['interfaces'][$dhcpif]['ipaddrv6'] == + 'track6') { + $isdelegated = true; + } else { + $isdelegated = false; + } + + foreach ($dhcpifconf['staticmap'] as $host) { + $ipaddrv6 = $host['ipaddrv6']; + + if (!$ipaddrv6 || !$host['hostname']) { + continue; + } + + if ($isdelegated) { + /* + * We are always in an "end-user" subnet + * here, which all are /64 for IPv6. + */ + $prefix6 = 64; + } else { + $prefix6 = get_interface_subnetv6($dhcpif); + } + $ipaddrv6 = merge_ipv6_delegated_prefix(get_interface_ipv6($dhcpif), $ipaddrv6, $prefix6); + + $fqdn = $host['hostname'] . "."; + $domain = ""; + if ($host['domain']) { + $domain = $host['domain']; + } elseif ($dhcpifconf['domain']) { + $domain = $dhcpifconf['domain']; + } else { + $domain = $syscfg['domain']; + } + + $hosts[] = array( + 'ipaddr' => $ipaddrv6, + 'fqdn' => $fqdn . $domain, + 'name' => $host['hostname'], + 'domain' => $domain + ); + } + } + unset($conf_dhcpdv6); + + return $hosts; +} + +/* Concatenate local, dnsmasq/unbound and dhcpd/dhcpdv6 hosts entries */ +function system_hosts_entries($dnscfg) { + $local = array(); + if (!isset($dnscfg['disable_auto_added_host_entries'])) { + $local = system_hosts_local_entries(); + } + + $dns = array(); + $dhcpd = array(); + if (isset($dnscfg['enable'])) { + $dns = system_hosts_override_entries($dnscfg); + if (isset($dnscfg['regdhcpstatic'])) { + $dhcpd = system_hosts_dhcpd_entries(); + } + } + + if (isset($dnscfg['dhcpfirst'])) { + return array_merge($local, $dns, $dhcpd); + } else { + return array_merge($local, $dhcpd, $dns); + } +} + +function system_hosts_generate() { + global $config, $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_hosts_generate() being called $mt\n"; + } + + // prefer dnsmasq for hosts generation where it's enabled. It relies + // on hosts for name resolution of its overrides, unbound does not. + if (isset($config['dnsmasq']) && isset($config['dnsmasq']['enable'])) { + $dnsmasqcfg = $config['dnsmasq']; + } else { + $dnsmasqcfg = $config['unbound']; + } + + $syscfg = $config['system']; + $hosts = ""; + $lhosts = ""; + $dhosts = ""; + + $hosts_array = system_hosts_entries($dnsmasqcfg); + foreach ($hosts_array as $host) { + $hosts .= "{$host['ipaddr']}\t"; + if ($host['name'] == "localhost") { + $hosts .= "{$host['name']} {$host['fqdn']}"; + } else { + $hosts .= "{$host['fqdn']} {$host['name']}"; + } + $hosts .= "\n"; + } + unset($hosts_array); + + /* + * Do not remove this because dhcpleases monitors with kqueue it needs + * to be killed before writing to hosts files. + */ + if (file_exists("{$g['varrun_path']}/dhcpleases.pid")) { + sigkillbypid("{$g['varrun_path']}/dhcpleases.pid", "TERM"); + @unlink("{$g['varrun_path']}/dhcpleases.pid"); + } + + $fd = fopen("{$g['etc_path']}/hosts", "w"); + if (!$fd) { + log_error(gettext( + "Error: cannot open hosts file in system_hosts_generate()." + )); + return 1; + } + + fwrite($fd, $hosts); + fclose($fd); + + if (isset($config['unbound']['enable'])) { + require_once("unbound.inc"); + unbound_hosts_generate(); + } + + /* restart dhcpleases */ + if (!platform_booting()) { + system_dhcpleases_configure(); + } + + return 0; +} + +function system_dhcpleases_configure() { + global $config, $g; + if (!function_exists('is_dhcp_server_enabled')) { + require_once('pfsense-utils.inc'); + } + $pidfile = "{$g['varrun_path']}/dhcpleases.pid"; + + /* Start the monitoring process for dynamic dhcpclients. */ + if (((isset($config['dnsmasq']['enable']) && isset($config['dnsmasq']['regdhcp'])) || + (isset($config['unbound']['enable']) && isset($config['unbound']['regdhcp']))) && + (is_dhcp_server_enabled())) { + /* Make sure we do not error out */ + mwexec("/bin/mkdir -p {$g['dhcpd_chroot_path']}/var/db"); + if (!file_exists("{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases")) { + @touch("{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases"); + } + + if (isset($config['unbound']['enable'])) { + $dns_pid = "unbound.pid"; + $unbound_conf = "-u {$g['unbound_chroot_path']}/dhcpleases_entries.conf"; + } else { + $dns_pid = "dnsmasq.pid"; + $unbound_conf = ""; + } + + if (isvalidpid($pidfile)) { + /* Make sure dhcpleases is using correct unbound or dnsmasq */ + $_gb = exec("/bin/pgrep -F {$pidfile} -f {$dns_pid}", $output, $retval); + if (intval($retval) == 0) { + sigkillbypid($pidfile, "HUP"); + return; + } else { + sigkillbypid($pidfile, "TERM"); + } + } + + /* To ensure we do not start multiple instances of dhcpleases, perform some clean-up first. */ + if (is_process_running("dhcpleases")) { + sigkillbyname('dhcpleases', "TERM"); + } + @unlink($pidfile); + mwexec("/usr/local/sbin/dhcpleases -l {$g['dhcpd_chroot_path']}/var/db/dhcpd.leases -d {$config['system']['domain']} -p {$g['varrun_path']}/{$dns_pid} {$unbound_conf} -h {$g['etc_path']}/hosts"); + } else { + if (isvalidpid($pidfile)) { + sigkillbypid($pidfile, "TERM"); + @unlink($pidfile); + } + if (file_exists("{$g['unbound_chroot_path']}/dhcpleases_entries.conf")) { + $dhcpleases = fopen("{$g['unbound_chroot_path']}/dhcpleases_entries.conf", "w"); + ftruncate($dhcpleases, 0); + fclose($dhcpleases); + } + } +} + +function system_get_dhcpleases() { + global $config, $g; + + $leases = array(); + $leases['lease'] = array(); + $leases['failover'] = array(); + + $leases_file = "{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases"; + + if (!file_exists($leases_file)) { + return $leases; + } + + $leases_content = file($leases_file, FILE_IGNORE_NEW_LINES | + FILE_IGNORE_NEW_LINES); + + if ($leases_content === FALSE) { + return $leases; + } + + $arp_table = system_get_arp_table(); + + $arpdata_ip = array(); + $arpdata_mac = array(); + foreach ($arp_table as $arp_entry) { + if (isset($arpentry['incomplete'])) { + continue; + } + $arpdata_ip[] = $arp_entry['ip-address']; + $arpdata_mac[] = $arp_entry['mac-address']; + } + unset($arp_table); + + /* + * Translate these once so we don't do it over and over in the loops + * below. + */ + $online_string = gettext("online"); + $offline_string = gettext("offline"); + $active_string = gettext("active"); + $expired_string = gettext("expired"); + $reserved_string = gettext("reserved"); + $dynamic_string = gettext("dynamic"); + $static_string = gettext("static"); + + $lease_regex = '/^lease\s+([^\s]+)\s+{$/'; + $starts_regex = '/^\s*(starts|ends)\s+\d+\s+([\d\/]+|never)\s*(|[\d:]*);$/'; + $binding_regex = '/^\s*binding\s+state\s+(.+);$/'; + $mac_regex = '/^\s*hardware\s+ethernet\s+(.+);$/'; + $hostname_regex = '/^\s*client-hostname\s+"(.+)";$/'; + + $failover_regex = '/^failover\s+peer\s+"(.+)"\s+state\s+{$/'; + $state_regex = '/\s*(my|partner)\s+state\s+(.+)\s+at\s+\d+\s+([\d\/]+)\s+([\d:]+);$/'; + + $lease = false; + $failover = false; + $dedup_lease = false; + $dedup_failover = false; + foreach ($leases_content as $line) { + /* Skip comments */ + if (preg_match('/^\s*(|#.*)$/', $line)) { + continue; + } + + if (preg_match('/}$/', $line)) { + if ($lease) { + if (empty($item['hostname'])) { + $hostname = gethostbyaddr($item['ip']); + if (!empty($hostname)) { + $item['hostname'] = $hostname; + } + } + $leases['lease'][] = $item; + $lease = false; + $dedup_lease = true; + } else if ($failover) { + $leases['failover'][] = $item; + $failover = false; + $dedup_failover = true; + } + continue; + } + + if (preg_match($lease_regex, $line, $m)) { + $lease = true; + $item = array(); + $item['ip'] = $m[1]; + $item['type'] = $dynamic_string; + continue; + } + + if ($lease) { + if (preg_match($starts_regex, $line, $m)) { + /* + * Quote from dhcpd.leases(5) man page: + * If a lease will never expire, date is never + * instead of an actual date + */ + if ($m[2] == "never") { + $item[$m[1]] = gettext("Never"); + } else { + $item[$m[1]] = dhcpd_date_adjust_gmt( + $m[2] . ' ' . $m[3]); + } + continue; + } + + if (preg_match($binding_regex, $line, $m)) { + switch ($m[1]) { + case "active": + $item['act'] = $active_string; + break; + case "free": + $item['act'] = $expired_string; + $item['online'] = + $offline_string; + break; + case "backup": + $item['act'] = $reserved_string; + $item['online'] = + $offline_string; + break; + } + continue; + } + + if (preg_match($mac_regex, $line, $m) && + is_macaddr($m[1])) { + $item['mac'] = $m[1]; + + if (in_array($item['ip'], $arpdata_ip)) { + $item['online'] = $online_string; + } else { + $item['online'] = $offline_string; + } + continue; + } + + if (preg_match($hostname_regex, $line, $m)) { + $item['hostname'] = $m[1]; + } + } + + if (preg_match($failover_regex, $line, $m)) { + $failover = true; + $item = array(); + $item['name'] = $m[1] . ' (' . + convert_friendly_interface_to_friendly_descr( + substr($m[1],5)) . ')'; + continue; + } + + if ($failover && preg_match($state_regex, $line, $m)) { + $item[$m[1] . 'state'] = $m[2]; + $item[$m[1] . 'date'] = dhcpd_date_adjust_gmt($m[3] . + ' ' . $m[4]); + continue; + } + } + + foreach ($config['interfaces'] as $ifname => $ifarr) { + if (!is_array($config['dhcpd'][$ifname]) || + !is_array($config['dhcpd'][$ifname]['staticmap'])) { + continue; + } + + foreach ($config['dhcpd'][$ifname]['staticmap'] as $idx => + $static) { + if (empty($static['mac']) && empty($static['cid'])) { + continue; + } + + $slease = array(); + $slease['ip'] = $static['ipaddr']; + $slease['type'] = $static_string; + if (!empty($static['cid'])) { + $slease['cid'] = $static['cid']; + } + $slease['mac'] = $static['mac']; + $slease['if'] = $ifname; + $slease['starts'] = ""; + $slease['ends'] = ""; + $slease['hostname'] = $static['hostname']; + $slease['descr'] = $static['descr']; + $slease['act'] = $static_string; + $slease['online'] = in_array(strtolower($slease['mac']), + $arpdata_mac) ? $online_string : $offline_string; + $slease['staticmap_array_index'] = $idx; + $leases['lease'][] = $slease; + $dedup_lease = true; + } + } + + if ($dedup_lease) { + $leases['lease'] = array_remove_duplicate($leases['lease'], + 'ip'); + } + if ($dedup_failover) { + $leases['failover'] = array_remove_duplicate( + $leases['failover'], 'name'); + asort($leases['failover']); + } + + return $leases; +} + +function system_hostname_configure() { + global $config, $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_hostname_configure() being called $mt\n"; + } + + $syscfg = $config['system']; + + /* set hostname */ + $status = mwexec("/bin/hostname " . + escapeshellarg("{$syscfg['hostname']}.{$syscfg['domain']}")); + + /* Setup host GUID ID. This is used by ZFS. */ + mwexec("/etc/rc.d/hostid start"); + + return $status; +} + +function system_routing_configure($interface = "") { + global $config, $g; + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_routing_configure() being called $mt\n"; + } + + $gateways_arr = return_gateways_array(false, true); + foreach ($gateways_arr as $gateway) { + // setup static interface routes for nonlocal gateways + if (isset($gateway["nonlocalgateway"])) { + $srgatewayip = $gateway['gateway']; + $srinterfacegw = $gateway['interface']; + if (is_ipaddr($srgatewayip) && !empty($srinterfacegw)) { + route_add_or_change($srgatewayip, '', + $srinterfacegw); + } + } + } + + $gateways_status = return_gateways_status(true); + fixup_default_gateway("inet", $gateways_status, $gateways_arr); + fixup_default_gateway("inet6", $gateways_status, $gateways_arr); + + system_staticroutes_configure($interface, false); + + return 0; +} + +function system_staticroutes_configure($interface = "", $update_dns = false) { + global $config, $g, $aliastable; + + $filterdns_list = array(); + + $static_routes = get_staticroutes(false, true); + if (count($static_routes)) { + $gateways_arr = return_gateways_array(false, true); + + foreach ($static_routes as $rtent) { + if (empty($gateways_arr[$rtent['gateway']])) { + log_error(sprintf(gettext("Static Routes: Gateway IP could not be found for %s"), $rtent['network'])); + continue; + } + $gateway = $gateways_arr[$rtent['gateway']]; + if (!empty($interface) && $interface != $gateway['friendlyiface']) { + continue; + } + + $gatewayip = $gateway['gateway']; + $interfacegw = $gateway['interface']; + + $blackhole = ""; + if (!strcasecmp("Null", substr($rtent['gateway'], 0, 4))) { + $blackhole = "-blackhole"; + } + + if (!is_fqdn($rtent['network']) && !is_subnet($rtent['network'])) { + continue; + } + + $dnscache = array(); + if ($update_dns === true) { + if (is_subnet($rtent['network'])) { + continue; + } + $dnscache = explode("\n", trim(compare_hostname_to_dnscache($rtent['network']))); + if (empty($dnscache)) { + continue; + } + } + + if (is_subnet($rtent['network'])) { + $ips = array($rtent['network']); + } else { + if (!isset($rtent['disabled'])) { + $filterdns_list[] = $rtent['network']; + } + $ips = add_hostname_to_watch($rtent['network']); + } + + foreach ($dnscache as $ip) { + if (in_array($ip, $ips)) { + continue; + } + route_del($ip); + } + + if (isset($rtent['disabled'])) { + /* + * XXX: This can break things by deleting + * routes that shouldn't be deleted - OpenVPN, + * dynamic routing scenarios, etc. + * redmine #3709 + */ + foreach ($ips as $ip) { + route_del($ip); + } + continue; + } + + foreach ($ips as $ip) { + if (is_ipaddrv4($ip)) { + $ip .= "/32"; + } + /* + * do NOT do the same check here on v6, + * is_ipaddrv6 returns true when including + * the CIDR mask. doing so breaks v6 routes + */ + if (is_subnet($ip)) { + if (is_ipaddr($gatewayip)) { + if (is_linklocal($gatewayip) == "6" && + !strpos($gatewayip, '%')) { + /* + * add interface scope + * for link local v6 + * routes + */ + $gatewayip .= "%$interfacegw"; + } + route_add_or_change($ip, + $gatewayip, '', $blackhole); + } else if (!empty($interfacegw)) { + route_add_or_change($ip, + '', $interfacegw, $blackhole); + } + } + } + } + unset($gateways_arr); + } + unset($static_routes); + + if ($update_dns === false) { + if (count($filterdns_list)) { + $interval = 60; + $hostnames = ""; + array_unique($filterdns_list); + foreach ($filterdns_list as $hostname) { + $hostnames .= "cmd {$hostname} '/usr/local/sbin/pfSctl -c \"service reload routedns\"'\n"; + } + file_put_contents("{$g['varetc_path']}/filterdns-route.hosts", $hostnames); + unset($hostnames); + + if (isvalidpid("{$g['varrun_path']}/filterdns-route.pid")) { + sigkillbypid("{$g['varrun_path']}/filterdns-route.pid", "HUP"); + } else { + mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns-route.pid -i {$interval} -c {$g['varetc_path']}/filterdns-route.hosts -d 1"); + } + } else { + killbypid("{$g['varrun_path']}/filterdns-route.pid"); + @unlink("{$g['varrun_path']}/filterdns-route.pid"); + } + } + unset($filterdns_list); + + return 0; +} + +function system_routing_enable() { + global $config, $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_routing_enable() being called $mt\n"; + } + + set_sysctl(array( + "net.inet.ip.forwarding" => "1", + "net.inet6.ip6.forwarding" => "1" + )); + + return; +} + +function system_webgui_create_certificate() { + global $config, $g, $cert_strict_values; + + init_config_arr(array('ca')); + $a_ca = &$config['ca']; + init_config_arr(array('cert')); + $a_cert = &$config['cert']; + log_error(gettext("Creating SSL/TLS Certificate for this host")); + + $cert = array(); + $cert['refid'] = uniqid(); + $cert['descr'] = sprintf(gettext("webConfigurator default (%s)"), $cert['refid']); + $cert_hostname = "{$config['system']['hostname']}-{$cert['refid']}"; + + $dn = array( + 'organizationName' => "{$g['product_label']} webConfigurator Self-Signed Certificate", + 'commonName' => $cert_hostname, + 'subjectAltName' => "DNS:{$cert_hostname}"); + $old_err_level = error_reporting(0); /* otherwise openssl_ functions throw warnings directly to a page screwing menu tab */ + if (!cert_create($cert, null, 2048, $cert_strict_values['max_server_cert_lifetime'], $dn, "self-signed", "sha256")) { + while ($ssl_err = openssl_error_string()) { + log_error(sprintf(gettext("Error creating WebGUI Certificate: openssl library returns: %s"), $ssl_err)); + } + error_reporting($old_err_level); + return null; + } + error_reporting($old_err_level); + + $a_cert[] = $cert; + $config['system']['webgui']['ssl-certref'] = $cert['refid']; + write_config(sprintf(gettext("Generated new self-signed SSL/TLS certificate for HTTPS (%s)"), $cert['refid'])); + return $cert; +} + +function system_webgui_start() { + global $config, $g; + + if (platform_booting()) { + echo gettext("Starting webConfigurator..."); + } + + chdir($g['www_path']); + + /* defaults */ + $portarg = "80"; + $crt = ""; + $key = ""; + $ca = ""; + + /* non-standard port? */ + if (isset($config['system']['webgui']['port']) && $config['system']['webgui']['port'] <> "") { + $portarg = "{$config['system']['webgui']['port']}"; + } + + if ($config['system']['webgui']['protocol'] == "https") { + // Ensure that we have a webConfigurator CERT + $cert =& lookup_cert($config['system']['webgui']['ssl-certref']); + if (!is_array($cert) || !$cert['crt'] || !$cert['prv']) { + $cert = system_webgui_create_certificate(); + } + $crt = base64_decode($cert['crt']); + $key = base64_decode($cert['prv']); + + if (!$config['system']['webgui']['port']) { + $portarg = "443"; + } + $ca = ca_chain($cert); + $hsts = isset($config['system']['webgui']['disablehsts']) ? false : true; + } + + /* generate nginx configuration */ + system_generate_nginx_config("{$g['varetc_path']}/nginx-webConfigurator.conf", + $crt, $key, $ca, "nginx-webConfigurator.pid", $portarg, "/usr/local/www/", + "cert.crt", "cert.key", false, $hsts); + + /* kill any running nginx */ + killbypid("{$g['varrun_path']}/nginx-webConfigurator.pid"); + + sleep(1); + + @unlink("{$g['varrun_path']}/nginx-webConfigurator.pid"); + + /* start nginx */ + $res = mwexec("/usr/local/sbin/nginx -c {$g['varetc_path']}/nginx-webConfigurator.conf"); + + if (platform_booting()) { + if ($res == 0) { + echo gettext("done.") . "\n"; + } else { + echo gettext("failed!") . "\n"; + } + } + + return $res; +} + +/****f* system.inc/get_dns_nameservers + * NAME + * get_dns_nameservers - Get system DNS servers + * INPUTS + * $add_v6_brackets: (boolean, false) + * Add brackets around IPv6 DNS servers, as expected by some + * daemons such as nginx. + * $hostns : (boolean, true) + * true : Return only DNS servers used by the firewall + * itself as upstream forwarding servers + * false: Return all DNS servers from the configuration and + * overrides (if allowed). + * RESULT + * $dns_nameservers - An array of the requested DNS servers + ******/ +function get_dns_nameservers($add_v6_brackets = false, $hostns=true) { + global $config; + + $dns_nameservers = array(); + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "get_dns_nameservers() being called $mt\n"; + } + + $syscfg = $config['system']; + if ((((isset($config['dnsmasq']['enable'])) && + (empty($config['dnsmasq']['port']) || $config['dnsmasq']['port'] == "53") && + (empty($config['dnsmasq']['interface']) || + in_array("lo0", explode(",", $config['dnsmasq']['interface'])))) || + ((isset($config['unbound']['enable'])) && + (empty($config['unbound']['port']) || $config['unbound']['port'] == "53") && + (empty($config['unbound']['active_interface']) || + in_array("lo0", explode(",", $config['unbound']['active_interface'])) || + in_array("all", explode(",", $config['unbound']['active_interface']), true)))) && + ($config['system']['dnslocalhost'] != 'remote')) { + $dns_nameservers[] = "127.0.0.1"; + } + + if ($hostns || ($config['system']['dnslocalhost'] != 'local')) { + if (isset($syscfg['dnsallowoverride'])) { + /* get dynamically assigned DNS servers (if any) */ + foreach (array_unique(get_dynamic_nameservers()) as $nameserver) { + if ($nameserver) { + if ($add_v6_brackets && is_ipaddrv6($nameserver)) { + $nameserver = "[{$nameserver}]"; + } + $dns_nameservers[] = $nameserver; + } + } + } + if (is_array($syscfg['dnsserver'])) { + foreach ($syscfg['dnsserver'] as $sys_dnsserver) { + if ($sys_dnsserver && (!in_array($sys_dnsserver, $dns_nameservers))) { + if ($add_v6_brackets && is_ipaddrv6($sys_dnsserver)) { + $sys_dnsserver = "[{$sys_dnsserver}]"; + } + $dns_nameservers[] = $sys_dnsserver; + } + } + } + } + return array_unique($dns_nameservers); +} + +function system_generate_nginx_config($filename, + $cert, + $key, + $ca, + $pid_file, + $port = 80, + $document_root = "/usr/local/www/", + $cert_location = "cert.crt", + $key_location = "cert.key", + $captive_portal = false, + $hsts = true) { + + global $config, $g; + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_generate_nginx_config() being called $mt\n"; + } + + if ($captive_portal !== false) { + $cp_interfaces = explode(",", $config['captiveportal'][$captive_portal]['interface']); + $cp_hostcheck = ""; + foreach ($cp_interfaces as $cpint) { + $cpint_ip = get_interface_ip($cpint); + if (is_ipaddr($cpint_ip)) { + $cp_hostcheck .= "\t\tif (\$http_host ~* $cpint_ip) {\n"; + $cp_hostcheck .= "\t\t\tset \$cp_redirect no;\n"; + $cp_hostcheck .= "\t\t}\n"; + } + } + if (isset($config['captiveportal'][$captive_portal]['httpsname']) && + is_domain($config['captiveportal'][$captive_portal]['httpsname'])) { + $cp_hostcheck .= "\t\tif (\$http_host ~* {$config['captiveportal'][$captive_portal]['httpsname']}) {\n"; + $cp_hostcheck .= "\t\t\tset \$cp_redirect no;\n"; + $cp_hostcheck .= "\t\t}\n"; + } + $cp_rewrite = "\t\tif (\$cp_redirect = '') {\n"; + $cp_rewrite .= "\t\t\trewrite ^ /index.php?zone=$captive_portal&redirurl=\$request_uri break;\n"; + $cp_rewrite .= "\t\t}\n"; + + $maxprocperip = $config['captiveportal'][$captive_portal]['maxprocperip']; + if (empty($maxprocperip)) { + $maxprocperip = 10; + } + $captive_portal_maxprocperip = "\t\tlimit_conn addr $maxprocperip;\n"; + } + + if (empty($port)) { + $nginx_port = "80"; + } else { + $nginx_port = $port; + } + + $memory = get_memory(); + $realmem = $memory[1]; + + // Determine web GUI process settings and take into account low memory systems + if ($realmem < 255) { + $max_procs = 1; + } else { + $max_procs = ($config['system']['webgui']['max_procs']) ? $config['system']['webgui']['max_procs'] : 2; + } + + // Ramp up captive portal max procs, assuming each PHP process can consume up to 64MB RAM + if ($captive_portal !== false) { + if ($realmem > 135 and $realmem < 256) { + $max_procs += 1; // 2 worker processes + } else if ($realmem > 255 and $realmem < 513) { + $max_procs += 2; // 3 worker processes + } else if ($realmem > 512) { + $max_procs += 4; // 6 worker processes + } + } + + $nginx_config = << "" and $key <> "") { + $nginx_config .= "\n"; + $nginx_config .= "\tserver {\n"; + $nginx_config .= "\t\tlisten {$nginx_port} ssl http2;\n"; + $nginx_config .= "\t\tlisten [::]:{$nginx_port} ssl http2;\n"; + $nginx_config .= "\n"; + $nginx_config .= "\t\tssl_certificate {$g['varetc_path']}/{$cert_location};\n"; + $nginx_config .= "\t\tssl_certificate_key {$g['varetc_path']}/{$key_location};\n"; + $nginx_config .= "\t\tssl_session_timeout 10m;\n"; + $nginx_config .= "\t\tkeepalive_timeout 70;\n"; + $nginx_config .= "\t\tssl_session_cache shared:SSL:10m;\n"; + if ($captive_portal !== false) { + // leave TLSv1.1 for CP for now for compatibility + $nginx_config .= "\t\tssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;\n"; + } else { + $nginx_config .= "\t\tssl_protocols TLSv1.2 TLSv1.3;\n"; + } + $nginx_config .= "\t\tssl_ciphers \"EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305\";\n"; + $nginx_config .= "\t\tssl_prefer_server_ciphers on;\n"; + if ($captive_portal === false && $hsts !== false) { + $nginx_config .= "\t\tadd_header Strict-Transport-Security \"max-age=31536000\";\n"; + } + $nginx_config .= "\t\tadd_header X-Content-Type-Options nosniff;\n"; + $nginx_config .= "\t\tssl_session_tickets off;\n"; + $nginx_config .= "\t\tssl_dhparam /etc/dh-parameters.4096;\n"; + $cert_temp = lookup_cert($config['system']['webgui']['ssl-certref']); + if (($config['system']['webgui']['ocsp-staple'] == true) or + (cert_get_ocspstaple($cert_temp['crt']) == true)) { + $nginx_config .= "\t\tssl_stapling on;\n"; + $nginx_config .= "\t\tssl_stapling_verify on;\n"; + $nginx_config .= "\t\tresolver " . implode(" ", get_dns_nameservers(true)) . " valid=300s;\n"; + $nginx_config .= "\t\tresolver_timeout 5s;\n"; + } + } else { + $nginx_config .= "\n"; + $nginx_config .= "\tserver {\n"; + $nginx_config .= "\t\tlisten {$nginx_port};\n"; + $nginx_config .= "\t\tlisten [::]:{$nginx_port};\n"; + } + + $nginx_config .= << "" and $key <> "") { + $fd = fopen("{$g['varetc_path']}/{$cert_location}", "w"); + if (!$fd) { + printf(gettext("Error: cannot open certificate file in system_webgui_start().%s"), "\n"); + return 1; + } + chmod("{$g['varetc_path']}/{$cert_location}", 0644); + if ($ca <> "") { + $cert_chain = $cert . "\n" . $ca; + } else { + $cert_chain = $cert; + } + fwrite($fd, $cert_chain); + fclose($fd); + $fd = fopen("{$g['varetc_path']}/{$key_location}", "w"); + if (!$fd) { + printf(gettext("Error: cannot open certificate key file in system_webgui_start().%s"), "\n"); + return 1; + } + chmod("{$g['varetc_path']}/{$key_location}", 0600); + fwrite($fd, $key); + fclose($fd); + } + + // Add HTTP to HTTPS redirect + if ($captive_portal === false && $config['system']['webgui']['protocol'] == "https" && !isset($config['system']['webgui']['disablehttpredirect'])) { + if ($nginx_port != "443") { + $redirectport = ":{$nginx_port}"; + } + $nginx_config .= << 0) { + return true; + } + } + if (strpos($contents, '0') > 0) { + $filters = ['`', '?', '/', '~']; + foreach ($filters as $filter) { + if (strpos($contents, $filter) !== false) { + return false; + } + } + return true; + } + } + return false; +} + +/* Generate list of possible NTP poll values + * https://redmine.pfsense.org/issues/9439 */ +global $ntp_poll_min_value, $ntp_poll_max_value; +global $ntp_poll_min_default_gps, $ntp_poll_max_default_gps; +global $ntp_poll_min_default_pps, $ntp_poll_max_default_pps; +global $ntp_poll_min_default, $ntp_poll_max_default; +global $ntp_auth_halgos; +$ntp_poll_min_value = 4; +$ntp_poll_max_value = 17; +$ntp_poll_min_default_gps = 4; +$ntp_poll_max_default_gps = 4; +$ntp_poll_min_default_pps = 4; +$ntp_poll_max_default_pps = 4; +$ntp_poll_min_default = 'omit'; +$ntp_poll_max_default = 9; +$ntp_auth_halgos = array( + 'md5' => 'MD5', + 'sha1' => 'SHA1' +); + +function system_ntp_poll_values() { + global $ntp_poll_min_value, $ntp_poll_max_value; + $poll_values = array("" => gettext('Default')); + + for ($i = $ntp_poll_min_value; $i <= $ntp_poll_max_value; $i++) { + $sec = 2 ** $i; + $poll_values[$i] = $i . ': ' . number_format($sec) . ' ' . gettext('seconds') . + ' (' . convert_seconds_to_dhms($sec) . ')'; + } + + $poll_values['omit'] = gettext('Omit (Do not set)'); + return $poll_values; +} + +function system_ntp_fixup_poll_value($type, $configvalue, $default) { + $pollstring = ""; + + if (empty($configvalue)) { + $configvalue = $default; + } + + if ($configvalue != 'omit') { + $pollstring = " {$type} {$configvalue}"; + } + + return $pollstring; +} + +function system_ntp_setup_gps($serialport) { + global $config, $g; + + if (is_array($config['ntpd']) && ($config['ntpd']['enable'] == 'disabled')) { + return false; + } + + init_config_arr(array('ntpd', 'gps')); + + $gps_device = '/dev/gps0'; + $serialport = '/dev/'.$serialport; + + if (!file_exists($serialport)) { + return false; + } + + // Create symlink that ntpd requires + unlink_if_exists($gps_device); + @symlink($serialport, $gps_device); + + $gpsbaud = '4800'; + $speeds = array( + 0 => '4800', + 16 => '9600', + 32 => '19200', + 48 => '38400', + 64 => '57600', + 80 => '115200' + ); + if (!empty($config['ntpd']['gps']['speed']) && array_key_exists($config['ntpd']['gps']['speed'], $speeds)) { + $gpsbaud = $speeds[$config['ntpd']['gps']['speed']]; + } + + system_ntp_setup_rawspeed($serialport, $gpsbaud); + + $autospeed = ($config['ntpd']['gps']['speed'] == 'autoalways' || $config['ntpd']['gps']['speed'] == 'autoset'); + if ($autospeed || ($config['ntpd']['gps']['autobaudinit'] && !check_gps_speed($gps_device))) { + $found = false; + foreach ($speeds as $baud) { + system_ntp_setup_rawspeed($serialport, $baud); + if ($found = check_gps_speed($gps_device)) { + if ($autospeed) { + $saveconfig = ($config['ntpd']['gps']['speed'] == 'autoset'); + $config['ntpd']['gps']['speed'] = array_search($baud, $speeds); + $gpsbaud = $baud; + if ($saveconfig) { + write_config(sprintf(gettext('Autoset GPS baud rate to %s'), $baud)); + } + } + break; + } + } + if ($found === false) { + log_error(gettext("Could not find correct GPS baud rate.")); + return false; + } + } + + /* Send the following to the GPS port to initialize the GPS */ + if (is_array($config['ntpd']) && is_array($config['ntpd']['gps']) && !empty($config['ntpd']['gps']['type'])) { + $gps_init = base64_decode($config['ntpd']['gps']['initcmd']); + } else { + $gps_init = base64_decode('JFBVQlgsNDAsR1NWLDAsMCwwLDAqNTkNCiRQVUJYLDQwLEdMTCwwLDAsMCwwKjVDDQokUFVCWCw0MCxaREEsMCwwLDAsMCo0NA0KJFBVQlgsNDAsVlRHLDAsMCwwLDAqNUUNCiRQVUJYLDQwLEdTViwwLDAsMCwwKjU5DQokUFVCWCw0MCxHU0EsMCwwLDAsMCo0RQ0KJFBVQlgsNDAsR0dBLDAsMCwwLDANCiRQVUJYLDQwLFRYVCwwLDAsMCwwDQokUFVCWCw0MCxSTUMsMCwwLDAsMCo0Ng0KJFBVQlgsNDEsMSwwMDA3LDAwMDMsNDgwMCwwDQokUFVCWCw0MCxaREEsMSwxLDEsMQ=='); + } + + /* XXX: Why not file_put_contents to the device */ + @file_put_contents('/tmp/gps.init', $gps_init); + mwexec("cat /tmp/gps.init > {$serialport}"); + + if ($found && $config['ntpd']['gps']['autobaudinit']) { + system_ntp_setup_rawspeed($serialport, $gpsbaud); + } + + /* Remove old /etc/remote entry if it exists */ + if (mwexec("/usr/bin/grep -c '^gps0' /etc/remote") == 0) { + mwexec("/usr/bin/sed -i '' -n '/gps0/!p' /etc/remote"); + } + + /* Add /etc/remote entry in case we need to read from the GPS with tip */ + if (mwexec("/usr/bin/grep -c '^gps0' /etc/remote") != 0) { + @file_put_contents("/etc/remote", "gps0:dv={$serialport}:br#{$gpsbaud}:pa=none:\n", FILE_APPEND); + } + + return true; +} + +// Configure the serial port for raw IO and set the speed +function system_ntp_setup_rawspeed($serialport, $baud) { + mwexec("/bin/stty -f " . escapeshellarg($serialport) . " raw speed " . escapeshellarg($baud)); + mwexec("/bin/stty -f " . escapeshellarg($serialport) . ".init raw speed " . escapeshellarg($baud)); +} + +function system_ntp_setup_pps($serialport) { + global $config, $g; + + $pps_device = '/dev/pps0'; + $serialport = '/dev/'.$serialport; + + if (!file_exists($serialport)) { + return false; + } + // If ntpd is disabled, just return + if (is_array($config['ntpd']) && ($config['ntpd']['enable'] == 'disabled')) { + return false; + } + + // Create symlink that ntpd requires + unlink_if_exists($pps_device); + @symlink($serialport, $pps_device); + + + return true; +} + +function system_ntp_configure() { + global $config, $g; + global $ntp_poll_min_default_gps, $ntp_poll_max_default_gps; + global $ntp_poll_min_default_pps, $ntp_poll_max_default_pps; + global $ntp_poll_min_default, $ntp_poll_max_default; + + $driftfile = "/var/db/ntpd.drift"; + $statsdir = "/var/log/ntp"; + $gps_device = '/dev/gps0'; + + safe_mkdir($statsdir); + + if (!is_array($config['ntpd'])) { + $config['ntpd'] = array(); + } + // ntpd is disabled, just stop it and return + if ($config['ntpd']['enable'] == 'disabled') { + while (isvalidpid("{$g['varrun_path']}/ntpd.pid")) { + killbypid("{$g['varrun_path']}/ntpd.pid"); + } + @unlink("{$g['varrun_path']}/ntpd.pid"); + @unlink("{$g['varetc_path']}/ntpd.conf"); + @unlink("{$g['varetc_path']}/ntp.keys"); + log_error("NTPD is disabled."); + return; + } + + if (platform_booting()) { + echo gettext("Starting NTP Server..."); + } + + /* if ntpd is running, kill it */ + while (isvalidpid("{$g['varrun_path']}/ntpd.pid")) { + killbypid("{$g['varrun_path']}/ntpd.pid"); + } + @unlink("{$g['varrun_path']}/ntpd.pid"); + + /* set NTP server authentication key */ + if ($config['ntpd']['serverauth'] == 'yes') { + $ntpkeyscfg = "1 " . strtoupper($config['ntpd']['serverauthalgo']) . " " . base64_decode($config['ntpd']['serverauthkey']) . "\n"; + if (!@file_put_contents("{$g['varetc_path']}/ntp.keys", $ntpkeyscfg)) { + log_error(sprintf(gettext("Could not open %s/ntp.keys for writing"), $g['varetc_path'])); + return; + } + } else { + unlink_if_exists("{$g['varetc_path']}/ntp.keys"); + } + + $ntpcfg = "# \n"; + $ntpcfg .= "# pfSense ntp configuration file \n"; + $ntpcfg .= "# \n\n"; + $ntpcfg .= "tinker panic 0 \n\n"; + + if ($config['ntpd']['serverauth'] == 'yes') { + $ntpcfg .= "# Authentication settings \n"; + $ntpcfg .= "keys /var/etc/ntp.keys \n"; + $ntpcfg .= "trustedkey 1 \n"; + $ntpcfg .= "requestkey 1 \n"; + $ntpcfg .= "controlkey 1 \n"; + $ntpcfg .= "\n"; + } + + /* Add Orphan mode */ + $ntpcfg .= "# Orphan mode stratum and Maximum candidate NTP peers\n"; + $ntpcfg .= 'tos orphan '; + if (!empty($config['ntpd']['orphan'])) { + $ntpcfg .= $config['ntpd']['orphan']; + } else { + $ntpcfg .= '12'; + } + /* Add Maximum candidate NTP peers */ + $ntpcfg .= ' maxclock '; + if (!empty($config['ntpd']['ntpmaxpeers'])) { + $ntpcfg .= $config['ntpd']['ntpmaxpeers']; + } else { + $ntpcfg .= '5'; + } + $ntpcfg .= "\n"; + + /* Add PPS configuration */ + if (is_array($config['ntpd']['pps']) && !empty($config['ntpd']['pps']['port']) && + file_exists('/dev/'.$config['ntpd']['pps']['port']) && + system_ntp_setup_pps($config['ntpd']['pps']['port'])) { + $ntpcfg .= "\n"; + $ntpcfg .= "# PPS Setup\n"; + $ntpcfg .= 'server 127.127.22.0'; + $ntpcfg .= system_ntp_fixup_poll_value('minpoll', $config['ntpd']['pps']['ppsminpoll'], $ntp_poll_min_default_pps); + $ntpcfg .= system_ntp_fixup_poll_value('maxpoll', $config['ntpd']['pps']['ppsmaxpoll'], $ntp_poll_max_default_pps); + if (empty($config['ntpd']['pps']['prefer'])) { /*note: this one works backwards */ + $ntpcfg .= ' prefer'; + } + if (!empty($config['ntpd']['pps']['noselect'])) { + $ntpcfg .= ' noselect '; + } + $ntpcfg .= "\n"; + $ntpcfg .= 'fudge 127.127.22.0'; + if (!empty($config['ntpd']['pps']['fudge1'])) { + $ntpcfg .= ' time1 '; + $ntpcfg .= $config['ntpd']['pps']['fudge1']; + } + if (!empty($config['ntpd']['pps']['flag2'])) { + $ntpcfg .= ' flag2 1'; + } + if (!empty($config['ntpd']['pps']['flag3'])) { + $ntpcfg .= ' flag3 1'; + } else { + $ntpcfg .= ' flag3 0'; + } + if (!empty($config['ntpd']['pps']['flag4'])) { + $ntpcfg .= ' flag4 1'; + } + if (!empty($config['ntpd']['pps']['refid'])) { + $ntpcfg .= ' refid '; + $ntpcfg .= $config['ntpd']['pps']['refid']; + } + $ntpcfg .= "\n"; + } + /* End PPS configuration */ + + /* Add GPS configuration */ + if (is_array($config['ntpd']['gps']) && !empty($config['ntpd']['gps']['port']) && + system_ntp_setup_gps($config['ntpd']['gps']['port'])) { + $ntpcfg .= "\n"; + $ntpcfg .= "# GPS Setup\n"; + $ntpcfg .= 'server 127.127.20.0 mode '; + if (!empty($config['ntpd']['gps']['nmea']) || !empty($config['ntpd']['gps']['speed']) || !empty($config['ntpd']['gps']['subsec']) || !empty($config['ntpd']['gps']['processpgrmf'])) { + if (!empty($config['ntpd']['gps']['nmea'])) { + $ntpmode = (int) $config['ntpd']['gps']['nmea']; + } + if (!empty($config['ntpd']['gps']['speed'])) { + $ntpmode += (int) $config['ntpd']['gps']['speed']; + } + if (!empty($config['ntpd']['gps']['subsec'])) { + $ntpmode += 128; + } + if (!empty($config['ntpd']['gps']['processpgrmf'])) { + $ntpmode += 256; + } + $ntpcfg .= (string) $ntpmode; + } else { + $ntpcfg .= '0'; + } + $ntpcfg .= system_ntp_fixup_poll_value('minpoll', $config['ntpd']['gps']['gpsminpoll'], $ntp_poll_min_default_gps); + $ntpcfg .= system_ntp_fixup_poll_value('maxpoll', $config['ntpd']['gps']['gpsmaxpoll'], $ntp_poll_max_default_gps); + + if (empty($config['ntpd']['gps']['prefer'])) { /*note: this one works backwards */ + $ntpcfg .= ' prefer'; + } + if (!empty($config['ntpd']['gps']['noselect'])) { + $ntpcfg .= ' noselect '; + } + $ntpcfg .= "\n"; + $ntpcfg .= 'fudge 127.127.20.0'; + if (!empty($config['ntpd']['gps']['fudge1'])) { + $ntpcfg .= ' time1 '; + $ntpcfg .= $config['ntpd']['gps']['fudge1']; + } + if (!empty($config['ntpd']['gps']['fudge2'])) { + $ntpcfg .= ' time2 '; + $ntpcfg .= $config['ntpd']['gps']['fudge2']; + } + if (!empty($config['ntpd']['gps']['flag1'])) { + $ntpcfg .= ' flag1 1'; + } else { + $ntpcfg .= ' flag1 0'; + } + if (!empty($config['ntpd']['gps']['flag2'])) { + $ntpcfg .= ' flag2 1'; + } + if (!empty($config['ntpd']['gps']['flag3'])) { + $ntpcfg .= ' flag3 1'; + } else { + $ntpcfg .= ' flag3 0'; + } + if (!empty($config['ntpd']['gps']['flag4'])) { + $ntpcfg .= ' flag4 1'; + } + if (!empty($config['ntpd']['gps']['refid'])) { + $ntpcfg .= ' refid '; + $ntpcfg .= $config['ntpd']['gps']['refid']; + } + if (!empty($config['ntpd']['gps']['stratum'])) { + $ntpcfg .= ' stratum '; + $ntpcfg .= $config['ntpd']['gps']['stratum']; + } + $ntpcfg .= "\n"; + } elseif (is_array($config['ntpd']) && !empty($config['ntpd']['gpsport']) && + system_ntp_setup_gps($config['ntpd']['gpsport'])) { + /* This handles a 2.1 and earlier config */ + $ntpcfg .= "# GPS Setup\n"; + $ntpcfg .= "server 127.127.20.0 mode 0 minpoll 4 maxpoll 4 prefer\n"; + $ntpcfg .= "fudge 127.127.20.0 time1 0.155 time2 0.000 flag1 1 flag2 0 flag3 1\n"; + // Fall back to local clock if GPS is out of sync? + $ntpcfg .= "server 127.127.1.0\n"; + $ntpcfg .= "fudge 127.127.1.0 stratum 12\n"; + } + /* End GPS configuration */ + $auto_pool_suffix = "pool.ntp.org"; + $have_pools = false; + $ntpcfg .= "\n\n# Upstream Servers\n"; + /* foreach through ntp servers and write out to ntpd.conf */ + foreach (explode(' ', $config['system']['timeservers']) as $ts) { + if ((substr_compare($ts, $auto_pool_suffix, strlen($ts) - strlen($auto_pool_suffix), strlen($auto_pool_suffix)) === 0) + || substr_count($config['ntpd']['ispool'], $ts)) { + $ntpcfg .= 'pool '; + $have_pools = true; + } else { + $ntpcfg .= 'server '; + if ($config['ntpd']['dnsresolv'] == 'inet') { + $ntpcfg .= '-4 '; + } elseif ($config['ntpd']['dnsresolv'] == 'inet6') { + $ntpcfg .= '-6 '; + } + } + + $ntpcfg .= "{$ts} iburst"; + + $ntpcfg .= system_ntp_fixup_poll_value('minpoll', $config['ntpd']['ntpminpoll'], $ntp_poll_min_default); + $ntpcfg .= system_ntp_fixup_poll_value('maxpoll', $config['ntpd']['ntpmaxpoll'], $ntp_poll_max_default); + + if (substr_count($config['ntpd']['prefer'], $ts)) { + $ntpcfg .= ' prefer'; + } + if (substr_count($config['ntpd']['noselect'], $ts)) { + $ntpcfg .= ' noselect'; + } + $ntpcfg .= "\n"; + } + unset($ts); + + $ntpcfg .= "\n\n"; + if (!empty($config['ntpd']['clockstats']) || !empty($config['ntpd']['loopstats']) || !empty($config['ntpd']['peerstats'])) { + $ntpcfg .= "enable stats\n"; + $ntpcfg .= 'statistics'; + if (!empty($config['ntpd']['clockstats'])) { + $ntpcfg .= ' clockstats'; + } + if (!empty($config['ntpd']['loopstats'])) { + $ntpcfg .= ' loopstats'; + } + if (!empty($config['ntpd']['peerstats'])) { + $ntpcfg .= ' peerstats'; + } + $ntpcfg .= "\n"; + } + $ntpcfg .= "statsdir {$statsdir}\n"; + $ntpcfg .= 'logconfig =syncall +clockall'; + if (!empty($config['ntpd']['logpeer'])) { + $ntpcfg .= ' +peerall'; + } + if (!empty($config['ntpd']['logsys'])) { + $ntpcfg .= ' +sysall'; + } + $ntpcfg .= "\n"; + $ntpcfg .= "driftfile {$driftfile}\n"; + + /* Default Access restrictions */ + $ntpcfg .= 'restrict default'; + if (empty($config['ntpd']['kod'])) { /*note: this one works backwards */ + $ntpcfg .= ' kod limited'; + } + if (empty($config['ntpd']['nomodify'])) { /*note: this one works backwards */ + $ntpcfg .= ' nomodify'; + } + if (!empty($config['ntpd']['noquery'])) { + $ntpcfg .= ' noquery'; + } + if (empty($config['ntpd']['nopeer'])) { /*note: this one works backwards */ + $ntpcfg .= ' nopeer'; + } + if (empty($config['ntpd']['notrap'])) { /*note: this one works backwards */ + $ntpcfg .= ' notrap'; + } + if (!empty($config['ntpd']['noserve'])) { + $ntpcfg .= ' noserve'; + } + $ntpcfg .= "\nrestrict -6 default"; + if (empty($config['ntpd']['kod'])) { /*note: this one works backwards */ + $ntpcfg .= ' kod limited'; + } + if (empty($config['ntpd']['nomodify'])) { /*note: this one works backwards */ + $ntpcfg .= ' nomodify'; + } + if (!empty($config['ntpd']['noquery'])) { + $ntpcfg .= ' noquery'; + } + if (empty($config['ntpd']['nopeer'])) { /*note: this one works backwards */ + $ntpcfg .= ' nopeer'; + } + if (!empty($config['ntpd']['noserve'])) { + $ntpcfg .= ' noserve'; + } + if (empty($config['ntpd']['notrap'])) { /*note: this one works backwards */ + $ntpcfg .= ' notrap'; + } + + /* Pools require "restrict source" and cannot contain "nopeer" and "noserve". */ + if ($have_pools) { + $ntpcfg .= "\nrestrict source"; + if (empty($config['ntpd']['kod'])) { /*note: this one works backwards */ + $ntpcfg .= ' kod limited'; + } + if (empty($config['ntpd']['nomodify'])) { /*note: this one works backwards */ + $ntpcfg .= ' nomodify'; + } + if (!empty($config['ntpd']['noquery'])) { + $ntpcfg .= ' noquery'; + } + if (empty($config['ntpd']['notrap'])) { /*note: this one works backwards */ + $ntpcfg .= ' notrap'; + } + } + + /* Custom Access Restrictions */ + if (is_array($config['ntpd']['restrictions']) && is_array($config['ntpd']['restrictions']['row'])) { + $networkacl = $config['ntpd']['restrictions']['row']; + foreach ($networkacl as $acl) { + $restrict = ""; + if (is_ipaddrv6($acl['acl_network'])) { + $restrict .= "{$acl['acl_network']} mask " . gen_subnet_mask_v6($acl['mask']) . " "; + } elseif (is_ipaddrv4($acl['acl_network'])) { + $restrict .= "{$acl['acl_network']} mask " . gen_subnet_mask($acl['mask']) . " "; + } else { + continue; + } + if (!empty($acl['kod'])) { + $restrict .= ' kod limited'; + } + if (!empty($acl['nomodify'])) { + $restrict .= ' nomodify'; + } + if (!empty($acl['noquery'])) { + $restrict .= ' noquery'; + } + if (!empty($acl['nopeer'])) { + $restrict .= ' nopeer'; + } + if (!empty($acl['noserve'])) { + $restrict .= ' noserve'; + } + if (!empty($acl['notrap'])) { + $restrict .= ' notrap'; + } + if (!empty($restrict)) { + $ntpcfg .= "\nrestrict {$restrict} "; + } + } + } + /* End Custom Access Restrictions */ + + /* A leapseconds file is really only useful if this clock is stratum 1 */ + $ntpcfg .= "\n"; + if (!empty($config['ntpd']['leapsec'])) { + $leapsec .= base64_decode($config['ntpd']['leapsec']); + file_put_contents('/var/db/leap-seconds', $leapsec); + $ntpcfg .= "leapfile /var/db/leap-seconds\n"; + } + + + if (empty($config['ntpd']['interface'])) { + if (is_array($config['installedpackages']['openntpd']) && !empty($config['installedpackages']['openntpd']['config'][0]['interface'])) { + $interfaces = explode(",", $config['installedpackages']['openntpd']['config'][0]['interface']); + } else { + $interfaces = array(); + } + } else { + $interfaces = explode(",", $config['ntpd']['interface']); + } + + if (is_array($interfaces) && count($interfaces)) { + $finterfaces = array(); + $ntpcfg .= "interface ignore all\n"; + $ntpcfg .= "interface ignore wildcard\n"; + foreach ($interfaces as $interface) { + $interface = get_real_interface($interface); + if (!empty($interface)) { + $finterfaces[] = $interface; + } + } + foreach ($finterfaces as $interface) { + $ntpcfg .= "interface listen {$interface}\n"; + } + } + + /* open configuration for writing or bail */ + if (!@file_put_contents("{$g['varetc_path']}/ntpd.conf", $ntpcfg)) { + log_error(sprintf(gettext("Could not open %s/ntpd.conf for writing"), $g['varetc_path'])); + return; + } + + /* if /var/empty does not exist, create it */ + if (!is_dir("/var/empty")) { + mkdir("/var/empty", 0555, true); + } + + /* start ntpd, set time now and use /var/etc/ntpd.conf */ + mwexec("/usr/local/sbin/ntpd -g -c {$g['varetc_path']}/ntpd.conf -p {$g['varrun_path']}/ntpd.pid", false, true); + + // Note that we are starting up + log_error("NTPD is starting up."); + + if (platform_booting()) { + echo gettext("done.") . "\n"; + } + + return; +} + +function system_halt() { + global $g; + + system_reboot_cleanup(); + + mwexec("/usr/bin/nohup /etc/rc.halt > /dev/null 2>&1 &"); +} + +function system_reboot() { + global $g; + + system_reboot_cleanup(); + + mwexec("nohup /etc/rc.reboot > /dev/null 2>&1 &"); +} + +function system_reboot_sync($reroot=false) { + global $g; + + if ($reroot) { + $args = " -r "; + } + + system_reboot_cleanup(); + + mwexec("/etc/rc.reboot {$args} > /dev/null 2>&1"); +} + +function system_reboot_cleanup() { + global $config, $g, $cpzone; + + mwexec("/usr/local/bin/beep.sh stop"); + require_once("captiveportal.inc"); + if (is_array($config['captiveportal'])) { + foreach ($config['captiveportal'] as $cpzone=>$cp) { + if (!isset($cp['preservedb'])) { + /* send Accounting-Stop packet for all clients, termination cause 'Admin-Reboot' */ + captiveportal_radius_stop_all(7); // Admin-Reboot + unlink_if_exists("{$g['vardb_path']}/captiveportal{$cpzone}.db"); + captiveportal_free_dnrules(); + } + /* Send Accounting-Off packet to the RADIUS server */ + captiveportal_send_server_accounting('off'); + } + /* Remove the pipe database */ + unlink_if_exists("{$g['vardb_path']}/captiveportaldn.rules"); + } + require_once("voucher.inc"); + voucher_save_db_to_config(); + require_once("pkg-utils.inc"); + stop_packages(); +} + +function system_do_shell_commands($early = 0) { + global $config, $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_do_shell_commands() being called $mt\n"; + } + + if ($early) { + $cmdn = "earlyshellcmd"; + } else { + $cmdn = "shellcmd"; + } + + if (is_array($config['system'][$cmdn])) { + + /* *cmd is an array, loop through */ + foreach ($config['system'][$cmdn] as $cmd) { + exec($cmd); + } + + } elseif ($config['system'][$cmdn] <> "") { + + /* execute single item */ + exec($config['system'][$cmdn]); + + } +} + +function system_dmesg_save() { + global $g; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_dmesg_save() being called $mt\n"; + } + + $dmesg = ""; + $_gb = exec("/sbin/dmesg", $dmesg); + + /* find last copyright line (output from previous boots may be present) */ + $lastcpline = 0; + + for ($i = 0; $i < count($dmesg); $i++) { + if (strstr($dmesg[$i], "Copyright (c) 1992-")) { + $lastcpline = $i; + } + } + + $fd = fopen("{$g['varlog_path']}/dmesg.boot", "w"); + if (!$fd) { + printf(gettext("Error: cannot open dmesg.boot in system_dmesg_save().%s"), "\n"); + return 1; + } + + for ($i = $lastcpline; $i < count($dmesg); $i++) { + fwrite($fd, $dmesg[$i] . "\n"); + } + + fclose($fd); + unset($dmesg); + + // vm-bhyve expects dmesg.boot at the standard location + @symlink("{$g['varlog_path']}/dmesg.boot", "{$g['varrun_path']}/dmesg.boot"); + + return 0; +} + +function system_set_harddisk_standby() { + global $g, $config; + + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_set_harddisk_standby() being called $mt\n"; + } + + if (isset($config['system']['harddiskstandby'])) { + if (platform_booting()) { + echo gettext('Setting hard disk standby... '); + } + + $standby = $config['system']['harddiskstandby']; + // Check for a numeric value + if (is_numeric($standby)) { + // Get only suitable candidates for standby; using get_smart_drive_list() + // from utils.inc to get the list of drives. + $harddisks = get_smart_drive_list(); + + // Since get_smart_drive_list() only matches ad|da|ada; lets put the check below + // just in case of some weird pfSense platform installs. + if (count($harddisks) > 0) { + // Iterate disks and run the camcontrol command for each + foreach ($harddisks as $harddisk) { + mwexec("/sbin/camcontrol standby {$harddisk} -t {$standby}"); + } + if (platform_booting()) { + echo gettext("done.") . "\n"; + } + } else if (platform_booting()) { + echo gettext("failed!") . "\n"; + } + } else if (platform_booting()) { + echo gettext("failed!") . "\n"; + } + } +} + +function system_setup_sysctl() { + global $config; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_setup_sysctl() being called $mt\n"; + } + + activate_sysctls(); + + if (isset($config['system']['sharednet'])) { + system_disable_arp_wrong_if(); + } +} + +function system_disable_arp_wrong_if() { + global $config; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_disable_arp_wrong_if() being called $mt\n"; + } + set_sysctl(array( + "net.link.ether.inet.log_arp_wrong_iface" => "0", + "net.link.ether.inet.log_arp_movements" => "0" + )); +} + +function system_enable_arp_wrong_if() { + global $config; + if (isset($config['system']['developerspew'])) { + $mt = microtime(); + echo "system_enable_arp_wrong_if() being called $mt\n"; + } + set_sysctl(array( + "net.link.ether.inet.log_arp_wrong_iface" => "1", + "net.link.ether.inet.log_arp_movements" => "1" + )); +} + +function enable_watchdog() { + global $config; + return; + $install_watchdog = false; + $supported_watchdogs = array("Geode"); + $file = file_get_contents("/var/log/dmesg.boot"); + foreach ($supported_watchdogs as $sd) { + if (stristr($file, "Geode")) { + $install_watchdog = true; + } + } + if ($install_watchdog == true) { + if (is_process_running("watchdogd")) { + mwexec("/usr/bin/killall watchdogd", true); + } + exec("/usr/sbin/watchdogd"); + } +} + +function system_check_reset_button() { + global $g; + + $specplatform = system_identify_specific_platform(); + + switch ($specplatform['name']) { + case 'SG-2220': + $binprefix = "RCC-DFF"; + break; + case 'alix': + case 'wrap': + case 'FW7541': + case 'APU': + case 'RCC-VE': + case 'RCC': + $binprefix = $specplatform['name']; + break; + default: + return 0; + } + + $retval = mwexec("/usr/local/sbin/" . $binprefix . "resetbtn"); + + if ($retval == 99) { + /* user has pressed reset button for 2 seconds - + reset to factory defaults */ + echo <<= 10 && strlen($serial) <= 16 && + $vm_guest == 'none') { + return $serial; + } + + return ""; +} + +function system_get_uniqueid() { + global $g; + + $uniqueid_file="{$g['vardb_path']}/uniqueid"; + + if (empty($g['uniqueid'])) { + if (!file_exists($uniqueid_file)) { + mwexec("/usr/sbin/gnid > {$g['vardb_path']}/uniqueid " . + "2>/dev/null"); + } + if (file_exists($uniqueid_file)) { + $g['uniqueid'] = @file_get_contents($uniqueid_file); + } + } + + return ($g['uniqueid'] ?: ''); +} + +/* + * attempt to identify the specific platform (for embedded systems) + * Returns an array with two elements: + * name => platform string (e.g. 'wrap', 'alix' etc.) + * descr => human-readable description (e.g. "PC Engines WRAP") + */ +function system_identify_specific_platform() { + global $g; + + $hw_model = get_single_sysctl('hw.model'); + $hw_ncpu = get_single_sysctl('hw.ncpu'); + + /* Try to guess from smbios strings */ + unset($product); + unset($maker); + unset($bios); + $_gb = exec('/bin/kenv -q smbios.system.product 2>/dev/null', $product); + $_gb = exec('/bin/kenv -q smbios.system.maker 2>/dev/null', $maker); + $_gb = exec('/bin/kenv -q smbios.bios.version 2>/dev/null', $bios); + + // AWS can only be identified via the bios version + if (stripos($bios[0], "amazon") !== false) { + return (array('name' => 'AWS', 'descr' => 'Amazon Web Services')); + } else if (stripos($bios[0], "Google") !== false) { + return (array('name' => 'Google', 'descr' => 'Google Cloud Platform')); + } + + switch ($product[0]) { + case 'FW7541': + return (array('name' => 'FW7541', 'descr' => 'Netgate FW7541')); + break; + case 'APU': + return (array('name' => 'APU', 'descr' => 'Netgate APU')); + break; + case 'RCC-VE': + $result = array(); + $result['name'] = 'RCC-VE'; + + /* Detect specific models */ + if (!function_exists('does_interface_exist')) { + require_once("interfaces.inc"); + } + if (!does_interface_exist('igb4')) { + $result['model'] = 'SG-2440'; + } elseif (strpos($hw_model, "C2558") !== false) { + $result['model'] = 'SG-4860'; + } elseif (strpos($hw_model, "C2758") !== false) { + $result['model'] = 'SG-8860'; + } else { + $result['model'] = 'RCC-VE'; + } + $result['descr'] = 'Netgate ' . $result['model']; + return $result; + break; + case 'DFFv2': + return (array('name' => 'SG-2220', 'descr' => 'Netgate SG-2220')); + break; + case 'RCC': + return (array('name' => 'RCC', 'descr' => 'Netgate XG-2758')); + break; + case 'SG-5100': + return (array('name' => 'SG-5100', 'descr' => 'Netgate SG-5100')); + break; + case 'Minnowboard Turbot D0 PLATFORM': + case 'Minnowboard Turbot D0/D1 PLATFORM': + $result = array(); + $result['name'] = 'Turbot Dual-E'; + /* Detect specific model */ + switch ($hw_ncpu) { + case '4': + $result['model'] = 'MBT-4220'; + break; + case '2': + $result['model'] = 'MBT-2220'; + break; + default: + $result['model'] = $result['name']; + break; + } + $result['descr'] = 'Netgate ' . $result['model']; + return $result; + break; + case 'SYS-5018A-FTN4': + case 'A1SAi': + if (strpos($hw_model, "C2558") !== false) { + return (array( + 'name' => 'C2558', + 'descr' => 'Super Micro C2558')); + } elseif (strpos($hw_model, "C2758") !== false) { + return (array( + 'name' => 'C2758', + 'descr' => 'Super Micro C2758')); + } + break; + case 'SYS-5018D-FN4T': + if (strpos($hw_model, "D-1541") !== false) { + return (array('name' => 'XG-1541', 'descr' => 'Super Micro XG-1541')); + } else { + return (array('name' => 'XG-1540', 'descr' => 'Super Micro XG-1540')); + } + break; + case 'apu2': + case 'APU2': + return (array('name' => 'apu2', 'descr' => 'PC Engines APU2')); + break; + case 'VirtualBox': + return (array('name' => 'VirtualBox', 'descr' => 'VirtualBox Virtual Machine')); + break; + case 'Virtual Machine': + if ($maker[0] == "Microsoft Corporation") { + if (stripos($bios[0], "Hyper") !== false) { + return (array('name' => 'Hyper-V', 'descr' => 'Hyper-V Virtual Machine')); + } else { + return (array('name' => 'Azure', 'descr' => 'Microsoft Azure')); + } + } + break; + case 'VMware Virtual Platform': + if ($maker[0] == "VMware, Inc.") { + return (array('name' => 'VMware', 'descr' => 'VMware Virtual Machine')); + } + break; + } + + $_gb = exec('/bin/kenv -q smbios.planar.product 2>/dev/null', + $planar_product); + if (isset($planar_product[0]) && + $planar_product[0] == 'X10SDV-8C-TLN4F+') { + return array('name' => 'XG-1537', 'descr' => 'Super Micro XG-1537'); + } + + if (strpos($hw_model, "PC Engines WRAP") !== false) { + return array('name' => 'wrap', 'descr' => gettext('PC Engines WRAP')); + } + + if (strpos($hw_model, "PC Engines ALIX") !== false) { + return array('name' => 'alix', 'descr' => gettext('PC Engines ALIX')); + } + + if (preg_match("/Soekris net45../", $hw_model, $matches)) { + return array('name' => 'net45xx', 'descr' => $matches[0]); + } + + if (preg_match("/Soekris net48../", $hw_model, $matches)) { + return array('name' => 'net48xx', 'descr' => $matches[0]); + } + + if (preg_match("/Soekris net55../", $hw_model, $matches)) { + return array('name' => 'net55xx', 'descr' => $matches[0]); + } + + unset($hw_model); + + $dmesg_boot = system_get_dmesg_boot(); + if (strpos($dmesg_boot, "PC Engines ALIX") !== false) { + return array('name' => 'alix', 'descr' => gettext('PC Engines ALIX')); + } + unset($dmesg_boot); + + return array('name' => $g['product_name'], 'descr' => $g['product_label']); +} + +function system_get_dmesg_boot() { + global $g; + + return file_get_contents("{$g['varlog_path']}/dmesg.boot"); +} + +function system_get_arp_table($resolve_hostnames = false) { + $params="-a"; + if (!$resolve_hostnames) { + $params .= "n"; + } + + $arp_table = array(); + $_gb = exec("/usr/sbin/arp --libxo json {$params}", $rawdata, $rc); + if ($rc == 0) { + $arp_table = json_decode(implode(" ", $rawdata), + JSON_OBJECT_AS_ARRAY); + if ($rc == 0) { + $arp_table = $arp_table['arp']['arp-cache']; + } + } + + return $arp_table; +} + +?> \ No newline at end of file diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallNATPortForwardUpdate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallNATPortForwardUpdate.inc index 2257422a6..148761b58 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallNATPortForwardUpdate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIFirewallNATPortForwardUpdate.inc @@ -21,6 +21,7 @@ class APIFirewallNATPortForwardUpdate extends APIModel { private $nat_reflection; private $updated_by_msg; private $port_required; + private $port_protocol; # Create our method constructor public function __construct() { @@ -30,6 +31,7 @@ class APIFirewallNATPortForwardUpdate extends APIModel { $this->protocols = ["tcp", "udp", "tcp/udp", "icmp", "esp", "ah", "gre", "ipv6", "igmp", "pim", "ospf"]; $this->nat_reflection = ["enable", "disable", "purenat"]; $this->port_required = false; + $this->port_protocol = false; } public function action() { @@ -48,6 +50,11 @@ class APIFirewallNATPortForwardUpdate extends APIModel { if (array_key_exists($this->initial_data["id"], $this->config["nat"]["rule"])) { $this->id = $this->initial_data["id"]; $this->validated_data = $this->config["nat"]["rule"][$this->id]; + + # Check if current protocol is a port based protocol + if (in_array($this->validated_data["protocol"], ["tcp", "udp", "tcp/udp"])) { + $this->port_protocol = true; + } } else { $this->errors[] = APIResponse\get(4016); } @@ -74,11 +81,15 @@ class APIFirewallNATPortForwardUpdate extends APIModel { if (isset($this->initial_data['protocol'])) { # Require protocol to be a known/supported protocol if (in_array($this->initial_data['protocol'], $this->protocols)) { - # Only require ports if updating to port protocol from non-port protocol + # Check if we are updating to a port based protocol if (in_array($this->initial_data["protocol"], ["tcp", "udp", "tcp/udp"])) { + $this->port_protocol = true; + # Only require ports if updating to port protocol from non-port protocol if (!in_array($this->validated_data["protocol"], ["tcp", "udp", "tcp/udp"])) { $this->port_required = true; } + } else { + $this->port_protocol = false; } $this->validated_data["protocol"] = $this->initial_data['protocol']; } else { @@ -103,7 +114,7 @@ class APIFirewallNATPortForwardUpdate extends APIModel { private function __validate_local_port() { # Only require a local port if the protocol requires a port - if ($this->port_required) { + if ($this->port_required or (isset($this->initial_data['local-port']) and $this->port_protocol)) { # Require client to pass in a local port to forward to the target if (isset($this->initial_data['local-port'])) { # Require the port to be a valid TCP/UDP port or range @@ -159,7 +170,7 @@ class APIFirewallNATPortForwardUpdate extends APIModel { private function __validate_srcport() { # Only require a source port value if our protocol requires ports - if ($this->port_required) { + if ($this->port_required or (isset($this->initial_data['srcport']) and $this->port_protocol)) { $this->initial_data['srcport'] = str_replace("-", ":", $this->initial_data['srcport']); # Require port to be a valid port or range, or be any if (!is_port_or_range($this->initial_data['srcport']) and $this->initial_data['srcport'] !== "any") { @@ -174,7 +185,7 @@ class APIFirewallNATPortForwardUpdate extends APIModel { private function __validate_dstport() { # Only require a destination port value if our protocol requires ports - if ($this->port_required) { + if ($this->port_required or (isset($this->initial_data['dstport']) and $this->port_protocol)) { $this->initial_data['dstport'] = str_replace("-", ":", $this->initial_data['dstport']); # Require port to be a valid port or range, or be any if (!is_port_or_range($this->initial_data['dstport']) and $this->initial_data['dstport'] !== "any") { diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIInterfaceDelete.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIInterfaceDelete.inc index 396010475..148bbd49a 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIInterfaceDelete.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIInterfaceDelete.inc @@ -28,7 +28,16 @@ class APIInterfaceDelete extends APIModel { public function action() { $curr_config = [$this->validated_data["if"] => $this->config["interfaces"][$this->validated_data["if"]]]; - $del_stat = $this->destroy_interface($this->validated_data["if"]); // Destroy our interface + + # Run the delete interface function for the corresponding pfSense version + # TODO: remove support for 2.4.x once it approaches EOL + if (APITools\get_pfsense_version()["program"] < 250) { + $del_stat = $this->destroy_interface_legacy($this->validated_data["if"]); + } + else { + $del_stat = $this->destroy_interface($this->validated_data["if"]); + } + $this->write_config(); return APIResponse\get($del_stat, ($del_stat === 0) ? $curr_config : []); } @@ -45,8 +54,66 @@ class APIInterfaceDelete extends APIModel { } } - // Delete an interface + # Delete an interface given it's interface ID - for pfSense 2.5+! private function destroy_interface($id) { + if ($id === "wan") { + $err_msg = 3042; + } elseif (link_interface_to_group($id)) { + $err_msg = 3043; + } elseif (link_interface_to_bridge($id)) { + $err_msg = 3044; + } elseif (link_interface_to_tunnelif($id, 'gre')) { + $err_msg = 3045; + } elseif (link_interface_to_tunnelif($id, 'gif')) { + $err_msg = 3046; + } elseif (interface_has_queue($id)) { + $err_msg = 3047; + } else { + unset($this->config['interfaces'][$id]['enable']); + $realid = get_real_interface($id); + interface_bring_down($id); // Bring down interface + unset($this->config['interfaces'][$id]); // Delete our interface from configuration + // Remove DHCP config for interface + if (is_array($this->config['dhcpd']) && is_array($this->config['dhcpd'][$id])) { + unset($this->config['dhcpd'][$id]); + services_dhcpd_configure('inet'); + } + // Removed interface config for dhcp6 + if (is_array($this->config['dhcpdv6']) && is_array($this->config['dhcpdv6'][$id])) { + unset($this->config['dhcpdv6'][$id]); + services_dhcpd_configure('inet6'); + } + // Remove ACL for interface + if (count($this->config['filter']['rule']) > 0) { + foreach ($this->config['filter']['rule'] as $x => $rule) { + if ($rule['interface'] == $id) { + unset($this->config['filter']['rule'][$x]); + } + } + } + // Remove NAT config for interface + if (is_array($this->config['nat']['rule']) && count($this->config['nat']['rule']) > 0) { + foreach ($this->config['nat']['rule'] as $x => $rule) { + if ($rule['interface'] == $id) { + unset($this->config['nat']['rule'][$x]['interface']); + } + } + } + + // Disable DHCP if last interface + if ($this->config['interfaces']['lan'] && $this->config['dhcpd']['wan']) { + unset($this->config['dhcpd']['wan']); + } + // Update VLAN assignments + link_interface_to_vlans($realid, "update"); + $err_msg = 0; + } + return $err_msg; + } + + # Delete an interface given it's interface ID - for pfSense 2.4.x! + # TODO: remove this function once pfSense 2.4 nears EOL + private function destroy_interface_legacy($id) { if ($id === "wan") { $err_msg = 3042; } elseif (link_interface_to_group($id)) { diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIServicesUnboundHostOverrideAliasCreate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIServicesUnboundHostOverrideAliasCreate.inc index 67c484ce0..85eed8ab7 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIServicesUnboundHostOverrideAliasCreate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIServicesUnboundHostOverrideAliasCreate.inc @@ -41,6 +41,18 @@ class APIServicesUnboundHostOverrideAliasCreate extends APIModel { } public function validate_payload() { + $this->__validate_id(); + $this->validate_host(); + $this->validate_domain(); + $this->validate_description(); + + # Ensure alias host/domain combo isn't already used + if (($this->alias_exists($this->validated_data))) { + $this->errors[] = APIResponse\get(2009); + } + } + + private function __validate_id() { # Check for our required 'id' payload value. Allow this to be bypassed internally for nested calls if ($this->validate_id === false) { } elseif (isset($this->initial_data["id"])) { @@ -52,29 +64,71 @@ class APIServicesUnboundHostOverrideAliasCreate extends APIModel { } else { $this->errors[] = APIResponse\get(2015); } + } + public function validate_host() { # Check for our required 'host' payload value if (isset($this->initial_data["host"])) { - $this->validated_data["host"] = trim($this->initial_data["host"]); + # Ensure it is a valid hostname + if (is_hostname($this->initial_data["host"])) { + $this->validated_data["host"] = $this->initial_data['host']; + } else { + $this->errors[] = APIResponse\get(2046); + } } else { $this->errors[] = APIResponse\get(2007); } + } + public function validate_domain() { # Check for our required 'domain' payload value if (isset($this->initial_data["domain"])) { - $this->validated_data["domain"] = trim($this->initial_data["domain"]); + # Ensure it is a validate hostname + if (is_hostname($this->initial_data["domain"])) { + $this->validated_data["domain"] = $this->initial_data['domain']; + } else { + $this->errors[] = APIResponse\get(2047); + } } else { $this->errors[] = APIResponse\get(2008); } + } - # Check for our required 'description' payload value - if (isset($this->initial_data["description"])) { - $this->validated_data["description"] = trim($this->initial_data["description"]); + public function validate_description() { + # Check for our required 'domain' payload value + if (isset($this->initial_data["domain"])) { + $this->validated_data["domain"] = trim($this->initial_data["domain"]); + } else { + $this->errors[] = APIResponse\get(2008); } + } - # Ensure this hostname doesn't already exis - if (APITools\is_unbound_fqdn($this->validated_data["host"], $this->validated_data["domain"])) { - $this->errors[] = APIResponse\get(2009); + public function alias_exists($parent, $id=null) { + # Local variables + $host = $this->validated_data["host"]; + $domain = $this->validated_data["domain"]; + + # Loop through each host override and check if the FQDN already exists + foreach ($this->config["unbound"]["hosts"] as $index=>$ent) { + # Check the FQDN matches this entry + if ($ent["host"] === $host and $ent["domain"] === $domain) { + $this->errors[] = APIResponse\get(2009); + } + + # Check FQDN within host override aliases as well + if (is_array($ent["aliases"])) { + foreach ($ent["aliases"]["item"] as $alias_ent) { + if ($alias_ent["host"] === $host and $alias_ent["domain"] === $domain) { + $ip = $parent["ip"]; + # Aliases are allowed for an IPv4 and IPv6 alias, check if it already has one. + if ((is_ipaddrv4($ent["ip"]) and is_ipaddrv4($ip)) or (is_ipaddrv6($ent["ip"]) and is_ipaddrv6($ip))){ + if ($index !== $id) { + $this->errors[] = APIResponse\get(2009); + } + } + } + } + } } } } \ No newline at end of file diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIServicesUnboundHostOverrideCreate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIServicesUnboundHostOverrideCreate.inc index e90f2baa2..5ea718eb5 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIServicesUnboundHostOverrideCreate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIServicesUnboundHostOverrideCreate.inc @@ -41,23 +41,48 @@ class APIServicesUnboundHostOverrideCreate extends APIModel { } public function validate_payload() { + $this->__validate_host(); + $this->__validate_domain(); + $this->__validate_ip(); + + # Before proceeding, check that a host override matching this host, domain and IP type doesn't already exist + if ($this->host_override_exists()) { + $this->errors[] = APIResponse\get(2010); + } + + $this->__validate_descr(); + $this->__validate_aliases(); + } + + private function __validate_host() { # Check for our required 'host' payload value if (isset($this->initial_data['host'])) { - $this->validated_data["host"] = trim($this->initial_data['host']); + # Ensure it is a valid hostname + if (is_hostname($this->initial_data["host"])) { + $this->validated_data["host"] = $this->initial_data['host']; + } else { + $this->errors[] = APIResponse\get(2046); + } } else { $this->errors[] = APIResponse\get(2004); } + } + private function __validate_domain() { # Check for our required 'domain' payload value if (isset($this->initial_data['domain'])) { - $this->validated_data["domain"] = trim($this->initial_data['domain']); - if (APITools\is_unbound_fqdn($this->validated_data["host"], $this->validated_data["domain"])) { - $this->errors[] = APIResponse\get(2010); + # Ensure it is a validate hostname + if (is_hostname($this->initial_data["domain"])) { + $this->validated_data["domain"] = $this->initial_data['domain']; + } else { + $this->errors[] = APIResponse\get(2047); } } else { $this->errors[] = APIResponse\get(2005); } + } + private function __validate_ip() { # Check for our required 'ip' payload value if (isset($this->initial_data['ip'])) { if (!is_ipaddrv4($this->initial_data["ip"]) and !is_ipaddrv6($this->initial_data["ip"])) { @@ -68,12 +93,16 @@ class APIServicesUnboundHostOverrideCreate extends APIModel { } else { $this->errors[] = APIResponse\get(2006); } + } + private function __validate_descr() { # Check for our optional 'descr' payload value if (isset($this->initial_data['descr'])) { $this->validated_data["descr"] = $this->initial_data['descr']; } + } + private function __validate_aliases() { # Check for our optional 'aliases' payload value. This is a nested model creation. if (isset($this->initial_data['aliases'])) { # Allow aliases to be nested under "item" as found in the XML config @@ -85,11 +114,42 @@ class APIServicesUnboundHostOverrideCreate extends APIModel { foreach ($this->initial_data['aliases'] as $alias) { $alias_create = new APIServicesUnboundHostOverrideAliasCreate(); $alias_create->initial_data = $alias; - $alias_create->validate_id = false; - $alias_create->validate_payload(); + $alias_create->validate_host(); + $alias_create->validate_domain(); + $alias_create->validate_description(); + $alias_create->alias_exists($this->validated_data); $this->errors = array_merge($this->errors, $alias_create->errors); $this->validated_data["aliases"]["item"][] = $alias_create->validated_data; } } } + + public function host_override_exists() { + # Local variables + $host = $this->validated_data["host"]; + $domain = $this->validated_data["domain"]; + $ip = $this->validated_data["ip"]; + + # Loop through each host override and check if the FQDN already exists + foreach ($this->config["unbound"]["hosts"] as $ent) { + # Check the FQDN matches this entry + if ($ent["host"] === $host and $ent["domain"] === $domain) { + # Host overrides are allowed an IPv4 and IPv6 address, check if it already has one. + if ((is_ipaddrv4($ent["ip"]) and is_ipaddrv4($ip)) or (is_ipaddrv6($ent["ip"]) and is_ipaddrv6($ip))){ + return true; + } + } + + # Check FQDN within host override aliases as well + if (is_array($ent["aliases"])) { + foreach ($ent["aliases"]["item"] as $alias_ent) { + if ($alias_ent["host"] === $host and $alias_ent["domain"] === $domain) { + return true; + } + } + } + } + return false; + } + } \ No newline at end of file diff --git a/pfSense-pkg-API/files/etc/inc/api/models/APIServicesUnboundHostOverrideUpdate.inc b/pfSense-pkg-API/files/etc/inc/api/models/APIServicesUnboundHostOverrideUpdate.inc index 6f0e8f305..b83f6fbc6 100644 --- a/pfSense-pkg-API/files/etc/inc/api/models/APIServicesUnboundHostOverrideUpdate.inc +++ b/pfSense-pkg-API/files/etc/inc/api/models/APIServicesUnboundHostOverrideUpdate.inc @@ -37,6 +37,22 @@ class APIServicesUnboundHostOverrideUpdate extends APIModel { } public function validate_payload() { + $this->__validate_id(); + $this->__validate_ip(); + $this->__validate_host(); + $this->__validate_domain(); + $this->__validate_descr(); + $this->__validate_aliases(); + + # If host or domain was updated, ensure this host override doesn't already exist + if (isset($this->initial_data["host"]) or isset($this->initial_data["domain"])) { + if ($this->host_override_exists()) { + $this->errors[] = APIResponse\get(2010); + } + } + } + + private function __validate_id() { # Check for our required 'id' payload value. if (isset($this->initial_data["id"])) { if (array_key_exists($this->initial_data["id"], $this->config["unbound"]["hosts"])) { @@ -48,17 +64,9 @@ class APIServicesUnboundHostOverrideUpdate extends APIModel { } else { $this->errors[] = APIResponse\get(2015); } + } - # Check for our optional 'host' payload value - if (isset($this->initial_data['host'])) { - $this->validated_data["host"] = trim($this->initial_data['host']); - } - - # Check for our optional 'domain' payload value - if (isset($this->initial_data['domain'])) { - $this->validated_data["domain"] = trim($this->initial_data['domain']); - } - + private function __validate_ip() { # Check for our required 'ip' payload value if (isset($this->initial_data['ip'])) { if (!is_ipaddrv4($this->initial_data["ip"]) and !is_ipaddrv6($this->initial_data["ip"])) { @@ -67,19 +75,39 @@ class APIServicesUnboundHostOverrideUpdate extends APIModel { $this->validated_data["ip"] = trim($this->initial_data['ip']); } } + } + + private function __validate_host() { + # Check for our optional 'host' payload value + if (isset($this->initial_data['host'])) { + # Ensure it is a valid hostname + if (is_hostname($this->initial_data["host"])) { + $this->validated_data["host"] = $this->initial_data['host']; + } else { + $this->errors[] = APIResponse\get(2046); + } } + } + + private function __validate_domain() { + # Check for our optional 'domain' payload value + if (isset($this->initial_data['domain'])) { + # Ensure it is a validate hostname + if (is_hostname($this->initial_data["domain"])) { + $this->validated_data["domain"] = $this->initial_data['domain']; + } else { + $this->errors[] = APIResponse\get(2047); + } + } + } + private function __validate_descr() { # Check for our optional 'descr' payload value if (isset($this->initial_data['descr'])) { $this->validated_data["descr"] = $this->initial_data['descr']; } + } - # If host or domain was updated, ensure this host override doesn't already exist - if (isset($this->initial_data["host"]) or isset($this->initial_data["domain"])) { - if (APITools\is_unbound_fqdn($this->validated_data["host"], $this->validated_data["domain"], $this->id)) { - $this->errors[] = APIResponse\get(2010); - } - } - + private function __validate_aliases() { # Check for our optional 'aliases' payload value. This is a nested model creation. if (isset($this->initial_data['aliases'])) { # Allow aliases to be nested under "item" as found in the XML config @@ -91,11 +119,45 @@ class APIServicesUnboundHostOverrideUpdate extends APIModel { foreach ($this->initial_data['aliases'] as $alias) { $alias_create = new APIServicesUnboundHostOverrideAliasCreate(); $alias_create->initial_data = $alias; - $alias_create->validate_id = false; - $alias_create->validate_payload(); + $alias_create->validate_host(); + $alias_create->validate_domain(); + $alias_create->validate_description(); + $alias_create->alias_exists($this->validated_data, $this->id); $this->errors = array_merge($this->errors, $alias_create->errors); $this->validated_data["aliases"]["item"][] = $alias_create->validated_data; } } } + + public function host_override_exists() { + # Local variables + $host = $this->validated_data["host"]; + $domain = $this->validated_data["domain"]; + $ip = $this->validated_data["ip"]; + $id = $this->id; + + # Loop through each host override and check if the FQDN already exists + foreach ($this->config["unbound"]["hosts"] as $index=>$ent) { + # Check the FQDN matches this entry + if ($ent["host"] === $host and $ent["domain"] === $domain) { + # Host overrides are allowed an IPv4 and IPv6 address, check if it already has one. + if ((is_ipaddrv4($ent["ip"]) and is_ipaddrv4($ip)) or (is_ipaddrv6($ent["ip"]) and is_ipaddrv6($ip))){ + # If we are working with an existing instance, allow existing FQDN if ID matches + if ($index !== $id) { + return true; + } + } + } + + # Check FQDN within host override aliases as well + if (is_array($ent["aliases"])) { + foreach ($ent["aliases"]["item"] as $alias_ent) { + if ($alias_ent["host"] === $host and $alias_ent["domain"] === $domain) { + return true; + } + } + } + } + return false; + } } \ No newline at end of file diff --git a/pfSense-pkg-API/files/pkg-deinstall.in b/pfSense-pkg-API/files/pkg-deinstall.in index f14e71d24..0707de228 100644 --- a/pfSense-pkg-API/files/pkg-deinstall.in +++ b/pfSense-pkg-API/files/pkg-deinstall.in @@ -4,6 +4,12 @@ if [ "${2}" != "POST-DEINSTALL" ]; then exit 0 fi +# Unlink this package from pfSense /usr/local/bin/php -f /etc/rc.packages %%PORTNAME%% ${2} + +# Remove the pfsense-api command line tool /bin/rm /usr/local/bin/pfsense-api -/bin/mv /etc/inc/system.original.inc /etc/inc/system.inc + +# Restore overriden files to their original state +/bin/mv /etc/inc/system.inc.original /etc/inc/system.inc +echo "Restoring file overrides to their original state... done." diff --git a/pfSense-pkg-API/files/pkg-install.in b/pfSense-pkg-API/files/pkg-install.in index 23497605b..8e1455362 100644 --- a/pfSense-pkg-API/files/pkg-install.in +++ b/pfSense-pkg-API/files/pkg-install.in @@ -2,11 +2,34 @@ if [ "${2}" != "POST-INSTALL" ]; then exit 0 fi + +# Make this package known to pfSense /usr/local/bin/php -f /etc/rc.packages %%PORTNAME%% ${2} -/bin/cp /etc/inc/system.inc /etc/inc/system.original.inc -/bin/cp /etc/inc/api/framework/overrides/system.inc /etc/inc/system.inc +echo "Creating backups of files to override... done." + +# Backup original files before overriding +/bin/cp /etc/inc/system.inc /etc/inc/system.inc.original + +# Check the local systems version of pfSense before assigning file overrides +PFSENSE_VERSION=$(/bin/cat /etc/version) +echo "Checking pfSense version... done." + +# Use the corresponding pfSense version's file overrides if they exist. Otherwise print warning and use default. +if [ -d "/etc/inc/api/framework/overrides/${PFSENSE_VERSION}" ] +then + /bin/cp "/etc/inc/api/framework/overrides/${PFSENSE_VERSION}/system.inc" "/etc/inc/system.inc" + echo "Installing file overrides for ${PFSENSE_VERSION}... done." +else + echo "WARNING: No overrides exist for ${PFSENSE_VERSION}, it may be unsupported. Using default overrides." + /bin/cp "/etc/inc/api/framework/overrides/default/system.inc" "/etc/inc/system.inc" + echo "Installing default file overrides... done." +fi + +# Setup the pfsense-api command line tool /bin/chmod +x /usr/local/share/pfSense-pkg-API/manage.php /bin/ln -s /usr/local/share/pfSense-pkg-API/manage.php /usr/local/bin/pfsense-api + +# Build API endpoints and restore any existing configuration /usr/local/bin/php -f /usr/local/share/pfSense-pkg-API/manage.php buildendpoints /usr/local/bin/php -f /usr/local/share/pfSense-pkg-API/manage.php restore diff --git a/pfSense-pkg-API/files/usr/local/share/pfSense-pkg-API/manage.php b/pfSense-pkg-API/files/usr/local/share/pfSense-pkg-API/manage.php index d5c2e0fdf..11c48087a 100644 --- a/pfSense-pkg-API/files/usr/local/share/pfSense-pkg-API/manage.php +++ b/pfSense-pkg-API/files/usr/local/share/pfSense-pkg-API/manage.php @@ -37,9 +37,9 @@ function build_endpoints() { # Print success output if file now exists, otherwise output error and exit on non-zero code if (!is_null($endpoint_obj->url) and is_file("/usr/local/www".$endpoint_obj->url."/index.php")) { - echo "Builing ".$endpoint_class." endpoint at URL \"".$endpoint_obj->url."\"... done.".PHP_EOL; + echo "Building ".$endpoint_class." endpoint at URL \"".$endpoint_obj->url."\"... done.".PHP_EOL; } else { - echo "Builing ".$endpoint_class." endpoint at URL \"".$endpoint_obj->url."\"... failed.".PHP_EOL; + echo "Building ".$endpoint_class." endpoint at URL \"".$endpoint_obj->url."\"... failed.".PHP_EOL; exit(1); } } diff --git a/tests/test_api_v1_firewall_rule.py b/tests/test_api_v1_firewall_rule.py index 5a451e236..bdb301541 100644 --- a/tests/test_api_v1_firewall_rule.py +++ b/tests/test_api_v1_firewall_rule.py @@ -40,7 +40,7 @@ class APIUnitTestFirewallRule(unit_test_framework.APIUnitTest): "protocol": "tcp/udp", "src": "172.16.77.125", "srcport": "8080-8081", - "dst": "127.0.0.1", + "dst": "(self)", "dstport": "2222-4444", "descr": "Updated Unit test", "gateway": "WAN_DHCP", diff --git a/tests/test_api_v1_services_unbound_host_override.py b/tests/test_api_v1_services_unbound_host_override.py index 51e48fc94..bf18b6dbd 100644 --- a/tests/test_api_v1_services_unbound_host_override.py +++ b/tests/test_api_v1_services_unbound_host_override.py @@ -22,7 +22,20 @@ class APIUnitTestServicesUnboundHostOverride(unit_test_framework.APIUnitTest): "host": "pfsense-api", "domain": "unit.test", "ip": "1.2.3.4", - "descr": "Unit Test", + "descr": "Unit Test IPv4", + "aliases": [ + { + "host": "pfsense-api-alias", + "domain": "unit.test", + "description": "Unit Test" + } + ] + }, + { + "host": "pfsense-api", + "domain": "unit.test", + "ip": "fd00:abcd::", + "descr": "Unit Test IPv6", "aliases": [ { "host": "pfsense-api-alias", @@ -57,7 +70,8 @@ class APIUnitTestServicesUnboundHostOverride(unit_test_framework.APIUnitTest): } ] delete_payloads = [ - {"id": 0} + {"id": 0}, + {"id": 0, "apply": True} ] APIUnitTestServicesUnboundHostOverride() \ No newline at end of file diff --git a/tools/templates/Makefile.j2 b/tools/templates/Makefile.j2 index 57beedd32..30cec4e0d 100644 --- a/tools/templates/Makefile.j2 +++ b/tools/templates/Makefile.j2 @@ -2,7 +2,7 @@ PORTNAME=pfSense-pkg-API PORTVERSION=1.1 -PORTREVISION=4 +PORTREVISION=5 CATEGORIES=sysutils MASTER_SITES=# empty DISTFILES=# empty