. * 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. */ /* holds the items that will be executed *AFTER* the filter is fully loaded */ $after_filter_configure_run = array(); /* For installing cron job of schedules */ $time_based_rules = false; /* Used to hold the interface list that will be used on ruleset creation. */ $FilterIflist = array(); /* Create a global array to avoid errors on rulesets. */ $GatewaysList = array(); /* Used for the hostname dns resolver */ $filterdns = array(); /* Used for aliases and interface macros */ $aliases = ""; /* ICMP v4+v6 subtypes */ $icmptypes = array( 'althost' => array('descrip' => gettext('Alternate Host'), 'valid4' => true, 'valid6' => false), 'dataconv' => array('descrip' => gettext('Datagram conversion error'), 'valid4' => true, 'valid6' => false), 'echorep' => array('descrip' => gettext('Echo reply'), 'valid4' => true, 'valid6' => true), 'echoreq' => array('descrip' => gettext('Echo request'), 'valid4' => true, 'valid6' => true), 'fqdnrep' => array('descrip' => gettext('FQDN reply'), 'valid4' => false, 'valid6' => true), 'fqdnreq' => array('descrip' => gettext('FQDN query'), 'valid4' => false, 'valid6' => true), 'groupqry' => array('descrip' => gettext('Group membership query'), 'valid4' => false, 'valid6' => true), 'grouprep' => array('descrip' => gettext('Group membership report'), 'valid4' => false, 'valid6' => true), 'groupterm' => array('descrip' => gettext('Group membership termination'), 'valid4' => false, 'valid6' => true), 'inforep' => array('descrip' => gettext('Information reply'), 'valid4' => true, 'valid6' => false), 'inforeq' => array('descrip' => gettext('Information request'), 'valid4' => true, 'valid6' => false), 'ipv6-here' => array('descrip' => gettext('IPv6 I-am-here'), 'valid4' => true, 'valid6' => false), 'ipv6-where' => array('descrip' => gettext('IPv6 where-are-you'), 'valid4' => true, 'valid6' => false), 'listendone' => array('descrip' => gettext('Multicast listener done'), 'valid4' => false, 'valid6' => true), 'listenrep' => array('descrip' => gettext('Multicast listener report'), 'valid4' => false, 'valid6' => true), 'listqry' => array('descrip' => gettext('Multicast listener query'), 'valid4' => false, 'valid6' => true), 'maskrep' => array('descrip' => gettext('Address mask reply'), 'valid4' => true, 'valid6' => false), 'maskreq' => array('descrip' => gettext('Address mask request'), 'valid4' => true, 'valid6' => false), 'mobredir' => array('descrip' => gettext('Mobile host redirect'), 'valid4' => true, 'valid6' => false), 'mobregrep' => array('descrip' => gettext('Mobile registration reply'), 'valid4' => true, 'valid6' => false), 'mobregreq' => array('descrip' => gettext('Mobile registration request'), 'valid4' => true, 'valid6' => false), 'mtrace' => array('descrip' => gettext('mtrace messages'), 'valid4' => false, 'valid6' => true), 'mtraceresp' => array('descrip' => gettext('mtrace resp'), 'valid4' => false, 'valid6' => true), 'neighbradv' => array('descrip' => gettext('Neighbor advertisement'), 'valid4' => false, 'valid6' => true), 'neighbrsol' => array('descrip' => gettext('Neighbor solicitation'), 'valid4' => false, 'valid6' => true), 'niqry' => array('descrip' => gettext('Node information request'), 'valid4' => false, 'valid6' => true), 'nirep' => array('descrip' => gettext('Node information reply'), 'valid4' => false, 'valid6' => true), 'paramprob' => array('descrip' => gettext('Parameter problem (invalid IP header)'), 'valid4' => true, 'valid6' => true), 'photuris' => array('descrip' => gettext('Photuris'), 'valid4' => true, 'valid6' => false), 'redir' => array('descrip' => gettext('Redirect'), 'valid4' => true, 'valid6' => true), 'routeradv' => array('descrip' => gettext('Router advertisement'), 'valid4' => true, 'valid6' => true), 'routersol' => array('descrip' => gettext('Router solicitation'), 'valid4' => true, 'valid6' => true), 'routrrenum' => array('descrip' => gettext('Router renumbering'), 'valid4' => false, 'valid6' => true), 'skip' => array('descrip' => gettext('SKIP'), 'valid4' => true, 'valid6' => false), 'squench' => array('descrip' => gettext('Source quench'), 'valid4' => true, 'valid6' => false), 'timerep' => array('descrip' => gettext('Timestamp reply'), 'valid4' => true, 'valid6' => false), 'timereq' => array('descrip' => gettext('Timestamp'), 'valid4' => true, 'valid6' => false), 'timex' => array('descrip' => gettext('Time exceeded'), 'valid4' => true, 'valid6' => true), 'toobig' => array('descrip' => gettext('Packet too big'), 'valid4' => false, 'valid6' => true), 'trace' => array('descrip' => gettext('Traceroute'), 'valid4' => true, 'valid6' => false), 'unreach' => array('descrip' => gettext('Destination unreachable'), 'valid4' => true, 'valid6' => true), 'wrurep' => array('descrip' => gettext('Who are you reply'), 'valid4' => false, 'valid6' => true), 'wrureq' => array('descrip' => gettext('Who are you request'), 'valid4' => false, 'valid6' => true) ); /* * Fixed tracker values (used to group and track usage in GUI): * * anti-lockout rules: 10000 * bogons rules: 11000 * RFC1918 rules: 12000 * */ define("ANTILOCKOUT_TRACKER", 10000); define("BOGONS_TRACKER", 11000); define("RFC1918_TRACKER", 12000); define("PFLABEL_MAXLEN", 63); define("USER_LABEL_INTRO", "USER_RULE: "); $tracker = 1000000000; $negate_tracker = 10000000; function filter_rule_tracker($tracker) { global $tracker; return (++$tracker); } function filter_negaterule_tracker() { global $negate_tracker; ++$negate_tracker; return "tracker {$negate_tracker} "; } function user_rule_descr_maxlen() { return PFLABEL_MAXLEN - strlen(USER_LABEL_INTRO); } function fix_rule_label($descr) { $descr = str_replace('"', '', $descr); if (strlen($descr) > PFLABEL_MAXLEN) { $dots = "..."; return substr($descr, 0, PFLABEL_MAXLEN - strlen($dots)) . $dots; } else { return $descr; } } function is_bogonsv6_used() { global $config, $g; # Only use bogonsv6 table if IPv6 Allow is on, and at least 1 enabled interface also has "blockbogons" enabled. $usebogonsv6 = false; if (isset($config['system']['ipv6allow'])) { foreach ($config['interfaces'] as $ifacedata) { if (isset($ifacedata['enable']) && isset($ifacedata['blockbogons'])) { $usebogonsv6 = true; break; } } } return $usebogonsv6; } function filter_pflog_start($kill_first = false) { global $config, $g; if (isset($config['system']['developerspew'])) { $mt = microtime(); echo "filter_pflog_start() being called $mt\n"; } if ((!file_exists("{$g['varrun_path']}/filterlog.pid")) || (!isvalidpid("{$g['varrun_path']}/filterlog.pid"))) { mwexec("/usr/local/sbin/filterlog -i pflog0 -p {$g['varrun_path']}/filterlog.pid"); } } /* reload filter async */ function filter_configure() { global $g; if (isset($config['system']['developerspew'])) { $mt = microtime(); echo "filter_configure() being called $mt\n"; } /* * NOTE: Check here for bootup status since this should not be triggered during bootup. * The reason is that rc.bootup calls filter_configure_sync directly which does this too. */ if (!platform_booting()) { send_event("filter reload"); } } function filter_delete_states_for_down_gateways() { global $config, $GatewaysList; if (!isset($config['system']['gw_down_kill_states'])) { return; } $any_gateway_down = false; $a_gateways = return_gateways_status(); if (is_array($GatewaysList)) { foreach ($GatewaysList as $gwname => $gateway) { if (empty($gateway['monitor'])) { continue; } if (!is_ipaddr($gateway['monitor'])) { continue; } if (strstr($gateway['monitor'], "127.0.0.")) { continue; } if (empty($a_gateways[$gateway['monitor']])) { continue; } $gwstatus =& $a_gateways[$gateway['monitor']]; if (strstr($gwstatus['status'], "down")) { $any_gateway_down = true; break; } } } if ($any_gateway_down == true) { mwexec("/sbin/pfctl -Fs"); } } /* reload filter sync */ function filter_configure_sync($delete_states_if_needed = true) { global $config, $g, $after_filter_configure_run, $FilterIflist; global $time_based_rules, $filterdns, $aliases, $dummynet_name_list; /* Use filter lock to not allow concurrent filter reloads during this run. */ $filterlck = lock('filter', LOCK_EX); filter_pflog_start(); update_filter_reload_status(gettext("Initializing"), true); // second argument = overwrite existing file /* invalidate interface cache */ get_interface_arr(true); if (isset($config['system']['developerspew'])) { $mt = microtime(); echo "filter_configure_sync() being called $mt\n"; } /* Get interface list to work with. */ filter_generate_optcfg_array(); if (platform_booting() == true) { echo gettext("Configuring firewall"); } /* generate aliases */ if (platform_booting() == true) { echo "."; } update_filter_reload_status(gettext("Creating aliases")); $aliases = filter_generate_aliases(); $gateways = filter_generate_gateways(); if (platform_booting() == true) { echo "."; } update_filter_reload_status(gettext("Generating Limiter rules")); $dummynet_rules = filter_generate_dummynet_rules(); $dummynet_name_list = get_unique_dnqueue_list(); update_filter_reload_status(gettext("Generating NAT rules")); /* generate nat rules */ $natrules = filter_nat_rules_generate(); if (platform_booting() == true) { echo "."; } update_filter_reload_status(gettext("Generating filter rules")); /* generate pfctl rules */ $pfrules = filter_rules_generate(); /* generate altq, limiter */ if (platform_booting() == true) { echo "."; } update_filter_reload_status(gettext("Generating ALTQ queues")); $altq_queues = filter_generate_altq_queues(); if (platform_booting() == true) { echo "."; } update_filter_reload_status(gettext("Loading filter rules")); /* enable pf if we need to, otherwise disable */ if (!isset ($config['system']['disablefilter'])) { mwexec("/sbin/pfctl -e", true); } else { mwexec("/sbin/pfctl -d", true); unlink_if_exists("{$g['tmp_path']}/filter_loading"); update_filter_reload_status(gettext("Filter is disabled. Not loading rules.")); if (platform_booting() == true) { echo gettext("done.") . "\n"; } unlock($filterlck); return; } $limitrules = ""; /* User defined maximum table entries in Advanced menu. */ if ($config['system']['maximumtableentries'] <> "" && is_numeric($config['system']['maximumtableentries'])) { $limitrules .= "set limit table-entries {$config['system']['maximumtableentries']}\n"; } if ($config['system']['optimization'] <> "") { $limitrules .= "set optimization {$config['system']['optimization']}\n"; if ($config['system']['optimization'] == "conservative") { $limitrules .= "set timeout { udp.first 300, udp.single 150, udp.multiple 900 }\n"; } } else { $limitrules .= "set optimization normal\n"; } $timeoutlist = ""; if (isset($config['system']['tcpfirsttimeout']) && is_numericint($config['system']['tcpfirsttimeout'])) { $timeoutlist .= " tcp.first {$config['system']['tcpfirsttimeout']} "; } if (isset($config['system']['tcpopeningtimeout']) && is_numericint($config['system']['tcpopeningtimeout'])) { $timeoutlist .= " tcp.opening {$config['system']['tcpopeningtimeout']} "; } if (isset($config['system']['tcpestablishedtimeout']) && is_numericint($config['system']['tcpestablishedtimeout'])) { $timeoutlist .= " tcp.established {$config['system']['tcpestablishedtimeout']} "; } if (isset($config['system']['tcpclosingtimeout']) && is_numericint($config['system']['tcpclosingtimeout'])) { $timeoutlist .= " tcp.closing {$config['system']['tcpclosingtimeout']} "; } if (isset($config['system']['tcpfinwaittimeout']) && is_numericint($config['system']['tcpfinwaittimeout'])) { $timeoutlist .= " tcp.finwait {$config['system']['tcpfinwaittimeout']} "; } if (isset($config['system']['tcpclosedtimeout']) && is_numericint($config['system']['tcpclosedtimeout'])) { $timeoutlist .= " tcp.closed {$config['system']['tcpclosedtimeout']} "; } if (isset($config['system']['udpfirsttimeout']) && is_numericint($config['system']['udpfirsttimeout'])) { $timeoutlist .= " udp.first {$config['system']['udpfirsttimeout']} "; } if (isset($config['system']['udpsingletimeout']) && is_numericint($config['system']['udpsingletimeout'])) { $timeoutlist .= " udp.single {$config['system']['udpsingletimeout']} "; } if (isset($config['system']['udpmultipletimeout']) && is_numericint($config['system']['udpmultipletimeout'])) { $timeoutlist .= " udp.multiple {$config['system']['udpmultipletimeout']} "; } if (isset($config['system']['icmpfirsttimeout']) && is_numericint($config['system']['icmpfirsttimeout'])) { $timeoutlist .= " icmp.first {$config['system']['icmpfirsttimeout']} "; } if (isset($config['system']['icmperrortimeout']) && is_numericint($config['system']['icmperrortimeout'])) { $timeoutlist .= " icmp.error {$config['system']['icmperrortimeout']} "; } if (isset($config['system']['otherfirsttimeout']) && is_numericint($config['system']['otherfirsttimeout'])) { $timeoutlist .= " other.first {$config['system']['otherfirsttimeout']} "; } if (isset($config['system']['othersingletimeout']) && is_numericint($config['system']['othersingletimeout'])) { $timeoutlist .= " other.single {$config['system']['othersingletimeout']} "; } if (isset($config['system']['othermultipletimeout']) && is_numericint($config['system']['othermultipletimeout'])) { $timeoutlist .= " other.multiple {$config['system']['othermultipletimeout']} "; } if ($timeoutlist <> "") { $limitrules .= "set timeout { $timeoutlist }\n"; } if (is_numericint($config['system']['adaptivestart']) && is_numericint($config['system']['adaptiveend'])) { $limitrules .= "set timeout { adaptive.start {$config['system']['adaptivestart']}, adaptive.end {$config['system']['adaptiveend']} }\n"; } if ($config['system']['maximumstates'] <> "" && is_numeric($config['system']['maximumstates'])) { /* User defined maximum states in Advanced menu. */ $limitrules .= "set limit states {$config['system']['maximumstates']}\n"; $limitrules .= "set limit src-nodes {$config['system']['maximumstates']}\n"; } else { $max_states = pfsense_default_state_size(); $limitrules .= "set limit states {$max_states}\n"; $limitrules .= "set limit src-nodes {$max_states}\n"; } /* Frag limit. pf default is 5000 */ if ($config['system']['maximumfrags'] <> "" && is_numeric($config['system']['maximumfrags'])) { $limitrules .= "set limit frags {$config['system']['maximumfrags']}\n"; } if (isset($config['system']['lb_use_sticky']) && is_numeric($config['system']['srctrack']) && ($config['system']['srctrack'] > 0)) { $limitrules .= "set timeout src.track {$config['system']['srctrack']}\n"; } $rules = ""; $rules = "{$limitrules}\n"; $rules .= "{$aliases} \n"; $rules .= "{$gateways} \n"; update_filter_reload_status(gettext("Setting up logging information")); $rules .= filter_setup_logging_interfaces(); $rules .= "\n"; $rules .= "set skip on pfsync0\n"; $rules .= "\n"; update_filter_reload_status(gettext("Setting up SCRUB information")); $rules .= filter_generate_scrubing(); $rules .= "\n"; $rules .= "{$altq_queues}\n"; $rules .= "{$natrules}\n"; $rules .= "{$pfrules}\n"; $rules .= discover_pkg_rules("filter"); unset($aliases, $gateways, $altq_queues, $natrules, $pfrules); // Copy rules.debug to rules.debug.old if (file_exists("{$g['tmp_path']}/rules.debug")) { @copy("{$g['tmp_path']}/rules.debug", "{$g['tmp_path']}/rules.debug.old"); } if (!@file_put_contents("{$g['tmp_path']}/rules.debug", $rules, LOCK_EX)) { log_error("WARNING: Could not write new rules!"); unlock($filterlck); return; } @file_put_contents("{$g['tmp_path']}/rules.limits", $limitrules); mwexec("/sbin/pfctl -Of {$g['tmp_path']}/rules.limits"); unset($rules, $limitrules); if (isset($config['system']['developerspew'])) { $mt = microtime(); echo "pfctl being called at $mt\n"; } unset($rules_loading, $rules_error); $_grbg = exec("/sbin/pfctl -o basic -f {$g['tmp_path']}/rules.debug 2>&1", $rules_error, $rules_loading); if (isset($config['system']['developerspew'])) { $mt = microtime(); echo "pfctl done at $mt\n"; } /* * check for a error while loading the rules file. if an error has occurred * then output the contents of the error to the caller */ if ($rules_loading <> 0) { foreach ($rules_error as $errorline) { $saved_line_error = $errorline; $line_error = explode(":", $errorline); $line_number = $line_error[1]; $line_split = file("{$g['tmp_path']}/rules.debug"); if (is_array($line_split)) { $line_error = sprintf(gettext('The line in question reads [%1$d]: %2$s'), $line_number, $line_split[$line_number-1]); } unset($line_split); /* Brutal ugly hack but required -- PF is stuck, unwedge */ if (strstr("$rules_error[0]", "busy")) { exec("/sbin/pfctl -d; /sbin/pfctl -e; /sbin/pfctl -f {$g['tmp_path']}/rules.debug"); $error_msg = gettext("PF was wedged/busy and has been reset."); file_notice("pf_busy", $error_msg, "pf_busy", ""); } else { $_grbg = exec("/sbin/pfctl -o basic -f {$g['tmp_path']}/rules.debug.old 2>&1"); } if ($line_error and $line_number) { file_notice("filter_load", sprintf(gettext('There were error(s) loading the rules: %1$s - %2$s'), $saved_line_error, $line_error), "Filter Reload", ""); update_filter_reload_status(sprintf(gettext('There were error(s) loading the rules: %1$s - %2$s'), $saved_line_error, $line_error)); unlock($filterlck); return; } } unset($rules_loading, $rules_error); } # If we are not using bogonsv6 then we can remove any bogonsv6 table from the running pf (if the table is not there, the kill is still fine). if (!is_bogonsv6_used()) { $_grbg = exec("/sbin/pfctl -t bogonsv6 -T kill 2>/dev/null"); } if (!platform_booting()) { if (!empty($filterdns)) { @file_put_contents("{$g['varetc_path']}/filterdns.conf", implode("", $filterdns)); unset($filterdns); if (isvalidpid("{$g['varrun_path']}/filterdns.pid")) { sigkillbypid("{$g['varrun_path']}/filterdns.pid", "HUP"); } else { /* * FilterDNS has three debugging levels. The default chosen is 1. * Available are level 2 and greater then 2. */ if (isset($config['system']['aliasesresolveinterval']) && is_numeric($config['system']['aliasesresolveinterval'])) { $resolve_interval = $config['system']['aliasesresolveinterval']; } else { $resolve_interval = 300; } mwexec("/usr/local/sbin/filterdns -p {$g['varrun_path']}/filterdns.pid -i {$resolve_interval} -c {$g['varetc_path']}/filterdns.conf -d 1"); } } else { killbypid("{$g['varrun_path']}/filterdns.pid"); @unlink("{$g['varrun_path']}/filterdns.pid"); } } /* run items scheduled for after filter configure run */ $fda = fopen("{$g['tmp_path']}/commands.txt", "w"); if ($fda) { if ($after_filter_configure_run) { foreach ($after_filter_configure_run as $afcr) { fwrite($fda, $afcr . "\n"); } unset($after_filter_configure_run); } /* * we need a way to let a user run a shell cmd after each * filter_configure() call. run this xml command after * each change. */ if ($config['system']['afterfilterchangeshellcmd'] <> "") { fwrite($fda, $config['system']['afterfilterchangeshellcmd'] . "\n"); } fclose($fda); } if (file_exists("{$g['tmp_path']}/commands.txt")) { mwexec("sh {$g['tmp_path']}/commands.txt &"); unlink("{$g['tmp_path']}/commands.txt"); } /* if time based rules are enabled then swap in the set */ if ($time_based_rules == true) { filter_tdr_install_cron(true); } else { filter_tdr_install_cron(false); } if (platform_booting() == true) { echo "."; } if ($delete_states_if_needed) { update_filter_reload_status(gettext("Processing down interface states")); filter_delete_states_for_down_gateways(); } update_filter_reload_status(gettext("Running plugins")); if (is_dir("/usr/local/pkg/pf/")) { /* process packager manager custom rules */ update_filter_reload_status(gettext("Running plugins (pf)")); run_plugins("/usr/local/pkg/pf/"); update_filter_reload_status(gettext("Plugins completed.")); } update_filter_reload_status(gettext("Done")); if (platform_booting() == true) { echo gettext("done.") . "\n"; } unlock($filterlck); return 0; } function filter_generate_scrubing() { global $config, $FilterIflist; $scrubrules = ""; if (isset($config['system']['maxmss_enable'])) { $maxmss = 1400; if (!empty($config['system']['maxmss'])) { $maxmss = $config['system']['maxmss']; } $scrubrules .= "scrub from any to max-mss {$maxmss}\n"; $scrubrules .= "scrub from to any max-mss {$maxmss}\n"; } /* disable scrub option */ foreach ($FilterIflist as $scrubif => $scrubcfg) { if (isset($scrubcfg['virtual']) || empty($scrubcfg['descr'])) { continue; } /* set up MSS clamping */ if (($scrubcfg['mss'] <> "") && (is_numeric($scrubcfg['mss'])) && ($scrubcfg['if'] != "pppoe") && ($scrubcfg['if'] != "pptp") && ($scrubif['if'] != "l2tp")) { $mssclamp = "max-mss " . (intval($scrubcfg['mss'] - 40)); } else { $mssclamp = ""; } /* configure no-df for linux nfs and others */ if ($config['system']['scrubnodf']) { $scrubnodf = "no-df"; } else { $scrubnodf = ""; } if ($config['system']['scrubrnid']) { $scrubrnid = "random-id"; } else { $scrubrnid = ""; } if (!isset($config['system']['disablescrub'])) { $scrubrules .= "scrub on \${$scrubcfg['descr']} all {$scrubnodf} {$scrubrnid} {$mssclamp} fragment reassemble\n"; // reassemble all directions } else if (!empty($mssclamp)) { $scrubrules .= "scrub on \${$scrubcfg['descr']} {$mssclamp}\n"; } } return $scrubrules; } function filter_generate_nested_alias($name) { global $aliastable; $aliasnesting = array(); $aliasaddrnesting = array(); if (($name == "") || !isset($aliastable[$name])) { return ""; } return filter_generate_nested_alias_recurse($name, $aliastable[$name], $aliasnesting, $aliasaddrnesting); } function filter_generate_nested_alias_recurse($name, $alias, &$aliasnesting, &$aliasaddrnesting, &$use_filterdns = false) { global $aliastable, $filterdns; $addresses = explode(" ", $alias); $finallist = ""; $builtlist = ""; $urltable_nesting = ""; $aliasnesting[$name] = $name; $alias_type = alias_get_type($name); foreach ($addresses as $address) { if (empty($address)) { continue; } $linelength = strlen($builtlist); $tmpline = ""; if (is_alias($address)) { if (alias_get_type($address) == 'urltable') { // Feature#1603. For this type of alias we do not need to recursively call filter_generate_nested_alias_recurse. Just load IPs from the file. $urltable_nesting = alias_expand_urltable($address); if (!empty($urltable_nesting)) { $urlfile_as_arr = file($urltable_nesting); foreach ($urlfile_as_arr as $line) { $address= rtrim($line); if ((strlen($tmpline) + $linelength) > 4036) { $finallist .= "{$tmpline} \\\n"; $tmpline = ""; } $tmpline .= " {$address}"; } } } /* We already expanded this alias so there is no necessity to do it again. */ else if (!isset($aliasnesting[$address])) { $tmpline = filter_generate_nested_alias_recurse($name, $aliastable[$address], $aliasnesting, $aliasaddrnesting, $use_filterdns); } } else if (!isset($aliasaddrnesting[$address])) { if (!is_ipaddr($address) && !is_subnet($address) && !((($alias_type == 'port') || ($alias_type == 'url_ports')) && is_port_or_range($address)) && is_hostname($address)) { if (!isset($filterdns["{$address}{$name}"])) { $use_filterdns = true; $filterdns["{$address}{$name}"] = "pf {$address} {$name}\n"; } continue; } $aliasaddrnesting[$address] = $address; $tmpline = " {$address}"; } if ((strlen($tmpline)+ $linelength) > 4036) { $finallist .= "{$builtlist} \\\n"; $builtlist = ""; } if (!empty($tmpline)) { $builtlist .= " {$tmpline}"; } } $finallist .= $builtlist; if ($use_filterdns === true && !empty($finallist)) { foreach (explode(" ", $finallist) as $address) { if (empty($address)) { continue; } if ((is_ipaddr($address) || is_subnet($address)) && !isset($filterdns["{$address}{$name}"])) { $filterdns["{$address}{$name}"] = "pf {$address} {$name}\n"; } } $finallist = ''; } return $finallist; } function filter_expand_alias($alias_name) { global $config; if (isset($config['aliases']['alias'])) { foreach ($config['aliases']['alias'] as $aliased) { if ($aliased['name'] == $alias_name) { return filter_generate_nested_alias($aliased['name']); } } } } function filter_expand_alias_array($alias_name) { $expansion = filter_expand_alias($alias_name); return explode(" ", preg_replace('/\s+/', ' ', trim($expansion))); } function filter_generate_aliases() { global $config, $FilterIflist, $after_filter_configure_run; if (isset($config['system']['developerspew'])) { $mt = microtime(); echo "filter_generate_aliases() being called $mt\n"; } $alias = "#System aliases\n "; $aliases = "loopback = \"{ lo0 }\"\n"; foreach ($FilterIflist as $if => $ifcfg) { if (is_array($ifcfg[0])) { if ($ifcfg[0]['if'] == 'pppoe') { $aliases .= "{$ifcfg[0]['descr']} = \"{ {$ifcfg[0]['if']}"; $aliases .= " }\"\n"; } } elseif (!empty($ifcfg['descr']) && !empty($ifcfg['if'])) { if ($ifcfg['type6'] == '6rd') { $aliases .= "{$ifcfg['descr']} = \"{ {$ifcfg['if']} {$if}_stf"; } else if ($ifcfg['type6'] == '6to4') { $aliases .= "{$ifcfg['descr']} = \"{ {$ifcfg['if']} {$if}_stf"; } else { $aliases .= "{$ifcfg['descr']} = \"{ {$ifcfg['if']}"; if ($ifcfg['type'] == 'pptp') { foreach (get_parent_interface($ifcfg['if']) as $parent_if) { if ($parent_if != $ifcfg['if']) { $aliases .= " {$parent_if}"; } } } } $aliases .= " }\"\n"; } } $aliases .= "\n#SSH Lockout Table\n"; $aliases .= "table persist\n"; $aliases .= "table persist\n"; $aliases .= "#Snort tables\n"; $aliases .= "table \n"; $aliases .= "table \n"; if (!file_exists("/etc/bogons") || !file_exists("/etc/bogonsv6")) { if (!file_exists("/etc/bogons")) { @file_put_contents("/etc/bogons", ""); } if (!file_exists("/etc/bogonsv6")) { @file_put_contents("/etc/bogonsv6", ""); } } $aliases .= "table persist file \"/etc/bogons\"\n"; if (is_bogonsv6_used()) { $aliases .= "table persist file \"/etc/bogonsv6\"\n"; } $vpns_list = filter_get_vpns_list(); if ($vpns_list) { $aliases .= "table { $vpns_list }\n"; } /* add a Negate_networks table */ $aliases .= "table "; if ($vpns_list) { $aliases .= "{ $vpns_list }"; } $aliases .= "\n"; $aliases .= "\n# User Aliases \n"; /* Setup pf groups */ if (isset($config['aliases']['alias'])) { foreach ($config['aliases']['alias'] as $aliased) { if (is_numericint($aliased['name'])) { // skip aliases with numeric-only names. redmine #4289 file_notice("Filter_Reload", sprintf(gettext("Aliases with numeric-only names are not valid. Skipping alias %s"), $aliased['name'])); continue; } $addrlist = filter_generate_nested_alias($aliased['name']); switch ($aliased['type']) { case "host": case "network": case "url": $tableaddrs = "{$addrlist}"; if (empty($tableaddrs)) { $aliases .= "table <{$aliased['name']}> persist\n"; if (empty($aliased['address'])) { $after_filter_configure_run[] = "/sbin/pfctl -T flush -t " . escapeshellarg($aliased['name']); } } else { $aliases .= "table <{$aliased['name']}> { {$addrlist} } \n"; } $aliases .= "{$aliased['name']} = \"<{$aliased['name']}>\"\n"; break; case "openvpn": $openvpncfg = array(); if ($config['openvpn']['user']) { /* XXX: Check if we have a correct ip? */ foreach ($config['openvpn']['user'] as $openvpn) { $openvpncfg[$openvpn['name']] = $openvpn['ip']; } } $vpn_lines = explode("\n", $addrlist); foreach ($vpn_lines as $vpn_line) { $vpn_address_split = explode(" ", $vpn_line); foreach ($vpn_address_split as $vpnsplit) { if (isset($openvpncfg[$vpnsplit])) { $newaddress .= " "; $newaddress .= $openvpn[$vpnsplit]; break; } } } $aliases .= "table <{$aliased['name']}> { {$newaddress} } \n"; $aliases .= "{$aliased['name']} = \"<{$aliased['name']}>\"\n"; break; case "urltable": $urlfn = alias_expand_urltable($aliased['name']); if ($urlfn) { $aliases .= "table <{$aliased['name']}> persist file \"{$urlfn}\"\n"; $aliases .= "{$aliased['name']} = \"<{$aliased['name']}>\"\n"; } break; case "urltable_ports": // TODO: Change it when pf supports tables with ports $urlfn = alias_expand_urltable($aliased['name']); if ($urlfn) { $ports_tmp = parse_aliases_file($urlfn, "urltable_ports", "-1", false); $aliases .= "{$aliased['name']} = \"{ " . preg_replace("/\n/", " ", implode("\n", $ports_tmp)) . " }\"\n"; } break; case "port": case "url_ports": $aliases .= "{$aliased['name']} = \"{ {$addrlist} }\"\n"; break; default: $aliases .= "{$aliased['name']} = \"{ {$aliased['address']} }\"\n"; break; } } } $result = "{$alias} \n"; $result .= "{$aliases}"; return $result; } function filter_generate_gateways() { global $config, $g, $GatewaysList; $rules = "# Gateways\n"; update_filter_reload_status(gettext("Creating gateway group item...")); /* Lookup Gateways to be used in filter rules once */ $GatewaysList = return_gateways_array(); $GatewayGroupsList = return_gateway_groups_array(); if (is_array($GatewaysList)) { foreach ($GatewaysList as $gwname => $gateway) { $int = $gateway['interface']; $gwip = $gateway['gateway']; $route = ""; if (!is_ipaddr($gwip)) { $gwip = get_interface_gateway($gateway['friendlyiface']); } if (is_ipaddr($gwip) && !empty($int) && !isset($gateway['force_down'])) { $route = "route-to ( {$int} {$gwip} )"; } if (($route === "") && isset($config['system']['skip_rules_gw_down'])) { unset($GatewaysList[$gwname]); } else { $rules .= "GW{$gwname} = \" {$route} \"\n"; } } } if (is_array($GatewayGroupsList)) { foreach ($GatewayGroupsList as $gateway => $members) { $route = ""; /* hey, that's not a group member! */ unset($members['ipprotocol']); unset($members['descr']); if (count($members) > 0) { $foundlb = 0; $routeto = ""; $routetomembers = 0; foreach ($members as $idx => $member) { $int = $member['int']; $gatewayip = $member['gwip']; if (($int <> "") && is_ipaddr($gatewayip)) { if ($g['debug']) { log_error(sprintf(gettext('Setting up route with %1$s on %2$s'), $gatewayip, $int)); } if ($routetomembers + $member['weight'] > 384) { // would create invalid ruleset, bail log_error(sprintf(gettext("Too many members in group %s, gateway group truncated in ruleset."), $member['name'])); continue; } if ($member['weight'] > 1) { $routeto .= str_repeat("( {$int} {$gatewayip} ) ", $member['weight']); $routetomembers += $member['weight']; } else { $routeto .= "( {$int} {$gatewayip} ) "; $routetomembers++; } $foundlb++; } else { log_error(sprintf(gettext("An error occurred while trying to find the interface got %s . The rule has not been added."), $gatewayip)); } } $route = ""; if ($foundlb > 0) { $route = " route-to { {$routeto} } "; if ($foundlb > 1) { $route .= " round-robin "; if (isset($config['system']['lb_use_sticky'])) { $route .= " sticky-address "; } } } } if (($route === "") && isset($config['system']['skip_rules_gw_down'])) { unset($GatewayGroupsList[$gateway]); } else { $rules .= "GW{$gateway} = \" {$route} \"\n"; } } } /* Create a global array to avoid errors on rulesets. */ $GatewaysList = $GatewaysList + $GatewayGroupsList; $rules .= "\n"; return $rules; } /* returns space separated list of vpn subnets */ function filter_get_vpns_list() { global $config; $vpns = ""; $vpns_arr = array(); /* ipsec */ if (!function_exists('ipsec_enabled')) { require_once("ipsec.inc"); } if (ipsec_enabled()) { /* Include mobile IPsec client subnet in the VPN network list. See https://redmine.pfsense.org/issues/7005 */ if (is_array($config['ipsec']['client']) && isset($config['ipsec']['client']['enable']) && isset($config['ipsec']['client']['pool_address']) && isset($config['ipsec']['client']['pool_netbits'])) { $client_subnet = "{$config['ipsec']['client']['pool_address']}/{$config['ipsec']['client']['pool_netbits']}"; if (is_subnet($client_subnet)) { $vpns_arr[] = $client_subnet; } } if (is_array($config['ipsec']['phase2'])) { foreach ($config['ipsec']['phase2'] as $ph2ent) { if ((!$ph2ent['mobile']) && ($ph2ent['mode'] != 'transport')) { if (!is_array($ph2ent['remoteid'])) { continue; } $ph2ent['remoteid']['mode'] = $ph2ent['mode']; $vpns_subnet = ipsec_idinfo_to_cidr($ph2ent['remoteid']); if (!is_subnet($vpns_subnet) || $vpns_subnet == "0.0.0.0/0") { continue; } $vpns_arr[] = $vpns_subnet; } } } } /* openvpn */ foreach (array('client', 'server') as $type) { if (is_array($config['openvpn']["openvpn-$type"])) { foreach ($config['openvpn']["openvpn-$type"] as $settings) { if (is_array($settings)) { if (!isset($settings['disable'])) { $remote_networks = explode(',', $settings['remote_network']); foreach ($remote_networks as $remote_network) { if (is_subnet($remote_network) && ($remote_network <> "0.0.0.0/0")) { $vpns_arr[] = $remote_network; } } if (is_subnet($settings['tunnel_network']) && $settings['tunnel_network'] <> "0.0.0.0/0") { $vpns_arr[] = $settings['tunnel_network']; } } } } } } /* pppoe */ if (is_array($config['pppoes']['pppoe'])) { foreach ($config['pppoes']['pppoe'] as $pppoe) { if ($pppoe['mode'] == "server") { if (is_ipaddr($pppoe['remoteip'])) { $pppoesub = gen_subnet($pppoe['remoteip'], $pppoe['pppoe_subnet']); if (is_subnet($pppoesub)) { $vpns_arr[] = $pppoesub; } } } } } if (!empty($vpns_arr)) { $vpns = implode(" ", $vpns_arr); } return $vpns; } /* returns space separated list of directly connected networks * optionally returns an array instead, including friendly interface and gateway (if applicable) */ function filter_get_direct_networks_list($returnsubnetsonly = true) { global $config, $FilterIflist, $GatewaysList; /* build list of directly connected interfaces and networks */ $networks = ""; $networks_arr = array(); if (empty($FilterIflist)) { filter_generate_optcfg_array(); } foreach ($FilterIflist as $ifent => $ifcfg) { $subnet = "{$ifcfg['sa']}/{$ifcfg['sn']}"; if (is_subnet($subnet)) { if ($returnsubnetsonly) { $networks_arr[] = $subnet; } else { $networks_arr[] = array( 'subnet' => $subnet, 'if' => $ifent, 'ip' => $ifcfg['ip']); } } } $viplist = get_configured_vip_list(); foreach ($viplist as $vid => $address) { $vip = get_configured_vip($vid); $subnet = "{$vip['subnet']}/{$vip['subnet_bits']}"; if (is_subnet($subnet) && !(is_subnetv4($subnet) && $vip['subnet_bits'] == 32) && !(is_subnetv6($subnet) && $vip['subnet_bits'] == 128)) { if (is_subnetv4($subnet)) { $subnet = gen_subnet($vip['subnet'], $vip['subnet_bits']) . "/{$vip['subnet_bits']}"; } else if (is_subnetv6($subnet)) { $subnet = gen_subnetv6($vip['subnet'], $vip['subnet_bits']) . "/{$vip['subnet_bits']}"; } if ($returnsubnetsonly) { $networks_arr[] = $subnet; } else { $networks_arr[] = array( 'subnet' => $subnet, 'if' => $vip['interface'], 'ip' => $vip['subnet']); } } } // Add any enabled static routes foreach (get_staticroutes(false, false, true) as $netent) { if (is_subnet($netent['network'])) { if ($returnsubnetsonly) { $networks_arr[] = $netent['network']; } else if (isset($GatewaysList[$netent['gateway']])) { $networks_arr[] = array( 'subnet' => $netent['network'], 'if' => $GatewaysList[$netent['gateway']]['friendlyiface'], 'gateway' => $GatewaysList[$netent['gateway']]['gateway']); } } } if ($returnsubnetsonly) { if (!empty($networks_arr)) { $networks = implode(" ", $networks_arr); } return $networks; } else { return $networks_arr; } } function filter_generate_optcfg_array() { global $config, $FilterIflist; if (isset($config['system']['developerspew'])) { $mt = microtime(); echo "filter_generate_optcfg_array() being called $mt\n"; } /* if list */ $iflist = get_configured_interface_with_descr(); foreach ($iflist as $if => $ifdetail) { $oc = $config['interfaces'][$if]; $oic = array(); $oic['if'] = get_real_interface($if); if (!does_interface_exist($oic['if'])) { continue; } $oic['ifv6'] = get_real_interface($if, "inet6"); $oic['ip'] = get_interface_ip($if); $oic['ipv6'] = get_interface_ipv6($if); if (!is_ipaddrv4($oc['ipaddr']) && !empty($oc['ipaddr'])) { $oic['type'] = $oc['ipaddr']; } if (!is_ipaddrv6($oc['ipaddrv6']) && !empty($oc['ipaddrv6'])) { $oic['type6'] = $oc['ipaddrv6']; } if (!empty($oc['track6-interface'])) { $oic['track6-interface'] = $oc['track6-interface']; } $oic['sn'] = get_interface_subnet($if); $oic['snv6'] = get_interface_subnetv6($if); $oic['mtu'] = empty($oc['mtu']) ? 1500 : $oc['mtu']; $oic['mss'] = empty($oc['mss']) ? '' : $oc['mss']; $oic['descr'] = $ifdetail; $oic['sa'] = gen_subnet($oic['ip'], $oic['sn']); $oic['sav6'] = gen_subnetv6($oic['ipv6'], $oic['snv6']); $oic['nonat'] = $oc['nonat']; $oic['alias-address'] = $oc['alias-address']; $oic['alias-subnet'] = $oc['alias-subnet']; $oic['gateway'] = $oc['gateway']; $oic['gatewayv6'] = $oc['gatewayv6']; $oic['spoofcheck'] = "yes"; $oic['bridge'] = link_interface_to_bridge($if); $vips = link_interface_to_vips($if); if (!empty($vips)) { foreach ($vips as $vipidx => $vip) { if (is_ipaddrv4($vip['subnet'])) { if (!is_array($oic['vips'])) { $oic['vips'] = array(); } $oic['vips'][$vipidx]['ip'] = $vip['subnet']; if (empty($vip['subnet_bits'])) { $oic['vips'][$vipidx]['sn'] = 32; } else { $oic['vips'][$vipidx]['sn'] = $vip['subnet_bits']; } } else if (is_ipaddrv6($vip['subnet'])) { if (!is_array($oic['vips6'])) { $oic['vips6'] = array(); } $oic['vips6'][$vipidx]['ip'] = $vip['subnet']; if (empty($vip['subnet_bits'])) { $oic['vips6'][$vipidx]['sn'] = 128; } else { $oic['vips6'][$vipidx]['sn'] = $vip['subnet_bits']; } } } } unset($vips); $FilterIflist[$if] = $oic; } if ($config['l2tp']['mode'] == "server") { $oic = array(); $oic['if'] = 'l2tp'; $oic['descr'] = 'L2TP'; $oic['ip'] = $config['l2tp']['localip']; $oic['sa'] = $config['l2tp']['remoteip']; if ($config['l2tp']['l2tp_subnet'] <> "") { $oic['sn'] = $config['l2tp']['l2tp_subnet']; } else { $oic['sn'] = "32"; } $oic['mode'] = $config['l2tp']['mode']; $oic['virtual'] = true; $FilterIflist['l2tp'] = $oic; } if (is_array($config['pppoes']['pppoe']) && (count($config['pppoes']['pppoe']) > 0)) { $pppoeifs = array(); foreach ($config['pppoes']['pppoe'] as $pppoe) { if ($pppoe['mode'] == "server") { $oic = array(); $oic['if'] = 'pppoe'; $oic['descr'] = 'pppoe'; $oic['ip'] = $pppoe['localip']; $oic['sa'] = $pppoe['remoteip']; $oic['mode'] = $pppoe['mode']; $oic['virtual'] = true; if ($pppoe['pppoe_subnet'] <> "") { $oic['sn'] = $pppoe['pppoe_subnet']; } else { $oic['sn'] = "32"; } $pppoeifs[] = $oic; } } if (count($pppoeifs)) { $FilterIflist['pppoe'] = $pppoeifs; } } /* add ipsec interfaces */ if (!function_exists('ipsec_enabled')) { require_once("ipsec.inc"); } if (ipsec_enabled()) { $oic = array(); $oic['if'] = 'enc0'; $oic['descr'] = 'IPsec'; $oic['type'] = "none"; $oic['virtual'] = true; $FilterIflist['enc0'] = $oic; } /* add openvpn interfaces */ if ($config['openvpn']['openvpn-server'] || $config['openvpn']['openvpn-client']) { $oic = array(); $oic['if'] = "openvpn"; $oic['descr'] = 'OpenVPN'; $oic['type'] = "none"; $oic['virtual'] = true; $FilterIflist['openvpn'] = $oic; } /* add interface groups */ if (is_array($config['ifgroups']['ifgroupentry'])) { foreach ($config['ifgroups']['ifgroupentry'] as $ifgen) { $oc = array(); $oc['if'] = $ifgen['ifname']; $oc['descr'] = $ifgen['ifname']; $oc['virtual'] = true; $FilterIflist[$ifgen['ifname']] = $oc; } } } function filter_flush_nat_table() { global $config, $g; if (isset($config['system']['developerspew'])) { $mt = microtime(); echo "filter_flush_nat_table() being called $mt\n"; } return mwexec("/sbin/pfctl -F nat"); } function filter_flush_state_table() { return mwexec("/sbin/pfctl -F state"); } function filter_get_reflection_interfaces($natif = "") { global $FilterIflist; $nat_if_list = array(); foreach ($FilterIflist as $ifent => $ifname) { if ($ifname['if'] == $natif) { continue; } /* Do not add reflection redirects for interfaces with gateways */ if (interface_has_gateway($ifent)) { continue; } $nat_if_list[] = $ifname['if']; } return $nat_if_list; } function filter_generate_reflection_nat($rule, &$route_table, $nat_ifs, $protocol, $target, $target_ip, $target_subnet = "") { global $config, $FilterIflist; if (!isset($config['system']['enablenatreflectionhelper'])) { return ""; } // Initialize natrules holder string $natrules = ""; update_filter_reload_status(sprintf(gettext("Creating reflection NAT rule for %s..."), $rule['descr'])); /* TODO: Add this option to port forwards page. */ if (isset($rule['staticnatport'])) { $static_port = " static-port"; } else { $static_port = " port 1024:65535"; } if (!empty($protocol)) { $protocol_text = " proto {$protocol}"; } else { $protocol_text = ""; } if (empty($target_subnet) || !is_numeric($target_subnet)) { $target_subnet = 32; } if (!is_array($route_table)) { /* get a simulated IPv4-only route table based on the config */ $route_table = filter_get_direct_networks_list(false); foreach ($route_table as $rt_key => $rt_ent) { if (!is_subnetv4($rt_ent['subnet'])) { unset($route_table[$rt_key]); } if (isset($route_table[$rt_key]) && isset($FilterIflist[$rt_ent['if']]['if'])) { $route_table[$rt_key]['if'] = $FilterIflist[$rt_ent['if']]['if']; } } } /* Check if the target is accessed through a static route */ foreach ($route_table as $route) { if (isset($route['gateway']) && is_ipaddr($route['gateway'])) { $subnet_split = explode("/", $route['subnet']); if (in_array($route['if'], $nat_ifs) && check_subnets_overlap($target_ip, $target_subnet, $subnet_split[0], $subnet_split[1])) { $target_ip = $route['gateway']; $target_subnet = 32; break; } } } /* Search for matching subnets in the routing table */ foreach ($route_table as $route) { $subnet = $route['subnet']; $subnet_split = explode("/", $subnet); $subnet_if = $route['if']; /* Blacklist invalid "from" sources since they can be picked up accidentally and cause rule errors. */ $no_reflect_from = array("l2tp"); if (in_array($subnet_if, $nat_ifs) && check_subnets_overlap($target_ip, $target_subnet, $subnet_split[0], $subnet_split[1])) { $ifsubnet_ip = ""; /* Find interface IP to use for NAT */ foreach ($route_table as $ifnetwork) { if (isset($ifnetwork['ip']) && is_ipaddr($ifnetwork['ip']) && $ifnetwork['if'] == $subnet_if && ip_in_subnet($ifnetwork['ip'], $subnet)) { $ifsubnet_ip = $ifnetwork['ip']; break; } } if (!empty($ifsubnet_ip) && !in_array($subnet, $no_reflect_from)) { $subnets = array($subnet); /* Find static routes that also need to be referenced in the NAT rule */ foreach ($route_table as $rtentry) { if (isset($rtentry['gateway']) && is_ipaddr($rtentry['gateway']) && $rtentry['if'] == $subnet_if && ip_in_subnet($rtentry['gateway'], $subnet)) { $subnets[] = $rtentry['subnet']; } } if (count($subnets) > 1) { $subnet = "{ " . implode(" ", $subnets) . " }"; } $natrules .= "no nat on {$subnet_if}{$protocol_text} from {$subnet_if} to {$target}\n"; $natrules .= "nat on {$subnet_if}{$protocol_text} from {$subnet} to {$target} -> {$ifsubnet_ip}{$static_port}\n"; } } } if (!empty($natrules)) { $natrules .= "\n"; } return $natrules; } function filter_generate_reflection_proxy($rule, $nordr, $rdr_ifs, $srcaddr, $dstaddr_port, &$starting_localhost_port, &$reflection_rules) { global $FilterIflist, $config; // Initialize natrules holder string $natrules = ""; $reflection_rules = array(); if (!empty($rdr_ifs)) { if ($config['system']['reflectiontimeout']) { $reflectiontimeout = $config['system']['reflectiontimeout']; } else { $reflectiontimeout = "2000"; } update_filter_reload_status(sprintf(gettext("Creating reflection rule for %s..."), $rule['descr'])); $rdr_if_list = implode(" ", $rdr_ifs); if (count($rdr_ifs) > 1) { $rdr_if_list = "{ {$rdr_if_list} }"; } $natrules .= "\n# Reflection redirects\n"; $localport = $rule['local-port']; if (!empty($localport) && is_alias($localport)) { $localport = filter_expand_alias($localport); $localport = explode(" ", trim($localport)); // The translation port for rdr, when specified, does not support more than one port or range. // Emulating for behavior consistent with the original port forward. $localport = $localport[0]; } if (is_alias($rule['destination']['port'])) { if (empty($localport) || $rule['destination']['port'] == $rule['local-port']) { $dstport = filter_expand_alias($rule['destination']['port']); $dstport = array_filter(explode(" ", trim($dstport))); $localport = ""; } else if (!empty($localport)) { $dstport = array($localport); } } else { $dstport = array(str_replace("-", ":", $rule['destination']['port'])); $dstport_split = explode(":", $dstport[0]); if (!empty($localport) && $dstport_split[0] != $rule['local-port']) { if (!is_alias($rule['local-port']) && $dstport_split[1] && $dstport_split[0] != $dstport_split[1]) { $localendport = $localport + ($dstport_split[1] - $dstport_split[0]); $localport .= ":$localendport"; } $dstport = array($localport); } else { $localport = ""; } } $dstaddr = explode(" ", $dstaddr_port); if ($dstaddr[2]) { $rflctintrange = array_pop($dstaddr); array_pop($dstaddr); } else { return ""; } $dstaddr = implode(" ", $dstaddr); if (empty($dstaddr) || trim($dstaddr) == "0.0.0.0" || strtolower(trim($dstaddr)) == "port") { return ""; } if (isset($rule['destination']['any'])) { if (!$rule['interface']) { $natif = "wan"; } else { $natif = $rule['interface']; } if (!isset($FilterIflist[$natif])) { return ""; } if (is_ipaddr($FilterIflist[$natif]['ip'])) { $dstaddr = $FilterIflist[$natif]['ip']; } else { return ""; } if (!empty($FilterIflist[$natif]['sn'])) { $dstaddr = gen_subnet($dstaddr, $FilterIflist[$natif]['sn']) . '/' . $FilterIflist[$natif]['sn']; } } switch ($rule['protocol']) { case "tcp/udp": $protocol = "{ tcp udp }"; $reflect_protos = array('tcp', 'udp'); break; case "tcp": case "udp": $protocol = $rule['protocol']; $reflect_protos = array($rule['protocol']); break; default: return ""; break; } if (!empty($nordr)) { $natrules .= "no rdr on {$rdr_if_list} proto {$protocol} from {$srcaddr} to {$dstaddr} port {$rflctintrange}\n"; return $natrules; } if (is_alias($rule['target'])) { $target = filter_expand_alias($rule['target']); } else if (is_ipaddr($rule['target'])) { $target = $rule['target']; } else if (is_ipaddr($FilterIflist[$rule['target']]['ip'])) { $target = $FilterIflist[$rule['target']]['ip']; } else { return ""; } $starting_localhost_port_tmp = $starting_localhost_port; $toomanyports = false; /* only install reflection rules for < 19991 items */ foreach ($dstport as $loc_pt) { if ($starting_localhost_port < 19991) { $toadd_array = array(); $inetdport = $starting_localhost_port; $rflctrange = $starting_localhost_port; $loc_pt = explode(":", $loc_pt); if ($loc_pt[1] && $loc_pt[1] > $loc_pt[0]) { $delta = $loc_pt[1] - $loc_pt[0]; } else { $delta = 0; } if (($inetdport + $delta + 1) - $starting_localhost_port_tmp > 500) { log_error(gettext("Not installing NAT reflection rules for a port range > 500")); $inetdport = $starting_localhost_port; $toadd_array = array(); $toomanyports = true; break; } else if (($inetdport + $delta) > 19990) { log_error(gettext("Installing partial NAT reflection rules. Maximum 1,000 reached.")); $delta = 19990 - $inetdport; $loc_pt[1] = $loc_pt[0] + $delta; if ($delta == 0) { unset($loc_pt[1]); } $toomanyports = true; if (!empty($localport)) { if (is_alias($rule['destination']['port'])) { $rflctintrange = alias_expand($rule['destination']['port']); } else { if ($dstport_split[1]) { $dstport_split[1] = $dstport_split[0] + $inetdport + $delta - $starting_localhost_port; } $rflctintrange = implode(":", $dstport_split); } } } if (empty($localport)) { $rflctintrange = implode(":", $loc_pt); } if ($inetdport + $delta > $starting_localhost_port) { $rflctrange .= ":" . ($inetdport + $delta); } $starting_localhost_port = $inetdport + $delta + 1; $toadd_array = array_merge($toadd_array, range($loc_pt[0], $loc_pt[0] + $delta)); if (!empty($toadd_array)) { $rtarget = explode(" ", trim($target)); foreach ($toadd_array as $tda) { if (empty($tda)) { continue; } foreach ($reflect_protos as $reflect_proto) { if ($reflect_proto == "udp") { $socktype = "dgram"; $dash_u = "-u "; $wait = "yes"; } else { $socktype = "stream"; $dash_u = ""; $wait = "no"; } foreach ($rtarget as $targip) { if (empty($targip)) { continue; } $reflection_rule = array( 'port' => $inetdport, 'socket_type' => $socktype, 'protocol' => $reflect_proto, 'wait' => $wait, 'user' => 'nobody', 'server' => '/usr/bin/nc', 'server_args' => "{$dash_u}-w {$reflectiontimeout} {$targip} {$tda}" ); $reflection_rules[] = $reflection_rule; unset($reflection_rule); } } $inetdport++; } $natrules .= "rdr on {$rdr_if_list} proto {$protocol} from {$srcaddr} to {$dstaddr} port {$rflctintrange} tag PFREFLECT -> 127.0.0.1 port {$rflctrange}\n"; } } if ($toomanyports) { break; } } } return $natrules; } function filter_nat_rules_automatic_tonathosts($with_descr = false) { global $config, $FilterIflist, $GatewaysList; $tonathosts = array("127.0.0.0/8"); $descriptions = array(gettext("localhost")); // Add any enabled static routes foreach (get_staticroutes(false, false, true) as $route) { $netip = explode("/", $route['network']); if (isset($GatewaysList[$route['gateway']])) { $gateway =& $GatewaysList[$route['gateway']]; if (!interface_has_gateway($gateway['interface']) && is_private_ip($netip[0])) { $tonathosts[] = $route['network']; $descriptions[] = gettext("static route"); } } } /* create outbound nat entries for all local networks */ foreach ($FilterIflist as $ocname => $oc) { if (interface_has_gateway($ocname)) { continue; } if (is_ipaddr($oc['alias-address'])) { $tonathosts[] = "{$oc['alias-address']}/{$oc['alias-subnet']}"; $descriptions[] = $oc['descr'] . " " . gettext("DHCP alias address"); } if ($oc['sa']) { $tonathosts[] = "{$oc['sa']}/{$oc['sn']}"; $descriptions[] = $oc['descr']; if (isset($oc['vips']) && is_array($oc['vips'])) { $if_subnets = array("{$oc['sa']}/{$oc['sn']}"); foreach ($oc['vips'] as $vip) { if (!is_ipaddrv4($vip['ip'])) { continue; } foreach ($if_subnets as $subnet) { if (ip_in_subnet($vip['ip'], $subnet)) { continue 2; } } $network = gen_subnet($vip['ip'], $vip['sn']); array_unshift($tonathosts, $network . '/' . $vip['sn']); array_unshift($descriptions, "Virtual IP ({$oc['descr']})"); $if_subnets[] = $network . '/' . $vip['sn']; unset($network); } unset($if_subnets); } } } /* PPPoE subnet */ if (is_array($FilterIflist['pppoe'])) { foreach ($FilterIflist['pppoe'] as $pppoe) { if (is_private_ip($pppoe['ip'])) { $tonathosts[] = "{$pppoe['sa']}/{$pppoe['sn']}"; $descriptions[] = gettext("PPPoE server"); } } } /* L2TP subnet */ if (isset($FilterIflist['l2tp']) && $FilterIflist['l2tp']['mode'] == "server") { $l2tp_sa = $FilterIflist['l2tp']['sa']; $l2tp_sn = $FilterIflist['l2tp']['sn']; if (is_private_ip($l2tp_sa) && !empty($l2tp_sn)) { $tonathosts[] = "{$l2tp_sa}/{$l2tp_sn}"; $descriptions[] = gettext("L2TP server"); } } /* add openvpn interfaces */ if (is_array($config['openvpn']['openvpn-server'])) { foreach ($config['openvpn']['openvpn-server'] as $ovpnsrv) { if (!isset($ovpnsrv['disable']) && !empty($ovpnsrv['tunnel_network'])) { $tonathosts[] = $ovpnsrv['tunnel_network']; $descriptions[] = gettext("OpenVPN server"); } } } if (is_array($config['openvpn']['openvpn-client'])) { foreach ($config['openvpn']['openvpn-client'] as $ovpncli) { if (!isset($ovpncli['disable']) && !empty($ovpncli['tunnel_network'])) { $tonathosts[] = $ovpncli['tunnel_network']; $descriptions[] = gettext("OpenVPN client"); } } } /* IPsec mode_cfg subnet */ if ((isset($config['ipsec']['client']['enable'])) && (!empty($config['ipsec']['client']['pool_address'])) && (!empty($config['ipsec']['client']['pool_netbits']))) { $tonathosts[] = "{$config['ipsec']['client']['pool_address']}/{$config['ipsec']['client']['pool_netbits']}"; $descriptions[] = gettext("IPsec client"); } if ($with_descr) { $combined = array(); foreach ($tonathosts as $idx => $subnet) { $combined[] = array( "subnet" => $subnet, "descr" => $descriptions[$idx]); } return $combined; } else { return $tonathosts; } } function filter_nat_rules_outbound_automatic($src) { global $config, $FilterIflist; $rules = array(); foreach ($FilterIflist as $if => $ifcfg) { if (substr($ifcfg['if'], 0, 4) == "ovpn") { continue; } if (!interface_has_gateway($if)) { continue; } $natent = array(); $natent['interface'] = $if; $natent['source']['network'] = $src; $natent['dstport'] = "500"; $natent['target'] = ""; $natent['destination']['any'] = true; $natent['staticnatport'] = true; $natent['descr'] = gettext('Auto created rule for ISAKMP'); $rules[] = $natent; $natent = array(); $natent['interface'] = $if; $natent['source']['network'] = $src; $natent['sourceport'] = ""; $natent['target'] = ""; $natent['destination']['any'] = true; $natent['natport'] = ""; $natent['descr'] = gettext('Auto created rule'); if (isset($ifcfg['nonat'])) { $natent['nonat'] = true; } $rules[] = $natent; } return $rules; } /* Generate a 'nat on' or 'no nat on' rule for given interface */ function filter_nat_rules_generate_if ($if, $src = "any", $srcport = "", $dst = "any", $dstport = "", $natip = "", $natport = "", $nonat = false, $staticnatport = false, $proto = "", $poolopts = "") { global $config, $FilterIflist; /* XXX: billm - any idea if this code is needed? */ if ($src == "/32" || $src{0} == "/") { return "# src incorrectly specified\n"; } if ($natip != "") { if (is_subnet($natip)) { $tgt = $natip; } elseif (is_alias($natip)) { $tgt = "\${$natip}"; } else { $tgt = "{$natip}/32"; } } else { $natip = get_interface_ip($if); if (is_ipaddr($natip)) { $tgt = "{$natip}/32"; } else { $tgt = "(" . $FilterIflist[$if]['if'] . ")"; } } /* Add the protocol, if defined */ if (!empty($proto) && $proto != "any") { if ($proto == "tcp/udp") { $protocol = " proto { tcp udp }"; } else { $protocol = " proto {$proto}"; } } else { $protocol = ""; } /* Set tgt for IPv6 */ if ($proto == "ipv6") { $natip = get_interface_ipv6($if); if (is_ipaddrv6($natip)) { $tgt = "{$natip}/128"; } } /* Add the hard set source port (useful for ISAKMP) */ if ($natport != "") { $tgt .= " port {$natport}"; } /* sometimes this gets called with "" instead of a value */ if ($src == "") { $src = "any"; } /* Match on this source port */ if ($srcport != "") { $srcportexpand = alias_expand($srcport); if (!$srcportexpand) { $srcportexpand = $srcport; } $src .= " port {$srcportexpand}"; } /* sometimes this gets called with "" instead of a value */ if ($dst == "") { $dst = "any"; } /* Match on this dest port */ if ($dstport != "") { $dstportexpand = alias_expand($dstport); if (!$dstportexpand) { $dstportexpand = $dstport; } $dst .= " port {$dstportexpand}"; } /* outgoing static-port option, hamachi, Grandstream, VOIP, etc */ $staticnatport_txt = ""; if ($staticnatport) { $staticnatport_txt = "static-port"; } elseif (!$natport) { $tgt .= " port 1024:65535"; // set source port range } /* Allow for negating NAT entries */ if ($nonat) { $nat = "no nat"; $target = ""; $staticnatport_txt = ""; $poolopts = ""; } else { $nat = "nat"; $target = "-> {$tgt}"; } $if_friendly = $FilterIflist[$if]['descr']; /* Put all the pieces together */ if ($if_friendly) { $natrule = "{$nat} on \${$if_friendly} {$protocol} from {$src} to {$dst} {$target} {$poolopts} {$staticnatport_txt}\n"; } else { $natrule .= "# Could not convert {$if} to friendly name(alias)\n"; } return $natrule; } function xinetd_service_entry($entry_array) { $entry = << 0) { update_filter_reload_status(gettext('Creating automatic outbound rules')); if ($numberofnathosts > 4) { $natrules .= "table { {$tonathosts} }\n"; $macroortable = ""; } else { $natrules .= "tonatsubnets = \"{ {$tonathosts} }\"\n"; $macroortable = "\$tonatsubnets"; } $a_outs = filter_nat_rules_outbound_automatic($macroortable); foreach ($a_outs as $a_out) { $natrules .= filter_nat_rules_generate_if($a_out['interface'], $a_out['source']['network'], $a_out['sourceport'], $a_out['destination']['address'], $a_out['dstport'], $a_out['target'], $a_out['natport'], isset($a_out['nonat']), isset($a_out['staticnatport'])); } } unset($tonathosts, $tonathosts_array, $numberofnathosts); } /* load balancer anchor */ $natrules .= "\n# Load balancing anchor\n"; $natrules .= "rdr-anchor \"relayd/*\"\n"; update_filter_reload_status(gettext("Setting up TFTP helper")); $natrules .= "# TFTP proxy\n"; $natrules .= "rdr-anchor \"tftp-proxy/*\"\n"; if (!empty($config['system']['tftpinterface'])) { $tftpifs = explode(",", $config['system']['tftpinterface']); foreach ($tftpifs as $tftpif) { if ($FilterIflist[$tftpif]) { $natrules .= "rdr pass on {$FilterIflist[$tftpif]['if']} proto udp from any to any port tftp -> 127.0.0.1 port 6969\n"; } } } /* DIAG: add ipv6 NAT, if requested */ if ((isset($config['diag']['ipv6nat']['enable'])) && (is_ipaddr($config['diag']['ipv6nat']['ipaddr'])) && (is_array($FilterIflist['wan']))) { /* XXX: FIX ME! IPV6 */ $natrules .= "rdr on \${$FilterIflist['wan']['descr']} proto ipv6 from any to any -> {$config['diag']['ipv6nat']['ipaddr']}\n"; } unlink_if_exists("{$g['varetc_path']}/xinetd.conf"); // Open xinetd.conf write handle $xinetd_fd = fopen("{$g['varetc_path']}/xinetd.conf", "w"); if (!empty($config['system']['tftpinterface'])) { /* add tftp helper */ $ftp_proxy_entry = array( 'port' => 6969, 'socket_type' => 'dgram', 'protocol' => 'udp', 'wait' => 'yes', 'user' => 'root', 'server' => '/usr/libexec/tftp-proxy', 'server_args' => '-v' ); fwrite($xinetd_fd, xinetd_service_entry($ftp_proxy_entry)); } if (isset($config['nat']['rule'])) { /* start reflection redirects on port 19000 of localhost */ $starting_localhost_port = 19000; $natrules .= "# NAT Inbound Redirects\n"; foreach ($config['nat']['rule'] as $rule) { update_filter_reload_status(sprintf(gettext("Creating NAT rule %s"), $rule['descr'])); if (isset($rule['disabled'])) { continue; } /* if item is an alias, expand */ $dstport = ""; $dstport[0] = alias_expand($rule['destination']['port']); if (!$dstport[0]) { $dstport = explode("-", $rule['destination']['port']); } /* if item is an alias, expand */ $localport = alias_expand($rule['local-port']); if (!$localport || $dstport[0] == $localport) { $localport = ""; } else if (is_alias($rule['local-port'])) { $localport = filter_expand_alias($rule['local-port']); if ($localport) { $localport = explode(" ", trim($localport)); $localport = $localport[0]; $localport = " port {$localport}"; } } else if (is_alias($rule['destination']['port'])) { $localport = " port {$localport}"; } else { if (($dstport[1]) && ($dstport[0] != $dstport[1])) { $localendport = $localport + ($dstport[1] - $dstport[0]); $localport .= ":$localendport"; } $localport = " port {$localport}"; } switch (strtolower($rule['protocol'])) { case "tcp/udp": $protocol = "{ tcp udp }"; break; case "tcp": case "udp": $protocol = strtolower($rule['protocol']); break; default: $protocol = strtolower($rule['protocol']); $localport = ""; break; } $target = alias_expand($rule['target']); if (!$target && !isset($rule['nordr'])) { $natrules .= "# Unresolvable alias {$rule['target']}\n"; continue; /* unresolvable alias */ } if (is_alias($rule['target'])) { $target_ip = filter_expand_alias($rule['target']); } else if (is_ipaddr($rule['target'])) { $target_ip = $rule['target']; } else if (is_ipaddr($FilterIflist[$rule['target']]['ip'])) { $target_ip = $FilterIflist[$rule['target']]['ip']; } else { $target_ip = $rule['target']; } $target_ip = trim($target_ip); if ($rule['associated-rule-id'] == "pass") { $rdrpass = "pass "; } else { $rdrpass = ""; } if (isset($rule['nordr'])) { $nordr = "no "; $rdrpass = ""; } else { $nordr = ""; } if (!$rule['interface']) { $natif = "wan"; } else { $natif = $rule['interface']; } if (!isset($FilterIflist[$natif])) { continue; } $srcaddr = filter_generate_address($rule, 'source', true); $dstaddr = filter_generate_address($rule, 'destination', true); $srcaddr = trim($srcaddr); $dstaddr = trim($dstaddr); if (!$dstaddr) { $dstaddr = $FilterIflist[$natif]['ip']; } $dstaddr_port = explode(" ", $dstaddr); if (empty($dstaddr_port[0]) || strtolower(trim($dstaddr_port[0])) == "port") { continue; // Skip port forward if no destination address found } $dstaddr_reflect = $dstaddr; if (isset($rule['destination']['any'])) { /* With reflection enabled, destination of 'any' has side effects * that most people would not expect, so change it on reflection rules. */ if (!empty($FilterIflist[$natif]['ip'])) { $dstaddr_reflect = $FilterIflist[$natif]['ip']; } else { // no IP, bail continue; } if (!empty($FilterIflist[$natif]['sn'])) { $dstaddr_reflect = gen_subnet($dstaddr_reflect, $FilterIflist[$natif]['sn']) . '/' . $FilterIflist[$natif]['sn']; } if ($dstaddr_port[2]) { $dstaddr_reflect .= " port " . $dstaddr_port[2]; } } $natif = $FilterIflist[$natif]['if']; $reflection_type = "none"; if ($rule['natreflection'] != "disable" && $dstaddr_port[0] != "0.0.0.0") { if ($rule['natreflection'] == "enable") { $reflection_type = "proxy"; } else if ($rule['natreflection'] == "purenat") { $reflection_type = "purenat"; } else if (!isset($config['system']['disablenatreflection'])) { if (isset($config['system']['enablenatreflectionpurenat'])) { $reflection_type = "purenat"; } else { $reflection_type = "proxy"; } } } if ($reflection_type != "none") { $nat_if_list = filter_get_reflection_interfaces($natif); } else { $nat_if_list = array(); } if (empty($nat_if_list)) { $reflection_type = "none"; } $localport_nat = $localport; if (empty($localport_nat) && $dstaddr_port[2]) { $localport_nat = " port " . $dstaddr_port[2]; } if ($srcaddr <> "" && $dstaddr <> "" && $natif) { $natrules .= "{$nordr}rdr {$rdrpass}on {$natif} proto {$protocol} from {$srcaddr} to {$dstaddr}" . ($nordr == "" ? " -> {$target}{$localport}" : ""); /* Does this rule redirect back to a internal host? */ if (isset($rule['destination']['any']) && !isset($rule['nordr']) && !isset($config['system']['enablenatreflectionhelper']) && !interface_has_gateway($rule['interface'])) { $rule_interface_ip = find_interface_ip($natif); $rule_interface_subnet = find_interface_subnet($natif); if (!empty($rule_interface_ip) && !empty($rule_interface_subnet)) { $rule_subnet = gen_subnet($rule_interface_ip, $rule_interface_subnet); $natrules .= "\n"; $natrules .= "no nat on {$natif} proto tcp from ({$natif}) to {$rule_subnet}/{$rule_interface_subnet}\n"; $natrules .= "nat on {$natif} proto tcp from {$rule_subnet}/{$rule_interface_subnet} to {$target} port {$dstport[0]} -> ({$natif})\n"; } } if ($reflection_type != "none") { if ($reflection_type == "proxy" && !isset($rule['nordr'])) { $natrules .= filter_generate_reflection_proxy($rule, $nordr, $nat_if_list, $srcaddr, $dstaddr, $starting_localhost_port, $reflection_rules); $nat_if_list = array($natif); foreach ($reflection_rules as $reflection_rule) { fwrite($xinetd_fd, xinetd_service_entry($reflection_rule)); } } else if ($reflection_type == "purenat" || isset($rule['nordr'])) { $rdr_if_list = implode(" ", $nat_if_list); if (count($nat_if_list) > 1) { $rdr_if_list = "{ {$rdr_if_list} }"; } $natrules .= "\n# Reflection redirect\n"; $natrules .= "{$nordr}rdr {$rdrpass}on {$rdr_if_list} proto {$protocol} from {$srcaddr} to {$dstaddr_reflect}" . ($nordr == "" ? " -> {$target}{$localport}" : ""); $nat_if_list = array_merge(array($natif), $nat_if_list); } } if (empty($nat_if_list)) { $nat_if_list = array($natif); } $natrules .= "\n"; if (!isset($rule['nordr'])) { $natrules .= filter_generate_reflection_nat($rule, $route_table, $nat_if_list, $protocol, "{$target}{$localport_nat}", $target_ip); } } } } fclose($xinetd_fd); // Close file handle $natrules .= discover_pkg_rules("nat"); $natrules .= "# UPnPd rdr anchor\n"; $natrules .= "rdr-anchor \"miniupnpd\"\n"; if (!empty($reflection_txt)) { $natrules .= "\n# Reflection redirects and NAT for 1:1 mappings\n" . $reflection_txt; } // Check if xinetd is running, if not start it. If so, restart it gracefully. if (file_exists("{$g['varetc_path']}/xinetd.conf") && (filesize("{$g['varetc_path']}/xinetd.conf") > 0)) { if (isvalidpid("{$g['varrun_path']}/xinetd.pid")) { sigkillbypid("{$g['varrun_path']}/xinetd.pid", "HUP"); } else { mwexec("/usr/local/sbin/xinetd " . "-syslog daemon " . "-f {$g['varetc_path']}/xinetd.conf " . "-pidfile {$g['varrun_path']}/xinetd.pid"); } } elseif (isvalidpid("{$g['varrun_path']}/xinetd.pid")) { killbypid("{$g['varrun_path']}/xinetd.pid"); } return $natrules; } function filter_generate_user_rule_arr($rule) { global $config; update_filter_reload_status(sprintf(gettext("Creating filter rule %s ..."), $rule['descr'])); $ret = array(); $line = filter_generate_user_rule($rule); $ret['rule'] = $line; $ret['interface'] = $rule['interface']; if ($rule['descr'] != "" and $line != "") { $ret['descr'] = "label \"" . fix_rule_label(USER_LABEL_INTRO . "{$rule['descr']}") . "\""; } else { $ret['descr'] = "label \"USER_RULE\""; } return $ret; } function filter_generate_port(& $rule, $target = "source", $isnat = false) { $src = ""; $rule['protocol'] = strtolower($rule['protocol']); if (in_array($rule['protocol'], array("tcp", "udp", "tcp/udp"))) { if ($rule[$target]['port']) { $srcport = explode("-", $rule[$target]['port']); $srcporta = alias_expand($srcport[0]); if (!$srcporta) { log_error(sprintf(gettext('filter_generate_port: %1$s is not a valid %2$s port.'), $srcport[0], $target)); } else if ((!$srcport[1]) || ($srcport[0] == $srcport[1])) { $src .= " port {$srcporta} "; } else if (($srcport[0] == 1) && ($srcport[1] == 65535)) { /* no need for a port statement here */ } else if ($isnat) { $src .= " port {$srcport[0]}:{$srcport[1]}"; } else { if (is_port($srcporta) && $srcport[1] == 65535) { $src .= " port >= {$srcporta} "; } else if ($srcport[0] == 1) { $src .= " port <= {$srcport[1]} "; } else { $srcport[0]--; $srcport[1]++; $src .= " port {$srcport[0]} >< {$srcport[1]} "; } } } } return $src; } function filter_address_add_vips_subnets(&$subnets, $if, $not) { global $FilterIflist; $if_subnets = array($subnets); if ($not == true) { $subnets = "!{$subnets}"; } if (!isset($FilterIflist[$if]['vips']) || !is_array($FilterIflist[$if]['vips'])) { return; } foreach ($FilterIflist[$if]['vips'] as $vip) { foreach ($if_subnets as $subnet) { if (ip_in_subnet($vip['ip'], $subnet)) { continue 2; } } if (is_ipaddrv4($vip['ip'])) { if (!is_subnetv4($if_subnets[0])) { continue; } $network = gen_subnet($vip['ip'], $vip['sn']); } else if (is_ipaddrv6($vip['ip'])) { if (!is_subnetv6($if_subnets[0])) { continue; } $network = gen_subnetv6($vip['ip'], $vip['sn']); } else { continue; } $subnets .= ' ' . ($not == true ? '!' : '') . $network . '/' . $vip['sn']; $if_subnets[] = $network . '/' . $vip['sn']; } unset($if_subnets); if (strpos($subnets, ' ') !== false) { $subnets = "{ {$subnets} }"; } } function filter_generate_address(& $rule, $target = "source", $isnat = false) { global $FilterIflist, $config; $src = ""; if (isset($rule[$target]['any'])) { $src = "any"; } else if ($rule[$target]['network']) { if (strstr($rule[$target]['network'], "opt")) { $optmatch = ""; $matches = ""; if ($rule['ipprotocol'] == "inet6") { if (preg_match("/opt([0-9]*)$/", $rule[$target]['network'], $optmatch)) { $opt_sa = $FilterIflist["opt{$optmatch[1]}"]['sav6']; if (!is_ipaddrv6($opt_sa)) { return ""; } $src = $opt_sa . "/" . $FilterIflist["opt{$optmatch[1]}"]['snv6']; /* check for opt$NUMip here */ } else if (preg_match("/opt([0-9]*)ip/", $rule[$target]['network'], $matches)) { $src = $FilterIflist["opt{$matches[1]}"]['ipv6']; if (!is_ipaddrv6($src)) { return ""; } if (isset($rule[$target]['not'])) { $src = " !{$src}"; } } } else { if (preg_match("/opt([0-9]*)$/", $rule[$target]['network'], $optmatch)) { $opt_sa = $FilterIflist["opt{$optmatch[1]}"]['sa']; if (!is_ipaddrv4($opt_sa)) { return ""; } $src = $opt_sa . "/" . $FilterIflist["opt{$optmatch[1]}"]['sn']; /* check for opt$NUMip here */ } else if (preg_match("/opt([0-9]*)ip/", $rule[$target]['network'], $matches)) { $src = $FilterIflist["opt{$matches[1]}"]['ip']; if (!is_ipaddrv4($src)) { return ""; } if (isset($rule[$target]['not'])) { $src = " !{$src}"; } } } } else { if ($rule['ipprotocol'] == "inet6") { switch ($rule[$target]['network']) { case 'wan': $wansa = $FilterIflist['wan']['sav6']; if (!is_ipaddrv6($wansa)) { return ""; } $wansn = $FilterIflist['wan']['snv6']; $src = "{$wansa}/{$wansn}"; break; case 'wanip': $src = $FilterIflist["wan"]['ipv6']; if (!is_ipaddrv6($src)) { return ""; } break; case 'lanip': $src = $FilterIflist["lan"]['ipv6']; if (!is_ipaddrv6($src)) { return ""; } break; case 'lan': $lansa = $FilterIflist['lan']['sav6']; if (!is_ipaddrv6($lansa)) { return ""; } $lansn = $FilterIflist['lan']['snv6']; $src = "{$lansa}/{$lansn}"; break; case '(self)': $src = "(self)"; break; case 'pppoe': /* XXX: This needs to be fixed somehow! */ if (is_array($FilterIflist['pppoe'])) { $pppoesav6 = gen_subnetv6($FilterIflist['pppoe'][0]['ipv6'], $FilterIflist['pppoe'][0]['snv6']); $pppoesnv6 = $FilterIflist['pppoe'][0]['snv6']; $src = "{$pppoesav6}/{$pppoesnv6}"; } } if (isset($rule[$target]['not']) && !is_subnet($src)) { $src = " !{$src}"; } } else { switch ($rule[$target]['network']) { case 'wan': $wansa = $FilterIflist['wan']['sa']; if (!is_ipaddrv4($wansa)) { return ""; } $wansn = $FilterIflist['wan']['sn']; $src = "{$wansa}/{$wansn}"; break; case 'wanip': $src = $FilterIflist["wan"]['ip']; break; case 'lanip': $src = $FilterIflist["lan"]['ip']; break; case 'lan': $lansa = $FilterIflist['lan']['sa']; if (!is_ipaddrv4($lansa)) { return ""; } $lansn = $FilterIflist['lan']['sn']; $src = "{$lansa}/{$lansn}"; break; case '(self)': $src = "(self)"; break; case 'pppoe': /* XXX: This needs to be fixed somehow! */ if (is_array($FilterIflist['pppoe'])) { $pppoesa = gen_subnet($FilterIflist['pppoe'][0]['sa'], $FilterIflist['pppoe'][0]['sn']); $pppoesn = $FilterIflist['pppoe'][0]['sn']; $src = "{$pppoesa}/{$pppoesn}"; } break; } if ((isset($rule[$target]['not'])) && (!is_subnet($src)) && (strpos($src, '{') === false)) { $src = " !{$src}"; } } } if (is_subnet($src)) { filter_address_add_vips_subnets($src, $rule[$target]['network'], isset($rule[$target]['not'])); } } else if ($rule[$target]['address']) { $expsrc = alias_expand($rule[$target]['address']); if (isset($rule[$target]['not'])) { $not = "!"; } else { $not = ""; } $src = " {$not} {$expsrc}"; } if (empty($src)) { return ''; } $src .= filter_generate_port($rule, $target, $isnat); return $src; } function filter_generate_user_rule($rule) { global $config, $g, $FilterIflist, $GatewaysList; global $dummynet_name_list; if (isset($config['system']['developerspew'])) { $mt = microtime(); echo "filter_generate_user_rule() being called $mt\n"; } /* don't include disabled rules */ if (isset($rule['disabled'])) { return "# rule " . $rule['descr'] . " disabled \n"; } update_filter_reload_status(sprintf(gettext("Creating filter rules %s ..."), $rule['descr'])); $int = ""; $aline = array(); /* Check to see if the interface is in our list */ if (isset($rule['floating'])) { if (isset($rule['interface']) && $rule['interface'] <> "") { $interfaces = explode(",", $rule['interface']); $ifliste = ""; foreach ($interfaces as $iface) { if (array_key_exists($iface, $FilterIflist)) { if (isset($FilterIflist[$iface]['if'])) { $ifliste .= " " . $FilterIflist[$iface]['if'] . " "; } else if (isset($FilterIflist[$iface][0]['if'])) { $ifliste .= " " . $FilterIflist[$iface][0]['if'] . " "; } } } if ($ifliste <> "") { $aline['interface'] = " on { {$ifliste} } "; } else { $aline['interface'] = ""; } } else { $aline['interface'] = ""; } } else if (!array_key_exists($rule['interface'], $FilterIflist)) { foreach ($FilterIflist as $oc) { $items .= $oc['descr'] . " "; } return "# array key \"{$rule['interface']}\" does not exist for \"" . $rule['descr'] . "\" in array: {{$items}}"; } else if ((array_key_exists($rule['interface'], $FilterIflist)) && (is_array($FilterIflist[$rule['interface']])) && (is_array($FilterIflist[$rule['interface']][0]))) { /* Currently the only case for this is the pppoe server. There should be an existing macro with this name. */ $aline['interface'] = " on \$" . $rule['interface'] . " "; } else { $aline['interface'] = " on \$" . $FilterIflist[$rule['interface']]['descr'] . " "; } $ifcfg = $FilterIflist[$rule['interface']]; switch ($rule['ipprotocol']) { case "inet": $aline['ipprotocol'] = "inet"; break; case "inet6": $aline['ipprotocol'] = "inet6"; break; default: $aline['ipprotocol'] = "inet"; break; } /* check for unresolvable aliases */ if ($rule['source']['address'] && !alias_expand($rule['source']['address'])) { $error_text = sprintf(gettext("Unresolvable source alias '%1\$s' for rule '%2\$s'"), $rule['source']['address'], $rule['descr']); file_notice("Filter_Reload", $error_text); return "# {$error_text}"; } if ($rule['destination']['address'] && !alias_expand($rule['destination']['address'])) { $error_text = sprintf(gettext("Unresolvable destination alias '%1\$s' for rule '%2\$s'"), $rule['destination']['address'], $rule['descr']); file_notice("Filter_Reload", $error_text); return "# {$error_text}"; } if ($rule['source']['port'] && !is_port_or_range(str_replace("-", ":", $rule['source']['port']))) { $error_text = ""; // It is not a literal port or port range, so alias should exist, and expand to something non-empty if (!alias_expand($rule['source']['port'])) { $error_text = sprintf(gettext("Unresolvable source port alias '%1\$s' for rule '%2\$s'"), $rule['source']['port'], $rule['descr']); } else if (trim(filter_generate_nested_alias($rule['source']['port'])) == "") { $error_text = sprintf(gettext("Empty source port alias '%1\$s' for rule '%2\$s'"), $rule['source']['port'], $rule['descr']); } if ($error_text) { file_notice("Filter_Reload", $error_text); return "# {$error_text}"; } } if ($rule['destination']['port'] && !is_port_or_range(str_replace("-", ":", $rule['destination']['port']))) { $error_text = ""; // It is not a literal port or port range, so alias should exist, and expand to something non-empty if (!alias_expand($rule['destination']['port'])) { $error_text = sprintf(gettext("Unresolvable destination port alias '%1\$s' for rule '%2\$s'"), $rule['destination']['port'], $rule['descr']); } else if (trim(filter_generate_nested_alias($rule['destination']['port'])) == "") { $error_text = sprintf(gettext("Empty destination port alias '%1\$s' for rule '%2\$s'"), $rule['destination']['port'], $rule['descr']); } if ($error_text) { file_notice("Filter_Reload", $error_text); return "# {$error_text}"; } } update_filter_reload_status(gettext("Setting up pass/block rules")); $type = $rule['type']; if ($type != "pass" && $type != "block" && $type != "reject" && $type != "match") { /* default (for older rules) is pass */ $type = "pass"; } if ($type == "reject") { $aline['type'] = "block return "; } else { $aline['type'] = $type . " "; } if (isset($rule['floating']) && $rule['floating'] == "yes") { if ($rule['direction'] != "any") { $aline['direction'] = " " . $rule['direction'] . " "; } } else { /* ensure the direction is in */ $aline['direction'] = " in "; } if (isset($rule['log'])) { $aline['log'] = "log "; } if (!isset($rule['floating']) || isset($rule['quick'])) { $aline['quick'] = " quick "; } /* set the gateway interface */ update_filter_reload_status(sprintf(gettext("Setting up pass/block rules %s"), $rule['descr'])); /* do not process reply-to for gateway'd rules */ if ($rule['gateway'] == "" && $aline['direction'] <> "" && (interface_has_gateway($rule['interface']) || interface_has_gatewayv6($rule['interface'])) && !isset($config['system']['disablereplyto']) && !isset($rule['disablereplyto']) && $type != "match") { if ($rule['ipprotocol'] == "inet6") { $rg = get_interface_gateway_v6($rule['interface']); if (is_ipaddrv6($rg)) { $aline['reply'] = "reply-to ( {$ifcfg['ifv6']} {$rg} ) "; } } else { $rg = get_interface_gateway($rule['interface']); if (is_ipaddrv4($rg)) { $aline['reply'] = "reply-to ( {$ifcfg['if']} {$rg} ) "; } } } /* if user has selected a custom gateway, lets work with it */ else if ($rule['gateway'] <> "" && $type == "pass") { if (isset($GatewaysList[$rule['gateway']])) { /* Add the load balanced gateways */ $aline['route'] = " \$GW{$rule['gateway']} "; } else if (isset($config['system']['skip_rules_gw_down'])) { return "# rule " . $rule['descr'] . " disabled because gateway " . $rule['gateway'] . " is down "; } else { log_error(sprintf(gettext("The gateway: %s is invalid or unknown, not using it."), $rule['gateway'])); } } if (isset($rule['protocol']) && !empty($rule['protocol'])) { if ($rule['protocol'] == "tcp/udp") { $aline['prot'] = " proto { tcp udp } "; } elseif (($rule['protocol'] == "icmp") && ($rule['ipprotocol'] == "inet6")) { $aline['prot'] = " proto ipv6-icmp "; } elseif ($rule['protocol'] == "icmp") { $aline['prot'] = " proto icmp "; } else { $aline['prot'] = " proto {$rule['protocol']} "; } } else { if ($rule['source']['port'] <> "" || $rule['destination']['port'] <> "") { $aline['prot'] = " proto tcp "; } } update_filter_reload_status(sprintf(gettext("Creating rule %s"), $rule['descr'])); /* source address */ $src = trim(filter_generate_address($rule, "source")); if (empty($src) || ($src == "/")) { return "# at the break!"; } $aline['src'] = " from $src "; /* OS signatures */ if (($rule['protocol'] == "tcp") && ($rule['os'] <> "")) { $aline['os'] = " os \"{$rule['os']}\" "; } /* destination address */ $dst = trim(filter_generate_address($rule, "destination")); if (empty($dst) || ($dst == "/")) { return "# returning at dst $dst == \"/\""; } $aline['dst'] = "to $dst "; if ($rule['protocol'] == "icmp" && $rule['icmptype'] && ($rule['icmptype'] != 'any')) { $icmptype_key = ($rule['ipprotocol'] == 'inet6' ? 'icmp6-type' : 'icmp-type'); // XXX: Bug #7372 $icmptype_text = replace_element_in_list($rule['icmptype'], ',', 'skip', '39'); $icmptype_text = (strpos($icmptype_text, ",") === false ? $icmptype_text : '{ ' . $icmptype_text . ' }'); $aline[$icmptype_key] = "{$icmptype_key} {$icmptype_text} "; } if (!empty($rule['tag'])) { $aline['tag'] = " tag \"" .$rule['tag']. "\" "; } if (!empty($rule['tagged'])) { $aline['tagged'] = " tagged \"" .$rule['tagged']. "\" "; } if (!empty($rule['dscp'])) { switch (strtolower($rule['dscp'])) { case 'va': $aline['dscp'] = " dscp \"44\" "; break; case 'VA': $aline['dscp'] = " dscp \"44\" "; break; case 'cs1': $aline['dscp'] = " dscp \"8\" "; break; case 'cs2': $aline['dscp'] = " dscp \"16\" "; break; case 'cs3': $aline['dscp'] = " dscp \"24\" "; break; case 'cs4': $aline['dscp'] = " dscp \"32\" "; break; case 'cs5': $aline['dscp'] = " dscp \"40\" "; break; case 'cs6': $aline['dscp'] = " dscp \"48\" "; break; case 'cs7': $aline['dscp'] = " dscp \"56\" "; break; default: $aline['dscp'] = " dscp " . $rule['dscp'] . " "; break; } } if (!empty($rule['vlanprio']) && ($rule['vlanprio'] != "none")) { $aline['vlanprio'] = " ieee8021q-pcp " . $rule['vlanprio'] . " "; } if (!empty($rule['vlanprioset']) && ($rule['vlanprioset'] != "none")) { $aline['vlanprioset'] = " ieee8021q-setpcp " . $rule['vlanprioset'] . " "; } if ($type == "pass") { if (isset($rule['allowopts'])) { $aline['allowopts'] = " allow-opts "; } } $aline['flags'] = ""; if ($rule['protocol'] == "tcp") { if (isset($rule['tcpflags_any'])) { $aline['flags'] = "flags any "; } else if (!empty($rule['tcpflags2'])) { $aline['flags'] = "flags "; if (!empty($rule['tcpflags1'])) { $flags1 = explode(",", $rule['tcpflags1']); foreach ($flags1 as $flag1) { // CWR flag needs special treatment if ($flag1[0] == "c") { $aline['flags'] .= "W"; } else { $aline['flags'] .= strtoupper($flag1[0]); } } } $aline['flags'] .= "/"; if (!empty($rule['tcpflags2'])) { $flags2 = explode(",", $rule['tcpflags2']); foreach ($flags2 as $flag2) { // CWR flag needs special treatment if ($flag2[0] == "c") { $aline['flags'] .= "W"; } else { $aline['flags'] .= strtoupper($flag2[0]); } } } $aline['flags'] .= " "; } else { $aline['flags'] = "flags S/SA "; } } if ($type == "pass") { /* * # keep state * works with TCP, UDP, and ICMP. * # modulate state * deprecated * # synproxy state * proxies incoming TCP connections to help protect servers from spoofed TCP SYN floods. * # none * do not use state mechanisms to keep track. this is only useful if your doing advanced * queueing in certain situations. please check the faq. */ $noadvoptions = false; if (isset($rule['statetype']) && $rule['statetype'] <> "") { switch ($rule['statetype']) { case "none": $noadvoptions = true; $aline['flags'] .= " no state "; break; case "modulate state": case "synproxy state": if ($rule['protocol'] == "tcp") { $aline['flags'] .= "{$rule['statetype']} "; } break; case "sloppy state": $aline['flags'] .= "keep state "; $rule['sloppy'] = true; break; default: $aline['flags'] .= "{$rule['statetype']} "; break; } } else { $aline['flags'] .= "keep state "; } if ($noadvoptions == false && isset($rule['nopfsync'])) { $rule['nopfsync'] = true; } if ($noadvoptions == false) { if ((isset($rule['source-track']) and $rule['source-track'] <> "") or (isset($rule['max']) and $rule['max'] <> "") or (isset($rule['max-src-nodes']) and $rule['max-src-nodes'] <> "") or (isset($rule['max-src-states']) and $rule['max-src-states'] <> "") or ((in_array($rule['protocol'], array("tcp", "tcp/udp"))) and ((isset($rule['statetimeout']) and $rule['statetimeout'] <> "") or (isset($rule['max-src-conn']) and $rule['max-src-conn'] <> "") or (isset($rule['max-src-conn-rate']) and $rule['max-src-conn-rate'] <> "") or (isset($rule['max-src-conn-rates']) and $rule['max-src-conn-rates'] <> ""))) or (isset($rule['sloppy'])) or (isset($rule['nopfsync']))) { $aline['flags'] .= "( "; if (isset($rule['sloppy'])) { $aline['flags'] .= "sloppy "; } if (isset($rule['nopfsync'])) { $aline['flags'] .= "no-sync "; } if (isset($rule['source-track']) and $rule['source-track'] <> "") { $aline['flags'] .= "source-track rule "; } if (isset($rule['max']) and $rule['max'] <> "") { $aline['flags'] .= "max " . $rule['max'] . " "; } if (isset($rule['max-src-nodes']) and $rule['max-src-nodes'] <> "") { $aline['flags'] .= "max-src-nodes " . $rule['max-src-nodes'] . " "; } if ((in_array($rule['protocol'], array("tcp", "tcp/udp"))) and (isset($rule['max-src-conn'])) and ($rule['max-src-conn'] <> "")) { $aline['flags'] .= "max-src-conn " . $rule['max-src-conn'] . " "; } if (isset($rule['max-src-states']) and $rule['max-src-states'] <> "") { $aline['flags'] .= "max-src-states " . $rule['max-src-states'] . " "; } if ((in_array($rule['protocol'], array("tcp", "tcp/udp"))) and (isset($rule['statetimeout'])) and ($rule['statetimeout'] <> "")) { $aline['flags'] .= "tcp.established " . $rule['statetimeout'] . " "; } if ((in_array($rule['protocol'], array("tcp", "tcp/udp"))) and (isset($rule['max-src-conn-rate'])) and ($rule['max-src-conn-rate'] <> "") and (isset($rule['max-src-conn-rates'])) and ($rule['max-src-conn-rates'] <> "")) { $aline['flags'] .= "max-src-conn-rate " . $rule['max-src-conn-rate'] . " "; $aline['flags'] .= "/" . $rule['max-src-conn-rates'] . ", overload flush global "; } $aline['flags'] .= " ) "; } } } if ($rule['defaultqueue'] <> "") { $aline['queue'] = " queue (".$rule['defaultqueue']; if ($rule['ackqueue'] <> "") { $aline['queue'] .= "," . $rule['ackqueue']; } $aline['queue'] .= ") "; } if ($rule['dnpipe'] <> "") { if (!empty($dummynet_name_list[$rule['dnpipe']])) { if ($dummynet_name_list[$rule['dnpipe']][0] == "?") { $aline['dnpipe'] = " dnqueue( "; $aline['dnpipe'] .= substr($dummynet_name_list[$rule['dnpipe']], 1); if ($rule['pdnpipe'] <> "") { $aline['dnpipe'] .= "," . substr($dummynet_name_list[$rule['pdnpipe']], 1); } } else { $aline['dnpipe'] = " dnpipe ( " . $dummynet_name_list[$rule['dnpipe']]; if ($rule['pdnpipe'] <> "") { $aline['dnpipe'] .= "," . $dummynet_name_list[$rule['pdnpipe']]; } } $aline['dnpipe'] .= ") "; } } /* is a time based rule schedule attached? */ if (!empty($rule['sched']) && !empty($config['schedules'])) { $aline['schedlabel'] = ""; foreach ($config['schedules']['schedule'] as $sched) { if ($sched['name'] == $rule['sched']) { if (!filter_get_time_based_rule_status($sched)) { if (!isset($config['system']['schedule_states'])) { mwexec("/sbin/pfctl -y {$sched['schedlabel']}"); } return "# schedule finished - {$rule['descr']}"; } else if ($g['debug']) { log_error(sprintf(gettext("[TDR DEBUG] status true -- rule type '%s'"), $type)); } $aline['schedlabel'] = " schedule \"{$sched['schedlabel']}\" "; break; } } } if (!empty($rule['tracker'])) { $aline['tracker'] = "tracker {$rule['tracker']} "; } $line = ""; /* exception(s) to a user rules can go here. */ /* rules with a gateway or pool should create another rule for routing to vpns */ if ((($aline['route'] <> "") && (trim($aline['type']) == "pass") && strstr($dst, "any")) && (!isset($config['system']['disablenegate']))) { /* negate VPN/PPTP/PPPoE/Static Route networks for load balancer/gateway rules */ $negate_networks = " to " . filter_generate_port($rule, "destination"); $line .= $aline['type'] . $aline['direction'] . $aline['log'] . $aline['quick'] . $aline['interface'] . $aline['ipprotocol'] . $aline['prot'] . $aline['src'] . $aline['os'] . $negate_networks . $aline['icmp-type'] . $aline['icmp6-type'] . $aline['tag'] . $aline['tagged'] . $aline['vlanprio'] . $aline['vlanprioset'] . $aline['dscp'] . filter_negaterule_tracker() . $aline['allowopts'] . $aline['flags'] . $aline['queue'] . $aline['dnpipe'] . $aline['schedlabel'] . " label \"NEGATE_ROUTE: Negate policy routing for destination\"\n"; } /* piece together the actual user rule */ $line .= $aline['type'] . $aline['direction'] . $aline['log'] . $aline['quick'] . $aline['interface'] . $aline['reply'] . $aline['route'] . $aline['ipprotocol'] . $aline['prot'] . $aline['src'] . $aline['os'] . $aline['dst'] . $aline['icmp-type'] . $aline['icmp6-type'] . $aline['tag'] . $aline['tagged'] . $aline['dscp'] . $aline['tracker'] . $aline['vlanprio'] . $aline['vlanprioset'] . $aline['allowopts'] . $aline['flags'] . $aline['queue'] . $aline['dnpipe'] . $aline['schedlabel']; unset($aline); return $line; } function filter_rules_generate() { global $config, $g, $FilterIflist, $time_based_rules, $GatewaysList, $tracker; $fix_rule_label = 'fix_rule_label'; $increment_tracker = 'filter_rule_tracker'; update_filter_reload_status(gettext("Creating default rules")); if (isset($config['system']['developerspew'])) { $mt = microtime(); echo "filter_rules_generate() being called $mt\n"; } $ipfrules = ""; $ipfrules .= discover_pkg_rules("pfearly"); /* relayd */ $ipfrules .= "anchor \"relayd/*\"\n"; /* OpenVPN user rules from radius */ $ipfrules .= "anchor \"openvpn/*\"\n"; /* IPsec user rules from radius */ $ipfrules .= "anchor \"ipsec/*\"\n"; # BEGIN OF firewall rules /* default block logging? */ $log = array(); if (!isset($config['syslog']['nologdefaultblock'])) { $log['block'] = "log"; } if (isset($config['syslog']['nologdefaultpass'])) { $log['pass'] = "log"; } $saved_tracker = $tracker; if (!isset($config['system']['ipv6allow'])) { $ipfrules .= "# Allow IPv6 on loopback\n"; $ipfrules .= "pass in {$log['pass']} quick on \$loopback inet6 all tracker {$increment_tracker($tracker)} label \"pass IPv6 loopback\"\n"; $ipfrules .= "pass out {$log['pass']} quick on \$loopback inet6 all tracker {$increment_tracker($tracker)} label \"pass IPv6 loopback\"\n"; $ipfrules .= "# Block all IPv6\n"; $ipfrules .= "block in {$log['block']} quick inet6 all tracker {$increment_tracker($tracker)} label \"Block all IPv6\"\n"; $ipfrules .= "block out {$log['block']} quick inet6 all tracker {$increment_tracker($tracker)} label \"Block all IPv6\"\n"; } $saved_tracker += 100; $tracker = $saved_tracker; if (!isset($config['system']['no_apipa_block'])) { $ipfrules .= << to any tracker {$increment_tracker($tracker)} label "Block snort2c hosts" block {$log['block']} quick from any to tracker {$increment_tracker($tracker)} label "Block snort2c hosts" EOD; $saved_tracker += 100; $tracker = $saved_tracker; $ipfrules .= filter_process_carp_rules($log); $saved_tracker += 100; $tracker = $saved_tracker; $ipfrules .= "\n# SSH lockout\n"; if (is_array($config['system']['ssh']) && !empty($config['system']['ssh']['port'])) { $ipfrules .= "block in {$log['block']} quick proto tcp from to (self) port "; $ipfrules .= $config['system']['ssh']['port']; $ipfrules .= " tracker {$increment_tracker($tracker)} label \"sshlockout\"\n"; } else { if ($config['system']['ssh']['port'] <> "") { $sshport = $config['system']['ssh']['port']; } else { $sshport = 22; } if ($sshport) { $ipfrules .= "block in {$log['block']} quick proto tcp from to (self) port {$sshport} tracker {$increment_tracker($tracker)} label \"sshlockout\"\n"; } } $saved_tracker += 50; $tracker = $saved_tracker; $ipfrules .= "\n# webConfigurator lockout\n"; if (!$config['system']['webgui']['port']) { if ($config['system']['webgui']['protocol'] == "http") { $webConfiguratorlockoutport = "80"; } else { $webConfiguratorlockoutport = "443"; } } else { $webConfiguratorlockoutport = $config['system']['webgui']['port']; } if ($webConfiguratorlockoutport) { $ipfrules .= "block in {$log['block']} quick proto tcp from to (self) port {$webConfiguratorlockoutport} tracker {$increment_tracker($tracker)} label \"webConfiguratorlockout\"\n"; } $saved_tracker += 100; $tracker = $saved_tracker; /* * Support for allow limiting of TCP connections by establishment rate * Useful for protecting against sudden outbursts, etc. */ $ipfrules .= "block in {$log['block']} quick from to any tracker 1000000400 label \"virusprot overload table\"\n"; $saved_tracker += 100; $tracker = $saved_tracker; /* if captive portal is enabled, ensure that access to this port * is allowed on a locked down interface */ if (is_array($config['captiveportal'])) { foreach ($config['captiveportal'] as $cpcfg) { if (!isset($cpcfg['enable'])) { continue; } $cpinterfaces = explode(",", $cpcfg['interface']); $cpiflist = array(); $cpiplist = array(); foreach ($cpinterfaces as $cpifgrp) { if (!isset($FilterIflist[$cpifgrp])) { continue; } $tmpif = get_real_interface($cpifgrp); if (!empty($tmpif)) { $cpiflist[] = "{$tmpif}"; $cpipm = get_interface_ip($cpifgrp); if (is_ipaddr($cpipm)) { $cpiplist[] = $cpipm; if (!is_array($config['virtualip']) || !is_array($config['virtualip']['vip'])) { continue; } foreach ($config['virtualip']['vip'] as $vip) { if (($vip['interface'] == $cpifgrp) && (($vip['mode'] == "carp") || ($vip['mode'] == "ipalias"))) { $cpiplist[] = $vip['subnet']; } } } } } if (count($cpiplist) > 0 && count($cpiflist) > 0) { $cpinterface = implode(" ", $cpiflist); $cpaddresses = implode(" ", $cpiplist); $listenporthttps = $cpcfg['listenporthttps'] ? $cpcfg['listenporthttps'] : 8000 + ($cpcfg['zoneid'] + 1); $listenporthttp = $cpcfg['listenporthttp'] ? $cpcfg['listenporthttp'] : 8000 + $cpcfg['zoneid']; $portalias = $listenporthttps; $portalias .= " {$listenporthttp}"; $ipfrules .= "pass in {$log['pass']} quick on { {$cpinterface} } proto tcp from any to { {$cpaddresses} } port { {$portalias} } tracker {$increment_tracker($tracker)} keep state(sloppy)\n"; $ipfrules .= "pass out {$log['pass']} quick on { {$cpinterface} } proto tcp from any to any flags any tracker {$increment_tracker($tracker)} keep state(sloppy)\n"; } } } $bogontableinstalled = 0; foreach ($FilterIflist as $on => $oc) { $saved_tracker += 10; $tracker = $saved_tracker; if (isset($config['system']['ipv6allow']) && ($oc['type6'] == "slaac" || $oc['type6'] == "dhcp6")) { // The DHCPv6 client rules ***MUST BE ABOVE BOGONSV6!*** https://redmine.pfsense.org/issues/3395 $ipfrules .= << to any tracker $bogons_tracker label "{$fix_rule_label("block bogon IPv4 networks from {$oc['descr']}")}" EOD; if (isset($config['system']['ipv6allow'])) { $ipfrules .= << to any tracker $bogons_tracker label "{$fix_rule_label("block bogon IPv6 networks from {$oc['descr']}")}" EOD; } } $saved_tracker += 10; $tracker = $saved_tracker; $isbridged = false; if (is_array($config['bridges']['bridged'])) { foreach ($config['bridges']['bridged'] as $oc2) { if (stristr($oc2['members'], $on)) { $isbridged = true; break; } } } if ($oc['ip'] && !($isbridged) && isset($oc['spoofcheck'])) { $ipfrules .= filter_rules_spoofcheck_generate($on, $oc, $log); } /* block private networks ? */ if (!isset($config['syslog']['nologprivatenets'])) { $privnetlog = "log"; } else { $privnetlog = ""; } $saved_tracker += 10; $tracker = $saved_tracker; if (isset($config['interfaces'][$on]['blockpriv'])) { if ($isbridged == false) { $rfc1918_tracker = RFC1918_TRACKER; $ipfrules .= << "") { $ipfrules .= << $ifcfg) { if (isset($ifcfg['virtual'])) { continue; } $gw = get_interface_gateway($ifdescr); if (is_ipaddrv4($gw) && is_ipaddrv4($ifcfg['ip'])) { $ipfrules .= "pass out {$log['pass']} route-to ( {$ifcfg['if']} {$gw} ) from {$ifcfg['ip']} to !{$ifcfg['sa']}/{$ifcfg['sn']} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n"; if (is_array($ifcfg['vips'])) { foreach ($ifcfg['vips'] as $vip) { if (ip_in_subnet($vip['ip'], "{$ifcfg['sa']}/{$ifcfg['sn']}")) { $ipfrules .= "pass out {$log['pass']} route-to ( {$ifcfg['if']} {$gw} ) from {$vip['ip']} to !{$ifcfg['sa']}/{$ifcfg['sn']} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n"; } else { $ipfrules .= "pass out {$log['pass']} route-to ( {$ifcfg['if']} {$gw} ) from {$vip['ip']} to !" . gen_subnet($vip['ip'], $vip['sn']) . "/{$vip['sn']} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n"; } } } } $gwv6 = get_interface_gateway_v6($ifdescr); $stf = get_real_interface($ifdescr, "inet6"); $pdlen = 64 - calculate_ipv6_delegation_length($ifdescr); if (is_ipaddrv6($gwv6) && is_ipaddrv6($ifcfg['ipv6'])) { $ipfrules .= "pass out {$log['pass']} route-to ( {$stf} {$gwv6} ) inet6 from {$ifcfg['ipv6']} to !{$ifcfg['ipv6']}/{$pdlen} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n"; if (is_array($ifcfg['vips6'])) { foreach ($ifcfg['vips6'] as $vip) { $ipfrules .= "pass out {$log['pass']} route-to ( {$stf} {$gwv6} ) inet6 from {$vip['ip']} to !{$vip['ip']}/{$pdlen} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n"; } } } } $saved_tracker += 300; $tracker = $saved_tracker; /* add ipsec interfaces */ if (!function_exists('ipsec_enabled')) { require_once("ipsec.inc"); } if (ipsec_enabled()) { $ipfrules .= "pass out {$log['pass']} on \$IPsec all tracker {$increment_tracker($tracker)} tracker {$increment_tracker($tracker)} keep state label \"IPsec internal host to host\"\n"; } $saved_tracker += 10; $tracker = $saved_tracker; if (is_array($config['system']['webgui']) && !isset($config['system']['webgui']['noantilockout'])) { $alports = filter_get_antilockout_ports(); $lockout_tracker = ANTILOCKOUT_TRACKER; if (count($config['interfaces']) > 1 && !empty($FilterIflist['lan']['if'])) { /* if antilockout is enabled, LAN exists and has * an IP and subnet mask assigned */ $lanif = $FilterIflist['lan']['if']; $ipfrules .= << ScheduleMultipleTime main descr */ function filter_get_time_based_rule_status($schedule) { /* no schedule? rule should be installed */ if (empty($schedule)) { return true; } /* * iterate through time blocks and determine * if the rule should be installed or not. */ foreach ($schedule['timerange'] as $timeday) { if (empty($timeday['month'])) { $monthstatus = true; } else { $monthstatus = filter_tdr_month($timeday['month']); } if (empty($timeday['day'])) { $daystatus = true; } else { $daystatus = filter_tdr_day($timeday['day']); } if (empty($timeday['hour'])) { $hourstatus = true; } else { $hourstatus = filter_tdr_hour($timeday['hour']); } if (empty($timeday['position'])) { $positionstatus = true; } else { $positionstatus = filter_tdr_position($timeday['position']); } if ($monthstatus == true && $daystatus == true && $positionstatus == true && $hourstatus == true) { return true; } } return false; } function filter_tdr_day($schedule) { global $g; if ($g['debug']) { log_error("[TDR DEBUG] filter_tdr_day($schedule)"); } /* * Calculate day of month. * IE: 29th of may */ $date = date("d"); $defined_days = explode(",", $schedule); foreach ($defined_days as $dd) { if ($date == $dd) { return true; } } return false; } function filter_tdr_hour($schedule) { global $g; /* $schedule should be a string such as 16:00-19:00 */ $tmp = explode("-", $schedule); $starting_time = strtotime($tmp[0]); $ending_time = strtotime($tmp[1]); $now = strtotime("now"); if ($g['debug']) { log_error("[TDR DEBUG] S: $starting_time E: $ending_time N: $now"); } if ($now >= $starting_time and $now < $ending_time) { return true; } return false; } function filter_tdr_position($schedule) { global $g; /* * Calculate position, ie: day of week. * Sunday = 7, Monday = 1, Tuesday = 2 * Weds = 3, Thursday = 4, Friday = 5, * Saturday = 6 * ... */ $weekday = date("w"); if ($g['debug']) { log_error("[TDR DEBUG] filter_tdr_position($schedule) $weekday"); } if ($weekday == 0) { $weekday = 7; } $schedule_days = explode(",", $schedule); foreach ($schedule_days as $day) { if ($day == $weekday) { return true; } } return false; } function filter_tdr_month($schedule) { global $g; /* * Calculate month */ $todays_month = date("n"); $months = explode(",", $schedule); if ($g['debug']) { log_error("[TDR DEBUG] filter_tdr_month($schedule)"); } foreach ($months as $month) { if ($month == $todays_month) { return true; } } return false; } function filter_setup_logging_interfaces() { global $config, $FilterIflist; if (isset($config['system']['developerspew'])) { $mt = microtime(); echo "filter_setup_logging_interfaces() being called $mt\n"; } $rules = ""; if (isset($FilterIflist['lan'])) { $rules .= "set loginterface {$FilterIflist['lan']['if']}\n"; } else if (isset($FilterIflist['wan'])) { $rules .= "set loginterface {$FilterIflist['wan']['if']}\n"; } return $rules; } function filter_process_carp_rules($log) { global $g, $config, $tracker; if (isset($config['system']['developerspew'])) { $mt = microtime(); echo "filter_process_carp_rules() being called $mt\n"; } $increment_tracker = 'filter_rule_tracker'; $lines = ""; /* return if there are no carp configured items */ if (!empty($config['hasync']) or !empty($config['virtualip']['vip'])) { $lines .= "block in {$log['block']} quick proto carp from (self) to any tracker {$increment_tracker($tracker)}\n"; $lines .= "pass {$log['pass']} quick proto carp tracker {$increment_tracker($tracker)} no state\n"; } return $lines; } /* Generate IPsec Filter Items */ function filter_generate_ipsec_rules($log = array()) { global $config, $g, $FilterIflist, $tracker; if (isset($config['system']['developerspew'])) { $mt = microtime(); echo "filter_generate_ipsec_rules() being called $mt\n"; } if (isset($config['system']['disablevpnrules'])) { return "\n# VPN Rules not added disabled in System->Advanced.\n"; } $increment_tracker = 'filter_rule_tracker'; $ipfrules = "\n# VPN Rules\n"; if (!function_exists('ipsec_enabled')) { require_once("ipsec.inc"); } if (ipsec_enabled()) { /* step through all phase1 entries */ foreach ($config['ipsec']['phase1'] as $ph1ent) { $tracker += 10; if (isset ($ph1ent['disabled'])) { continue; } /* determine local and remote peer addresses */ if (!isset($ph1ent['mobile'])) { $rgip = ipsec_get_phase1_dst($ph1ent); if (!$rgip) { $ipfrules .= "# ERROR! Unable to determine remote IPsec peer address for {$ph1ent['remote-gateway']}\n"; continue; } } else { $rgip = " any "; } /* Determine best description */ if ($ph1ent['descr']) { $descr = $ph1ent['descr']; } else { $descr = $rgip; } /* * Step through all phase2 entries and determine * which protocols are in use with this peer */ $prot_used_esp = false; $prot_used_ah = false; if (is_array($config['ipsec']['phase2'])) { foreach ($config['ipsec']['phase2'] as $ph2ent) { /* only evaluate ph2's bound to our ph1 */ if ($ph2ent['ikeid'] != $ph1ent['ikeid']) { continue; } if ($ph2ent['protocol'] == 'esp') { $prot_used_esp = true; } if ($ph2ent['protocol'] == 'ah') { $prot_used_ah = true; } } } $a_groups = return_gateway_groups_array(); if (is_array($a_groups[$ph1ent['interface']])) { // bound to gateway group $parentinterface = get_failover_interface($ph1ent['interface']); if (substr($parentinterface, 0, 4) == "_vip") { $parentinterface = get_configured_vip_interface($parentinterface); /* IP Alias -> CARP */ if (substr($parentinterface, 0, 4) == "_vip") { $parentinterface = get_configured_vip_interface($parentinterface); } } else { $parentinterface = convert_real_interface_to_friendly_interface_name($parentinterface); } } elseif (substr($ph1ent['interface'], 0, 4) == "_vip") { $parentinterface = get_configured_vip_interface($ph1ent['interface']); /* IP Alias -> CARP */ if (substr($parentinterface, 0, 4) == "_vip") { $parentinterface = get_configured_vip_interface($parentinterface); } } else { $parentinterface = $ph1ent['interface']; } if (empty($FilterIflist[$parentinterface]['descr'])) { $ipfrules .= "# Could not locate interface for IPsec: {$descr}\n"; continue; } unset($gateway); /* add endpoint routes to correct gateway on interface if the remote endpoint is not on this interface's subnet */ if ((isset($ph1ent['mobile']) || is_ipaddrv4($rgip)) && (interface_has_gateway($parentinterface))) { $parentifsubnet = get_interface_ip($parentinterface) . "/" . get_interface_subnet($parentinterface); if (isset($ph1ent['mobile']) || !ip_in_subnet($rgip, $parentifsubnet)) { $gateway = get_interface_gateway($parentinterface); $interface = $FilterIflist[$parentinterface]['if']; $route_to = " route-to ( $interface $gateway ) "; $reply_to = " reply-to ( $interface $gateway ) "; } } else if ((isset($ph1ent['mobile']) || is_ipaddrv6($rgip)) && (interface_has_gatewayv6($parentinterface))) { $parentifsubnet = get_interface_ipv6($parentinterface) . "/" . get_interface_subnetv6($parentinterface); if (isset($ph1ent['mobile']) || !ip_in_subnet($rgip, $parentifsubnet)) { $gateway = get_interface_gateway_v6($parentinterface); $interface = $FilterIflist[$parentinterface]['if']; $route_to = " route-to ( $interface $gateway ) "; $reply_to = " reply-to ( $interface $gateway ) "; } } /* Just in case */ if ((!is_ipaddr($gateway) || empty($interface))) { $route_to = " "; $reply_to = " "; } /* Add rules to allow IKE to pass */ $shorttunneldescr = substr($descr, 0, 35); // don't add "pass out" rules where $rgip is any as it will over-match and often break VPN clients behind the system in multi-WAN scenarios. redmine #5819 if ($rgip != " any ") { $ipfrules .= "pass out {$log['pass']} $route_to proto udp from (self) to {$rgip} port = 500 tracker {$increment_tracker($tracker)} keep state label \"IPsec: {$shorttunneldescr} - outbound isakmp\"\n"; } $ipfrules .= << 0) { $errorrules = sprintf(gettext( "There was an error while parsing the package filter rules for %s."), $pkg_config['include_file']) . "\n"; log_error($errorrules); file_put_contents("{$g['tmp_path']}/rules.packages.{$pkgname}", "#{$errorrules}\n{$tmprules}\n"); continue; } $rules .= $tmprules; } return $rules; } function filter_get_antilockout_ports($wantarray = false) { global $config; $lockoutports = array(); $guiport = ($config['system']['webgui']['protocol'] == "https") ? "443" : "80"; $guiport = empty($config['system']['webgui']['port']) ? $guiport : $config['system']['webgui']['port']; $lockoutports[] = $guiport; if (($config['system']['webgui']['protocol'] == "https") && !isset($config['system']['webgui']['disablehttpredirect']) && ($guiport != "80")) { $lockoutports[] = "80"; } if (isset($config['system']['enablesshd'])) { $lockoutports[] = empty($config['system']['ssh']['port']) ? "22" : $config['system']['ssh']['port']; } if ($wantarray) { return $lockoutports; } else { return implode(" ", $lockoutports); } } /* get rule index within interface */ function ifridx($if, $ridx) { global $config; if ($ridx < 0) { return $ridx; } $i = $ifridx = 0; if (is_array($config['filter']['rule'])) { foreach ($config['filter']['rule'] as $rulen => $filterent) { if (($filterent['interface'] == $if && !isset($filterent['floating'])) || (isset($filterent['floating']) && "FloatingRules" == $if)) { if ($i == $ridx) { return $ifridx; } $ifridx++; } $i++; } } return $ifridx; } /* display rules separators */ function display_separator($separators, $nrules, $columns_in_table) { if (is_array($separators)) { foreach ($separators as $sepn => $separator) { if ($separator['row'][0] == "fr" . $nrules) { $cellcolor = $separator['color']; print('' . '' . '' . $separator['text'] . '' . '' . '' . "\n"); } } } } /* Return a list of separator rows */ function separator_rows($separators) { $seprowns = array(); if (is_array($separators)) { foreach ($separators as $sepn => $separator) { $seprows[substr($separator['row']['0'], 2)] = true; } } return $seprows; } /* If the separator is located after the place ($ridx) where the rule was added or deleted, move the separator ($mvrows) */ function move_separators(&$a_separators, $ridx, $mvnrows) { if (is_array($a_separators)) { foreach ($a_separators as $sepi => $separator) { $seprow = substr($separator['row']['0'], 2); if ($seprow > $ridx) { $a_separators[$sepi]['row']['0'] = 'fr' . ($seprow + $mvnrows); } } } } ?>