'pfB_Top', 'Africa' => 'pfB_Africa', 'Antarctica' => 'pfB_Antarctica', 'Asia' => 'pfB_Asia', 'Europe' => 'pfB_Europe', 'North America' => 'pfB_NAmerica', 'Oceania' => 'pfB_Oceania', 'South America' => 'pfB_SAmerica', 'Proxy and Satellite' => 'pfB_PS' ); $pfb['continent_list'] = array_flip(array('pfB_Africa', 'pfB_Antarctica', 'pfB_Asia', 'pfB_Europe', 'pfB_NAmerica', 'pfB_Oceania', 'pfB_SAmerica', 'pfB_Top')); // Base rule array $pfb['base_rule_reg'] = array('ipprotocol' => 'inet'); // Floating rules, base rule array $pfb['base_rule_float'] = array('quick' => 'yes', 'floating' => 'yes', 'ipprotocol' => 'inet'); // Define Arrays for managing the IP mastefile foreach (array('existing', 'actual') as $pftype) { $pfb[$pftype]['match'] = array(); $pfb[$pftype]['permit'] = array(); $pfb[$pftype]['deny'] = array(); $pfb[$pftype]['native'] = array(); $pfb[$pftype]['dnsbl'] = array(); } // Default cURL options $pfb['curl_defaults'] = array( CURLOPT_USERAGENT => 'pfSense/pfBlockerNG cURL download agent-' . system_get_uniqueid(), CURLOPT_SSL_CIPHER_LIST => 'TLSv1.3, TLSv1.2', CURLOPT_FOLLOWLOCATION => true, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => true, CURLOPT_FRESH_CONNECT => true, CURLOPT_FILETIME => true, CURLOPT_TCP_NODELAY => true, CURLOPT_CONNECTTIMEOUT => 15 ); // RFC7231 HTTP response codes $pfb['rfc7231'] = array(100 => '100 Continue', 101 => '101 Switching Protocols', 102 => '102 Processing', 200 => '200 OK', 201 => '201 Created', 202 => '202 Accepted', 203 => '203 Non-Authoritative Info', 204 => '204 No Content', 205 => '205 Reset Content', 206 => '206 Partial Content', 207 => '207 Multi-Status', 208 => '208 Already Reported', 226 => '226 IM Used', 300 => '300 Multiple Choices', 301 => '301 Moved Permanently', 302 => '302 Found', 303 => '303 See Other', 304 => '304 Not Modified', 305 => '305 Use Proxy', 306 => '306 Switch Proxy', 307 => '307 Temporary Redirect', 308 => '308 Permanent Redirect', 400 => '400 Bad Request', 401 => '401 Unauthorized', 402 => '402 Payment Required', 403 => '403 Forbidden', 404 => '404 Not Found', 405 => '405 Method Not Allowed', 406 => '406 Not Acceptable', 407 => '407 Proxy Authentication Required', 408 => '408 Request Timeout', 409 => '409 Conflict', 410 => '410 Gone', 411 => '411 Length Required', 412 => '412 Precondition Failed', 413 => '413 Request Entity Too Large', 414 => '414 Request-URI Too Long', 415 => '415 Unsupported Media Type', 416 => '416 Requested Range Not Satisfiable', 417 => '417 Expectation Failed', 418 => '418 Im a teapot', 419 => '419 Authentication Timeout', 420 => '420 Method Failure', 421 => '421 Misdirected Request', 422 => '422 Unprocessable Entity', 423 => '423 Locked', 424 => '424 Failed Dependency', 426 => '426 Upgrade Required', 428 => '428 Precondition Required', 429 => '429 Too Many Requests', 431 => '431 Request Header Fields Large', 440 => '440 Login Timeout', 444 => '444 No Response', 449 => '449 Retry With', 450 => '450 Blocked Windows Parental Controls', 451 => '451 Unavailable Legal Reasons', 494 => '494 Request Header too Large', 495 => '495 Cert Error', 496 => '496 No Cert', 497 => '497 HTTP to HTTPS', 498 => '498 Token expired/invalid', 499 => '499 Client Closed Request', 500 => '500 Internal Server Error', 501 => '501 Not Implemented', 502 => '502 Bad Gateway', 503 => '503 Service Unavailable', 504 => '504 Gateway Timeout', 505 => '505 HTTP Version Not Supported', 506 => '506 Variant Also Negotiates', 507 => '507 Insufficient Storage', 508 => '508 Loop Detected', 509 => '509 Bandwidth Limit Exceeded', 510 => '510 Not Extended', 511 => '511 Network Authentication Required', 521 => '521 Web Server is down', 598 => '598 Network read timeout error', 599 => '599 Network connect timeout error', 520 => 'CF 520 Unknown Error', 521 => 'CF 521 Web Server is Down', 522 => 'CF 522 Connection Timed Out', 523 => 'CF 523 Origin is Unreachable', 524 => 'CF 524 A Timeout Occured', 525 => 'CF 525 SSL Handshake Failed', 526 => 'CF 526 Invalid SSL Certificate',527 => 'CF 527 Railgun Error' ); // Function to filter/sanitize user input - ** TODO: Improve filter ** function pfb_filter($input, $type) { $result = ''; switch ($type) { case 1: $result = filter_var($input, FILTER_SANITIZE_STRING); break; case 2: $result = filter_var($input, FILTER_SANITIZE_URL); break; default: break; } return $result == FALSE ? '' : $result; } // [ $pfb ] pfBlockerNG global array. This needs to be called to get the updated settings. function pfb_global() { global $g, $config, $pfb; // Create folders if not exist. foreach ($pfb['folder_array'] as $folder) { safe_mkdir("{$folder}", 0755); } // Reload config.xml to get any recent changes $config = parse_config(true); init_config_arr(array('installedpackages', 'pfblockerng', 'config', 0)); init_config_arr(array('installedpackages', 'pfblockerngipsettings', 'config', 0)); init_config_arr(array('installedpackages', 'pfblockerngdnsblsettings', 'config', 0)); init_config_arr(array('installedpackages', 'pfblockerngblacklist')); init_config_arr(array('installedpackages', 'pfblockerngglobal')); // General variables $pfb['config'] = $config['installedpackages']['pfblockerng']['config'][0]; $pfb['ipconfig'] = $config['installedpackages']['pfblockerngipsettings']['config'][0]; $pfb['dnsblconfig'] = $config['installedpackages']['pfblockerngdnsblsettings']['config'][0]; $pfb['blconfig'] = $config['installedpackages']['pfblockerngblacklist']; $pfb['config_global'] = $config['installedpackages']['pfblockerngglobal']; $pfb['enable'] = $pfb['config']['enable_cb']; // Enable/Disable of pfBlockerNG $pfb['keep'] = $pfb['config']['pfb_keep']; // Keep blocklists on pfBlockerNG Disable $pfb['interval'] = $pfb['config']['pfb_interval'] ?: '1'; // Hour cycle for scheduler $pfb['min'] = $pfb['config']['pfb_min'] ?: '0'; // User defined CRON start minute $pfb['hour'] = $pfb['config']['pfb_hour'] ?: '0'; // Start hour of the scheduler $pfb['24hour'] = $pfb['config']['pfb_dailystart'] ?: '0'; // Start hour of the 'Once a day' schedule $pfb['supp'] = $pfb['ipconfig']['suppression']; // Enable Suppression $pfb['cc'] = $pfb['ipconfig']['database_cc']; // Disable Country database CRON updates $pfb['maxmind_locale'] = $pfb['ipconfig']['maxmind_locale'] ?: 'en';// MaxMind Localized Language setting $pfb['maxmind_key'] = $pfb['ipconfig']['maxmind_key'] ?: ''; // Maxmind License Key $pfb['asn_reporting'] = $pfb['ipconfig']['asn_reporting'] ?: 'disabled'; // ASN Reporting $pfb['iplocal'] = $config['interfaces']['lan']['ipaddr']; // Lan IP address $pfb['dnsbl'] = $pfb['dnsblconfig']['pfb_dnsbl']; // Enabled state of DNSBL $pfb['dnsbl_vip_type'] = $pfb['dnsblconfig']['pfb_dnsvip_type'] ?: 'ipalias'; // Virtual IP type $pfb['dnsbl_vip_vhid'] = $pfb['dnsblconfig']['pfb_dnsvip_vhid']; // Virtual IP Carp VHID $pfb['dnsbl_vip_base'] = $pfb['dnsblconfig']['pfb_dnsvip_base']; // Virtual IP Carp advbase $pfb['dnsbl_vip_skew'] = $pfb['dnsblconfig']['pfb_dnsvip_skew']; // Virtual IP Carp skew $pfb['dnsbl_vip_pass'] = $pfb['dnsblconfig']['pfb_dnsvip_pass']; // Virtual IP Carp password (if required) $pfb['dnsbl_iface'] = $pfb['dnsblconfig']['dnsbl_interface']?: 'lo0'; // VIP Local Interface setting $pfb['dnsbl_vip'] = $pfb['dnsblconfig']['pfb_dnsvip'] ?: ''; // Virtual IP local address $pfb['dnsbl_v6'] = $pfb['dnsblconfig']['pfb_dnsblv6'] ?: ''; // Enable/Disable DNSBL IPv6 $pfb['dnsbl_port'] = $pfb['dnsblconfig']['pfb_dnsport']; // Lighttpd web server http port setting $pfb['dnsbl_port_ssl'] = $pfb['dnsblconfig']['pfb_dnsport_ssl']; // Lighttpd web server https port setting $pfb['dnsbl_alexa'] = $pfb['dnsblconfig']['alexa_enable']; // TOP1M whitelist $pfb['dnsbl_alexatype'] = $pfb['dnsblconfig']['alexa_type'] ?: 'tranco'; // TOP1M type (Tranco, Alexa or Cisco) $pfb['dnsbl_res_cache'] = $pfb['dnsblconfig']['pfb_cache']; // DNSBL Option to backup/restore Resolver cache $pfb['dnsbl_sync'] = $pfb['dnsblconfig']['pfb_dnsbl_sync']; // Live Updates to Resolver without a Reload $pfb['dnsbl_global_log']= $pfb['dnsblconfig']['global_log'] ?: ''; // Global Logging/Blocking mode $pfb['dnsbl_python'] = $pfb['dnsblconfig']['pfb_python']; // DNSBL Resolver python integration $pfb['dnsbl_mode'] = $pfb['dnsblconfig']['dnsbl_mode']; // DNSBL Mode (Unbound/python mode) $pfb['dnsbl_py_reply'] = $pfb['dnsblconfig']['pfb_py_reply']; // DNSBL Resolver python DNS Reply logging $pfb['dnsbl_py_block'] = $pfb['dnsblconfig']['pfb_py_block']; // DNSBL Resolver python blocking mode $pfb['dnsbl_hsts'] = $pfb['dnsblconfig']['pfb_hsts']; // DNSBL Resolver python block HSTS via Null Block mode $pfb['dnsbl_idn'] = $pfb['dnsblconfig']['pfb_idn']; // DNSBL Resolver python block IDN domains $pfb['dnsbl_regex'] = $pfb['dnsblconfig']['pfb_regex']; // DNSBL Resolver python regex $pfb['dnsbl_regex_list']= $pfb['dnsblconfig']['pfb_regex_list']; // DNSBL Resolver python regex list $pfb['dnsbl_cname'] = $pfb['dnsblconfig']['pfb_cname']; // DNSBL Resolver python CNAME Validation $pfb['dnsbl_pytld'] = $pfb['dnsblconfig']['pfb_pytld']; // DNSBL Resolver python TLD Allow option $pfb['dnsbl_py_nolog'] = $pfb['dnsblconfig']['pfb_py_nolog']; // DNSBL Resolver python - Log events via DNSBL Webserver vs python $pfb['dnsbl_noaaaa'] = $pfb['dnsblconfig']['pfb_noaaaa']; // DNSBL Resolver python no AAAA $pfb['dnsbl_noaaaa_list']=$pfb['dnsblconfig']['pfb_noaaaa_list']; // DNSBL Resolver python no AAAA list $pfb['dnsbl_gp'] = $pfb['dnsblconfig']['pfb_gp']; // DNSBL Resolver python - DNSBL Bypass $pfb['dnsbl_gp_bypass_list'] = $pfb['dnsblconfig']['pfb_gp_bypass_list']; // DNSBL Resolver python - List of Local IPs to bypass DNSBL // DNSBL Resolver mode (Unbound/Python) $pfb['dnsbl_py_blacklist'] = FALSE; if ($pfb['dnsbl_mode'] == 'dnsbl_python' && $pfb['dnsbl_py_block'] == 'on') { $pfb['dnsbl_py_blacklist'] = TRUE; } // SafeSearch init_config_arr(array('installedpackages', 'pfblockerngsafesearch')); $pfb['safesearch_enable'] = $config['installedpackages']['pfblockerngsafesearch']['safesearch_enable'] ?: 'Disable'; $pfb['safesearch_youtube'] = $config['installedpackages']['pfblockerngsafesearch']['safesearch_youtube'] ?: 'Disable'; $pfb['safesearch_doh'] = $config['installedpackages']['pfblockerngsafesearch']['safesearch_doh'] ?: 'Disable'; $pfb['safesearch_doh_list'] = explode(',', $config['installedpackages']['pfblockerngsafesearch']['safesearch_doh_list']) ?: array(); // DNSBL SafeSearch $pfb['dnsbl_safe_search'] = FALSE; if ($pfb['safesearch_enable'] !== 'Disable' || $pfb['safesearch_youtube'] !== 'Disable' || $pfb['safesearch_doh'] !== 'Disable') { $pfb['dnsbl_safe_search'] = TRUE; } $pfb['extdns'] = $pfb['config_global']['pfbextdns'] ?: '8.8.8.8'; // External DNS Server for TLD drill and CNAME Queries // Unbound chroot cmd $pfb['chroot_cmd'] = "/usr/sbin/chroot -u unbound -g unbound / /usr/local/sbin/unbound-control -c {$g['unbound_chroot_path']}/unbound.conf"; // Define SQLite3 parameters $pfb['sqlite_timeout'] = 100000; // Max daily download failure threshold (default to '0' unlimited failures) $pfb['skipfeed'] = $pfb['config']['skipfeed'] != '' ? $pfb['config']['skipfeed'] : 0; if (isset($config['unbound']['enable'])) { $pfb['unbound_state'] = 'on'; } else { $pfb['unbound_state'] = ''; } // cURL - system proxy server setttings, if configured if (!empty($config['system']['proxyurl'])) { $pfb['curl_defaults'][CURLOPT_PROXY] = $config['system']['proxyurl']; if (!empty($config['system']['proxyport'])) { $pfb['curl_defaults'][CURLOPT_PROXYPORT] = $config['system']['proxyport']; } if (!empty($config['system']['proxyuser']) && !empty($config['system']['proxypass'])) { $pfb['curl_defaults'][CURLOPT_PROXYAUTH] = 'CURLAUTH_ANY | CURLAUTH_ANYSAFE'; $pfb['curl_defaults'][CURLOPT_PROXYUSERPWD] = "{$config['system']['proxyuser']}:{$config['system']['proxypass']}"; } } else { $pfb['curl_defaults'][CURLOPT_TCP_FASTOPEN] = true; } // Set pfBlockerNG to disabled on 're-install' if (isset($pfb['install']) && $pfb['install']) { $pfb['enable'] = $pfb['dnsbl'] = ''; $pfb['install'] = FALSE; } } pfb_global(); // Function to get pfBlockerNG package version function pfb_pkg_ver() { global $config; $pkg_ver = 'v??'; if (is_array($config['installedpackages']['package'])) { foreach ($config['installedpackages']['package'] as $pkg_info) { if (strpos($pkg_info['name'], 'pfBlockerNG') !== FALSE) { $pkg_ver = 'v' . $pkg_info['version']; break; } } } return $pkg_ver; } // Firewall Filter Service function pfb_filter_service() { $rc = array(); $rc['file'] = 'pfb_filter.sh'; $rc['start'] = << '2.5' && file_exists('/usr/local/sbin/clog_pfb')) { unlink_if_exists('/usr/local/sbin/clog_pfb'); unlink_if_exists('/usr/bin/tail_pfb'); link('/usr/bin/tail', '/usr/bin/tail_pfb'); restart_service('pfb_filter'); } // Commandline arguments for daemons if (isset($argv[1])) { // DNSBL Lighttpd HTTPS daemon (Collects HTTPS events from the Lighttpd dnsbl_error.log) if ($argv[1] == 'dnsbl') { ignore_user_abort(TRUE); set_time_limit(0); pfb_daemon_dnsbl(); exit; } // DNSBL Lighttpd HTTP daemon (Collects HTTP events from the index.php script) elseif ($argv[1] == 'index') { ignore_user_abort(TRUE); set_time_limit(0); pfb_daemon_dnsbl_index(); exit; } // DNSBL daemon to monitor Resolver queries and manage SQLite3 database elseif ($argv[1] == 'queries') { ignore_user_abort(TRUE); set_time_limit(0); pfb_daemon_queries(); exit; } // IP filter daemon to convert filter.log to ip_block|ip_permit|ip_match log format elseif ($argv[1] == 'filterlog') { ignore_user_abort(TRUE); set_time_limit(0); if (!file_exists('/var/log/filter.log')) { log_error('[pfBlockerNG] pfSense Firewall log missing'); exit; } pfb_daemon_filterlog(); exit; } } // Function to convert string to lowercase (Not for comment line section) function pfb_strtolower($line) { if (strpos($line, '#') === FALSE) { return trim(strtolower($line)); } return trim($line); } // Function to decode alias custom entry box. // Default (False, True): Return as string with comments function pfbng_text_area_decode($text, $mode=FALSE, $type=TRUE, $idn=FALSE) { if ($mode) { $custom = array(); } $customlist = explode("\r\n", base64_decode($text)); if (!empty($customlist)) { foreach ($customlist as $line) { if (substr(trim($line), 0, 1) != '#' && !empty($line)) { if ($idn && !ctype_print($line)) { $line_old = $line; // Convert encodings to UTF-8 $line = mb_convert_encoding($line, 'UTF-8', mb_detect_encoding($line, 'UTF-8, ASCII, ISO-8859-1')); if (strpos($line, '#') !== FALSE) { $tmpline = preg_split('/(?=#)/', $line); if (substr($tmpline[0], 0, 1) == '.') { // idn_to_ascii() returns empty string if it starts with '.' $tmpline[0] = idn_to_ascii(ltrim($tmpline[0], '.')); if (!empty($tmpline[0])) { $tmpline[0] = '.' . $tmpline[0]; } } else { $tmpline[0] = idn_to_ascii($tmpline[0]); } if (empty($tmpline[0])) { $log = "\nError converting IDN line '{$line_old}'\n"; pfb_logger($log, 2); continue; } $line = implode(' ', $tmpline); } else { if (substr($line, 0, 1) == '.') { $line = idn_to_ascii(ltrim($line, '.')); if (!empty($line)) { $line = '.' . $line; } } else { $line = idn_to_ascii($line); } if (empty($line)) { $log = "\nError converting IDN line '{$line_old}'\n"; pfb_logger($log, 2); continue; } } } // '#' commentline found if (strpos($line, '#') !== FALSE) { if ($mode) { if ($type) { // Split line into two elements (array) $custom[] = array_map('pfb_strtolower', preg_split('/(?=#)/', $line)); } else { // Remove commentline $custom[] = trim(strtolower(strstr($line, '#', TRUE))); } } else { // Remove commentline $custom .= trim(strtolower(strstr($line, '#', TRUE))) . "\n"; } } // No '#' commentline found else { $line = trim(strtolower($line)); if ($mode) { if ($type) { $custom[][0] = $line; } else { $custom[] = $line; } } else { $custom .= "{$line}\n"; } } } } return $custom; } } // Manage log files line limit function pfb_log_mgmt() { global $g, $pfb; pfb_global(); $chroot_folder = '/var/unbound'; foreach (array( 'log', 'errlog', 'extraslog', 'ip_blocklog', 'ip_permitlog', 'ip_matchlog', 'dnslog', 'dnsbl_parse_err', 'dnsreplylog', 'unilog') as $logtype) { // Max lines in Log file $logmax = $pfb['config']['log_max_' . $logtype] ?: 20000; if ($logmax != 'nolimit' && file_exists($pfb[$logtype])) { if ($logtype == 'dnslog' || $logtype == 'dnsreplylog' || $logtype == 'unilog') { // Set DNSBL python logfile permissions using chroot folder if (is_dir("{$chroot_folder}/var/log/pfblockerng")) { $final_log_file = "{$chroot_folder}{$pfb[$logtype]}"; $temp = tempnam("{$chroot_folder}/var/log/pfblockerng", 'pfb_log_'); } else { $final_log_file = $pfb[$logtype]; $temp = tempnam("{$g['tmp_path']}/", 'pfb_log_'); } if (file_exists($final_log_file)) { exec("/usr/bin/tail -n {$logmax} {$final_log_file} > {$temp}"); @chown($temp, 'unbound'); @chgrp($temp, 'unbound'); exec("/bin/mv -f {$temp} {$final_log_file}"); } } else { $temp = tempnam("{$g['tmp_path']}/", 'pfb_log_'); exec("/usr/bin/tail -n {$logmax} {$pfb[$logtype]} > {$temp}"); exec("/bin/mv -f {$temp} {$pfb[$logtype]}"); } unlink_if_exists($temp); } } } // Record log messsages to pfBlockerNG log file and/or error log file. function pfb_logger($log, $logtype) { global $g, $pfb; $now = date('m/j/y H:i:s', time()); // Only log timestamp if new if (strpos($log, 'NOW') !== FALSE) { if ($now == $pfb['pnow']) { $log = str_replace(' [ NOW ]', '', "{$log}"); } else { $log = str_replace('NOW', $now, "{$log}"); } $pfb['pnow'] = "{$now}"; } switch ($logtype) { // Print to pfBlockerNG log case 1: @file_put_contents("{$pfb['log']}", "{$log}", FILE_APPEND); break; // Print to pfBlockerNG log and Error log case 2: @file_put_contents("{$pfb['log']}", "{$log}", FILE_APPEND); @file_put_contents("{$pfb['errlog']}", "{$log}", FILE_APPEND); break; // Print to Extras log case 3: @file_put_contents("{$pfb['extraslog']}", "{$log}", FILE_APPEND); break; // Print to screen and Extras log case 4: if (!$g['pfblockerng_install'] && !$pfb['extras_update']) { print "{$log}"; } @file_put_contents("{$pfb['extraslog']}", "{$log}", FILE_APPEND); break; // Print to debugger case 5: @file_put_contents("/tmp/pfb_debug", "{$now} | {$log}", FILE_APPEND); break; default: break; } } // Record failed IP/DNSBL Feed parse errors function pfb_parsed_fail($header, $line='', $oline, $logfile) { $line = $line ?: 'null'; $now = date('m/j/y H:i:s', time()); $log = "{$now},{$header},{$line},{$oline}"; @file_put_contents("{$logfile}", "{$log}", FILE_APPEND); } // Determine 'list' details function pfb_determine_list_detail($list='', $header='', $confconfig='', $key='') { global $config, $pfb, $pfbarr; $pfbarr = array(); switch($list) { case 'Deny_Both': case 'Deny_Inbound': case 'Deny_Outbound': case 'Alias_Deny': $pfbarr = array('adv' => TRUE, 'folder' => "{$pfb['denydir']}", 'orig' => "{$pfb['origdir']}", 'reuse' => "{$pfb['reuse']}"); break; case 'unbound': $pfbarr = array('adv' => FALSE, 'folder' => "{$pfb['dnsdir']}", 'orig' => "{$pfb['dnsorigdir']}", 'reuse' => "{$pfb['reuse_dnsbl']}"); break; case 'Permit_Both': case 'Permit_Inbound': case 'Permit_Outbound': case 'Alias_Permit': $pfbarr = array('adv' => FALSE, 'folder' => "{$pfb['permitdir']}", 'orig' => "{$pfb['origdir']}", 'reuse' => "{$pfb['reuse']}"); break; case 'Match_Both': case 'Match_Inbound': case 'Match_Outbound': case 'Alias_Match': $pfbarr = array('adv' => FALSE, 'folder' => "{$pfb['matchdir']}", 'orig' => "{$pfb['origdir']}", 'reuse' => "{$pfb['reuse']}"); break; case 'Alias_Native': $pfbarr = array('adv' => FALSE, 'folder' => "{$pfb['nativedir']}", 'orig' => "{$pfb['origdir']}", 'reuse' => "{$pfb['reuse']}"); break; } // Collect proper alias table description (alias only vs autorules) if (strpos($list, 'Alias') !== FALSE) { $pfbarr['descr'] = ''; } else { $pfbarr['descr'] = ' Auto '; } // Determine length of header to format log output $tabtype = strlen($header); if ($tabtype > 27) { $pfbarr['logtab'] = ''; } elseif ($tabtype > 19) { $pfbarr['logtab'] = "\t"; } elseif ($tabtype > 11) { $pfbarr['logtab'] = "\t\t"; } elseif ($tabtype < 4) { $pfbarr['logtab'] = "\t\t\t\t"; } else { $pfbarr['logtab'] = "\t\t\t"; } // Configure autoports/protocol and auto destination if required. if (!empty($confconfig) && is_array($config['installedpackages'][$confconfig]['config'][$key])) { $conf_config = $config['installedpackages'][$confconfig]['config'][$key]; $autotype = array( 'autoports' => 'aliasports', 'autoaddr' => 'aliasaddr'); foreach (array('_out', '_in') as $dir) { $pfbarr['aproto' . $dir] = $conf_config['autoproto' . $dir]; $pfbarr['anot' . $dir] = $conf_config['autonot' . $dir]; $pfbarr['aaddrnot' . $dir] = $conf_config['autoaddrnot' . $dir]; $pfbarr['agateway' . $dir] = $conf_config['agateway' . $dir]; foreach ($autotype as $akey => $atype) { if ($conf_config[$akey . $dir] == 'on' && isset($config['aliases']['alias'])) { foreach ($config['aliases']['alias'] as $palias) { if ($palias['name'] == $conf_config[$atype . $dir]) { if (!empty($palias['address'])) { $dalias = "{$atype}{$dir}"; switch($akey) { case 'autoports': $ctype = "aports{$dir}"; $pfbarr[$ctype] = $conf_config[$dalias]; break; case 'autoaddr': $ctype = "aaddr{$dir}"; $pfbarr[$ctype] = $conf_config[$dalias]; break; } } } } } } } } // Force 'Alias Native' setting to any Alias with 'Advanced Inbound/Outbound -Invert src/dst' settings. // This will bypass Deduplication and Reputation features. if ($pfbarr['aaddrnot_in'] == 'on' || $pfbarr['aaddrnot_out'] == 'on') { $pfbarr['adv'] = FALSE; $pfbarr['folder'] = "{$pfb['nativedir']}"; } return $pfbarr; } // Determine if cron task requires updating function pfblockerng_cron_exists($pfb_cmd, $pfb_min, $pfb_hour, $pfb_mday, $pfb_wday) { global $config; if (isset($config['cron']['item'])) { foreach ($config['cron']['item'] as $item) { if (strpos($item['command'], $pfb_cmd) !== FALSE) { if ($item['command'] != $pfb_cmd) { return FALSE; } if ($item['minute'] != $pfb_min) { return FALSE; } if ($item['mday'] != $pfb_mday) { return FALSE; } if ($item['wday'] != $pfb_wday) { return FALSE; } if ($pfb_hour == 'random' && $item['hour'] != '') { // MaxMind/Blacklist hour is randomized. Skip comparison. return TRUE; } if ($item['hour'] != $pfb_hour) { return FALSE; } return TRUE; } } } return FALSE; } // Calculate the cron task base hour setting function pfb_cron_base_hour($freq) { global $pfb; switch($freq) { case 'Disabled': case 1: $j = 23; $k = 1; break; case 2: $j = 11; $k = 2; break; case 3: $j = 7; $k = 3; break; case 4: $j = 5; $k = 4; break; case 6: $j = 3; $k = 6; break; case 8: $j = 2; $k = 8; break; case 12: $j = 1; $k = 12; break; case 24: return array($pfb['24hour']); break; default: $pfb['interval'] = 1; return; } $shour = intval(substr($pfb['hour'], 0, 2)); $sch = strval($shour); for ($i=0; $i < $j; $i++) { $shour += $k; if ($shour >= 24) { $shour -= 24; } $sch .= ',' . strval($shour); } $sch = explode(',', $sch); sort($sch); return $sch; } // Collect 'gateway(s)' and 'gateway group(s)' for Adv. In/Outbound customizations function pfb_get_gateways() { global $config; $gateway = array(); $gateway['default'] = 'default'; if (is_array($config['gateways']['gateway_item'])) { foreach ($config['gateways']['gateway_item'] as $item) { $gateway[$item['name']] = $item['name']; } } if (is_array($config['gateways']['gateway_group'])) { foreach ($config['gateways']['gateway_group'] as $item) { $gateway[$item['name']] = $item['name']; } } return $gateway; } // Collect all Interfaces for General Tab and DNSBL Firewall Permit Rule function pfb_build_if_list($show_wan=FALSE, $show_groups=FALSE) { global $config; $pfb_interfaces = array(); foreach (get_configured_interface_with_descr() as $ifent => $ifdesc) { if ($show_wan || $ifent != 'wan') { $pfb_interfaces[$ifent] = $ifdesc; } } if ($show_groups && is_array($config['ifgroups']['ifgroupentry'])) { foreach ($config['ifgroups']['ifgroupentry'] as $ifgen) { $pfb_interfaces[$ifgen['ifname']] = $ifgen['ifname']; } } if (ipsec_enabled()) { $pfb_interfaces['enc0'] = 'IPsec'; } if ($config['openvpn']['openvpn-server'] || $config['openvpn']['openvpn-client']) { $pfb_interfaces['openvpn'] = 'OpenVPN'; } if ($config['l2tp']['mode'] == 'server') { $pfb_interfaces['l2tp'] = 'L2TP VPN'; } if (function_exists('is_wg_enabled') && is_wg_enabled()) { $pfb_interfaces['wireguard'] = 'WireGuard'; } return $pfb_interfaces; } // Create suppression file from suppression list function pfb_create_suppression_file() { global $pfb; $v4suppression = pfbng_text_area_decode($pfb['ipconfig']['v4suppression'], FALSE, TRUE); if (!empty($v4suppression)) { @file_put_contents("{$pfb['supptxt']}", $v4suppression, LOCK_EX); } else { unlink_if_exists("{$pfb['supptxt']}"); } } // Function to update DNSBL aliases and widget stats function dnsbl_alias_update($mode, $alias, $pfbfolder, $lists_dnsbl_current, $alias_cnt) { global $pfb; if ($mode == 'update') { // Create master alias file if ($lists_dnsbl_current != '') { $pfb_output = @fopen("{$pfb['dnsalias']}/{$alias}", 'w'); foreach ($lists_dnsbl_current as $clist) { if (($handle = @fopen("{$pfbfolder}/{$clist}.txt", 'r')) !== FALSE) { while (($line = @fgets($handle)) !== FALSE) { @fwrite($pfb_output, $line); } } @fclose($handle); } @fclose($pfb_output); } // Update DNSBL alias statistics $dns_now = date('M j H:i:s', time()); $pfbfound = FALSE; if (!empty($pfb['dnsbl_info_stats'])) { foreach ($pfb['dnsbl_info_stats'] as $key => $line) { // Update existing alias stats if ($line['groupname'] == "{$alias}") { $pfbfound = TRUE; $pfb['dnsbl_info_stats'][$key]['timestamp'] = "{$dns_now}"; $pfb['dnsbl_info_stats'][$key]['entries'] = "{$alias_cnt}"; break; } } } if (!$pfbfound) { $pfb['dnsbl_info_stats'][] = array ( 'groupname' => $alias, 'timestamp' => $dns_now, 'entries' => $alias_cnt, 'counter' => 0); } } elseif ($mode == 'disabled') { // Record disabled alias statistics $pfbfound = FALSE; if (!empty($pfb['dnsbl_info_stats'])) { foreach ($pfb['dnsbl_info_stats'] as $line) { if ($line['groupname'] == "{$alias}") { $pfbfound = TRUE; break; } } } if (!$pfbfound) { $dns_now = date('M j H:i:s', time()); $pfb['dnsbl_info_stats'][] = array ('groupname' => $alias, 'timestamp' => $dns_now, 'entries' => 'disabled', 'counter' => 0); } } } // Function to save DNSBL Group statistics function dnsbl_save_stats() { global $pfb; // Save group statistics to SQLite3 database (Remove any feeds that are not referenced) $db_update = $db_delete = ''; pfb_logger("\nSaving DNSBL statistics...", 1); // Collect existing SQL group names $sql_groupnames = array(); $db_handle = pfb_open_sqlite(1, 'Collect Group'); if ($db_handle) { $result = $db_handle->query("SELECT * FROM dnsbl;"); if ($result) { while ($res = $result->fetchArray(SQLITE3_ASSOC)) { $sql_groupnames[$res['groupname']] = ''; } } } pfb_close_sqlite($db_handle); // Compare SQL database Group names to latest Group names if (!empty($pfb['dnsbl_info_stats'])) { $db_handle = pfb_open_sqlite(1, 'Save DNSBL stats'); if ($db_handle) { foreach ($pfb['dnsbl_info_stats'] as $group) { // Keep row $pfb_delete = FALSE; if (in_array($group['groupname'], $pfb['alias_dnsbl_all'])) { // Update existing row if (isset($sql_groupnames[$group['groupname']])) { $db_update = "UPDATE dnsbl SET timestamp = :timestamp, entries = :entries" . " WHERE groupname = :groupname;\n"; } // Add new row else { $db_update = "INSERT INTO dnsbl (groupname, timestamp, entries, counter)" . " VALUES (:groupname, :timestamp, :entries, 0);\n"; } } // Remove row else { $db_update = "DELETE FROM dnsbl WHERE groupname = :groupname;\n"; $pfb_delete = TRUE; } if (is_numeric($group['entries'])) { $group['groupname'] = pfb_filter($group['groupname'], 1); $stmt = $db_handle->prepare($db_update); if ($stmt) { $stmt->bindValue(':groupname', $group['groupname'], SQLITE3_TEXT); if (!$pfb_delete) { $group['timestamp'] = pfb_filter($group['timestamp'], 1); $stmt->bindValue(':timestamp', $group['timestamp'], SQLITE3_TEXT); $stmt->bindValue(':entries', $group['entries'], SQLITE3_TEXT); } $stmt->execute(); } } } } pfb_close_sqlite($db_handle); } else { $db_delete = 'DROP TABLE dnsbl;'; $db_handle = pfb_open_sqlite(1, 'Delete table'); if ($db_handle) { $db_handle->exec("BEGIN TRANSACTION;" . "{$db_delete}" . "END TRANSACTION;"); } pfb_close_sqlite($db_handle); } pfb_logger(" completed [ NOW ]", 1); } // Function to create DNSBL Lighttpd configuration file function pfb_create_lighttpd() { global $pfb; $lighttpd_bind = $pfb['dnsbl_iface'] != 'lo0' ? "127.0.0.1" : "{$pfb['dnsbl_vip']}"; $lighttpd_port = $pfb['dnsbl_iface'] != 'lo0' ? "{$pfb['dnsbl_port']}" : '80'; $pfb_conf = ''; $pfb_conf = << "text/html", ".gif" => "image/gif" ) url.access-deny = ( "~", ".inc" ) fastcgi.server = ( ".php" => ( "localhost" => ( "socket" => "/var/run/php-fpm.socket", "broken-scriptfilename" => "enable" ) ) ) EOF; if (!$pfb['dnsbl_py_blacklist'] || $pfb['dnsbl_py_nolog'] == 'on') { $pfb_conf .= <<&1'); if ((!$pfb['dnsbl_py_blacklist'] || $pfb['dnsbl_py_nolog'] == 'on') && (strpos($lighty_ver, 'lighttpd/1.4.58') !== FALSE || strpos($lighty_ver, 'lighttpd/1.4.59') !== FALSE || strpos($lighty_ver, 'lighttpd/1.4.60') !== FALSE || strpos($lighty_ver, 'lighttpd/1.4.61') !== FALSE || strpos($lighty_ver, 'lighttpd/1.5') !== FALSE)) { $pfb_conf .= << "/index.php" ) } \$HTTP["remoteip"] =~ ".*" { EOF; if ($pfb['dnsbl_iface'] != 'lo0') { $pfb_conf .= << "-ALL, TLSv1.2") } \$SERVER["socket"] == "{$pfb['dnsbl_vip']}:80" { # } EOF; } $pfb_conf .= << "-ALL, TLSv1.2") } EOF; if ($pfb['dnsbl_iface'] != 'lo0') { $pfb_conf .= << "-ALL, TLSv1.2") } EOF; } if ($pfb['dnsbl_v6'] == 'on') { $pfb_conf .= << "-ALL, TLSv1.2") } EOF; } $pfb_conf .= << "/index.php" ) } } EOF; return $pfb_conf; } // Function to create DNSBL SSL certificate function pfb_create_dnsbl_cert() { global $pfb, $config, $cert_strict_values; $cert = array(); $cert['refid'] = uniqid(); $cert['descr'] = sprintf(gettext("pfBlockerNG DNSBL (%s)"), $cert['refid']); $cert_hostname = "{$config['system']['hostname']}-pfBNG-DNSBL-{$cert['refid']}"; $dn = array( 'organizationName' => "pfBlockerNG DNSBL Self-Signed Certificate", 'commonName' => $cert_hostname, 'subjectAltName' => "DNS:{$cert_hostname}"); $old_err_level = error_reporting(0); /* otherwise openssl_ functions throw warnings directly to a page screwing menu tab */ if (!cert_create($cert, null, 2048, $cert_strict_values['max_server_cert_lifetime'] ?: 398, $dn, 'self-signed', 'sha256')) { while ($ssl_err = openssl_error_string()) { log_error(sprintf(gettext("Error creating pfBlockerNG DNSBL Certificate: openssl library returns: %s"), $ssl_err)); } error_reporting($old_err_level); return null; } error_reporting($old_err_level); $privatekey = base64_decode($cert['prv']); $publickey = base64_decode($cert['crt']); @file_put_contents("{$pfb['dnsbl_cert']}", "{$privatekey}{$publickey}", LOCK_EX); } // Create DNSBL VIP and NAT rules, lighttpd conf and services function pfb_create_dnsbl($mode) { global $config, $pfb; pfb_global(); // Reload config.xml to get any recent changes $config = parse_config(true); $new_nat = $new_vip = $pfb_ex_nat = $pfb_ex_vip = $dnsbl_ex_nat = $dnsbl_ex_vip = array(); $pfb['dnsbl_vip_changed'] = $pfbupdate = FALSE; $iface = escapeshellarg(get_real_interface($pfb['dnsbl_iface'])); if ((!empty($pfb['dnsbl_port']) && !empty($pfb['dnsbl_port_ssl']) && !empty($pfb['dnsbl_vip']) && $mode == 'enabled') || $mode == 'disabled') { // DNSBL NAT rules generation $pfbfound = FALSE; // Collect existing pfSense NAT rules if (isset($config['nat']['rule'])) { foreach ($config['nat']['rule'] as $ex_nat) { if (strpos($ex_nat['descr'], 'pfB DNSBL') !== FALSE) { // Collect DNSBL NAT rules $dnsbl_ex_nat[] = $ex_nat; $pfbfound = TRUE; } else { // Collect all 'other' NAT rules $pfb_ex_nat[] = $ex_nat; } } } if ($mode == 'enabled') { // Generate new DNSBL NAT per DNSBL listening ports except for 'localhost' interface setting. $dnsbl_new_nat = array(); if ($pfb['dnsbl_iface'] != 'lo0') { $selected_ports = array("{$pfb['dnsbl_port']}" => '80', "{$pfb['dnsbl_port_ssl']}" => '443'); foreach ($selected_ports as $port => $lport) { $dnsbl_new_nat[] = array ( 'source' => array('any' => ''), 'destination' => array('address' => "{$pfb['dnsbl_vip']}", 'port' => "{$lport}"), 'protocol' => 'tcp', 'target' => '127.0.0.1', 'local-port' => "{$port}", 'interface' => "{$pfb['dnsbl_iface']}", 'descr' => 'pfB DNSBL - DO NOT EDIT', 'associated-rule-id' => 'pass', 'natreflection' => 'purenat' ); // Add DNSBL IPv6 NAT Rules if ($pfb['dnsbl_v6'] == 'on') { $dnsbl_new_nat[] = array ( 'source' => array('any' => ''), 'destination' => array('address' => "::{$pfb['dnsbl_vip']}", 'port' => "{$lport}"), 'protocol' => 'tcp', 'target' => '::1', 'local-port' => "{$port}", 'interface' => "{$pfb['dnsbl_iface']}", 'ipprotocol' => 'inet6', 'descr' => 'pfB DNSBL - DO NOT EDIT', 'associated-rule-id' => 'pass', 'natreflection' => 'purenat' ); } } } // Compare existing to new and if they are not identical update if ($dnsbl_ex_nat !== $dnsbl_new_nat) { $pfbupdate = TRUE; $new_nat = array_merge($pfb_ex_nat, $dnsbl_new_nat); } else { $new_nat = array_merge($pfb_ex_nat, $dnsbl_ex_nat); } } else { $new_nat = array_merge($pfb_ex_nat, $new_nat); // Update when DNSBL NAT found but is now disabled. if ($pfbfound) { $pfbupdate = TRUE; } } // DNSBL VIP generation $dnsbl_new_vip = array(); $dnsbl_new_vip[0] = array(); $dnsbl_new_vip[0]['interface'] = "{$pfb['dnsbl_iface']}"; $dnsbl_new_vip[0]['descr'] = 'pfB DNSBL - DO NOT EDIT'; $dnsbl_new_vip[0]['type'] = 'single'; $dnsbl_new_vip[0]['subnet_bits'] = '32'; $dnsbl_new_vip[0]['subnet'] = "{$pfb['dnsbl_vip']}"; if ($pfb['dnsbl_vip_type'] == 'carp') { $dnsbl_new_vip[0]['mode'] = 'carp'; $dnsbl_new_vip[0]['vhid'] = "{$pfb['dnsbl_vip_vhid']}"; $dnsbl_new_vip[0]['advskew'] = "{$pfb['dnsbl_vip_skew']}"; $dnsbl_new_vip[0]['advbase'] = "{$pfb['dnsbl_vip_base']}"; $dnsbl_new_vip[0]['password'] = "{$pfb['dnsbl_vip_pass']}"; } else { $dnsbl_new_vip[0]['mode'] = 'ipalias'; } // Add DNSBL IPv6 VIP if ($pfb['dnsbl_v6'] == 'on') { $dnsbl_new_vip[1] = array(); $dnsbl_new_vip[1]['interface'] = "{$pfb['dnsbl_iface']}"; $dnsbl_new_vip[1]['descr'] = 'pfB DNSBL - DO NOT EDIT'; $dnsbl_new_vip[1]['type'] = 'single'; $dnsbl_new_vip[1]['subnet_bits'] = '128'; $dnsbl_new_vip[1]['subnet'] = "::{$pfb['dnsbl_vip']}"; if ($pfb['dnsbl_vip_type'] == 'carp') { $dnsbl_new_vip[1]['mode'] = 'carp'; $dnsbl_new_vip[1]['vhid'] = "{$pfb['dnsbl_vip_vhid']}"; $dnsbl_new_vip[1]['advskew'] = "{$pfb['dnsbl_vip_skew']}"; $dnsbl_new_vip[1]['advbase'] = "{$pfb['dnsbl_vip_base']}"; $dnsbl_new_vip[1]['password'] = "{$pfb['dnsbl_vip_pass']}"; } else { $dnsbl_new_vip[1]['mode'] = 'ipalias'; } } $pfbfound = FALSE; // Collect existing pfSense VIPs if (isset($config['virtualip']['vip'])) { foreach ($config['virtualip']['vip'] as $ex_vip) { if (strpos($ex_vip['descr'], 'pfB DNSBL') !== FALSE) { // Collect DNSBL VIP $dnsbl_ex_vip[] = $ex_vip; $pfbfound = TRUE; } else { // Collect all 'other' VIPs $pfb_ex_vip[] = $ex_vip; } } } if ($mode == 'enabled') { // Compare existing to new and if they are not identical update if ($dnsbl_ex_vip !== $dnsbl_new_vip) { $pfb['dnsbl_vip_changed'] = TRUE; $pfbupdate = TRUE; $new_vip = array_merge($pfb_ex_vip, $dnsbl_new_vip); } else { $new_vip = array_merge($pfb_ex_vip, $dnsbl_ex_vip); } } else { $new_vip = array_merge($pfb_ex_vip, $new_vip); // Update when DNSBL NAT found but is now disabled. if ($pfbfound) { $pfbupdate = TRUE; } } // Compare previous Lighttpd conf $pfb_lighty_conf = pfb_create_lighttpd(); $pfb_lighty_conf_ex = @file_get_contents($pfb['dnsbl_conf']); if ($pfb_lighty_conf !== $pfb_lighty_conf_ex) { $pfbupdate = TRUE; @file_put_contents($pfb['dnsbl_conf'], $pfb_lighty_conf, LOCK_EX); $log = "\nSaving new DNSBL web server configuration to port [ {$pfb['dnsbl_port']} and {$pfb['dnsbl_port_ssl']} ]"; pfb_logger("{$log}", 1); } // Validate DNSBL VIP address(es) $pfb['dnsbl_v6'] == 'on' ? $vip_count = 2 : $vip_count = 1; $result = array(); foreach (array("inet {$pfb['dnsbl_vip']}", "inet6 ::{$pfb['dnsbl_vip']}") as $g_vip) { $g_vip = escapeshellarg($g_vip); exec("/sbin/ifconfig {$iface} | {$pfb['grep']} {$g_vip} 2>&1", $result, $retval); } if (count($result) != $vip_count) { $pfbupdate = TRUE; } // Update config.xml, if changes required if ($pfbupdate) { init_config_arr(array('nat', 'rule')); $config['nat']['rule'] = $new_nat; init_config_arr(array('virtualip', 'vip')); $config['virtualip']['vip'] = $new_vip; write_config('pfBlockerNG: saving DNSBL changes'); } if ($mode == 'enabled' && $pfbupdate) { // Execute ifconfig to enable VIP address if (!empty($iface) && !empty($pfb['dnsbl_vip'])) { if (is_service_running('pfb_dnsbl')) { pfb_logger("\nStop Service DNSBL", 1); stop_service('pfb_dnsbl'); } foreach (array("{$pfb['dnsbl_vip']}", "::{$pfb['dnsbl_vip']}") as $vip) { $mask = '32'; $inet = 'inet'; if (strpos($vip, '::') !== FALSE) { $mask = '128'; $inet = 'inet6'; } $g_vip = escapeshellarg("{$inet} {$vip}"); $vip = escapeshellarg($vip); // Clear any existing VIP $result = exec("/sbin/ifconfig {$iface} | {$pfb['grep']} {$g_vip} 2>&1"); if (!empty($result)) { exec("/sbin/ifconfig {$iface} {$inet} {$vip} -alias 2>&1"); } if ($inet == 'inet6' && $pfb['dnsbl_v6'] != 'on') { break; } // Configure VIP type (ipalias or carp) if ($pfb['dnsbl_vip_type'] == 'carp') { $vhid = "vhid {$pfb['dnsbl_vip_vhid']}"; $advbase = "advbase {$pfb['dnsbl_vip_base']}"; $advskew = "advskew {$pfb['dnsbl_vip_skew']}"; if (isset($config['virtualip_carp_maintenancemode'])) { $advskew = 'advskew 254'; } $password = ''; if (!empty($pfb['dnsbl_vip_pass'])) { $password = 'pass ' . escapeshellarg(addslashes(str_replace(' ', '', $pfb['dnsbl_vip_pass']))); } exec("/sbin/ifconfig {$iface} {$inet} {$vhid} {$advskew} {$advbase} {$password} 2>&1"); exec("/sbin/ifconfig {$iface} {$inet} '{$vip}/{$mask}' alias {$vhid} 2>&1"); } else { exec("/sbin/ifconfig {$iface} {$inet} '{$vip}/{$mask}' alias 2>&1"); } // Validate 'tentative' interface state for ($i=10; $i > 0; $i--) { $result = exec("/sbin/ifconfig {$iface} | {$pfb['grep']} {$g_vip} | {$pfb['grep']} 'tentative' 2>&1"); if (!empty($result)) { pfb_logger('.', 1); usleep(500000); } else { break; } } } $log = "\nVIP address(es) configured"; pfb_logger("{$log}", 1); $pfb['filter_configure'] = TRUE; } else { $log = "DNSBL ifconfig error : Interface:{$iface}, VIP:{$pfb['dnsbl_iface']}\n"; pfb_logger("{$log}", 1); } } } // Save settings, restart services as required if ($mode == 'enabled') { // Create DNSBL SSL certificate if (!file_exists("{$pfb['dnsbl_cert']}")) { pfb_create_dnsbl_cert(); $log = "\nNew DNSBL certificate created"; pfb_logger("{$log}", 1); } if ($pfbupdate || !is_service_running('pfb_dnsbl')) { // Remove any existing and create link for DNSBL lighttpd executable unlink_if_exists('/usr/local/sbin/lighttpd_pfb'); link('/usr/local/sbin/lighttpd', '/usr/local/sbin/lighttpd_pfb'); $log = "\nRestarting DNSBL Service"; pfb_logger("{$log}", 1); restart_service('pfb_dnsbl'); } } else { if (is_service_running('pfb_dnsbl')) { pfb_logger("Stop Service DNSBL\n", 1); stop_service('pfb_dnsbl'); } // Remove DNSBL VIP address if (!empty($iface) && !empty($pfb['dnsbl_vip'])) { foreach (array("{$pfb['dnsbl_vip']}" => 'inet', "::{$pfb['dnsbl_vip']}" => 'inet6') as $vip => $inet) { $g_vip = escapeshellarg("{$inet} {$vip}"); $vip = escapeshellarg($vip); $result = exec("/sbin/ifconfig {$iface} | {$pfb['grep']} {$g_vip} 2>&1"); if (!empty($result)) { exec("/sbin/ifconfig {$iface} {$inet} {$vip} -alias 2>&1"); $pfb['filter_configure'] = TRUE; } // Validate 'tentative' interface state for ($i=10; $i > 0; $i--) { $result = exec("/sbin/ifconfig {$iface} | {$pfb['grep']} {$g_vip} | {$pfb['grep']} 'tentative' 2>&1"); if (!empty($result)) { pfb_logger('.', 1); usleep(500000); } else { break; } } } } } } // Define DNSBL Unbound include settings (config.xml) function pfb_unbound_dnsbl($mode) { global $g, $config, $pfb; pfb_global(); // Reload config.xml to get any recent changes $config = parse_config(true); $pfbupdate = FALSE; $unbound_include = "server:include: {$pfb['dnsbl_file']}.*conf"; // Collect Unbound custom option pfSense conf line init_config_arr(array('unbound')); $pfb['unboundconfig'] = &$config['unbound']['custom_options']; if (!empty($pfb['unboundconfig'])) { $unbound_custom = base64_decode($pfb['unboundconfig']); } else { $unbound_custom = ''; } // Determine if DNSBL include line exists if (!empty($unbound_custom)) { // Append DNSBL Unbound pfSense conf integration if (!strstr($unbound_custom, 'pfb_dnsbl.*conf')) { if ($mode == 'enabled') { if (!$pfb['dnsbl_py_blacklist']) { $pfbupdate = TRUE; $unbound_custom .= "\n{$unbound_include}"; $log = "\nAdding DNSBL Unbound mode (Resolver adv. setting)"; } } } else { // Remove DNSBL Unbound pfSense conf integration when disabled // or when DNSBL python mode is enabled but not when SafeSearch is enabled if ($mode == 'disabled' || $pfb['dnsbl_py_blacklist']) { $custom = explode ("\n", $unbound_custom); foreach ($custom as $key => $line) { if (strpos($line, 'pfb_dnsbl.*conf') !== FALSE) { $pfbupdate = TRUE; if (!$pfb['dnsbl_py_blacklist']) { $log = "\nRemoving DNSBL Unbound mode (Resolver adv. setting)"; } else { $log = "\nRemoving DNSBL SafeSearch mode (Resolver adv. setting)"; } unset($custom[$key]); } } $unbound_custom = implode("\n", $custom); } } } else { // Add DNSBL Unbound pfSense conf integration if ($mode == 'enabled') { if (!$pfb['dnsbl_py_blacklist']) { $pfbupdate = TRUE; $unbound_custom = "{$unbound_include}"; $log = "\nAdding DNSBL Unbound mode (Resolver adv. setting)"; } } } // Remove the previous include line, see Bug #6603 $custom = explode ("\n", $unbound_custom); foreach ($custom as $key => $line) { if (strpos($line, 'pfb_dnsbl.conf') !== FALSE) { $pfbupdate = TRUE; $log .= "\nDNSBL - Removing previous DNSBL Unbound custom option\n"; unset($custom[$key]); } } $unbound_custom = implode("\n", $custom); // Update config.xml, if changes required if ($pfbupdate) { pfb_logger("{$log}", 1); $unbound_custom = base64_encode(str_replace("\r\n", "\n", $unbound_custom)); $pfb['unboundconfig'] = "{$unbound_custom}"; write_config('pfBlockerNG: saving Unbound custom options'); } // Modify unbound.conf file as required if (file_exists("{$pfb['dnsbldir']}/unbound.conf")) { $conf = file("{$pfb['dnsbldir']}/unbound.conf"); if (empty($conf)) { pfb_logger("\nDNS Resolver configuration file missing or empty, Exiting!", 1); } $unbound = FALSE; // Unbound mode $unbound_py = FALSE; // Unbound python mode $u_update = FALSE; $u_msg = ''; foreach ($conf as $key => $line) { if (empty($line)) { continue; } elseif (strpos($line, 'pfb_dnsbl.*conf') !== FALSE) { if ($mode == 'enabled') { if (!$pfb['dnsbl_py_blacklist']) { $unbound = TRUE; } else { $u_update = TRUE; $u_msg .= " Removed DNSBL SafeSearch mode\n"; unset($conf[$key]); } } else { $u_update = TRUE; $u_msg .= " Removed DNSBL Unbound mode\n"; unset($conf[$key]); } } elseif (strpos($line, 'module-config:') !== FALSE) { if ($mode == 'enabled' && $pfb['dnsbl_mode'] == 'dnsbl_python') { if (strpos($line, 'module-config: "python') !== FALSE) { $unbound_py = TRUE; } else { $u_update = TRUE; $u_msg .= " Added DNSBL Unbound Python mode\n"; $conf[$key] = str_replace('module-config: "', 'module-config: "python ', $line); } } else { // Only remove python module if script is 'pfb_unbound' if (strpos($line, 'module-config: "python') !== FALSE && in_array("python-script: pfb_unbound.py\n", $conf)) { $u_update = TRUE; $u_msg .= " Removed DNSBL Unbound Python mode\n"; $conf[$key] = str_replace('module-config: "python ', 'module-config: "', $line); } } } // Remove any DNSBL VIPs added to unbound.conf on 'disable' elseif ((strpos($line, 'interface:') !== FALSE) && ($mode == 'disabled') && !empty($pfb['dnsbl_vip']) && (strpos($line, $pfb['dnsbl_vip']) !== FALSE)) { $u_update = TRUE; $u_msg .= " Removed DNSBL VIP from Unbound Interface settings\n"; unset($conf[$key]); } elseif (strpos($line, 'python-script: pfb_unbound.py') !== FALSE) { if ($mode == 'enabled' && $pfb['dnsbl_mode'] == 'dnsbl_python') { $unbound_py = TRUE; } else { $u_update = TRUE; $u_msg .= " Removed DNSBL Unbound Python mode script\n"; unset($conf[$key-1]); // remove 'python:' line above unset($conf[$key]); } } } // Add Unbound include line if (!$unbound && $mode == 'enabled') { if (!$pfb['dnsbl_py_blacklist']) { $u_update = TRUE; $u_msg .= " Added DNSBL Unbound mode\n"; $conf[] = "\nserver:include: {$pfb['dnsbl_file']}.*conf\n"; } } // Add python script line if (!$unbound_py && $mode == 'enabled' && $pfb['dnsbl_mode'] == 'dnsbl_python') { $u_update = TRUE; $u_msg .= " Added DNSBL Unbound Python mode script\n"; $conf[] = "\npython:\npython-script: pfb_unbound.py\n"; } if ($mode == 'enabled' && $pfb['dnsbl_mode'] == 'dnsbl_python') { if (!file_exists("{$g['unbound_chroot_path']}/pfb_unbound.py")) { @copy("/usr/local/pkg/pfblockerng/pfb_unbound.py", "{$g['unbound_chroot_path']}/pfb_unbound.py"); } if (!file_exists("{$g['unbound_chroot_path']}/pfb_unbound_include.inc")) { @copy("/usr/local/pkg/pfblockerng/pfb_unbound_include.inc", "{$g['unbound_chroot_path']}/pfb_unbound_include.inc"); } if (!file_exists("{$g['unbound_chroot_path']}/pfb_py_hsts.txt")) { @copy("/usr/local/pkg/pfblockerng/pfb_py_hsts.txt", "{$g['unbound_chroot_path']}/pfb_py_hsts.txt"); } } else { unlink_if_exists("{$g['unbound_chroot_path']}/pfb_unbound.py"); unlink_if_exists("{$g['unbound_chroot_path']}/pfb_unbound_include.inc"); unlink_if_exists("{$g['unbound_chroot_path']}/pfb_py_hsts.txt"); } // Save changes to unbound.conf if ($u_update) { pfb_logger("\nDNS Resolver ( {$mode} ) unbound.conf modifications:\n{$u_msg}", 1); @file_put_contents("{$pfb['dnsbldir']}/unbound.tmp", $conf, LOCK_EX); @chown("{$pfb['dnsbldir']}/unbound.tmp", 'unbound'); @chgrp("{$pfb['dnsbldir']}/unbound.tmp", 'unbound'); return TRUE; } } else { pfb_logger("\n\n*** [ Unbound.conf file missing. Exiting! ] ***\n\n", 1); } return FALSE; } // Create DNSBL Whitelist function pfb_unbound_python_whitelist($mode='') { global $pfb; pfb_global(); $dnsbl_whitelist = ''; $dnsbl_white = pfbng_text_area_decode($pfb['dnsblconfig']['suppression'], TRUE, FALSE, TRUE); if (!empty($dnsbl_white)) { foreach ($dnsbl_white as $key => $line) { if (!empty($line)) { if (substr($line, 0, 4) == 'www.') { $line = substr($line, 4); } // Minimize the python whitelist queries to the smallest tld segment count if (!isset($tld_segments)) { $tld_segments = (substr_count($line, '.') +1); } $tld_segments = @min((array((substr_count($line, '.') +1), $tld_segments) ?: 1)); if (substr($line, 0, 1) == '.') { $line = ltrim($line, '.'); $dnsbl_whitelist .= "{$line},1\n"; } else { $dnsbl_whitelist .= "{$line},0\n"; } } } } if ($mode == 'alerts') { @file_put_contents($pfb['unbound_py_wh'], $dnsbl_whitelist, LOCK_EX); } else { return $dnsbl_whitelist; } } // Unbound python configuration file function pfb_unbound_python($mode) { global $config, $pfb; pfb_global(); // Reload config.xml to get any recent changes $config = parse_config(true); $pfbpython = FALSE; init_config_arr(array('unbound')); // Ensure log file permissions are set as 'unbound:unbound' foreach (array('dnsbl.log', 'dns_reply.log', 'unified.log') as $logfile) { if (!file_exists("{$pfb['logdir']}/{$logfile}")) { touch("{$pfb['logdir']}/{$logfile}"); } @chown("{$pfb['logdir']}/{$logfile}", 'unbound'); @chgrp("{$pfb['logdir']}/{$logfile}", 'unbound'); } // Add python settings to DNS Resolver configuration $python_enable = 'off'; if ($mode == 'enabled' && $pfb['dnsbl_mode'] == 'dnsbl_python') { $python_enable = 'on'; if (!isset($config['unbound']['python']) || $config['unbound']['python_script'] != 'pfb_unbound') { $config['unbound']['python'] = ''; $config['unbound']['python_order'] = 'pre_validator'; $config['unbound']['python_script'] = 'pfb_unbound'; $pfbpython = TRUE; $log = 'Added DNSBL Unbound python integration settings'; pfb_logger("\n{$log}", 1); write_config("pfBlockerNG: {$log}"); } // If DNSBL python blocking mode enabled if ($pfb['dnsbl_py_blacklist']) { // Create DNSBL Whitelist $dnsbl_whitelist = pfb_unbound_python_whitelist(); // Compare previous DNSBL Whitelist to new Whitelist $pfb_py_whitelist_ex = @file_get_contents($pfb['unbound_py_wh']); if ($dnsbl_whitelist !== $pfb_py_whitelist_ex) { $pfbpython = TRUE; @file_put_contents($pfb['unbound_py_wh'], $dnsbl_whitelist, LOCK_EX); } } // Remove previous whitelist and reload elseif (file_exists($pfb['unbound_py_wh'])) { unlink_if_exists($pfb['unbound_py_wh']); $pfbpython = TRUE; } if (!isset($tld_segments)) { $tld_segments = '1'; } $python_ipv6 = 'off'; if ($pfb['dnsbl_v6'] == 'on') { $python_ipv6 = 'on'; } $python_reply = 'off'; if ($pfb['dnsbl_py_reply'] == 'on') { $python_reply = 'on'; } $python_blocking = 'off'; if ($pfb['dnsbl_py_blacklist']) { $python_blocking = 'on'; } $python_hsts = 'off'; if ($pfb['dnsbl_hsts'] == 'on') { $python_hsts = 'on'; } $python_idn = 'off'; if ($pfb['dnsbl_idn'] == 'on') { $python_idn = 'on'; } $python_cname = 'off'; if ($pfb['dnsbl_cname'] == 'on') { $python_cname = 'on'; } $python_control = 'off'; if ($pfb['dnsbl_control'] == 'on') { $python_control = 'on'; } $python_noaaaa = 'off'; if ($pfb['dnsbl_noaaaa'] == 'on') { $python_noaaaa = 'on'; } $python_tld = 'off'; if ($pfb['dnsbl_pytld'] == 'on') { $python_tlds = ''; if (!empty($pfb['dnsblconfig']['pfb_pytlds_gtld'])) { $python_tld = 'on'; $python_tlds = $pfb['dnsblconfig']['pfb_pytlds_gtld']; } if (!empty($pfb['dnsblconfig']['pfb_pytlds_cctld'])) { $python_tld = 'on'; $python_tlds .= ',' . $pfb['dnsblconfig']['pfb_pytlds_cctld']; } if (!empty($pfb['dnsblconfig']['pfb_pytlds_itld'])) { $python_tld = 'on'; $python_tlds .= ',' . $pfb['dnsblconfig']['pfb_pytlds_itld']; } if (!empty($pfb['dnsblconfig']['pfb_pytlds_bgtld'])) { $python_tld = 'on'; $python_tlds .= ',' . $pfb['dnsblconfig']['pfb_pytlds_bgtld']; } $python_tlds = ltrim($python_tlds, ','); } $python_nolog = 'off'; if ($pfb['dnsbl_py_nolog'] == 'on') { $python_nolog = 'on'; } $now = date('m/j/y H:i:s', time()); $pfb_py_conf = << $list) { if (!isset($list[1])) { $list[1] = "Regex_{$counter}"; } else { $list[1] = trim(ltrim($list[1], '#')); $list[1] = preg_replace("/\W/", '', str_replace(' ', '_', $list[1])); } $regex .= "{$list[1]} = {$list[0]}\n"; $counter++; } $pfb_py_conf .= << $list) { if (substr($list[0], 0, 1) == '.') { $list[0] = ltrim($list[0], '.'); $noaaaa .= "{$key} = {$list[0]},1\n"; } else { $noaaaa .= "{$key} = {$list[0]},0\n"; } } $pfb_py_conf .= << $list) { $gp_bypass .= "{$key} = {$list[0]}\n"; } $pfb_py_conf .= <<&1"); if ($mode == 'enabled' && empty($validate)) { if (!is_dir("{$pfb['dnsbldir']}{$base_py}{$dir}")) { $log .= "\n Creating: {$pfb['dnsbldir']}{$base_py}{$dir}"; safe_mkdir("{$pfb['dnsbldir']}{$base_py}{$dir}"); } $pfbpython = TRUE; $log .= "\n Mounting: /usr/local{$dir}"; exec("/sbin/mount_nullfs -o ro " . escapeshellarg("/usr/local{$dir}") . ' ' . escapeshellarg("{$pfb['dnsbldir']}{$base_py}{$dir}") . " 2>&1", $output, $retval); if ($retval != 0) { $log .= "\n Failed to mount [ /usr/local{$dir} ] to [ {$pfb['dnsbldir']}{$base_py}{$dir} ]!"; } } } if (!empty($log)) { pfb_logger("\nAdding DNSBL Unbound python mounts:{$log}\n", 1); } // Set flag to unmount python folders after the next reboot is completed to avoid crashing Unbound if ($mode == 'disabled') { $pfb['dnsbl_python_unmount'] = TRUE; } return $pfbpython; } // Unbound python unmount function pfb_unbound_python_unmount() { global $pfb; // Unmount lib and bin folders $base_py = '/usr/local'; $log = ''; foreach (array('/bin', '/lib') as $dir) { $validate = exec("/sbin/mount | {$pfb['grep']} '{$pfb['dnsbldir']}{$base_py}{$dir}' 2>&1"); if (!empty($validate)) { $log .= "\n Unmounting: /usr/local{$dir}"; exec("/sbin/umount -t nullfs {$pfb['dnsbldir']}{$base_py}{$dir} 2>&1", $output, $retval); if ($retval != 0) { $log .= "\n Failed to unmount [ {$pfb['dnsbldir']}{$base_py}{$dir} ]!"; } } foreach (array( "/usr/local{$dir}", '/usr/local', '/usr') as $folder) { if (is_dir("{$pfb['dnsbldir']}{$folder}")) { $log .= "\n Removing: {$pfb['dnsbldir']}{$folder}"; @rmdir("{$pfb['dnsbldir']}{$folder}"); } // Delete remaining subfolders on next loop if ($dir == '/bin') { break; } } } if (!empty($log)) { pfb_logger("\nRemoving DNSBL Unbound python mounts:{$log}\n", 1); } } // Search for TLD match function tld_search($tld, $dparts, $j, $k) { global $tlds; $tld_query = implode('.', array_slice($dparts, -$j, $j, TRUE)); if (isset($tlds[$tld][$tld_query])) { return implode('.', array_slice($dparts, -$k, $k, TRUE)); } return NULL; } // Function to determine if each Domain is a Sub-Domain ('transparent' zone) or a whole Domain ('redirect' zone) function tld_analysis() { global $pfb, $tlds; if (!file_exists("{$pfb['dnsbl_file']}.raw")) { pfb_logger("\n\nTLD Analysis not required.", 1); return; } pfb_logger("\nTLD:\n", 1); $domain_cnt = 0; $pfb_found = FALSE; // Flag to determine if TLD 'redirect' zones found rmdir_recursive("{$pfb['dnsbl_tmpdir']}"); if (!$pfb['dnsbl_py_blacklist']) { safe_mkdir("{$pfb['dnsbl_tmpdir']}"); } unlink_if_exists("{$pfb['dnsbl_file']}.tsp"); unlink_if_exists("{$pfb['dnsbl_tld_txt']}.*"); unlink_if_exists("{$pfb['dnsbl_tld_remove']}.tsp"); unlink_if_exists("{$pfb['dnsbl_tld_remove']}"); unlink_if_exists("{$pfb['dnsbl_tmp']}.sup"); unlink_if_exists("{$pfb['dnsbl_tmp']}.adup"); // Master TLD Domain list if (($t_handle = @fopen("{$pfb['dnsbl_tld_data']}", 'r')) !== FALSE) { while (($line = @fgets($t_handle)) !== FALSE) { $line = rtrim($line, "\x00..\x1F"); $tld = substr($line, strrpos($line, '.') + 1); if (!empty($tld)) { if (!is_array($tlds[$tld])) { $tlds[$tld] = array(); } $tlds[$tld][$line] = ''; } } @fclose($t_handle); } else { pfb_logger("\n ** TLD Master data file missing. Terminating TLD **\n", 1); return; } // DNSBL python - create file handles for data, zone, and remove files if ($pfb['dnsbl_py_blacklist']) { $p_data = @fopen("{$pfb['unbound_py_data']}.raw", 'w'); $p_zone = @fopen("{$pfb['unbound_py_zone']}.raw", 'w'); $p_tsp = @fopen("{$pfb['dnsbl_tld_remove']}", 'w'); if ((get_resource_type($p_data) != 'stream') || (get_resource_type($p_zone) != 'stream') || (get_resource_type($p_tsp) != 'stream')) { pfb_logger("\nFailed to create DNSBL python data|zone|remove file handles! Exiting\n", 1); return; } } // Collect TLD Blacklist(s). If configured the whole TLD will be blocked $tld_blacklist = pfbng_text_area_decode($pfb['dnsblconfig']['tldblacklist'], TRUE, FALSE, TRUE); if (!empty($tld_blacklist)) { $tld_blacklist = array_flip($tld_blacklist); } // Collect TLD Whitelist(s). If configured, create a 'static local-zone' Resolver entry (Not required for python mode blocking) $whitelist = array(); if (!$pfb['dnsbl_py_blacklist']) { $whitelist = pfbng_text_area_decode($pfb['dnsblconfig']['tldwhitelist'], TRUE, FALSE, TRUE); $tld_whitelist = array(); } if (!empty($tld_blacklist) && !empty($whitelist)) { foreach ($whitelist as $domain) { // Use user-defined IP address if (strpos($domain, '|') !== FALSE) { list($domain, $resolved_host) = array_map('trim', explode('|', $domain)); } // Resolve Domain IP address else { $resolved_host = exec("/usr/bin/drill @{$pfb['extdns']} {$domain} | grep -v '^;\|^$' | head -1 | cut -f5 2>&1"); } $tld = ''; if (strpos($domain, '.') !== FALSE && is_ipaddr($resolved_host)) { $dparts = explode('.', $domain); $dcnt = count($dparts); $tld = end($dparts); for ($i=($dcnt-1); $i > 0; $i--) { $d_query = implode('.', array_slice($dparts, -$i, $i, TRUE)); if (isset($tlds[$tld][$d_query])) { $tld = $d_query; break; } } } if (!empty($tld)) { if (!is_array($tld_whitelist[$tld])) { $tld_whitelist[$tld] = array(); } $tld_whitelist[$tld][] = array($domain, $resolved_host); pfb_logger(" TLD Whitelist {$domain}|{$resolved_host}\n", 1); } else { $resolved_host = htmlspecialchars($resolved_host) ?: 'No IP found!'; pfb_logger("\n TLD Whitelist - Missing data | {$domain} | {$resolved_host} |\n", 1); } } } // Process TLD Blacklist(s). If configured the whole TLD will be blocked if (!empty($tld_blacklist)) { $tld_list = ''; $tld_cnt = 0; $tld_segments = 0; pfb_logger(" Blocking full TLD/Sub-Domain(s)... |", 1); foreach ($tld_blacklist as $tld => $key) { unset($tld_blacklist[$tld]); // Remove old entry $tld = trim($tld, '.'); // Remove any leading/trailing dots $tld_blacklist[$tld] = ''; // Add new TLD entry // DNSBL python - TLD Blacklist (Set logging type to enabled '1') if ($pfb['dnsbl_py_blacklist']) { $tld_cnt++; $pfb_found = TRUE; $tld_segments = @max((array((substr_count($tld, '.') +1), $tld_segments) ?: 1)); pfb_logger("{$tld}|", 1); @fwrite($p_zone, ",{$tld},,1,DNSBL_TLD,DNSBL_TLD\n"); // Add TLD to remove file @fwrite($p_tsp, ".{$tld},,\n"); // Collect List of TLDs and save to DNSBL folder $tld_list .= ",{$tld},,\n"; // Remove any 'TLD Blacklists' from the 'TLD master list' if (isset($tlds[$tld])) { unset($tlds[$tld]); } continue; } // (Unbound mode only. Cannot have duplicate zones defined elseif (!empty($pfb['safesearch_tlds']) && isset($pfb['safesearch_tlds'][$tld])) { pfb_logger("\n{$tld}(Removed due to SafeSearch conflict)", 1); continue; } $dnsbl_file = "{$pfb['dnsbl_tmpdir']}/DNSBL_{$tld}.txt"; if (!file_exists($dnsbl_file)) { $tld_cnt++; $pfb_found = TRUE; $tld_segments = @max((array((substr_count($tld, '.') +1), $tld_segments) ?: 1)); // If a 'TLD Whitelist' exists, use 'static local-zone' if (isset($tld_whitelist[$tld])) { pfb_logger("{$tld}(static)|", 1); $dnsbl_line = "local-zone: \"{$tld}\" \"static\"\n"; $whitelist = $tld_whitelist[$tld]; foreach ($whitelist as $list) { $ip_type = is_ipaddr($list[1]); switch ($ip_type) { case 4: $dnsbl_line .= "local-data: \"{$list[0]} A {$list[1]}\"\n"; $tld_list .= "{$tld} | {$list[0]} A {$list[1]}\n"; if ($pfb['dnsbl_v6'] == 'on') { $r = exec("/usr/bin/drill @{$pfb['extdns']} AAAA {$list[0]} | grep -v '^;\|^$' | head -1 | cut -f5 2>&1"); if (is_ipaddrv6($r)) { $dnsbl_line .= "local-data: \"{$list[0]} AAAA {$r}\"\n"; $tld_list .= "{$list[0]} AAAA {$r}\"\n"; } } break; case 6: $r = exec("/usr/bin/drill @{$pfb['extdns']} A {$list[0]} | grep -v '^;\|^$' | head -1 | cut -f5 2>&1"); if (is_ipaddrv4($r)) { $dnsbl_line .= "local-data: \"{$list[0]} A {$r}\"\n"; $tld_list .= "{$tld} | {$list[0]} A {$r}\"\n"; } $dnsbl_line .= "local-data: \"{$list[0]} AAAA {$list[1]}\"\n"; $tld_list .= "{$tld} | {$list[0]} AAAA {$list[1]}\"\n"; break; default: break; } } } // Create 'redirect' zone for whole TLD else { pfb_logger("{$tld}|", 1); $ipv6_dnsbl = ''; if ($pfb['dnsbl_v6'] == 'on') { $ipv6_dnsbl = " local-data: \"{$tld} 60 IN AAAA ::{$pfb['dnsbl_vip']}\""; } $dnsbl_line = "local-zone: \"{$tld}\" redirect local-data: \"{$tld} 60 IN A {$pfb['dnsbl_vip']}\"{$ipv6_dnsbl}\n"; // Collect List of TLDs and save to DNSBL folder $tld_list .= "{$tld}\n"; } @file_put_contents($dnsbl_file, $dnsbl_line, LOCK_EX); // Add TLD to remove file (To be removed from 'transparent' zone) @file_put_contents("{$pfb['dnsbl_tld_remove']}.tsp", ".{$tld} 60\n", FILE_APPEND | LOCK_EX); // Remove any 'TLD Blacklists' from the 'TLD master list' if (isset($tlds[$tld])) { unset($tlds[$tld]); } } } // Save a list of TLDs in DNSBL folder (DNSBL total line count verification) if (!empty($tld_list)) { @file_put_contents("{$pfb['dnsbl_tld_txt']}", $tld_list, LOCK_EX); // Add 'TLD' to Alias/Feeds array if (!is_array($pfb['tld_update']['DNSBL_TLD'])) { $pfb['tld_update']['DNSBL_TLD'] = array(); } $pfb['tld_update']['DNSBL_TLD']['feeds'] = array('DNSBL_TLD'); $pfb['tld_update']['DNSBL_TLD']['count'] = $tld_cnt; $pfb['alias_dnsbl_all'][] = 'DNSBL_TLD'; } else { unlink_if_exists("{$pfb['dnsbl_tld_txt']}"); } pfb_logger(" completed\n", 1); } else { unlink_if_exists("{$pfb['dnsbl_tld_txt']}"); } // Collect TLD Exclusion list and remove any 'TLD Exclusions' from the 'TLD master list' $exclusion = pfbng_text_area_decode($pfb['dnsblconfig']['tldexclusion'], TRUE, FALSE, TRUE); $tld_exclusion = array(); if (!empty($exclusion)) { foreach ($exclusion as $key => $exclude) { $exclude = trim($exclude, '.'); // Remove any leading/trailing dots // Collect exclusion if (strpos($exclude, '.') !== FALSE) { $tld_exclusion[$exclude] = ''; } // Remove Exclusion from TLDS array if (isset($tlds[$exclude])) { unset($tlds[$exclude]); } } } pfb_logger("TLD analysis", 1); // [ $pfb['dnsbl_file']}.tsp ] Final DNSBL output file (using 'transparent' zone) // [ $pfb['dnsbl_tld_remove'] ] File of Sub-Domains to be removed (from 'redirect' zone) // DNSBL Unbound: Analyse DNSBL: 1) 'redirect' zone for whole Domain 2) 'transparent' zone only // DNSBL python: Analyse DNSBL into local and zone files if (($fhandle = @fopen("{$pfb['dnsbl_file']}.raw", 'r')) !== FALSE) { while (($line = @fgets($fhandle)) !== FALSE) { if (empty($line)) { continue; } // Display progress indicator if ($domain_cnt % 100000 == 0) { // Memory limitation exceeded for 'redirect' zones if ($domain_cnt >= $pfb['domain_max_cnt']) { pfb_logger('x', 1); } else { pfb_logger('.', 1); } } // DNSBL python blocking mode if ($pfb['dnsbl_py_blacklist']) { $eparts = explode(',', $line, 3); $domain = $eparts[1]; $dparts = explode('.', $domain); $dcnt = count($dparts); $tld = end($dparts); $d_info = $eparts[2]; // Logging Type/Header/Alias group details $dfound = ''; } // DNSBL Unbound blocking mode else { $eparts = explode(' ', str_replace('"', '', $line), 3); $domain = $eparts[1]; $s_info = trim($eparts[2]); if ($pfb['dnsbl_v6'] == 'on') { // Determine if DNSBL Logging is disabled and switch to '::0' if (strpos($s_info, ' A 0.0.0.0') !== FALSE) { $s_info6 = str_replace(' A 0.0.0.0', ' AAAA ::0', $s_info); } else { $s_info6 = str_replace(' A ', ' AAAA ::', $s_info); } } $dparts = explode('.', $domain); $dcnt = count($dparts); $tld = end($dparts); $dfound = ''; } // Determine if TLD exists in TLD Blacklist (skip for DNSBL python) if (!$pfb['dnsbl_py_blacklist'] && !empty($tld_blacklist)) { // Determine minimum 'tld level' for loop efficiency $min_cnt = @min(array($tld_segments, ($dcnt -1))); for ($i=1; $i <= $min_cnt; $i++) { $d_query = implode('.', array_slice($dparts, -$i, $i, TRUE)); if (isset($tld_blacklist[$d_query])) { continue 2; // Whole TLD being blocked } } } if ($domain_cnt <= $pfb['domain_max_cnt']) { // Search TLD master list (Levels 1-4) // If Domain is a Sub-Domain, create 'transparent' zone. Otherwise create 'redirect' zone switch($dcnt) { case ($dcnt > 5): break; case '5': $dfound = tld_search($tld, $dparts, 4, 5); break; case '4': $dfound = tld_search($tld, $dparts, 3, 4); break; case '3': $dfound = tld_search($tld, $dparts, 2, 3); break; case '2': $dfound = implode('.', array_slice($dparts, -2, 2, TRUE)); break; } } // If Domain is in the TLD Exclusion(s), use 'transparent zone' if (!empty($domain) && isset($tld_exclusion[$domain])) { $dfound = ''; } // Create 'redirect' zone for Domain if (!empty($dfound)) { $pfb_found = TRUE; // DNSBL python blocking mode if ($pfb['dnsbl_py_blacklist']) { @fwrite($p_zone, ",{$dfound},{$d_info}"); // TLD remove files - See below for description @fwrite($p_tsp, ".{$dfound},,\n"); } else { $ipv6_dnsbl = ''; if ($pfb['dnsbl_v6'] == 'on') { $ipv6_dnsbl = " local-data: \"{$dfound} {$s_info6}\""; } $domain_line = "local-zone: \"{$dfound}\" redirect local-data: \"{$dfound} {$s_info}\"{$ipv6_dnsbl}\n"; @file_put_contents("{$pfb['dnsbl_file']}.tsp", $domain_line, FILE_APPEND | LOCK_EX); // Add Domain to remove file for [ 1- 'redirect zone' Domains 2- Unbound memory domains ] // This removes any of these domains and sub-domains @file_put_contents("{$pfb['dnsbl_tld_remove']}", ".{$dfound} 60\n\"{$dfound} 60\n", FILE_APPEND | LOCK_EX); // Add Domain to remove file for 'transparent zone' domains // This removes any of these sub-domains @file_put_contents("{$pfb['dnsbl_tld_remove']}.tsp", ".{$dfound} 60\n", FILE_APPEND | LOCK_EX); } } // Create 'transparent zone' for Sub-Domain else { // DNSBL python blocking mode if ($pfb['dnsbl_py_blacklist']) { @fwrite($p_data, ",{$domain},{$d_info}"); } else { if (!empty($tld)) { $dnsbl_file = "{$pfb['dnsbl_tmpdir']}/DNSBL_{$tld}.txt"; // Create a temp file for each TLD. w/ 'transparent' header followed by each 'local-data' line if (!file_exists($dnsbl_file)) { $dnsbl_header = "local-zone: \"{$tld}\" \"transparent\"\n"; @file_put_contents($dnsbl_file, $dnsbl_header, LOCK_EX); } $ipv6_dnsbl = ''; if ($pfb['dnsbl_v6'] == 'on') { $ipv6_dnsbl = " local-data: \"{$domain} {$s_info6}\""; } $domain_line = "local-data: \"{$domain} {$s_info}\"{$ipv6_dnsbl}\n"; @file_put_contents($dnsbl_file, $domain_line, FILE_APPEND | LOCK_EX); } else { $oline = htmlentities($line); pfb_logger("\nDebug: Missing TLD: {$oline}", 1); } } } // Increment Domain counter $domain_cnt++; } } @fclose($fhandle); @fclose($p_data); @fclose($p_zone); @fclose($p_tsp); unset($tlds, $tld_blacklist, $tld_exclusion); // TLD 'redirect zones' found. Finalize TLD function if ($pfb_found) { $log = " completed [ NOW ]\n"; // Print TLD exceedance error message if ($domain_cnt >= $pfb['domain_max_cnt']) { $log .= "\n ** TLD Domain count exceeded. [ {$pfb['domain_max_cnt']} ] All subsequent Domains listed as-is **\n\n"; } $log .= "TLD finalize"; pfb_logger("{$log}", 1); // Execute Domain De-duplication if ($pfb['dnsbl_py_blacklist']) { exec("{$pfb['script']} domaintldpy >> {$pfb['log']} 2>&1"); } else { // Create a csv list of 'recently updated' DNSBL Feeds, as ordered by User $dnsbl_feeds = ''; if (!empty($pfb['tld_update'])) { foreach ($pfb['tld_update'] as $alias => $data) { foreach ($data['feeds'] as $feed) { $dnsbl_feeds .= "{$feed},"; } } } exec("{$pfb['script']} domaintld x x x {$dnsbl_feeds} >> {$pfb['log']} 2>&1"); } pfb_logger("\nTLD finalize... completed [ NOW ]\n", 1); // Update DNSBL Alias and Widget Stats if (!empty($pfb['tld_update'])) { foreach ($pfb['tld_update'] as $alias => $data) { // Create Alias summary file for each DNSBL Alias $lists_dnsbl_current = array(); foreach ($data['feeds'] as $feed) { $lists_dnsbl_current[] = "{$feed}"; } dnsbl_alias_update('update', $alias, $pfb['dnsdir'], $lists_dnsbl_current, $data['count']); } } } else { pfb_logger(" no changes\n", 1); } // Save DNSBL Alias statistics dnsbl_save_stats(); if ($pfb['dnsbl_py_blacklist'] && file_exists("{$pfb['dnsbl_file']}.raw")) { unlink_if_exists("{$pfb['dnsbl_file']}.raw"); } } // Function to Start Unbound function pfb_stop_start_unbound($type) { global $g, $pfb; $final = array(); if (file_exists("{$g['varrun_path']}/unbound.pid")) { pfb_logger("\nStopping Unbound Resolver", 1); sigkillbypid("{$g['varrun_path']}/unbound.pid", 'TERM'); } // If unbound is still running, wait up to 30 seconds for it to terminate. for ($i=1; $i <= 30; $i++) { if (is_process_running('unbound')) { pfb_logger('.', 1); sleep(1); } else { pfb_logger("\nUnbound stopped in {$i} sec.", 1); break; } } // Add/Remove additional python mounts if (file_exists('/var/unbound/pfb_unbound_include.inc')) { $g['pfblockerng_include_verbose'] = TRUE; pfb_logger("\nAdditional mounts{$type}:", 1); require_once('/var/unbound/pfb_unbound_include.inc'); unset($g['pfblockerng_include_verbose']); } // Remove Unbound python mounts if ($pfb['dnsbl_python_unmount']) { pfb_unbound_python_unmount(); } pfb_logger("\nStarting Unbound Resolver", 1); exec("/usr/local/sbin/unbound -c /var/unbound/unbound.conf 2>&1", $final['result'], $final['retval']); return $final; } // Reload Resolver function pfb_reload_unbound($mode, $cache=FALSE, $pfbpython=FALSE) { global $g, $pfb; $final = array(); $type = ''; if ($mode == 'enabled' && $pfb['dnsbl_py_blacklist']) { $type = ' (DNSBL python)'; } if (!$pfb['dnsbl_py_blacklist'] && file_exists("{$pfb['dnsbl_file']}.raw")) { @rename("{$pfb['dnsbl_file']}.raw", "{$pfb['dnsbl_file']}.conf"); } unlink_if_exists("{$cache_dumpfile}"); if ($mode == 'enabled' && is_process_running('unbound') && !$pfb['dnsbl_python_unmount'] && !$pfbpython) { $log = "\nReloading Unbound Resolver{$type}"; pfb_logger($log, 1); if ($cache && $pfb['dnsbl_res_cache'] == 'on') { $cache_dumpfile = '/var/tmp/unbound_cache'; exec("{$pfb['chroot_cmd']} dump_cache > {$cache_dumpfile} 2>&1"); pfb_logger('.', 1); } } $final = pfb_stop_start_unbound($type); pfb_logger('.', 1); if ($final['retval'] != 0) { @copy("{$pfb['dnsbldir']}/unbound.conf", "{$pfb['dnsbldir']}/unbound.conf.error"); if ($mode == 'enabled') { if (!$pfb['dnsbl_py_blacklist']) { $log = "\nDNSBL {$mode} FAIL - restoring Unbound conf *** Fix error(s) and a Force Reload required! ***\n"; // Try to restore previous DNSBL database if (file_exists("{$pfb['dnsbl_file']}.bk")) { @rename("{$pfb['dnsbl_file']}.bk", "{$pfb['dnsbl_file']}.conf"); } // Wipe DNSBL database else { $log .= ' Restore previous database Failed!'; unlink_if_exists("{$pfb['dnsbl_file']}.conf"); touch("{$pfb['dnsbl_file']}.conf"); // Restore previous unbound.conf if (file_exists("{$pfb['dnsbldir']}/unbound.bk")) { @rename("{$pfb['dnsbldir']}/unbound.bk", "{$pfb['dnsbldir']}/unbound.conf"); } } } else { $log = "\nDNSBL {$mode} FAIL *** Fix error(s) and a Force Reload required! ***\n"; if (file_exists("{$pfb['dnsbldir']}/unbound.bk")) { @rename("{$pfb['dnsbldir']}/unbound.bk", "{$pfb['dnsbldir']}/unbound.conf"); } } pfb_logger("{$log}", 2); } else { $log = "\nDNSBL {$mode} - Unbound conf update FAIL *** Fix error(s) and a Force Reload required! ***\n"; pfb_logger("{$log}", 2); } $log = htmlspecialchars(implode("\n", $final['result'])); pfb_logger("\n\n====================\n\n{$log}\n\n====================\n\n", 2); $final = pfb_stop_start_unbound($type); } // Confirm that Resolver is running if (is_process_running('unbound')) { pfb_logger('.', 1); // $final['result'] will be appended with previous result above exec("{$pfb['chroot_cmd']} status 2>&1", $final['result'], $final['retval']); pfb_logger('.', 1); if (preg_grep("/is running.../", $final['result'])) { pfb_logger(" completed [ NOW ]", 1); // Restore Resolver cache if ($cache && $pfb['dnsbl_res_cache'] == 'on' && file_exists($cache_dumpfile) && filesize($cache_dumpfile) > 0) { exec("{$pfb['chroot_cmd']} load_cache < {$cache_dumpfile} 2>&1"); $log = "\nResolver cache restored [ NOW ]"; pfb_logger($log, 1); } } else { $log = htmlspecialchars(implode("\n", $final['result'])); pfb_logger(" Not completed. [ NOW ]\n{$log}\n", 1); } } else { $log = htmlspecialchars(implode("\n", $final['result'])); pfb_logger(" Not completed. [ NOW ]\n{$log}\n", 1); } } // Function to clear Unbound/DNSBL work files function pfb_unbound_clear_work_files() { global $pfb; foreach (array( $pfb['dnsbl_cache'], "{$pfb['dnsbldir']}/unbound.bk", "{$pfb['dnsbldir']}/unbound.tmp", "{$pfb['dnsbl_file']}.bk", "{$pfb['dnsbl_file']}.tsp", "{$pfb['dnsbl_file']}.sync", "/tmp/dnsbl_remove*", "/tmp/dnsbl_add*", "/tmp/dnsbl_tld*", "{$pfb['unbound_py_data']}.raw", "{$pfb['unbound_py_zone']}.raw", '/var/tmp/unbound_cache') as $remove) { unlink_if_exists($remove); } } // Load new DNSBL updates to Unbound Resolver function pfb_update_unbound($mode, $pfbupdate, $pfbpython) { global $g, $pfb; if ($mode == 'enabled') { $ext = '.bk'; } else { $ext = '.*'; // Remove all DNSBL Unbound files } // Execute TLD analysis, if configured if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && !$pfb['save']) { if ($pfb['dnsbl_tld']) { tld_analysis(); } else { unlink_if_exists("{$pfb['dnsbl_tld_txt']}"); } } // Create file marker to disable DNSBL Queries daemon to avoid unbound-control collisions touch("{$pfb['dnsbl_file']}.sync"); // Marker file(s) to instruct Unbound to be reloaded if ($pfb['reuse_dnsbl'] == 'on' || file_exists("{$pfb['dnsbl_file']}.reload") || file_exists("{$pfb['dnsbl_unlock']}")) { $pfbupdate = TRUE; unlink_if_exists("{$pfb['dnsbl_file']}.reload"); unlink_if_exists("{$pfb['dnsbl_unlock']}"); unlink_if_exists("{$pfb['dnsbl_unlock']}.data"); } // Backup existing unbound.conf and rename new unbound.conf file if (file_exists("{$pfb['dnsbldir']}/unbound.tmp")) { @copy("{$pfb['dnsbldir']}/unbound.conf", "{$pfb['dnsbldir']}/unbound.bk"); @rename("{$pfb['dnsbldir']}/unbound.tmp", "{$pfb['dnsbldir']}/unbound.conf"); } // When pfBlockerNG is disabled and 'keep blocklists' is disabled. if ($pfb['enable'] == '' && $pfb['keep'] == '' && !$pfb['install']) { unlink_if_exists("{$pfb['dnsbl_file']}{$ext}"); } // Disable DNSBL if (($pfb['enable'] != 'on' || $pfb['dnsbl'] != 'on') && !$pfb['install']) { pfb_reload_unbound('disabled', FALSE, $pfbpython); if (is_service_running('pfb_dnsbl')) { pfb_logger("\nStop Service DNSBL", 1); stop_service('pfb_dnsbl'); } pfb_unbound_clear_work_files(); // Unmount Unbound python 'lib/bin' folders after Unbound has been reloaded with the python integration enabled if ($pfb['dnsbl_python_unmount']) { pfb_unbound_python_unmount(); unset($pfb['dnsbl_python_unmount']); } pfb_logger("\nDNSBL is disabled\n", 1); return; } // Load new DNSBL updates if (is_service_running('unbound')) { // 'Live sync' new DNSBL updates utilizing unbound-control if (!$pfb['dnsbl_py_blacklist'] && $pfb['dnsbl_sync'] && !$pfbpython && file_exists("{$pfb['dnsbl_file']}.conf") && filesize("{$pfb['dnsbl_file']}.conf") > 0) { $sync_fail = FALSE; pfb_logger("\nResolver Live Sync analysis", 1); exec("{$pfb['script']} dnsbl_livesync >> {$pfb['log']} 2>&1"); pfb_logger(" completed [ NOW ]", 1); $ucsync = array(array( 'dnsbl_remove_zone', 'local_zones_remove', 'Remove local-zone(s)' ), array( 'dnsbl_remove_data', 'local_datas_remove', 'Remove local-data(s)' ), array( 'dnsbl_add_zone', 'local_zones', 'Add local-zone(s)' ), array( 'dnsbl_add_data', 'local_datas', 'Add local-data(s)' )); // Disable zone updates when TLD is disabled if (!$pfb['dnsbl_tld']) { unset($ucsync[0], $ucsync[2]); } pfb_logger("\nResolver Live Sync finalizing:", 1); foreach ($ucsync as $skey => $sync) { $file = $pfb[$sync[0]]; if (filesize("{$file}") > 0) { $result = array(); exec("{$pfb['chroot_cmd']} {$sync[1]} < {$file}", $result, $retval); $result = implode("\n", $result); $log = "\n\t{$sync[2]}:\t\t{$result}"; } else { $log = "\n\t{$sync[2]}:\t\tno changes"; } pfb_logger("{$log}", 1); if (!$sync_fail && !empty($retval)) { $sync_fail = TRUE; } } if ($sync_fail) { pfb_logger("\nResolver Live Sync ... FAILED!", 1); pfb_reload_unbound('reload', FALSE, $pfbpython); } } // Do a full Reload of Unbound else { pfb_reload_unbound($mode, TRUE, $pfbpython); } } // Start Unbound Service with new DNSBL Updates else { pfb_reload_unbound($mode, FALSE, $pfbpython); } if ($pfbpython) { $log = "\nRestarting DNSBL Service (DNSBL python)"; pfb_logger("{$log}", 1); restart_service('pfb_dnsbl'); } $dnsbl_cnt = exec("/bin/cat {$pfb['dnsdir']}/*.txt | {$pfb['grep']} -c ^ 2>&1"); // Unbound blocking mode enabled if (!$pfb['dnsbl_py_blacklist']) { $final_cnt = exec("{$pfb['grep']} -v '\"transparent\"\|\"static\"' {$pfb['dnsbl_file']}.conf | {$pfb['grep']} -c ^ 2>&1"); if ($final_cnt == $dnsbl_cnt) { $log = "\nDNSBL update [ {$final_cnt} | PASSED ]... completed [ NOW ]"; } else { $log = "\n*** DNSBL update [ {$final_cnt} ] [ {$dnsbl_cnt} ] ... OUT OF SYNC ! *** [ NOW ]"; } pfb_logger("{$log}", 1); } // Python blocking mode enabled else { $tld_cnt = @file_get_contents($pfb['unbound_py_count']); $dnsbl_cnt = $dnsbl_cnt - $tld_cnt; $final_cnt = exec("/usr/bin/find {$pfb['unbound_py_data']} {$pfb['unbound_py_zone']} -type f 2>/dev/null | xargs cat | {$pfb['grep']} -c ^ 2>&1"); if ($final_cnt == $dnsbl_cnt) { $log = "\nDNSBL update [ {$final_cnt} | PASSED ]... completed [ NOW ]"; } else { $log = "\n*** DNSBL update [ {$final_cnt} ] [ {$dnsbl_cnt} ] ... OUT OF SYNC ! *** [ NOW ]"; } pfb_logger("{$log}", 1); } // DEBUG Live Sync if (!$pfb['dnsbl_py_blacklist'] && $pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && $pfb['dnsbl_sync'] && !$pfbupdate && !$pfbpython && !$sync_fail) { pfb_logger("\n\nDNSBL DEBUG", 1); $datacnt = $zonecnt = 0; exec("{$pfb['chroot_cmd']} list_local_data | {$pfb['grep']} '{$pfb['dnsbl_vip']}\|0\.0\.0\.0$' | {$pfb['grep']} -v 'AAAA' | {$pfb['grep']} -c ^ 2>&1", $datacnt, $retval); $datacnt = implode($datacnt); pfb_logger('.', 1); if ($pfb['dnsbl_tld']) { exec("{$pfb['chroot_cmd']} list_local_zones | {$pfb['grep']} \"redirect\" | {$pfb['grep']} -c ^ 2>&1", $zonecnt, $retval); pfb_logger('.', 1); $zonecnt = implode($zonecnt); $tldcnt = array('0'); if (file_exists('/var/db/pfblockerng/dnsbl/DNSBL_TLD.txt')) { exec("{$pfb['grep']} -c ' A \| AAAA ' /var/db/pfblockerng/dnsbl/DNSBL_TLD.txt 2>&1", $tldcnt, $retval); } $tldcnt = implode($tldcnt); $datacnt = $datacnt + $tldcnt; } pfb_logger("[ Data(s): {$datacnt}\tZone(s): {$zonecnt} | NOW ]", 1); } // Clear work files pfb_unbound_clear_work_files(); pfb_logger("\n------------------------------------------------------------------------", 1); } // Process TOP1M database function pfblockerng_top1m() { global $pfb; if (empty($pfb['dnsbl_alexa_inc'])) { pfb_logger("\n TOP1M: No TLD Inclusions found.\n", 1); return; } // Array of TLDs to include in Whitelist $pfb_include = explode(',', $pfb['dnsbl_alexa_inc']); if (!empty($pfb_include)) { $pfb_include = array_flip($pfb_include); } $linecnt = $x = 0; pfb_logger(" Building TOP1M Whitelist [", 1); if (($handle = @fopen("{$pfb['dbdir']}/top-1m.csv", 'r')) !== FALSE) { $pfb_output = @fopen("{$pfb['dbdir']}/pfbalexawhitelist.txt", 'w'); while (($line = @fgets($handle)) !== FALSE) { if (strpos($line, '.') === FALSE || strpos($line, ',') === FALSE || empty($line)) { continue; } // Display progress indicator if ($linecnt % 100000 == 0) { pfb_logger('.', 1); } // Collect Domain TLD $csvline = str_getcsv($line); $tld = substr($csvline[1], strrpos($csvline[1], '.') + 1); if (isset($pfb_include[$tld])) { // Whitelist both 'www.example.com' and 'example.com' if (substr($csvline[1], 0, 4) == 'www.') { $csvline[1] = substr($csvline[1], 4); } $x++; // Create three whitelist options per TOP1M whitelisted Domain if ($pfb['dnsbl_py_blacklist']) { @fwrite($pfb_output, ".{$csvline[1]},,\n,{$csvline[1]},,\n,www.{$csvline[1]},,\n"); } else { @fwrite($pfb_output, ".{$csvline[1]} 60\n\"{$csvline[1]} 60\n\"www.{$csvline[1]} 60\n"); } } if ($x >= $pfb['dnsbl_alexa_cnt']) { break; } $linecnt++; } pfb_logger("] [ Parsed {$linecnt} lines | Found {$x} of {$pfb['dnsbl_alexa_cnt']} ]...", 1); } else { $log = "\nTOP1M conversion Failed. File: top-1m.csv, not found..."; pfb_logger("{$log}", 2); } @fclose($handle); @fclose($pfb_output); // Remove Top1M update file marker unlink_if_exists("{$pfb['dbdir']}/top-1m.update"); } // Function to remove any leading zeros in octets and to exclude private/reserved addresses. function sanitize_ipaddr($ipaddr, $custom, $pfbcidr) { global $pfb; list ($subnet, $mask) = explode('/', $ipaddr); $iparr = explode('.', $subnet); foreach ($iparr as $key => $octet) { // Remove any leading zeros in octets if ($octet == 0) { $ip[$key] = 0; } else { $ip[$key] = ltrim($octet, '0'); } if ($key == 3) { // If mask is not defined and 4th octet is '0', set mask to '24' if ($octet == 0 && empty($mask)) { $mask = 24; } // If mask is '24', force 4th octet to '0' if ($mask == 24 && $octet != 0) { $ip[$key] = 0; } } } $mask = str_replace('32', '', $mask); // Strip '/32' mask $ip_final = implode('.', $ip); // Exclude private/reserved IPs (bypass exclusion for custom lists) if (!$custom) { // Remove 'loopback' and '0.0.0.0' IPs if ($ip[0] == 127 || $ip[0] == 0 || empty($ip[0])) { return; } // Advanced IPv4 Tunable (Set CIDR Block size limit) if ($pfbcidr != 'Disabled' && !empty($mask) && $mask < $pfbcidr) { pfb_logger("\n Suppression CIDR Limit: {$ip_final}/{$mask}", 1); $mask = '32'; } if ($mask > 32) { $mask = ''; } if (!filter_var($ip_final, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== FALSE) { return; } } if (!empty($mask)) { return "{$ip_final}/{$mask}"; } return "{$ip_final}"; } // Validate IPv4 IP addresses function validate_ipv4($ipaddr) { if (strpos($ipaddr, '/') !== FALSE) { return is_subnetv4($ipaddr); } return is_ipaddrv4($ipaddr); } // Function to check for loopback addresses (IPv4 range: 127.0.0.0/8, excluding IPv6) function FILTER_FLAG_NO_LOOPBACK_RANGE($value) { // http://www.php.net/manual/en/filter.filters.flags.php return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? $value : (((ip2long($value) & 0xff000000) == 0x7f000000) ? FALSE : $value); } // Explode IP for evaluations function ip_explode($ip) { $ix = explode('.', $ip); foreach ($ix as $key => $octet) { if ($key != 3) { $ix1 .= "{$octet}."; } } array_unshift($ix, $ip); $ix[] = "{$ix1}0/24"; $ix[] = "{$ix1}"; return $ix; } // Determine the header which Alerted an IP address and return the header name function find_reported_header($ip, $pfbfolder, $geoip=FALSE) { global $pfb; // Find exact IP match $q_ip = str_replace('.', '\.', $ip); $query = exec("{$pfb['grep']} -s '^{$q_ip}' {$pfbfolder} 2>&1"); if (!empty($query)) { $rx = pfb_parse_query($query); return $rx; } else { if (substr_count($ip, ':') > 1) { $query = strstr($ip, ':', TRUE); // IPv6 Prefix $type = 6; } else { $query = strstr($ip, '.', TRUE); // IPv4 Octet #1 $type = 4; } $cidrs = array(); if (!$geoip) { exec("{$pfb['grep']} -s '^{$query}\.' {$pfbfolder} 2>&1", $result); } else { $geoip_list = "Africa\|Antarctica\|Asia\|Europe\|North_America\|South_America\|Oceania\|Proxy_and_Satellite\|Top_Spammers"; exec("{$pfb['grep']} -s '^{$query}\.' {$pfb['ccdir']}/*.txt | {$pfb['grep']} -v '{$geoip_list}' 2>&1", $result); } if (!empty($result)) { foreach ($result as $line) { $rx = pfb_parse_query($line); // Collect all CIDRs for analysis if Alert is from a CIDR if (strpos($rx[1], '/') !== FALSE) { $cidrs[] = $rx; } } } // Determine which CIDR alerted the IP address if (!empty($cidrs)) { foreach ($cidrs as $line) { // Determine which CIDR alerted the IP address $validate = FALSE; if ($type == 4) { list($addr, $mask) = explode('/', $line[1]); $mask = (0xffffffff << (32 - $mask)) & 0xffffffff; $validate = ((ip2long($ip) & $mask) == (ip2long($addr) & $mask)); } else { $validate = (Net_IPv6::isInNetmask($ip, $line[1])); } // Return header on CIDR match if ($validate) { return $line; } } } } return array('Unknown', 'Unknown'); } // Function to download feeds function pfb_download($list_url, $file_dwn, $pflex=FALSE, $header, $format, $logtype, $vtype='', $timeout=300, $type='', $username='', $password='', $srcint=FALSE) { global $pfb; $http_status = ''; $elog = ">> {$pfb['log']} 2>&1"; // Remove any leading/trailing whitespace $list_url = trim($list_url); // Cron update function for md5 comparison if ($type == 'md5') { pfb_logger("\t\t\t\t( md5 feed )\t\t", 1); } // If the Cron update function 'md5 comparison' generated an md5 file, re-utilize instead of downloading twice if (file_exists("{$file_dwn}.md5.raw")) { $list_url = "{$file_dwn}.md5.raw"; pfb_logger(' ( md5 feed ) ', 1); } // Download RSYNC format if ($format == 'rsync') { $result = exec("/usr/local/bin/rsync --timeout=5 {$list_url} {$file_dwn}.raw"); if ($result == 0) { $http_status = '200 OK'; } else { $log = "\n RSYNC Failed...\n"; pfb_logger("{$log}", "{$logtype}"); return FALSE; } } elseif ($format == 'whois' || $format == 'asn') { // Convert a Domain name/AS into its respective IP addresses exec("{$pfb['script']} whoisconvert {$header} {$vtype} {$list_url} {$elog}"); return TRUE; } else { // Determine if URL is a localfile $host = @parse_url("{$list_url}"); if (empty($host['host']) || in_array($host['host'], array('127.0.0.1', $pfb['iplocal'], ''))) { $lof = 'local'; } else { $lof = ''; } // Download localfile format if ($lof == 'local') { $file_data = @file_get_contents($list_url); if ($file_data === FALSE) { $error = error_get_last(); $log = "\n[ {$header} ] {$error['message']}\n"; pfb_logger("{$log}", "{$logtype}"); return FALSE; } else { // Save original downloaded file @file_put_contents("{$file_dwn}.raw", $file_data, LOCK_EX); $http_status = '200 OK'; } } // Download using cURL else { if (($fhandle = @fopen("{$file_dwn}.raw", 'w')) !== FALSE) { if (!($ch = curl_init($list_url))) { $log = "\nFailed to create cURL resource... Exiting...\n"; pfb_logger("{$log}", "{$logtype}"); return FALSE; } curl_setopt_array($ch, $pfb['curl_defaults']); // Load curl default settings curl_setopt($ch, CURLOPT_FILE, $fhandle); // Add $fhandle setting to cURL curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); // Set cURL download timeout curl_setopt($ch, CURLOPT_ENCODING, 'gzip'); // Request 'gzip' encoding from server if available if ($srcint) { curl_setopt($ch, CURLOPT_INTERFACE, $srcint); // Use a specific interface when downloading lists pfb_logger("\nList: ${header} will be downloaded via interface: {$srcint}\n", 1); } if (!empty($username) && !empty($password)) { curl_setopt($ch, CURLOPT_USERPWD, "{$username}:{$password}"); curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); } // Attempt 3 Downloads before failing. for ($retries = 1; $retries <= 3; $retries++) { if (curl_exec($ch)) { // Collect remote timestamp. $remote_stamp = curl_getinfo($ch, CURLINFO_FILETIME); break; // Break on success } $curl_error = curl_errno($ch); if ($logtype != 3) { pfb_logger(" cURL Error: {$curl_error}\n", 1); } else { pfb_logger(" {$header}\t\tcURL Error: {$curl_error}\n\n", 3); } /* 'Flex' Downgrade cURL errors - [ 35 - GET_SERVER_HELLO:sslv3 ] [ 51 - NO alternative certificate ] [ 60 - Local Issuer Certificate Subject ] */ // Allow downgrade of cURL settings 'Flex' after 1st failure, if user configured. if ($retries == 1 && $pflex && in_array($curl_error, array( '35', '51', '60'))) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'TLSv1.3, TLSv1.2, TLSv1.1, TLSv1, SSLv3'); $log = "\n[ ! ] Downgrading SSL settings (Flex) "; pfb_logger("{$log}", "{$logtype}"); } else { $log = curl_error($ch) . " Retry in 5 seconds...\n"; pfb_logger("{$log}", "{$logtype}"); sleep(5); pfb_logger('.', "{$logtype}"); } } // Collect RFC7231 http status code $http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE); if (isset($pfb['rfc7231'][$http_status])) { if ($logtype < 3) { pfb_logger(". {$pfb['rfc7231'][$http_status]}", $logtype); } else { pfb_logger(" {$file_dwn}\t\t{$pfb['rfc7231'][$http_status]}\n", $logtype); } } else { if ($logtype < 3) { pfb_logger(". unknown http status code | {$http_status}", $logtype); } else { pfb_logger(". unknown http status code | {$http_status}", $logtype); } } curl_close($ch); } @fclose($fhandle); } } // Cron update function for md5 comparison if ($type == 'md5') { if ($http_status == '200 OK') { return TRUE; } return FALSE; } // Remove any downloaded files with md5 extension unlink_if_exists("{$file_dwn}.md5.raw"); // '304 not modified' - Utilize previously downloaded file if available if ($http_status == '304' && file_exists("{$file_dwn}.orig")) { return TRUE; } if ($http_status == '200 OK' || $http_status == '226') { // Collect file mime-type $file_type = exec("/usr/bin/file -b --mime-type {$file_dwn}.raw 2>&1"); unset($retval); // Create Top1m update file marker if ($type == 'top1m') { touch("{$pfb['dbdir']}/top-1m.update"); } // Decompress file if required if ($file_type == 'application/x-gzip' || $file_type == 'application/gzip') { if ($type == 'geoip') { // Extras - MaxMind downloads @rename("{$file_dwn}.raw", strstr("{$file_dwn}.raw", '.raw', TRUE)); exec("/usr/bin/tar -xzf {$file_dwn} --strip=1 -C {$pfb['geoipshare']} >/dev/null 2>&1"); return TRUE; } elseif ($type == 'blacklist') { // Extras - Blacklist downloads @rename("{$file_dwn}.raw", strstr("{$file_dwn}.raw", '.raw', TRUE)); $filename = basename("{$file_dwn}", '.tar.gz'); rmdir_recursive("{$pfb['dbdir']}/{$filename}/"); safe_mkdir("{$pfb['dbdir']}/{$filename}/"); // Extract Blacklist categories from sub-folders into a single folder structure $cmd = "--include='*domains' -s',.*/\\(.*\\)/\\(.*\\)/domains,{$filename}_\\1_\\2,' -s',.*/\\(.*\\)/domains,{$filename}_\\1,'"; exec("/usr/bin/tar -xf {$file_dwn} {$cmd} -C {$pfb['dbdir']}/{$filename}/ >/dev/null 2>&1"); // Create update file indicator for update process touch("{$pfb['dbdir']}/{$filename}/{$filename}.update"); } else { pfb_logger('.', 1); exec("/usr/bin/gunzip -c {$file_dwn}.raw > {$file_dwn}.orig", $output, $retval); } } elseif ($file_type == 'application/x-bzip2') { pfb_logger('.', 1); exec("/usr/bin/bzip2 -dkc {$file_dwn}.raw > {$file_dwn}.orig", $output, $retval); } elseif ($file_type == 'application/zip') { // Extras - MaxMind/TOP1M downloads if ($type == 'geoip' || $type == 'top1m') { // Determine if Zip contains multiple files exec("/usr/bin/tar -tf {$file_dwn}.raw 2>&1", $archive_count, $retval); if ($archive_count[0] == 'tar: Failed to set default locale') { unset($archive_count[0]); } if (count($archive_count) > 1) { exec("/usr/bin/tar -xf {$file_dwn}.raw --strip=1 -C {$header} >/dev/null 2>&1"); } else { exec("/usr/bin/tar -xOf {$file_dwn}.raw > {$header}"); } unlink_if_exists("{$file_dwn}.raw"); return TRUE; } pfb_logger('.', 1); // Check if ZIP archive contains xlsx files $xlsxtest = exec("/usr/bin/tar -tf {$file_dwn}.raw"); if (strpos($xlsxtest, '.xlsx') !== FALSE) { unlink_if_exists("{$file_dwn}.orig"); exec("{$pfb['script']} xlsx {$header} {$elog}"); if (file_exists("{$file_dwn}.orig")) { $retval = 0; } } else { // Process ZIP file (SFS and hpHosts workaround) exec("/usr/bin/tar -xOf {$file_dwn}.raw | /usr/bin/sed 's/,[[:space:]]/; /g' | /usr/bin/tr ',' '\n' > {$file_dwn}.orig", $output, $retval); } } elseif ($file_type == 'application/x-7z-compressed') { pfb_logger('.', 1); exec("/usr/local/bin/7z e -so {$file_dwn}.raw > {$file_dwn}.orig", $output, $retval); unlink_if_exists("{$file_dwn}.raw"); } else { // Uncompressed file format. if ($type == 'geoip') { // Extras - MaxMind/TOP1M downloads @rename("{$file_dwn}.raw", "{$header}"); return TRUE; } elseif ($type == 'blacklist') { $retval = 0; } else { // Rename file to 'orig' format @rename("{$file_dwn}.raw", "{$file_dwn}.orig"); $retval = 0; } } if ($retval == 0) { // Set downloaded file timestamp to remote timestamp if (isset($remote_stamp)) { if ($remote_stamp != -1 && file_exists("{$file_dwn}.orig")) { @touch("{$file_dwn}.orig", $remote_stamp); } } // Process Emerging Threats IQRisk if required if (strpos($list_url, 'iprepdata.txt') !== FALSE) { exec("{$pfb['script']} et {$header} x x x x x {$pfb['etblock']} {$pfb['etmatch']} {$elog}"); } return TRUE; } else { $log = " Decompression Failed\n"; pfb_logger("{$log}", 2); return FALSE; } } else { // Download failed unlink_if_exists("{$file_dwn}.raw"); } return FALSE; } // Determine reason for download failure function pfb_download_failure($alias, $header, $pfbfolder, $list_url) { global $pfb; $pfbfound = FALSE; // Determine if URL is a localfile $host = @parse_url("{$list_url}"); if (in_array($host['host'], array('127.0.0.1', $pfb['iplocal'], ''))) { $lof = 'local'; } else { $lof = ''; } // Log FAILED downloads and check if firewall or Snort/Suricata is blocking host $log = "\n\n [ {$alias} - {$header} ] Download FAIL [ NOW ]\n"; pfb_logger("{$log}", 2); // Only perform these checks if they are not 'localfiles' if ($lof == 'local') { $log = " Local File Failure\n"; pfb_logger("{$log}", 2); } else { // Determine if Firewall/IPS/DNSBL is blocking download. $ip = @gethostbyname($host['host']); if (!empty($ip)) { // Query Firewall aliastables $result = find_reported_header($ip, "{$pfbfolder}/*", FALSE); if (!empty($result) && $result[0] != 'Unknown') { $log = " [ {$ip} ] Firewall IP block found in: [ {$result[0]} | {$result[1]} ]\n"; pfb_logger("{$log}", 2); $pfbfound = TRUE; } // Determine if Host is listed in DNSBL if ($ip == $pfb['dnsbl_vip']) { $log = " [ {$host['host']} ] Domain listed in DNSBL\n"; pfb_logger("{$log}", 2); $pfbfound = TRUE; } // Query Snort/Suricata snort2c IP block table $result = exec("{$pfb['pfctl']} -t snort2c -T show | {$pfb['grep']} {$ip} 2>&1"); if (!empty($result)) { $log = " [ {$ip} ] IDS IP block found!\n"; pfb_logger("{$log}", 2); $pfbfound = TRUE; } } else { $log = " Could not determine IP address of host.\n"; pfb_logger("{$log}", 2); } if (!$pfbfound) { $log = " Firewall and/or IDS (Legacy mode only) are not blocking download.\n"; pfb_logger("{$log}", 2); } } // Call function to get all previous download fails pfb_failures(); // On download failure, create file marker for subsequent download attempts. ('0' no download failure threshold) if ($pfb['skipfeed'] == 0 || $pfb['failed'][$header] <= $pfb['skipfeed']) { touch("{$pfbfolder}/{$header}.fail"); return; } unlink_if_exists("{$pfbfolder}/{$header}.fail"); return; } // Collect all previously failed daily download notices function pfb_failures() { global $pfb; $pfb['failed'] = array(); if (file_exists("{$pfb['errlog']}")) { exec("{$pfb['grep']} 'FAIL' {$pfb['errlog']} | {$pfb['grep']} $(date +%m/%d/%y)", $results); if (!empty($results)) { foreach ($results as $result) { $header = explode(' ', $result); $pfb['failed'][$header[4]] += 1; } } } return; } // Convert unique Alias details (via ascii table number) and return a 10 digit tracker ID function pfb_tracker($alias, $int, $text) { global $config, $pfb; $pfbtracker = 0; $real_int = get_real_interface($int); $ipaddr = get_interface_ip($int); if (is_ipaddrv4($ipaddr)) { $ipaddr = ip2long32($ipaddr); $subnet = find_interface_subnet($real_int); } else { $ipaddr = get_interface_ipv6($real_int); $subnet = find_interface_subnetv6($real_int); } $search = array( '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' ); $replace = array( 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'zero' ); $line = "{$alias}{$int}{$text}{$real_int}{$ipaddr}{$subnet}"; $line = str_replace($search, $replace, $line); for ($i = 0; $i < strlen($line); $i++) { $pfbtracker += @ord($line[$i]); } $pfbtracker = '177' . str_pad($pfbtracker, 7, '0', STR_PAD_LEFT); if (strlen($pfbtracker) > 10) { $pfbtracker = substr($pfbtracker, 0, 10); } // If duplicate Tracker ID found, pre-define a Tracker ID (Starts at 1700000010) if (in_array($pfbtracker, $pfb['trackerids'])) { $pfbtracker = ($pfb['last_trackerid'] + 1); // Increment prefix (digits 1&2) and reset Last_tracker ID after 10 digits if (strlen($pfbtracker) > 10) { $tracker_prefix = substr($pfbtracker, 0, 2); $pfbtracker = ($tracker_prefix + 1) . '00000010'; } $pfb['last_trackerid'] = $pfbtracker; } $pfb['trackerids'][] = $pfbtracker; return (int)$pfbtracker; } // Define firewall rule settings function pfb_firewall_rule($action, $pfb_alias, $vtype, $pfb_log, $agateway_in='default', $agateway_out='default', $aaddrnot_in='', $adest_in='', $aports_in='', $aproto_in='', $anot_in='', $aaddrnot_out='', $asrc_out='', $aports_out='', $aproto_out='', $anot_out='') { global $pfb; $rule = array(); switch ($action) { case 'Deny_Both': case 'Deny_Outbound': $rule = $pfb['base_rule']; $rule['type'] = "{$pfb['deny_action_outbound']}"; if ($vtype == '_v6') { $rule['ipprotocol'] = 'inet6'; } if ($pfb['float'] == 'on') { $rule['direction'] = 'any'; } $rule['descr'] = "{$pfb_alias}{$pfb['suffix']}"; if (!empty($asrc_out)) { $rule['source'] = array('address' => "{$asrc_out}"); } else { $rule['source'] = array('any' => ''); } if (!empty($asrc_out) && $anot_out == 'on') { $rule['source']['not'] = ''; } if (!empty($aports_out)) { $rule['destination'] = array('address' => "{$pfb_alias}", 'port' => "{$aports_out}"); } else { $rule['destination'] = array('address' => "{$pfb_alias}"); } if ($aaddrnot_out == 'on') { $rule['destination']['not'] = ''; } if (!empty($aproto_out)) { $rule['protocol'] = "{$aproto_out}"; } if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') { $rule['log'] = ''; } if (!empty($agateway_out) && $agateway_out != 'default') { $rule['gateway'] = "{$agateway_out}"; if ($pfb['float'] == 'on') { $rule['direction'] = 'out'; } } $rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto'); $pfb['deny_outbound'][] = $rule; if ($action != 'Deny_Both') { break; } case 'Deny_Inbound': $rule = $pfb['base_rule']; $rule['type'] = "{$pfb['deny_action_inbound']}"; if ($vtype == '_v6') { $rule['ipprotocol'] = 'inet6'; } if ($pfb['float'] == 'on') { $rule['direction'] = 'any'; } $rule['descr'] = "{$pfb_alias}{$pfb['suffix']}"; $rule['source'] = array('address' => "{$pfb_alias}"); if ($aaddrnot_in == 'on') { $rule['source']['not'] = ''; } if (!empty($adest_in) && !empty($aports_in)) { $rule['destination'] = array('address' => "{$adest_in}", 'port' => "{$aports_in}"); } elseif (!empty($adest_in) && empty($aports_in)) { $rule['destination'] = array('address' => "{$adest_in}"); } elseif (empty($adest_in) && !empty($aports_in)) { $rule['destination'] = array('any' => '', 'port' => "{$aports_in}"); } else { $rule['destination'] = array('any' => ''); } if (!empty($adest_in) && $anot_in == 'on') { $rule['destination']['not'] = ''; } if (!empty($aproto_in)) { $rule['protocol'] = "{$aproto_in}"; } if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') { $rule['log'] = ''; } if (!empty($agateway_in) && $agateway_in != 'default') { $rule['gateway'] = "{$agateway_in}"; if ($pfb['float'] == 'on') { $rule['direction'] = 'in'; } } $rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto'); $pfb['deny_inbound'][] = $rule; break; case 'Permit_Both': case 'Permit_Outbound': $rule = $pfb['base_rule']; $rule['type'] = 'pass'; if ($vtype == '_v6') { $rule['ipprotocol'] = 'inet6'; } if ($pfb['float'] == 'on') { $rule['direction'] = 'any'; } $rule['descr'] = "{$pfb_alias}{$pfb['suffix']}"; if (!empty($asrc_out)) { $rule['source'] = array('address' => "{$asrc_out}"); } else { $rule['source'] = array('any' => ''); } if (!empty($asrc_out) && $anot_out == 'on') { $rule['source']['not'] = ''; } if (!empty($aports_out)) { $rule['destination'] = array('address' => "{$pfb_alias}", 'port' => "{$aports_out}"); } else { $rule['destination'] = array('address' => "{$pfb_alias}"); } if ($aaddrnot_out == 'on') { $rule['destination']['not'] = ''; } if (!empty($aproto_out)) { $rule['protocol'] = "{$aproto_out}"; } if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') { $rule['log'] = ''; } if (!empty($agateway_out) && $agateway_out != 'default') { $rule['gateway'] = "{$agateway_out}"; if ($pfb['float'] == 'on') { $rule['direction'] = 'out'; } } $rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto'); $pfb['permit_outbound'][] = $rule; if ($action != 'Permit_Both') { break; } case 'Permit_Inbound': $rule = $pfb['base_rule']; $rule['type'] = 'pass'; if ($vtype == '_v6') { $rule['ipprotocol'] = 'inet6'; } if ($pfb['float'] == 'on') { $rule['direction'] = 'any'; } $rule['descr'] = "{$pfb_alias}{$pfb['suffix']}"; $rule['source'] = array('address' => "{$pfb_alias}"); if ($aaddrnot_in == 'on') { $rule['source']['not'] = ''; } if (!empty($adest_in) && !empty($aports_in)) { $rule['destination'] = array('address' => "{$adest_in}", 'port' => "{$aports_in}"); } elseif (!empty($adest_in) && empty($aports_in)) { $rule['destination'] = array('address' => "{$adest_in}"); } elseif (empty($adest_in) && !empty($aports_in)) { $rule['destination'] = array('any' => '', 'port' => "{$aports_in}"); } else { $rule['destination'] = array('any' => ''); } if (!empty($adest_in) && $anot_in == 'on') { $rule['destination']['not'] = ''; } if (!empty($aproto_in)) { $rule['protocol'] = "{$aproto_in}"; } if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') { $rule['log'] = ''; } if (!empty($agateway_in) && $agateway_in != 'default') { $rule['gateway'] = "{$agateway_in}"; if ($pfb['float'] == 'on') { $rule['direction'] = 'in'; } } $rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto'); $pfb['permit_inbound'][] = $rule; break; case 'Match_Both': case 'Match_Outbound': $rule = $pfb['base_rule_float']; $rule['type'] = 'match'; if ($vtype == '_v6') { $rule['ipprotocol'] = 'inet6'; } $rule['direction'] = 'any'; $rule['descr'] = "{$pfb_alias}{$pfb['suffix']}"; if (!empty($asrc_out)) { $rule['source'] = array('address' => "{$asrc_out}"); } else { $rule['source'] = array('any' => ''); } if (!empty($asrc_out) && $anot_out == 'on') { $rule['source']['not'] = ''; } if (!empty($aports_out)) { $rule['destination'] = array('address' => "{$pfb_alias}", 'port' => "{$aports_out}"); } else { $rule['destination'] = array('address' => "{$pfb_alias}"); } if ($aaddrnot_out == 'on') { $rule['destination']['not'] = ''; } if (!empty($aproto_out)) { $rule['protocol'] = "{$aproto_out}"; } if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') { $rule['log'] = ''; } if (!empty($agateway_out) && $agateway_out != 'default') { $rule['gateway'] = "{$agateway_out}"; $rule['direction'] = 'out'; } $rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto'); $pfb['match_outbound'][] = $rule; if ($action != 'Match_Both') { break; } case 'Match_Inbound': $rule = $pfb['base_rule_float']; $rule['type'] = 'match'; if ($vtype == '_v6') { $rule['ipprotocol'] = 'inet6'; } $rule['direction'] = 'any'; $rule['descr'] = "{$pfb_alias}{$pfb['suffix']}"; $rule['source'] = array('address' => "{$pfb_alias}"); if ($aaddrnot_in == 'on') { $rule['source']['not'] = ''; } if (!empty($adest_in) && !empty($aports_in)) { $rule['destination'] = array('address' => "{$adest_in}", 'port' => "{$aports_in}"); } elseif (!empty($adest_in) && empty($aports_in)) { $rule['destination'] = array('address' => "{$adest_in}"); } elseif (empty($adest_in) && !empty($aports_in)) { $rule['destination'] = array('any' => '', 'port' => "{$aports_in}"); } else { $rule['destination'] = array('any' => ''); } if (!empty($adest_in) && $anot_in == 'on') { $rule['destination']['not'] = ''; } if (!empty($aproto_in)) { $rule['protocol'] = "{$aproto_in}"; } if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') { $rule['log'] = ''; } if (!empty($agateway_in) && $agateway_in != 'default') { $rule['gateway'] = "{$agateway_in}"; $rule['direction'] = 'in'; } $rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto'); $pfb['match_inbound'][] = $rule; break; } return; } // Archive IP aliastables and DNSBL database. ( Ramdisk installations only ) function pfb_aliastables($mode) { global $g, $config, $pfb; $msg = ''; $earlyshellcmd = '/usr/local/pkg/pfblockerng/pfblockerng.sh aliastables'; // Reload config.xml to get any recent changes $config = parse_config(true); init_config_arr(array('system', 'earlyshellcmd')); $a_earlyshellcmd = &$config['system']['earlyshellcmd']; init_config_arr(array('installedpackages', 'shellcmdsettings', 'config')); $a_shellcmdsettings = &$config['installedpackages']['shellcmdsettings']['config']; // Only execute function if Ramdisks are used. if (isset($config['system']['use_mfs_tmpvar'])) { // Archive aliastable folder if ($mode == 'update') { pfb_logger("\nArchiving Aliastable folder", 1); $files_to_backup = ''; if (glob("{$pfb['aliasdir']}/pfB_*.txt")) { $files_to_backup = "{$pfb['aliasdir']}/pfB_*.txt"; } if ($pfb['dnsbl'] == 'on') { if (file_exists("{$pfb['dnsbl_file']}.conf")) { $files_to_backup .= " {$pfb['dnsbl_file']}.conf"; } else { $files_to_backup .= " /var/unbound/pfb_unbound* /var/unbound/pfb_py_*"; } } // Archive IP Aliastables/Unbound DNSBL Database as required. if (!empty($files_to_backup)) { exec("/usr/bin/tar -jcvf {$pfb['aliasarchive']} {$files_to_backup} >/dev/null 2>&1"); pfb_logger("\nArchiving selected pfBlockerNG files.\n", 1); } else { pfb_logger("\nNo Files to archive.\n", 1); } } // Check conf file for earlyshellcmd/shellcmd package settings elseif ($mode == 'conf') { // Add earlyshellcmd settings if (!empty($a_earlyshellcmd)) { if (!preg_grep("/pfblockerng.sh aliastables/", $a_earlyshellcmd)) { $a_earlyshellcmd[] = "{$earlyshellcmd}"; $msg = "\n** Adding earlyshellcmd settings **\n"; } } else { $a_earlyshellcmd = "{$earlyshellcmd}"; $msg = "\n** Adding earlyshellcmd settings **\n"; } // Add shellcmd package settings $found = FALSE; if (!empty($a_shellcmdsettings)) { foreach ($a_shellcmdsettings as $key => $shellcmd) { if (strpos($shellcmd['cmd'], 'pfblockerng.sh aliastables') !== FALSE) { $found = TRUE; break; } } } if (!$found) { $add = array( 'cmd' => $earlyshellcmd, 'cmdtype' => 'earlyshellcmd', 'description' => 'pfBlockerNG earlyshellcmd. DO NOT EDIT/DELETE!'); $a_shellcmdsettings[] = $add; $msg .= "\n** Adding shellcmd package settings **\n"; } } } else { // Remove aliastables archive if found if (file_exists("{$pfb['aliasarchive']}")) { unlink_if_exists("{$pfb['aliasarchive']}"); } // Remove earlyshellcmd settings if (!empty($a_earlyshellcmd)) { if (preg_grep("/pfblockerng.sh aliastables/", $a_earlyshellcmd)) { $a_earlyshellcmd = preg_grep("/pfblockerng.sh aliastables/", $a_earlyshellcmd, PREG_GREP_INVERT); $msg = "\n** Removing earlyshellcmd settings **\n"; } } // Remove shellcmd package settings if (!empty($a_shellcmdsettings)) { foreach ($a_shellcmdsettings as $key => $shellcmd) { if (strpos($shellcmd['cmd'], 'pfblockerng.sh aliastables') !== FALSE) { unset($a_shellcmdsettings[$key]); $msg .= "\n** Removing shellcmd package settings**\n"; } } } } if (!empty($msg)) { pfb_logger("{$msg}", 1); write_config('pfBlockerNG: saving earlyshellcmd'); } } // Collect pfBlockerNG rule names and Tracker IDs function pfb_filterrules() { global $pfb; $rule_list = array(); $rule_list['id'] = array(); $rule_list['other'] = array(); $rule_list['int'] = array(); exec("{$pfb['pfctl']} -vvsr 2>&1", $results); if (!empty($results)) { foreach ($results as $result) { if (substr($result, 0, 1) == '@') { $r = explode(' ', $result, 2); // pfSense > v2.6 uses an 'ridentifier' string if (strpos($result, 'ridentifier') != FALSE) { $id = trim(strstr(strstr($r[1], 'ridentifier', FALSE), ' ', FALSE)); } else { $id = ltrim(strstr($r[0], '(', FALSE), '('); } // Find rule descriptions and type for pfBlockerNG Tracker IDs if (strpos($r[1], ' {$pfb['states_tmp']} 2>&1"); $state_count = $all_states = 0; $states = array(); $states[4] = array(); $states[6] = array(); if (($s_handle = @fopen("{$pfb['states_tmp']}", 'r')) !== FALSE) { while (($sline = @fgets($s_handle)) !== FALSE) { $all_states++; // SAMPLE : em0 udp 93.15.36.22:6881 -> 192.168.0.3:681 MULTIPLE:MULTIPLE // SAMPLE : pppoe0 udp 35.170.3.40:57197 (192.168.0.45:681) -> 22.41.123.206:1001 MULTIPLE:MULTIPLE // SAMPLE : em0 tcp 2001:65c:1398:101:124[443] <- 2001:170:2f:3e:a4c4:7b23:fe5f:b36e[52725] FIN_WAIT_2:FIN_WAIT_2 if (!empty($sline)) { $detail = array_filter(explode(' ', $sline)); // Validate states for pfB Interfaces only if (!isset($pfb_int[$detail[0]])) { continue; } $count = count($detail); if ($count == 6) { $orig_s_ip = $detail[2]; } elseif ($count == 7) { $orig_s_ip = $detail[5]; } else { continue; // Unknown state line } } else { continue; } $ip_version = 4; // Strip IPv6 port if (strpos($orig_s_ip, '[') !== FALSE) { list($s_ip, $s_port) = explode('[', $orig_s_ip); $ip_version = 6; } // Strip IPv4 port elseif (strpos($orig_s_ip, ':') !== FALSE && substr_count($orig_s_ip, ':') == 1) { list($s_ip, $s_port) = explode(':', $orig_s_ip); $ip_version = 4; } // No port listed else { $s_ip = $orig_s_ip; $s_port = ''; if (is_ipaddrv6($s_ip)) { $ip_version = 6; } } // Exclude local and reserved IPs (Validate unique IPs only once) if (!isset($states[$ip_version][$s_ip])) { if ($ip_version == 4) { if (isset($pfb_local[$s_ip]) || pfb_local_ip($s_ip, $pfb_localsub) || is_private_ip($s_ip) || substr($s_ip, 0, 2) == '0.' || substr($s_ip, 0, 4) == '127.' || substr($s_ip, 0, 3) >= 224) { continue; } } else { if (isset($pfb_local[$s_ip]) || pfb_local_ip($s_ip, $pfb_localsub) || !filter_var($s_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) || !filter_var($s_ip, FILTER_CALLBACK, array('options' => 'FILTER_FLAG_NO_LOOPBACK_RANGE'))) { continue; } } // Exclude any 'Permit' Customlist IPs foreach ($custom_supp as $custom) { if (ip_in_subnet($s_ip, $custom)) { continue; } } } $state = rtrim($sline, "\x00..\x1F"); $state_count++; // Collect IP for state removal verification if (!is_array($states[$ip_version][$s_ip])) { $states[$ip_version][$s_ip] = array(); } if (!in_array($state, $states[$ip_version][$s_ip])) { $states[$ip_version][$s_ip][] = $state; } } } else { pfb_logger("\n No Firewall States found", 1); } @fclose($s_handle); unlink_if_exists("{$pfb['states_tmp']}"); unset($pfb_local, $pfb_localsub, $custom_supp); $pfbfound = FALSE; foreach ($states as $ip_version => $details) { if (!empty($details)) { $log = "\nFirewall state(s) validation for [ " . count($details) . " ] IPv{$ip_version} address(es)..."; pfb_logger("{$log}", 1); ksort($details, SORT_NATURAL); } foreach ($details as $s_ip => $state) { foreach ($pfb_tables as $s_table) { // Compare IP version and aliastable type if ($ip_version == 4 && strpos($s_table, '_v4') !== FALSE || $ip_version == 6 && strpos($s_table, '_v6') !== FALSE) { $result = substr(exec("{$pfb['pfctl']} -t {$s_table} -T test {$s_ip} 2>&1"), 0, 1); if ($result > 0) { $pfbfound = TRUE; $log = "\n\n\t[ {$s_table} ] Removed " . count($state) . " state(s) for [ {$s_ip} ]\n\n"; pfb_logger("{$log}", 1); foreach ($state as $line) { pfb_logger("\t\t{$line}\n", 1); } // Kill all state entries originating from $s_ip exec("{$pfb['pfctl']} -k {$s_ip} 2>&1"); // Kill all state entries to the target $s_ip exec("{$pfb['pfctl']} -k 0.0.0.0/0 -k {$s_ip} 2>&1"); break; } } } } } unset($states, $pfb_tables); if ($pfbfound) { pfb_logger("\n======================================================================\n", 1); } else { pfb_logger("\nNo matching states found\n\n======================================================================\n", 1); } } // For subnet addresses - Determine if alert host 'dest' is within a local IP range. function pfb_local_ip($subnet, $pfb_localsub) { if (!empty($pfb_localsub)) { foreach ($pfb_localsub as $line) { if (ip_in_subnet($subnet, $line)) { return TRUE; } } } return FALSE; } // Collect local IP addresses function pfb_collect_localip() { global $config; $pfb_local = $pfb_localsub = array(); // Collect gateway IP addresses for inbound/outbound list matching $int_gateway = get_interfaces_with_gateway(); if (isset($int_gateway)) { foreach ($int_gateway as $gateway) { $pfb_local[] = get_interface_ip($gateway) ?: 'Disabled'; } } // Collect virtual IP aliases for inbound/outbound list matching if (is_array($config['virtualip']['vip'])) { foreach ($config['virtualip']['vip'] as $list) { if (!empty($list['subnet']) && !empty($list['subnet_bits'])) { if ($list['subnet_bits'] >= 24 && is_ipaddrv4($list['subnet'])) { $pfb_local = array_merge(subnetv4_expand("{$list['subnet']}/{$list['subnet_bits']}"), $pfb_local); } else { $pfb_localsub[] = "{$list['subnet']}/{$list['subnet_bits']}"; } } } } // Collect NAT IP addresses for inbound/outbound list matching if (is_array($config['nat']['rule'])) { foreach ($config['nat']['rule'] as $natent) { $pfb_local[] = $natent['target']; } } // Collect 1:1 NAT IP addresses for inbound/outbound list matching if (is_array($config['nat']['onetoone'])) { foreach ($config['nat']['onetoone'] as $onetoone) { $pfb_local[] = $onetoone['source']['address']; } } // Convert any 'Firewall Aliases' to IP address format if (is_array($config['aliases']['alias'])) { for ($cnt = 0; $cnt <= count($pfb_local); $cnt++) { foreach ($config['aliases']['alias'] as $i=> $alias) { if (isset($alias['name']) && isset($pfb_local[$cnt])) { if ($alias['name'] == $pfb_local[$cnt]) { $pfb_local[$cnt] = $alias['address']; } } } } } // Collect all interface addresses for inbound/outbound list matching if (is_array($config['interfaces'])) { foreach ($config['interfaces'] as $int) { if ($int['ipaddr'] != 'dhcp') { if (!empty($int['ipaddr']) && !empty($int['subnet'])) { if ($int['subnet'] >= 24 && is_ipaddrv4($int['ipaddr'])) { $pfb_local = array_merge(subnetv4_expand("{$int['ipaddr']}/{$int['subnet']}"), $pfb_local); } else { $pfb_localsub[] = "{$int['ipaddr']}/{$int['subnet']}"; } } } } } // Remove any duplicate IPs if (!empty($pfb_local)) { $pfb_local = array_flip(array_filter(array_unique($pfb_local))); } $pfb_localsub = array_unique($pfb_localsub); return array($pfb_local, $pfb_localsub); } // Collect local hostnames function pfb_collect_localhosts() { global $config, $g, $pfb; // Collect DHCP hostnames/IPs $local_hosts = array(); // Collect configured pfSense interfaces $pf_int = get_configured_ip_addresses(); if (isset($pf_int)) { $local_hosts = array_merge($local_hosts, array_flip(array_filter($pf_int))); } $pf_int = get_configured_ipv6_addresses(); if (isset($pf_int)) { $local_hosts = array_merge($local_hosts, array_flip(array_filter($pf_int))); } // Collect dynamic DHCP hostnames/IPs $leasesfile = "{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases"; if (file_exists("{$leasesfile}")) { if (($l_handle = @fopen("{$leasesfile}", 'r')) !== FALSE) { while (($line = @fgets($l_handle)) !== FALSE) { if (strpos($line, '{') !== FALSE) { $end = FALSE; $data = explode(' ', $line); $ip = $data[1]; } if (strpos($line, 'client-hostname') !== FALSE) { $data = explode(' ', $line); $hostname = trim(str_replace(array('"', ';'), '', $data[3])); } if (strpos($line, '}') !== FALSE) { $end = TRUE; } if ($end) { if (!empty($ip) && !empty($hostname)) { $local_hosts[$ip] = $hostname; } $ip = $hostname = ''; } } } } // Collect static DHCP hostnames/IPs if (is_array($config['dhcpd'])) { foreach ($config['dhcpd'] as $dhcp) { if (is_array($dhcp['staticmap'])) { foreach ($dhcp['staticmap'] as $smap) { $local_hosts[$smap['ipaddr']] = strtolower("{$smap['hostname']}"); } } } } // Collect static DHCPv6 hostnames/IPs if (is_array($config['dhcpdv6'])) { foreach ($config['dhcpdv6'] as $dhcpv6) { if (is_array($dhcpv6['staticmap'])) { foreach ($dhcpv6['staticmap'] as $smap) { $local_hosts[$smap['ipaddrv6']] = strtolower("{$smap['hostname']}"); } } } } // Collect Unbound Host overrides $hosts = $config['unbound']['hosts']; if (is_array($hosts)) { foreach ($hosts as $host) { $local_hosts[$host['ip']] = strtolower("{$host['descr']}"); } } // Collect NAT IP addresses by Target:Port if (is_array($config['nat']['rule'])) { foreach ($config['nat']['rule'] as $natent) { $local_hosts["{$natent['target']}:{$natent['local-port']}"] = strtolower("{$natent['descr']}"); } } // Collect virtual IP aliases if (is_array($config['virtualip']['vip'])) { foreach ($config['virtualip']['vip'] as $list) { if (!empty($list['subnet']) && !empty($list['subnet_bits'])) { // Use pfSense hostname for DNSBL vip if ($list['subnet'] == $pfb['dnsbl_vip']) { $list['descr'] = ($config['system']['hostname'] ?: 'pfSense') . '.' . ($config['system']['domain'] ?: 'localdomain'); } $local_hosts[$list['subnet']] = strtolower("{$list['descr']}"); } } } // Add localhost hostname if (!isset($local_hosts['127.0.0.1'])) { $local_hosts['127.0.0.1'] = strtolower(($config['system']['hostname'] ?: 'pfSense') . '.' . ($config['system']['domain'] ?: 'localdomain')); } return $local_hosts; } // Firewall filter.log parser daemon function pfb_daemon_filterlog() { global $config, $pfb; // ASN Reporting - cached setting if ($pfb['asn_reporting'] != 'disabled') { switch ($pfb['asn_reporting']) { case '1hour': $asn_cache = '-1 hour'; break; case '4hour': $asn_cache = '-4 hours'; break; case '12hour': $asn_cache = '-12 hours'; break; case '24hour': $asn_cache = '-24 hours'; break; case 'week': $asn_cache = '-1 week'; break; default: $asn_cache = '-24 hours'; } } // Application paths if (file_exists('/usr/local/bin/mmdblookup') && file_exists("{$pfb['geoipshare']}/GeoLite2-Country.mmdb")) { $pathgeoip = "/usr/local/bin/mmdblookup -f {$pfb['geoipshare']}/GeoLite2-Country.mmdb -i"; } else { $pathgeoip = ''; } // Proofpoint ET IQRisk header name reference init_config_arr(array('installedpackages', 'pfblockerngreputation', 'config', 0)); $et_header = $config['installedpackages']['pfblockerngreputation']['config'][0]['et_header'] ?: ''; $et_enabled = TRUE; if (empty($et_header)) { $et_enabled = FALSE; } $p_entry = ''; $line_cnt = 0; $rule_list = $rule_list['other'] = array(); if (file_exists('/usr/local/sbin/clog_pfb')) { // Parse full filter.log on first run, otherwise only parse new filter.log events if (!file_exists($pfb['ip_blocklog']) && !file_exists($pfb['ip_permitlog']) && !file_exists($pfb['ip_matchlog'])) { $filter_cnt = 0; } else { $filter_cnt = max( exec("{$pfb['grep']} -c ^ /var/log/filter.log 2>&1") -1, 0) ?: 0; } $skip_cnt = $filter_cnt; } else { $skip_cnt = $filter_cnt = 0; } /* filter.log reference: URL: https://docs.netgate.com/pfsense/en/latest/monitoring/logs/raw-filter-format.html $line -> $f [ BSD format ] [ syslog format ] [0][1][2] [1] = Date/timestamp [5] [7] = Event Details $f[5] -> $d [0] = Rule number [1] = Sub-rule number [2] = Anchor [3] = Tracker ID [4] = Real Interface [5] = Reason [6] = Action [7] = Direction [8] = IP version [9] = IP Specific data IPv4 IPv6 [10] [] = [11] [] = [12] [] = [13] [] = [14] [] = [15] [13] = Protocol ID [16] [12] = Protocol [17] [] = [18] [15] = SRC IP [19] [16] = DST IP [20] [17] = SRC Port [21] [18] = DST Port [22] [] = [23] [20] = TCP Protocol Flags Final output reference: [0] = Date/Timestamp [1] = Rulenum [2] = Real Interface [3] = Friendly Interface name [4] = Action [5] = Version [6] = Protocol ID [7] = Protocol [8] = SRC IP [9] = DST IP [10] = SRC Port [11] = DST Port [12] = Direction [13] = GeoIP code [14] = IP Alias Name [15] = IP evaluated [16] = Feed Name [17] = gethostbyaddr resolved hostname [18] = Client Hostname [19] = Duplicate ID indicator */ // Disable pfctl Tracker ID lookup on too many lookup failures $max_retries = 0; if (($s_handle = @fopen('php://stdin', 'r')) !== FALSE) { syslog(LOG_NOTICE, '[pfBlockerNG] filterlog daemon started'); while (!feof($s_handle)) { $line = @fgets($s_handle); // Only parse new filter events if ($filter_cnt > 0 && $skip_cnt > 0) { $skip_cnt--; continue; } $log_type = 'BSD'; $f_pos = 5; if (substr($line, 0, 1) == '<') { $log_type = 'syslog'; $f_pos = 7; } // Remove any '^M' characters $line = htmlspecialchars(rtrim($line, "\x00..\x1F")); $f = explode(' ', $line); // Remove double space for single date entry nuance if ($log_type == 'BSD' && empty($f[1])) { array_splice($f, 1, 1); } $d = explode(',', $f[$f_pos]); // Attempt to find the Tracker ID, if not found wait 5secs for filter_reload $other = FALSE; if (!empty($d[3])) { for ($retries = 1; $retries <= 5; $retries++) { // Skip known non-pfBlockerNG Tracker IDs if (isset($rule_list['other'][$d[3]])) { $other = TRUE; break; } // Break on pfBlockerNG rule Tracker ID and Rule type (block|permit|match) // If the user switched a manual rule from one type to another, the Tracker ID will stay the same // So this comparison will refresh the data on changes if (isset($rule_list[$d[3]]) && $rule_list[$d[3]]['type'] == $d[6]) { break; } // Collect updated pfctl Rule data, Host and local IP data else { $rule_list = pfb_filterrules(); $data = pfb_collect_localip(); $pfb_local = $data[0] ?: array(); $pfb_localsub = $data[1] ?: array(); $local_hosts = pfb_collect_localhosts(); } if ($retries < 5 && $max_retries < 30) { $max_retries++; sleep(5); } else { $other = TRUE; } } if ($other) { continue; } // Duplicate entry comparison: "Tracker ID/Action/SRC IP/DST IP/DST Port" $dup_entry = '+'; if ($d[8] == 4 && "{$d[3]}{$d[6]}{$d[18]}{$d[19]}{$d[21]}" == $p_entry) { $dup_entry = '-'; } elseif ($d[8] == 6 && "{$d[3]}{$d[6]}{$d[15]}{$d[16]}{$d[18]}" == $p_entry) { $dup_entry = '-'; } if ($dup_entry == '+') { $int = convert_real_interface_to_friendly_descr($d[4]); $pfb_alias = $rule_list[$d[3]]['name'] ?: 'Unknown'; // Action setting variables if ($d[6] == 'block') { $folder = "{$pfb['denydir']}/* {$pfb['nativedir']}/*"; $iplog = "{$pfb['ip_blocklog']}"; $l_type = 'Block'; } elseif ($d[6] == 'pass') { $folder = "{$pfb['permitdir']}/* {$pfb['nativedir']}/*"; $iplog = "{$pfb['ip_permitlog']}"; $l_type = 'Permit'; } elseif ($d[6] == 'unkn(%u)') { $d[6] = 'match'; $folder = "{$pfb['matchdir']}/* {$pfb['nativedir']}/*"; $iplog = "{$pfb['ip_matchlog']}"; $l_type = 'Match'; } if ($d[8] == 4) { $srcip = $d[18] ?: 'Unknown'; $dstip = $d[19] ?: 'Unknown'; $tcp_flags = $d[16] == 'tcp' ? $d[23] : ''; // Keep protocol flags for TCP only } else { $srcip = $d[15] ?: 'Unknown'; $dstip = $d[16] ?: 'Unknown'; $tcp_flags = $d[12] == 'tcp' ? $d[20] : ''; } // Determine if DST IP or SRC IP is the external host if (isset($pfb_local[$dstip]) || pfb_local_ip($dstip, $pfb_localsub)) { $dir = 'in'; $host = $srcip; $client = $dstip; $port = $d[21]; } else { $dir = 'out'; $host = $dstip; $client = $srcip; $port = $d[20]; } if (!is_ipaddr($host)) { continue; } switch (TRUE) { case isset($local_hosts["{$client}:{$port}"]) && !empty($local_hosts["{$client}:{$port}"]): $hostname = $local_hosts["{$client}:{$port}"]; break; case isset($local_hosts[$client]) && !empty($local_hosts[$client]): $hostname = $local_hosts[$client]; break; default: $hostname = 'Unknown'; } $ip_cache = FALSE; $db_handle = pfb_open_sqlite(7, 'Query ip cache'); if ($db_handle) { $db_update = "SELECT * FROM ipcache WHERE host = :host;"; $stmt = $db_handle->prepare($db_update); if ($stmt) { $stmt->bindValue(':host', $host, SQLITE3_TEXT); $result = $stmt->execute(); if ($result) { $ip_cache = $result->fetchArray(SQLITE3_ASSOC); } } } pfb_close_sqlite($db_handle); if (!$ip_cache) { // Find the header which alerted this host $geoip_folder = FALSE; $geoip_validate = substr($pfb_alias, 0, -3); if (isset($pfb['continent_list'][$geoip_validate])) { $geoip_folder = TRUE; } $pfb_query = find_reported_header($host, $folder, $geoip_folder); // Report specific ET IQRisk details if ($et_enabled && strpos($pfb_query[0], "{$et_header}") !== FALSE) { $ET_orig = $pfb_query; $pfb_query = find_reported_header($host, "{$pfb['etdir']}/*", FALSE); // ET IQRisk category is unknown. if ($pfb_query[1] == 'Unknown') { $pfb_query = $ET_orig; } else { // Prepend ET Header name $pfb_query[0] = "{$et_header}:{$pfb_query[0]}"; } } // Determine GeoIP isocode of host if (!empty($pathgeoip)) { $geoip = exec("{$pathgeoip} {$host} country iso_code | grep -v '^$' | cut -d '\"' -f2 2>&1"); if (empty($geoip)) { $geoip = 'Unk'; } } else { $geoip = 'Unk'; } // Save entry to IP cache $db_update = "INSERT into ipcache ( host, q0, q1, geoip ) VALUES ( :host, :q0, :q1, :geoip )"; $db_handle = pfb_open_sqlite(7, 'Add to IP cache'); if ($db_handle) { $pfb_host = pfb_filter($host, 1); $pfb_query[0] = pfb_filter($pfb_query[0], 1); $pfb_query[1] = pfb_filter($pfb_query[1], 1); $geoip = pfb_filter($geoip, 1); $stmt = $db_handle->prepare($db_update); if ($stmt) { $stmt->bindValue(':host', $host, SQLITE3_TEXT); $stmt->bindValue(':q0', $pfb_query[0], SQLITE3_TEXT); $stmt->bindValue(':q1', $pfb_query[1], SQLITE3_TEXT); $stmt->bindValue(':geoip', $geoip, SQLITE3_TEXT); $stmt->execute(); } } pfb_close_sqlite($db_handle); } // Use cached entries else { $host = $ip_cache['host'] ?: 'Unknown'; $pfb_query = array(); $pfb_query[0] = $ip_cache['q0'] ?: 'Unknown'; $pfb_query[1] = $ip_cache['q1'] ?: 'Unknown'; $geoip = $ip_cache['geoip'] ?: 'Unknown'; } // Modify host for ASN query if ($d[8] == 4) { // For ASN, query for IP Network address (x.x.x.0) $ix = ip_explode($host); $ip = "{$ix['6']}0"; } else { // For ASN, query for IP Network address with /48 subnet $ip = gen_subnetv6($host, 48); } // Determine ASN number of host $asn = 'null'; if ($pfb['asn_reporting'] != 'disabled') { $is_asn_cache = FALSE; $db_handle = pfb_open_sqlite(5, 'Query ASN cache'); if ($db_handle) { // Clear cached entries older than defined cache setting $db_update = "DELETE FROM asncache WHERE timestamp <= datetime('now', 'localtime', :asn_cache)"; $stmt = $db_handle->prepare($db_update); if ($stmt) { $stmt->bindValue(':asn_cache', $asn_cache, SQLITE3_TEXT); $stmt->execute(); } // Query for cached Host IP $db_update = "SELECT * FROM asncache WHERE host = :host;"; $stmt = $db_handle->prepare($db_update); if ($stmt) { $stmt->bindValue(':host', $ip, SQLITE3_TEXT); $result = $stmt->execute(); if ($result) { $is_asn_cache = $result->fetchArray(SQLITE3_ASSOC); } } } pfb_close_sqlite($db_handle); // If Host IP is not in ASN cache, collect ASN from BGPview API if (!$is_asn_cache) { $asn = ''; $bgp_url = "https://api.bgpview.io/ip/{$ip}"; $ch = curl_init(); curl_setopt_array($ch, $pfb['curl_defaults']); // Load curl default settings curl_setopt($ch, CURLOPT_TIMEOUT, 300); // Set cURL download timeout curl_setopt($ch, CURLOPT_URL, $bgp_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); // Attempt 3 Downloads before failing. for ($retries = 1; $retries <= 3; $retries++) { if ($result = curl_exec($ch)) { // Create string '| ASN | Name | Description |' from json fields $final = json_decode($result); if (is_object($final) && is_object($final->data->prefixes[0]->asn)) { foreach ($final->data->prefixes[0]->asn as $key => $line) { if ($key == 'country_code') { break; } $asn .= "| {$line} "; } $asn = str_replace(array("'", ','), '', $asn) . '|'; } break; // On download success } $asn = 'cURL Error | ' . curl_errno($ch); } curl_close($ch); if (empty($asn)) { $asn = 'Unknown'; } // Save entry to ASN cache else { $db_update = "INSERT into asncache ( asn, host, timestamp ) VALUES ( :asn, :host, datetime('now', 'localtime'))"; $db_handle = pfb_open_sqlite(5, 'Add to ASN cache'); if ($db_handle) { $asn = pfb_filter($asn, 1); $stmt = $db_handle->prepare($db_update); if ($stmt) { $stmt->bindValue(':asn', $asn, SQLITE3_TEXT); $stmt->bindValue(':host', $ip, SQLITE3_TEXT); $stmt->execute(); } } pfb_close_sqlite($db_handle); } } // Use cached ASN Entry else { $asn = "{$is_asn_cache['asn']}" ?: 'Unknown'; } } // Log: "Date timestamp/Tracker ID/Interface/Interface Name/Action/IP Version" if ($log_type == 'BSD') { $log = "{$f[0]} {$f[1]} {$f[2]},{$d[3]},{$d[4]},{$int},{$d[6]},{$d[8]},"; } else { $ts = date('M j H:i:s', strtotime($f[1])); $log = "{$ts},{$d[3]},{$d[4]},{$int},{$d[6]},{$d[8]},"; } $resolved_host = gethostbyaddr($host) ?: 'Unknown'; if ($host == $resolved_host) { $resolved_host = 'Unknown'; } // Details: "Direction/GeoIP/Aliasname/IP evaluated/Feed Name/Resolved Hostname/Client Hostname/ASN/Duplicate status" $details = "{$dir},{$geoip},{$pfb_alias},{$pfb_query[1]},{$pfb_query[0]},{$resolved_host},{$hostname},{$asn}"; // Reverse Match text if ($d[6] == 'match') { $d[6] = 'unkn(%u)'; } if ($d[8] == 4) { // Previous: "Tracker ID/Action/SRC IP/DST IP/DST Port" $p_entry = "{$d[3]}{$d[6]}{$d[18]}{$d[19]}{$d[21]}"; $d[16] = str_replace('TCP', 'TCP-', strtoupper($d[16]), $d[16]) . $tcp_flags; // Log: "Protocol ID/Protocol/SRC IP/DST IP/SRC Port/DST Port" $log .= "{$d[15]},{$d[16]},{$d[18]},{$d[19]},{$d[20]},{$d[21]}"; } else { $p_entry = "{$d[3]}{$d[6]}{$d[15]}{$d[16]}{$d[18]}"; $d[12] = str_replace('TCP', 'TCP-', strtoupper($d[12]), $d[12]) . $tcp_flags; $log .= "{$d[13]},{$d[12]},{$d[15]},{$d[16]},{$d[17]},{$d[18]}"; } } @file_put_contents("{$iplog}", "{$log},{$details},{$dup_entry}\n", FILE_APPEND | LOCK_EX); // Write to Unified Log if ($dup_entry == '+') { @file_put_contents("{$pfb['unilog']}", "{$l_type},{$log},{$details},{$dup_entry}\n", FILE_APPEND | LOCK_EX); } } } } else { log_error('[pfBlockerNG] filterlog - Failed to read STDIN'); } @fclose($s_handle); } // Function to parse grep output and return Aliasname and IP fields function pfb_parse_query($line) { $rx = explode('.txt:', $line); $rx[0] = ltrim(strrchr($rx[0], '/'), '/'); return $rx; } // Function to output Alias/Feed name string function pfb_parse_line($line) { $match = strstr($line, ':local', TRUE); if (strpos($match, '.txt') !== FALSE) { $match = strstr($match, '.txt', TRUE); } $match = substr($match, strrpos($match, '/') + 1); return $match; } // Functon to find which DNSBL Feed/Groupname blocked this event function pfb_dnsbl_parse($mode='daemon', $domain, $src_ip, $req_agent) { global $pfb; $dnsbl_cache = FALSE; $db_handle = pfb_open_sqlite(4, 'Query cache'); if ($db_handle) { $db_update = "SELECT * FROM dnsblcache WHERE domain = :domain;"; $stmt = $db_handle->prepare($db_update); if ($stmt) { $stmt->bindValue(':domain', $domain, SQLITE3_TEXT); $result = $stmt->execute(); if ($result) { $dnsbl_cache = $result->fetchArray(SQLITE3_ASSOC); } } } pfb_close_sqlite($db_handle); // If domain is not in DNSBL cache, query for blocked domain details if (!$dnsbl_cache) { $o_domain = $domain; // Store original domain for CNAME query while (TRUE) { $domainparse = str_replace('.', '\.', $domain); if ($pfb['dnsbl_py_blacklist']) { $dquery = ",{$domainparse},,"; $pfb_feed = exec("{$pfb['grep']} -shm1 '{$dquery}' {$pfb['unbound_py_data']} 2>&1"); } else { $dquery = " \"{$domainparse} 60"; $pfb_feed = pfb_parse_line(exec("{$pfb['grep']} -sHm1 '{$dquery}' {$pfb['dnsdir']}/*.txt 2>&1")); } $pfb_group = $pfb_mode = $pfb_final = 'Unknown'; // Exact Domain match found if (!empty($pfb_feed)) { if ($pfb['dnsbl_py_blacklist']) { list($dummy, $pfb_final, $empty_placeholder, $log_type, $pfb_feed, $pfb_group) = explode(',', $pfb_feed); $pfb_mode = 'DNSBL'; } else { $pfb_group = pfb_parse_line(exec("{$pfb['grep']} -sHm1 '{$dquery}' {$pfb['dnsalias']}/* 2>&1")); // Determine DNSBL Type $pfb_mode = 'DNSBL'; if (gethostbyname("dnsbl.test.{$domain}") == $pfb['dnsbl_vip']) { $pfb_mode = 'TLD'; } $pfb_final = $domain; } } else { $dparts = explode('.', $domain); if (!$pfb['dnsbl_py_blacklist']) { unset($dparts[0]); } $dcnt = count($dparts); for ($i=0; $i <= $dcnt; $i++) { $domainparse = str_replace('.', '\.', implode('.', $dparts)); if (!$pfb['dnsbl_py_blacklist']) { $dquery = " \"{$domainparse} 60"; // Determine if TLD exists in TLD Blacklist if (file_exists("{$pfb['dnsbl_tld_txt']}")) { exec("/usr/bin/grep -l '^{$domainparse}$' {$pfb['dnsbl_tld_txt']} 2>&1", $match); if (!empty($match[0])) { $pfb_group = $pfb_feed = $pfb_mode = 'DNSBL_TLD'; $pfb_final = $domainparse; break; } } } if ($pfb['dnsbl_py_blacklist']) { $dquery = ",{$domainparse},,"; $pfb_feed = exec("{$pfb['grep']} -shm1 '{$dquery}' {$pfb['unbound_py_zone']} 2>&1"); // Collect Alias Group name if (!empty($pfb_feed)) { list($dummy, $d_found, $empty_placeholder, $log_type, $pfb_feed, $pfb_group) = explode(',', $pfb_feed); $pfb_final = str_replace('\.', '.', $domainparse); $pfb_mode = 'TLD'; if ($pfb_feed == 'DNSBL_TLD') { $pfb_mode = 'DNSBL_TLD'; } break; } } else { $pfb_feed = pfb_parse_line(exec("{$pfb['grep']} -sHm1 '{$dquery}' {$pfb['dnsdir']}/*.txt 2>&1")); // Collect Alias Group name if (!empty($pfb_feed)) { $pfb_group = pfb_parse_line(exec("{$pfb['grep']} -sHm1 '{$dquery}' {$pfb['dnsalias']}/* 2>&1")); $pfb_mode = 'TLD'; $pfb_final = str_replace('\.', '.', $domainparse); break; } } unset($dparts[$i]); } } // Query for CNAME(s) if (empty($pfb_feed)) { if (!isset($cnames)) { exec("/usr/bin/drill {$domain} @{$pfb['extdns']} | /usr/bin/awk '/CNAME/ {sub(\"\.$\", \"\", $5); print $5;}'", $cnames); $cname_cnt = count($cnames); } if (is_array($cnames) && !empty($cnames)) { $domain = array_shift($cnames); } if ($cname_cnt == 0) { $domain = $o_domain; // No CNAME match found, revert back to original domain break; } $cname_cnt--; } else { break; } } $pfb_feed = $pfb_feed ?: 'Unknown'; if (isset($cnames)) { unset($cnames); if ($pfb_feed != 'Unknown') { $pfb_mode = "{$pfb_mode}-CNAME"; } } // Save entry to DNSBL cache $db_update = "INSERT into dnsblcache ( type, domain, groupname, final, feed ) VALUES ( :type, :domain, :groupname, :final, :feed )"; $db_handle = pfb_open_sqlite(4, 'Add to DNSBL cache'); if ($db_handle) { $domain = pfb_filter($domain, 1); $pfb_group = pfb_filter($pfb_group, 1); $pfb_final = pfb_filter($pfb_final, 1); $pfb_feed = pfb_filter($pfb_feed, 1); $stmt = $db_handle->prepare($db_update); if ($stmt) { $stmt->bindValue(':type', $pfb_mode, SQLITE3_TEXT); $stmt->bindValue(':domain', $domain, SQLITE3_TEXT); $stmt->bindValue(':groupname', $pfb_group, SQLITE3_TEXT); $stmt->bindValue(':final', $pfb_final, SQLITE3_TEXT); $stmt->bindValue(':feed', $pfb_feed, SQLITE3_TEXT); $stmt->execute(); } } pfb_close_sqlite($db_handle); } // Use cached entries else { $pfb_mode = $dnsbl_cache['type'] ?: 'Unknown'; $pfb_group = $dnsbl_cache['groupname'] ?: 'Unknown'; $pfb_final = $dnsbl_cache['final'] ?: 'Unknown'; $pfb_feed = $dnsbl_cache['feed'] ?: 'Unknown'; } if ($mode == 'daemon') { $details = "{$domain},{$src_ip},{$req_agent},{$pfb_mode},{$pfb_group},{$pfb_final},{$pfb_feed}"; return array ($pfb_group, $details); } else { return array ('pfb_mode' => $pfb_mode, 'pfb_group' => $pfb_group, 'pfb_final' => $pfb_final, 'pfb_feed' => $pfb_feed); } } // DNSBL Lighttpd 'dnsbl_error.log' conditional log parser function pfb_daemon_dnsbl() { global $pfb; if (($h_handle = @fopen('php://stdin', 'r')) !== FALSE) { syslog(LOG_NOTICE, '[pfBlockerNG] DNSBL parser daemon started'); $chk_ver = FALSE; $lighty_47 = FALSE; $lighty_58 = FALSE; $lighty_59 = FALSE; $checkpos = 3; // Pre Lighttpd v1.4.47 while (!feof($h_handle)) { $pfb_buffer = @fgets($h_handle); if (!$chk_ver) { // Lighttpd v1.4.58+ conditional error log with 'ssl.verifyclient.activate' to collect the domain name if (!$lighty_47 && strpos($pfb_buffer, 'lighttpd/1.4.58') !== FALSE) { $chk_ver = TRUE; $lighty_58 = TRUE; continue; } // Lighttpd v1.4.59+ syntax changes to IP line elseif (!$lighty_47 && (strpos($pfb_buffer, 'lighttpd/1.4.59') !== FALSE || strpos($pfb_buffer, 'lighttpd/1.4.60') !== FALSE || strpos($pfb_buffer, 'lighttpd/1.4.61') !== FALSE || strpos($pfb_buffer, 'lighttpd/1.5') !== FALSE)) { $chk_ver = TRUE; $lighty_59 = TRUE; continue; } // Lighttpd v1.4.47 uses mod_openssl with a different conditional log formatting elseif (strpos($pfb_buffer, 'global/HTTPscheme') !== FALSE) { $lighty_47 = TRUE; $checkpos = 1; continue; } } if ($lighty_58 || $lighty_59) { // Verify HTTP["remoteip"] if (empty($src_ip)) { if ($lighty_58) { if (strpos($pfb_buffer, '["re') !== FALSE) { $src_ip = strstr($pfb_buffer, ') compare', TRUE); $src_ip = ltrim(strstr($src_ip, '] (', FALSE), '] ('); $src_ip = (filter_var($src_ip, FILTER_VALIDATE_IP) !== FALSE) ? $src_ip : ''; } } else { if (strpos($pfb_buffer, '["re') !== FALSE && strpos($pfb_buffer, ' compare to ') !== FALSE) { $src_ip = strstr($pfb_buffer, ' compare to ', FALSE); $src_ip = trim(ltrim($src_ip, ' compare to ')); $src_ip = (filter_var($src_ip, FILTER_VALIDATE_IP) !== FALSE) ? $src_ip : ''; } } continue; } if (!empty($src_ip) && strpos($pfb_buffer, "SSL: ") === FALSE) { continue; } if (empty($domain) && strpos($pfb_buffer, "SSL: can't verify client without ssl.ca-file") !== FALSE) { $domain = str_replace(' server name ', '', strstr(trim($pfb_buffer), ' server name ', FALSE)); } if (!empty($domain) && !empty($src_ip)) { // URL/Referer/URI/Agent String not available for HTTPS events $req_agent = 'Unknown'; $type = 'DNSBL-HTTPS'; pfb_log_event($type, $domain, $src_ip, $req_agent); $domain = $src_ip = ''; } } else { // Parse only HTTP["xxx"] log lines if (strpos($pfb_buffer, 'HTTP[') === FALSE) { continue; } // Verify only HTTPS lines if (strpos($pfb_buffer, '( https') !== FALSE) { if ($lighty_47) { continue; } $checkpos = 0; } // Verify HTTP["remoteip"] if ($checkpos == 1 && strpos($pfb_buffer, '["re') !== FALSE) { $src_ip = strstr($pfb_buffer, ' ) compare', TRUE); $src_ip = ltrim(strstr($src_ip, '] ( ', FALSE), '] ( '); $src_ip = (filter_var($src_ip, FILTER_VALIDATE_IP) !== FALSE) ? $src_ip : ''; } // Verify HTTP["host"] elseif ($checkpos == 2 && strpos($pfb_buffer, '["ho') !== FALSE) { $lighty_47 = FALSE; $domain = strstr($pfb_buffer, ' ) compare', TRUE); $domain = ltrim(strstr($domain, '] ( ', FALSE), '] ( '); // URL/Referer/URI/Agent String not available for HTTPS events $req_agent = 'Unknown'; $type = 'DNSBL-HTTPS'; // Log event and Increment SQLite DNSBL Group counter if (!empty($domain) && !empty($src_ip)) { pfb_log_event($type, $domain, $src_ip, $req_agent); } } $checkpos++; } } } else { log_error('[pfBlockerNG] DNSBL conditional log parser - Failed to open handle'); } @fclose($h_handle); } // DNSBL Lighttpd 'index.php' event parser function pfb_daemon_dnsbl_index() { global $pfb; // Replace any [',' or '|' ] in HTTP_REFERER, REQUEST_URI or HTTP_USER_AGENT fields // Replace placeholder characters [ '!' and '*' ] to [ ',' and '|' ] $p1 = array( ',', '!', '|', ' * ' ); $p2 = array( '', ',', '--', '|' ); if (($i_handle = @fopen('php://stdin', 'r')) !== FALSE) { while (!feof($i_handle)) { $pfb_buffer = @fgets($i_handle); $pfb_buffer = str_replace($p1, $p2, $pfb_buffer); if (substr($pfb_buffer, 0, 6) == 'INDEX,' && substr_count($pfb_buffer, ',') == 4) { $csvline = str_getcsv($pfb_buffer, ',', '', '"'); // Determine blocked domain type (Full, 1x1 or JS) if (isset($csvline[1]) && !empty($csvline[1])) { $request = strstr($csvline[1], '/', FALSE); $request = strstr($request, ' ', TRUE); if (strlen($request) < 2) { $type = 'DNSBL-Full'; } else { if (pathinfo($request, PATHINFO_EXTENSION) == 'js') { $type = 'DNSBL-JS'; } else { $type = 'DNSBL-1x1'; } } } else { $type = 'DNSBL-Unknown'; } // Sanitize IP address $csvline[3] = (filter_var($csvline[3], FILTER_VALIDATE_IP) !== FALSE) ? $csvline[3] : ''; } else { continue; } // Log event and increment SQLite DNSBL Group counter if (!empty($csvline[2]) && !empty($csvline[3])) { pfb_log_event($type, $csvline[2], $csvline[3], $csvline[4]); } } } else { log_error('[pfBlockerNG] DNSBL index event parser - Failed to open handle'); } @fclose($i_handle); } // Function to create/open SQLite3 database(s) function pfb_open_sqlite($table, $message) { global $pfb; if ($table == 1) { $database = $pfb['dnsbl_info']; $db_table = 'dnsbl'; $db_create = "CREATE TABLE IF NOT EXISTS dnsbl ( groupname TEXT, timestamp TEXT, entries TEXT, counter INTEGER );"; } elseif ($table == 2) { $database = $pfb['dnsbl_resolver']; $db_table = 'lastevent'; $db_create = "CREATE TABLE IF NOT EXISTS lastevent ( row INTEGER, groupname TEXT, entry TEXT, details TEXT );"; } elseif ($table == 3) { $database = $pfb['dnsbl_resolver']; $db_table = 'resolver'; $db_create = "CREATE TABLE IF NOT EXISTS resolver ( row INTEGER, totalqueries INTEGER, queries INTEGER );"; } elseif ($table == 4) { $database = $pfb['dnsbl_cache']; $db_table = 'dnsblcache'; $db_create = "CREATE TABLE IF NOT EXISTS dnsblcache ( type TEXT, domain TEXT, groupname TEXT, final TEXT, feed TEXT );"; } elseif ($table == 5) { $database = $pfb['asn_cache']; $db_table = 'asncache'; $db_create = "CREATE TABLE IF NOT EXISTS asncache ( asn TEXT, host TEXT, timestamp TEXT );"; } elseif ($table == 6) { $database = $pfb['dnsbl_resolver']; $db_table = 'stats'; $db_create = "CREATE TABLE IF NOT EXISTS lastclear ( row INTEGER, lastipclear TEXT, lastdnsblclear TEXT );"; } elseif ($table == 7) { $database = $pfb['ip_cache']; $db_table = 'ipcache'; $db_create = "CREATE TABLE IF NOT EXISTS ipcache ( host TEXT, q0 TEXT, q1 TEXT, geoip TEXT );"; } try { $db_handle = new SQLite3($database); $db_handle->busyTimeout("{$pfb['sqlite_timeout']}"); } catch (Exception $e) { @file_put_contents($pfb['errlog'], "\nDNSBL_SQL: Failed to open DB - {$message}", FILE_APPEND | LOCK_EX); try { $db_handle = new SQLite3($database); $db_handle->busyTimeout("{$pfb['sqlite_timeout']}"); } catch (Exception $e) { @file_put_contents($pfb['errlog'], "\nDNSBL_SQL: Failed to open DB 2nd attempt - {$message}", FILE_APPEND | LOCK_EX); } } if ($db_handle) { // Validate database integrity $validate = $pfb_validate = FALSE; try { $validate = $db_handle->query("PRAGMA integrity_check;"); } catch (Exception $e) { @file_put_contents($pfb['errlog'], "\nDNSBL_SQL: Failed to validate database - {$message}", FILE_APPEND | LOCK_EX); } if ($validate) { while ($res = $validate->fetchArray(SQLITE3_ASSOC)) { if ($res['integrity_check'] == 'ok') { $pfb_validate = TRUE; } } } if (!$pfb_validate) { log_error("[pfBlockerNG] DNSBL SQLite3 database [ {$db_table} ] corrupt. Table deletion/re-creation completed."); @copy("{$database}", "{$database}.invalid"); $db_create = "DROP TABLE {$db_table}; {$db_create}"; } try { $db_handle->exec("BEGIN TRANSACTION; PRAGMA journal_mode = delete;" . "{$db_create}" . "END TRANSACTION;"); } catch (Exception $e) { @file_put_contents($pfb['errlog'], "\nDNSBL_SQL: Database failure - {$message}", FILE_APPEND | LOCK_EX); return; } if ($table <= 4 && file_exists($database)) { @chown($database, 'unbound'); @chgrp($database, 'unbound'); } return $db_handle; } return FALSE; } // Function to close SQLite3 database function pfb_close_sqlite($db_handle) { if (!empty($db_handle)) { $db_handle->close(); unset($db_handle); } } // Function to Log event, Increment SQLite 'dnsbl' database and save last event to SQLite 'lastevent' Database function pfb_log_event($type, $domain, $src_ip, $req_agent) { global $pfb; $datereq = date('M j H:i:s', time()); // Sanitize values $domain = pfb_filter($domain, 1); $req_agent = pfb_filter($req_agent, 1); $req_agent = str_replace("'", '', $req_agent); // Collect lastevent without saving new event $result = pfb_dnsbl_lastevent('', "{$domain}{$src_ip}", ''); $p_entry = $result['entry']; $details = $result['details']; // Duplicate entry comparison: "Domain/SRC IP" if ("{$domain}{$src_ip}" == $p_entry) { $dup_entry = '-'; $pfb_group = $result['groupname']; } // If not duplicate entry, determine TLD type, Group Name and Feed name else { $dup_entry = '+'; $data = pfb_dnsbl_parse('daemon', $domain, $src_ip, $req_agent); $pfb_group = $data[0]; $details = $data[1]; $req_agent = $data[2]; // Save new lastevent pfb_dnsbl_lastevent($pfb_group, "{$domain}{$src_ip}", $details); } $log = "{$type},{$datereq},{$details},{$dup_entry}\n"; @file_put_contents($pfb['dnslog'], "{$log}", FILE_APPEND | LOCK_EX); // Write to Unified Log @file_put_contents($pfb['unilog'], "{$log}", FILE_APPEND | LOCK_EX); // Increment DNSBL Widget counter if (!empty($pfb_group)) { $db_handle = pfb_open_sqlite(1, 'Increment Counter'); if ($db_handle) { $pfb_group = pfb_filter($pfb_group, 1); $db_update = "UPDATE dnsbl SET counter = counter + 1 WHERE groupname = :pfb_group"; $stmt = $db_handle->prepare($db_update); if ($stmt) { $stmt->bindValue(':pfb_group', $pfb_group, SQLITE3_TEXT); $stmt->execute(); } } pfb_close_sqlite($db_handle); } } // Function to 1) Collect lastevent and 2) Save lastevent to SQLite 'lastevent' Database function pfb_dnsbl_lastevent($p_group, $p_entry, $p_details) { global $pfb; $db_update = ''; $final = array(); $db_handle = pfb_open_sqlite(2, 'LastEvent'); if ($db_handle) { $result = $db_handle->query("SELECT * FROM lastevent WHERE row = 0;"); if ($result) { $final = $result->fetchArray(SQLITE3_ASSOC); } } pfb_close_sqlite($db_handle); // Collect or update existing row if (!empty($final)) { // Only collect lastevent entry if (empty($p_group)) { ; } // Update lastevent entry else { $db_update = "UPDATE lastevent SET groupname=:p_group, entry=:p_entry, details=:p_details WHERE row = 0"; } } // Add new lastevent entry else { $db_update = "INSERT into lastevent (row, groupname, entry, details) VALUES (0, :p_group, :p_entry, :p_details )"; } if (!empty($db_update)) { $db_handle = pfb_open_sqlite(2, 'LastEvent'); if ($db_handle) { $p_group = pfb_filter($p_group, 1); $p_entry = pfb_filter($p_entry, 1); $p_details = pfb_filter($p_details, 1); $stmt = $db_handle->prepare($db_update); if ($stmt) { $stmt->bindValue(':p_group', $p_group, SQLITE3_TEXT); $stmt->bindValue(':p_entry', $p_entry, SQLITE3_TEXT); $stmt->bindValue(':p_details', $p_details, SQLITE3_TEXT); $stmt->execute(); } } pfb_close_sqlite($db_handle); } return $final; } // Function to collect and update the Unbound Resolver query/pid entries into SQLite3 database function pfb_daemon_queries() { global $g, $config, $pfb; $sleep_freq = 5; if (isset($config['installedpackages']['pfblockerngglobal']) && isset($config['installedpackages']['pfblockerngglobal']['widget-dnsblquery'])) { $sleep_freq = $config['installedpackages']['pfblockerngglobal']['widget-dnsblquery'] ?: 5; } $nice = '/usr/bin/nice -n20'; $unbound_pid = "{$g['varrun_path']}/unbound.pid"; while (TRUE) { sleep($sleep_freq); // If 'Live Sync' file marker exists skip DNSBL Queries daemon to avoid unbound-control collisions if (platform_booting() || $g['pfblockerng_install'] || file_exists("{$pfb['dnsbl_file']}.sync")) { continue; } // Collect Unbound Resolver pid $pid = exec("{$nice} /usr/bin/pgrep -anx 'unbound' 2>&1"); if (!empty($pid)) { $output = exec("{$pfb['chroot_cmd']} stats_noreset | {$nice} {$pfb['grep']} 'total.num.queries=' | cut -d '=' -f2 2>&1"); $query = intval($output); if (!is_numeric($query)) { $query = ''; } // On an Unbound Reload, the query stat is reset, therefore reuse previous query value if ($query < $p_query) { $t_query = $query; $query = $query + $p_query; $pid = ''; } } // On initial daemon load if (empty($p_pid)) { $p_pid = $pid; continue; } $pfb_found = FALSE; $stats = array(); $stats['totalqueries'] = 0; $stats['queries'] = 0; $db_handle = pfb_open_sqlite(3, 'Resolver collect queries'); if ($db_handle) { $result = $db_handle->query("SELECT * FROM resolver WHERE row = 0;"); if ($result) { while ($res = $result->fetchArray(SQLITE3_ASSOC)) { $stats = $res; $pfb_found = TRUE; } } // Create new row if (!$pfb_found) { $db_update = "INSERT INTO resolver ( row, totalqueries, queries ) VALUES ( 0, 0, 0 );"; $db_handle->exec("BEGIN TRANSACTION;" . "{$db_update}" . "END TRANSACTION;"); } } pfb_close_sqlite($db_handle); // If Unbound Resolver pid has changed, clear SQLite database 'queries' entry, and update 'totalqueries/pid' entries if ($pfb_found && $pid != $p_pid) { $totalqueries = ($stats['totalqueries'] ?: 0) + ($query ?: 0); pfBlockerNG_clearsqlite('update_totalqueries', $totalqueries); } else { if ($query != '' && $query != $p_query) { // Update existing row $db_handle = pfb_open_sqlite(3, 'Widget update queries'); if ($db_handle) { $db_update = "UPDATE resolver SET queries = :query WHERE row = 0"; $stmt = $db_handle->prepare($db_update); if ($stmt) { $stmt->bindValue(':query', $query, SQLITE3_INTEGER); $stmt->execute(); } } pfb_close_sqlite($db_handle); } } $p_pid = $pid; if (isset($t_query)) { $p_query = $t_query; unset($t_query); } else { $p_query = $query; } } } // Read logfile in realtime (livetail) // Reference: http://stackoverflow.com/questions/3218895/php-how-to-read-a-file-live-that-is-constantly-being-written-to function pfb_livetail($logfile, $mode) { global $pfb; if (!file_exists("{$logfile}")) { touch("{$logfile}"); } $len = @filesize("{$logfile}"); // Start at EOF $lastpos_old = $pfb_output = ''; if ($mode == 'view') { // Start at EOF ( - 15000) if ($len > 15000) { $lastpos = ($len - 15000); } else { $lastpos = 0; } } else { $lastpos = $len; } while (TRUE) { usleep(300000); //0.3s clearstatcache(false, "{$logfile}"); $len = @filesize("{$logfile}"); if ($len < $lastpos) { $lastpos = $len; // File deleted or reset } else { $f = @fopen("{$logfile}", 'rb+'); if ($f === false) { break; } @fseek($f, $lastpos); while (!feof($f)) { $pfb_buffer = @fread($f, 2048); $pfb_output .= str_replace( array ("\r", "\")"), '', $pfb_buffer); // Refresh on new lines only. This allows Scrolling. if ($lastpos != $lastpos_old) { pfbupdate_output($pfb_output); } $lastpos_old = $lastpos; ob_flush(); flush(); } $lastpos = @ftell($f); @fclose($f); // Capture remaining output if ($mode != 'view' && strpos($pfb_output, 'UPDATE PROCESS ENDED') !== FALSE) { $f = @fopen($pfb['log'], 'rb'); @fseek($f, $lastpos); $pfb_buffer = @fread($f, 2048); $pfb_output .= str_replace( "\r", '', $pfb_buffer); pfbupdate_output($pfb_output); clearstatcache(false, $pfb['log']); ob_flush(); flush(); @fclose($f); // Call log mgmt function pfb_log_mgmt(); break; } } } } // Format 'Header/ISOs data' for Alias URLs value field and Firewall Rule Alias detail popup function pfb_url_value($urlvalue, $alias) { global $pfb; $urlfinal = ''; if (strlen($urlvalue) > 60) { $counter = 1; $urlvalue = rtrim($urlvalue, ','); $data = explode(',', $urlvalue); $keycount = (count($data) -1); foreach ($data as $key => $line) { $urlfinal .= "{$line}"; if ($key != $keycount) { $urlfinal .= ', '; if ($counter % 15 == 0) { $urlfinal .= "
"; } } $counter++; } } else { $urlfinal = str_replace(',', ', ', rtrim($urlvalue, ',')); } return "{$pfb['weblocal']}?pfb={$alias}
[ {$urlfinal} ]"; } // Load/convert Feeds (w/alternative aliasname(s), if user-configured) and return as array function convert_feeds_json() { global $config, $pfb; $aconfig = $config['installedpackages']['pfblockerngglobal']; $pfb['feeds_list'] = $merge_feeds = $feed_info = array(); $feed_info_raw = json_decode(@file_get_contents("{$pfb['feeds']}"), TRUE); if (json_last_error() !== JSON_ERROR_NONE || !is_array($feed_info_raw)) { return array('blank' => ''); } $feed_count = array(); foreach ($feed_info_raw as $type => $info) { if (!is_array($info) || $info[0] == '*') { continue; } $feed_count[$type] = 0; foreach ($info as $aliasname => $data) { $l_aliasname = strtolower($aliasname); $feed_count[$type] = $feed_count[$type] + count($data['feeds']); foreach ($data['feeds'] as $key => $feed) { // Remove discontinued Feeds if (isset($feed['status']) && $feed['status'] == 'discontinued') { unset($data['feeds'][$key]); $feed_count[$type]--; continue; } if (isset($feed['alternate'])) { foreach ($feed['alternate'] as $alternate) { $feed_count[$type]++; } } } if (!is_array($pfb['feeds_list'][$type])) { $pfb['feeds_list'][$type] = array(); } if (!is_array($feed_info[$type])) { $feed_info[$type] = array(); } // Use alternative Aliasname(s) and/or merge multiple aliasname Feeds together (if user configured) if (!empty($aconfig['feed_' . $l_aliasname])) { $alt_feed = $aconfig['feed_' . $l_aliasname]; $pfb['feeds_list'][$type][$aliasname] = $alt_feed; // Global list of all known Feed aliasnames if (!is_array($merge_feeds[$alt_feed])) { $merge_feeds[$alt_feed] = array(); } $merge_feeds[$alt_feed] = array_merge( $merge_feeds[$alt_feed], (array)$data['feeds'] ); if (!isset($feed_info[$type][$alt_feed])) { // Modify 'info' and 'description' fields to reference user-defined aliasname foreach (array('info', 'description') as $atype) { $match = strpos($data[$atype], $aliasname); if ($match !== FALSE) { $data[$atype] = substr_replace($data[$atype], $aconfig['feed_' . $l_aliasname], $match, strlen($aliasname)); } } $feed_info[$type][$aconfig['feed_' . $l_aliasname]] = $data; } $feed_info[$type][$alt_feed]['feeds'] = $merge_feeds[$alt_feed]; } else { $pfb['feeds_list'][$type][$aliasname] = $aliasname; $feed_info[$type][$aliasname] = $data; } } } $feed_info['count'] = $feed_count; return $feed_info; } // Define Alerts Tab 'default GET request' (Top row) function pfb_alerts_default_page() { global $pfb; if (isset($pfb['config_global']) && isset($pfb['config_global']['pfbpageload'])) { switch($pfb['config_global']['pfbpageload']) { case 'dnsbl_stat': return '?view=dnsbl_stat'; case 'dnsbl_reply_stat': return '?view=dnsbl_reply_stat'; case 'ip_block_stat': return '?view=ip_block_stat'; case 'ip_permit_stat': return '?view=ip_permit_stat'; case 'ip_match_stat': return '?view=ip_match_stat'; case 'reply': return '?view=reply'; case 'unified': return '?view=unified'; } } return ''; } // Clear IP Alias Packet Counts (widget) function pfBlockerNG_clearip() { global $pfb; exec("{$pfb['pfctl']} -z 2>&1"); /* TODO: Clear only pfB counters $pfb_tables = array(); exec("{$pfb['pfctl']} -sTables | {$pfb['grep']} 'pfB_' 2>&1", $pfb_tables); if (!empty($pfb_tables)) { foreach ($pfb_tables as $table) { exec("{$pfb['pfctl']} -t {$table} -T zero"); } } */ } // Clear DNSBL SQLite database statistics/queries as required function pfBlockerNG_clearsqlite($mode, $totalqueries = 0) { global $g, $pfb; // Format of todo array: database, error message, SQLite command $todo = array(); // Clear SQLite database 'queries' entry and update totalqueries (+queries), if Unbound Resolver PID changed (Reload) if ($mode == 'update_totalqueries') { $todo[] = array(3, 'Clear Resolver queries', 'UPDATE resolver SET totalqueries = :totalqueries, queries = 0 WHERE row = 0;'); } elseif ($mode == 'clearip') { $lastipclear = date('M j H:i:s', time()); $todo[] = array(6, 'Reset IP last clear timestamp', 'UPDATE lastclear SET lastipclear = :lastipclear WHERE row = 0;'); } elseif ($mode == 'cleardnsbl') { $lastdnsblclear = date('M j H:i:s', time()); $todo[] = array(1, 'Clear Widget counters', 'UPDATE dnsbl SET counter = 0;'); $todo[] = array(3, 'Clear Resolver queries', 'UPDATE resolver SET totalqueries = 0, queries = 0;'); $todo[] = array(6, 'Reset DNSBL last clear timestamp', 'UPDATE lastclear SET lastdnsblclear = :lastdnsblclear WHERE row = 0;'); } // Clear Unbound Resolver statistics if ($mode != 'clearip' && is_process_running('unbound')) { exec("{$pfb['chroot_cmd']} flush_stats 2>&1"); } if (!empty($todo)) { foreach ($todo as $data) { $db_handle = pfb_open_sqlite($data[0], $data[1]); if ($db_handle) { if ($mode == 'update_totalqueries') { if (is_numeric($totalqueries)) { $stmt = $db_handle->prepare($data[2]); if ($stmt) { $stmt->bindValue(':totalqueries', $totalqueries, SQLITE3_INTEGER); $stmt->execute(); } } } elseif ($mode == 'clearip') { $stmt = $db_handle->prepare($data[2]); if ($stmt) { $stmt->bindValue(':lastipclear', $lastipclear, SQLITE3_TEXT); $stmt->execute(); } } elseif ($mode == 'cleardnsbl') { $stmt = $db_handle->prepare($data[2]); if ($stmt) { $stmt->bindValue(':lastdnsblclear', $lastdnsblclear, SQLITE3_TEXT); $stmt->execute(); } } else { $db_handle->exec("BEGIN TRANSACTION;" . "{$data[2]}" . "END TRANSACTION;"); } } pfb_close_sqlite($db_handle); } } } // Function to read/lock/unlock IP/Domains from Aliastables/DNSBL (Called via Alerts Page) function pfb_unlock($mode, $type, $remove='', $r_type='', $filename_unlock) { global $pfb; if ($type == 'ip') { $filename = $pfb['ip_unlock']; } else { $filename = $pfb['dnsbl_unlock']; } if ($mode == 'read') { $filename_unlock = array(); if (($handle = @fopen("{$filename}", 'r')) !== FALSE) { while (($line = @fgetcsv($handle)) !== FALSE) { if (!empty($line)) { $filename_unlock[$line[0]] = $line[1]; } } } if (empty($filename_unlock)) { unlink_if_exists("{$filename}"); } return $filename_unlock; } elseif ($mode == 'unlock' && isset($filename_unlock[$remove])) { return; } elseif (empty($remove)) { return; } // Add/Remove IP/Domain in unlock file if (($pfb_output = @fopen("{$filename}", 'w')) !== FALSE) { foreach ($filename_unlock as $key => $line) { // 'Remove locked IP/Domains' or 'Add existing unlocked IP/Domain' in unlock file if ($mode == 'unlock' || ($mode == 'lock' && $key != $remove)) { @fwrite($pfb_output, "{$key},{$line}\n"); } } // Add IP/Domain to unlock file if ($mode == 'unlock') { $filename_unlock[$remove] = $r_type; @fwrite($pfb_output, "{$remove},{$r_type}\n"); } } @fclose($pfb_output); if (empty($filename_unlock)) { unlink_if_exists("{$filename}"); } } // Function to clear pfBlockerNG folder/files function pfb_clear_contents() { global $config, $pfb; unlink_if_exists("{$pfb['dbdir']}/masterfile"); unlink_if_exists("{$pfb['dbdir']}/mastercat"); unlink_if_exists("{$pfb['supptxt']}"); unlink_if_exists("{$pfb['dnsbl_supptxt']}"); unlink_if_exists("{$pfb['dnsbl_info']}"); unlink_if_exists("{$pfb['dnsbl_resolver']}"); unlink_if_exists("{$pfb['dnsbl_cache']}"); unlink_if_exists("/var/tmp/unbound_cache"); unlink_if_exists("{$pfb['asn_cache']}"); unlink_if_exists("{$pfb['ip_cache']}"); rmdir_recursive("{$pfb['origdir']}"); rmdir_recursive("{$pfb['matchdir']}"); rmdir_recursive("{$pfb['permitdir']}"); rmdir_recursive("{$pfb['denydir']}"); rmdir_recursive("{$pfb['nativedir']}"); rmdir_recursive("{$pfb['etdir']}"); rmdir_recursive("{$pfb['dnsdir']}"); rmdir_recursive("{$pfb['dnsorigdir']}"); rmdir_recursive("{$pfb['dnsalias']}"); } // Main pfBlockerNG function function sync_package_pfblockerng($cron='') { global $g, $config, $pfb, $pfbarr; pfb_global(); $pfb['conf_mod'] = FALSE; // Flag to check for mods to the config.xml file. ('$pfb_config' array to hold changes) $pfb['filter_configure'] = FALSE; // Flag to call filter_configure once // Detect boot process or package installation if (platform_booting() || $g['pfblockerng_install']) { // Create DNSBL NAT, VIP, Lighttpd service and certs if required on reboot. if ($pfb['dnsbl'] == 'on') { pfb_create_dnsbl('enabled'); } if ($pfb['dnsbl_mode'] == 'dnsbl_python') { if (!file_exists("{$g['unbound_chroot_path']}/pfb_unbound.py")) { @copy("/usr/local/pkg/pfblockerng/pfb_unbound.py", "{$g['unbound_chroot_path']}/pfb_unbound.py"); } if (!file_exists("{$g['unbound_chroot_path']}/pfb_unbound_include.inc")) { @copy("/usr/local/pkg/pfblockerng/pfb_unbound_include.inc", "{$g['unbound_chroot_path']}/pfb_unbound_include.inc"); } if (!file_exists("{$g['unbound_chroot_path']}/pfb_py_hsts.txt")) { @copy("/usr/local/pkg/pfblockerng/pfb_py_hsts.txt", "{$g['unbound_chroot_path']}/pfb_py_hsts.txt"); } } $log = 'Sync terminated during boot process.'; pfb_logger("\n{$log}\nUPDATE PROCESS ENDED [ NOW ]\n", 1); log_error("[pfBlockerNG] {$log}"); return; } // Reloads existing lists without downloading new lists when defined 'on' $pfb['reuse'] = $pfb['config']['pfb_reuse']; $pfb['reuse_dnsbl'] = ''; // Define update process (update or reload) switch ($cron) { case 'noupdates': // Force update - Set 'save' variable when 'No updates' found. $pfb['save'] = TRUE; break; case 'cron': if ($pfb['reuse'] == 'on') { $pfb['reuse_dnsbl'] = 'on'; unlink_if_exists("{$pfb['dbdir']}/masterfile"); unlink_if_exists("{$pfb['dbdir']}/mastercat"); } break; case 'updatednsbl': $pfb['reuse'] = ''; $pfb['reuse_dnsbl'] = 'on'; $pfb['updatednsbl'] = TRUE; break; case 'updateip': $pfb['reuse'] = 'on'; $pfb['reuse_dnsbl'] = ''; unlink_if_exists("{$pfb['dbdir']}/masterfile"); unlink_if_exists("{$pfb['dbdir']}/mastercat"); break; } // Start of pfBlockerNG logging to 'pfblockerng.log' if ($pfb['enable'] == 'on' && !$pfb['save']) { $log = " UPDATE PROCESS START [ " . pfb_pkg_ver() . " ] [ NOW ]\n"; pfb_logger("{$log}", 1); } else { if ($cron != 'noupdates') { $log = "\n**Saving configuration [ NOW ]**\n"; pfb_logger("{$log}", 1); } } // Call function for Ramdisk processes. pfb_aliastables('conf'); // If table limit not defined, set default to 2M if (empty($config['system']['maximumtableentries'])) { $config['system']['maximumtableentries'] = '2000000'; write_config('pfBlockerNG: save max Firewall table entries limit'); } $pfb['table_limit'] = $config['system']['maximumtableentries']; // Collect local web gui configuration $pfb['weblocal'] = $config['system']['webgui']['protocol'] ?: 'http'; $pfb['port'] = $config['system']['webgui']['port']; if (empty($pfb['port'])) { if ($config['system']['webgui']['protocol'] == 'http') { $pfb['port'] = '80'; } else { $pfb['port'] = '443'; } } $pfb['weblocal'] .= "://127.0.0.1:{$pfb['port']}/pfblockerng/pfblockerng.php"; // Define Inbound/Outbound action is not user selected. $pfb['deny_action_inbound'] = $pfb['ipconfig']['inbound_deny_action'] ?: 'block'; $pfb['deny_action_outbound'] = $pfb['ipconfig']['outbound_deny_action'] ?: 'reject'; $pfb['float'] = $pfb['ipconfig']['enable_float']; // Enable/Disable floating autorules $pfb['dup'] = $pfb['ipconfig']['enable_dup']; // Enable remove of duplicate IPs utilizing grepcidr $pfb['agg'] = $pfb['ipconfig']['enable_agg']; // Enable aggregation of CIDRs $pfb['order'] = $pfb['ipconfig']['pass_order']; // Order of the autorules $pfb['global_log'] = $pfb['ipconfig']['enable_log']; // Enable Global IP logging $pfb['suffix'] = $pfb['ipconfig']['autorule_suffix']; // Suffix used for autorules $pfb['kstates'] = $pfb['ipconfig']['killstates']; // Firewall states removal $pfb['ip_ph'] = $pfb['ipconfig']['ip_placeholder'] ?: '127.1.7.7'; // Placeholder IP Address // DNSBL settings $pfb['dnsbl_ip'] = $pfb['dnsblconfig']['action'] ?: 'Disabled'; // Enable/Disable IP blocking from DNSBL lists $pfb['dnsbl_rule'] = $pfb['dnsblconfig']['pfb_dnsbl_rule'] ?: 'Disabled'; // Auto create a Floating Pass Rule for other Lan subnets $pfb['dnsbl_alexa_cnt'] = $pfb['dnsblconfig']['alexa_count'] ?: '1000'; // TOP1M whitelist domain setting $pfb['dnsbl_alexa_inc'] = $pfb['dnsblconfig']['alexa_inclusion']?: ''; // TOP1M TLDs inclusions for whitelisting $pfb['dnsbl_tld'] = $pfb['dnsblconfig']['pfb_tld']; // Enable TLD Function $pfb['dnsbl_control'] = $pfb['dnsblconfig']['pfb_control'] ?: ''; // Python Control integration // Reputation config variables init_config_arr(array('installedpackages', 'pfblockerngreputation', 'config', 0)); $pfb['config_rep'] = $config['installedpackages']['pfblockerngreputation']['config'][0]; $pfb['rep'] = $pfb['config_rep']['enable_rep']; // Enable/Disable 'Max' Reputation $pfb['prep'] = $pfb['config_rep']['enable_pdup']; // Enable/Disable 'pRep' Reputation $pfb['drep'] = $pfb['config_rep']['enable_dedup'] ?: 'x'; // Enable/Disable 'dRep' Reputation $pfb['etupdate'] = $pfb['config_rep']['et_update']; // Perform a Force Update on ET categories $pfb['ccwhite'] = $pfb['config_rep']['ccwhite']; // Action for whitelist Country category $pfb['ccblack'] = $pfb['config_rep']['ccblack']; // Action for blacklist Country category $pfb['etblock'] = $pfb['config_rep']['etblock'] ?: 'x'; // Emerging Threats IQRisk block categories $pfb['etmatch'] = $pfb['config_rep']['etmatch'] ?: 'x'; // Emerging Threats IQRisk match categories $pfb['max'] = $pfb['config_rep']['p24_max_var'] ?: 'x'; // 'Max' variable setting for Reputation $pfb['dmax'] = $pfb['config_rep']['p24_dmax_var'] ?: 'x'; // 'dMax' variable setting for Reputation $pfb['pmax'] = $pfb['config_rep']['p24_pmax_var'] ?: 'x'; // 'pMax' variable setting for Reputation $pfb['ccexclude'] = $pfb['config_rep']['ccexclude'] ?: 'x'; // List of Countries to whitelist // Starting variable to skip Reputation functions, if no changes are required $pfb['repcheck'] = FALSE; // $pfb['save'] is used to determine if user pressed "save" button to avoid collision with CRON. // For 'script' calls using exec() (used to shorten length of line) $elog = ">> {$pfb['log']} 2>&1"; ################################# # Configure ARRAYS # ################################# $new_aliases = array(); // An array of aliases (full details) $new_aliases_list = array(); // An array of alias names $pfb_alias_lists = array(); // An array of aliases that have updated lists via CRON/force update. ('Reputation' disabled) $pfb_alias_lists_all = array(); // An array of all active aliases. ('Reputation' enabled) $ip_types = array( 'pfblockernglistsv4' => '_v4', 'pfblockernglistsv6' => '_v6'); $cont_types = array( 'countries4' => '_v4', 'countries6' => '_v6'); ################################# # Tracker IDs # ################################# $pfb['trackerids'] = array(); // An array of pfBlockerNG Firewall rule Tracker IDs. $pfb['last_trackerid'] = 1700000009; // Pre-defined 'starting' Tracker ID (Only used if duplicates found) ######################################### # Configure Rule Suffix # ######################################### // Discover if any rules are autorules (If no autorules found, $pfb['autorules'] is FALSE, skip rules re-order ) // To configure auto rule suffix. pfBlockerNG must be disabled to change suffix and to avoid duplicate rules $pfb['autorules'] = FALSE; $action = array('Deny_Both', 'Deny_Inbound', 'Deny_Outbound', 'Match_Both', 'Match_Inbound', 'Match_Outbound', 'Permit_Both', 'Permit_Inbound', 'Permit_Outbound'); foreach ($pfb['continents'] as $continent => $pfb_alias) { if (isset($config['installedpackages']['pfblockerng' . strtolower(str_replace(' ', '', $continent))]['config'])) { $continent_config = $config['installedpackages']['pfblockerng' . strtolower(str_replace(' ', '', $continent))]['config'][0]; if ($continent_config['action'] != 'Disabled' && in_array($continent_config['action'], $action)) { $pfb['autorules'] = TRUE; break; } } } if (!$pfb['autorules']) { foreach ($ip_types as $ip_type => $vtype) { if (isset($config['installedpackages'][$ip_type]['config'])) { foreach($config['installedpackages'][$ip_type]['config'] as $list) { if ($list['action'] != 'Disabled' && in_array($list['action'], $action)) { $pfb['autorules'] = TRUE; break; } } } } } // Check if DNSBL auto permit rule or DNSBL 'Auto Deny' rules for DNSBL IPs are defined if (!empty($pfb['dnsblconfig']['dnsbl_allow_int']) || strpos($pfb['dnsblconfig']['action'], 'Deny_') !== FALSE) { $pfb['autorules'] = TRUE; } // Configure auto rule suffix. $pfbfound = FALSE; $pfb_suffix_match = ''; if (is_array($config['filter']['rule'])) { foreach ($config['filter']['rule'] as $rule) { // Query for previous IPv4 pfBlockerNG 'alias type' aliasnames which are not in the new '_v4' suffix format foreach (array('source', 'destination') as $rtype) { if (substr($rule[$rtype]['address'], 0, 4) == 'pfB_' && substr($rule[$rtype]['address'], -3) != '_v4' && $rule['ipprotocol'] == 'inet') { $pfb['autorules'] = TRUE; // Set flag to re-configure Firewall rules and add missing '_v4' suffix } } // Collect any pre-existing suffix if (!empty($pfb_suffix_match) && preg_match('/^pfB_\w+(\s.*)/', $rule['descr'], $pfb_suffix_real)) { $pfb_suffix_match = $pfb_suffix_real[1]; } } } // Change suffix only if no pfB rules found and autorules are enabled. if ($pfb['autorules'] && !$pfbfound) { switch ($pfb['suffix']) { case 'autorule': $pfb['suffix'] = ' auto rule'; break; case 'standard': $pfb['suffix'] = ''; break; case 'ar': $pfb['suffix'] = ' AR'; break; } } else { $pfb['suffix'] = ''; if ($pfb['autorules']) { $pfb['suffix'] = $pfb_suffix_match; // Use existing suffix match } } ######################################################### # Configure INBOUND/OUTBOUND INTERFACES # ######################################################### // Collect pfSense interface order $ifaces = pfb_build_if_list(TRUE, FALSE); foreach (array('inbound', 'outbound') as $type) { $pfb["{$type}_interfaces"] = $pfb["{$type}_floating"] = array(); if (!empty($pfb['ipconfig']["{$type}_interface"])) { $interfaces = explode(',', $pfb['ipconfig']["{$type}_interface"]); // CSV string for 'pfB_' match rules $pfb["{$type}_floating"] = ltrim(implode(',', $interfaces), ','); // Assign base rule/interfaces if ($pfb['float'] == 'on') { $pfb['base_rule'] = $pfb['base_rule_float']; $pfb["{$type}_interfaces"] = explode(' ', $pfb["{$type}_floating"]); } else { $pfb['base_rule'] = $pfb['base_rule_reg']; $pfb["{$type}_interfaces"] = $interfaces; } } } // TO BE REMOVED AT FUTURE DATE: // DNSBL Python mode is not compatable with dhcpleases binary code, // as it attempts to HUP the Unbound PID and will cause DNSBL Python mode to crash Unbound // Force DNSBL Mode to DNSBL Unbound mode until the DNS Resolver DHCP Registration option has been disabled. if ($pfb['dnsbl_mode'] == 'dnsbl_python' && isset($config['unbound']['regdhcp'])) { $pfb['dnsbl_mode'] = 'dnsbl_unbound'; $pfb['dnsbl_py_blacklist'] = FALSE; $log = "\n [ ALERT ]\n" . " DNSBL Python mode is not compatable with the DNS Resolver DHCP Registration option!\n" . " The DNS Resolver DHCP Registration option must be disabled to utilize the DNSBL Python feature!\n" . " DNSBL has been automatically downgraded to the DNSBL Unbound mode!\n\n"; pfb_logger("{$log}", 1); $log = "[pfBlockerNG]: Terminating DNSBL Python mode due to DNS Resolver DHCP Registration option enabled!\n"; @file_put_contents('/var/log/pfblockerng/py_error.log', $log, FILE_APPEND); } // Force DNSBL Mode to DNSBL Unbound mode until the DNS Resolver DHCP OpenVPN Client Registration option has been disabled in pfSense < 2.5. if ($pfb['dnsbl_mode'] == 'dnsbl_python' && substr(trim(file_get_contents('/etc/version')), 0, 3) < '2.5' && isset($config['unbound']['regovpnclients'])) { $pfb['dnsbl_mode'] = 'dnsbl_unbound'; $pfb['dnsbl_py_blacklist'] = FALSE; $log = "\n [ ALERT ]\n" . " DNSBL Python mode is not compatable with the DNS Resolver OpenVPN Client Registration option enabled!\n" . " The DNS Resolver DHCP Registration option must be disabled to utilize the DNSBL Python feature!\n" . " DNSBL has been automatically downgraded to the DNSBL Unbound mode!\n\n"; pfb_logger("{$log}", 1); $log = "[pfBlockerNG]: Terminating DNSBL Python mode due to DNS Resolver OpenVPN Client Registration option enabled!\n"; @file_put_contents('/var/log/pfblockerng/py_error.log', $log, FILE_APPEND); } // DNSBL Python mode is only available for pfSense 2.4.5 and above if ($pfb['dnsbl_mode'] == 'dnsbl_python' && substr(trim(file_get_contents('/etc/version')), 0, 5) < '2.4.5') { $pfb['dnsbl_mode'] = 'dnsbl_unbound'; $pfb['dnsbl_py_blacklist'] = FALSE; $log = "\n [ ALERT ]\n" . " DNSBL Python mode is only compatible with pfSense versions 2.4.5 and above!\n" . " DNSBL has been automatically downgraded to the DNSBL Unbound mode!\n\n"; pfb_logger("{$log}", 1); $log = "[pfBlockerNG]: Terminating DNSBL Python mode due pfSense version less than 2.4.5."; @file_put_contents('/var/log/pfblockerng/py_error.log', $log, FILE_APPEND); } // Determine max Domain count available for DNSBL TLD analysis (Avoid Unbound memory exhaustion) $pfs_memory = (round(get_single_sysctl('hw.physmem') / (1024*1024)) ?: 1000); if (!$pfb['dnsbl_py_blacklist']) { $pfb['pfs_mem'] = array( '0' => '100000', '1500' => '150000', '2000' => '200000', '2500' => '250000', '3000' => '400000', '4000' => '600000', '5000' => '1000000', '6000' => '1500000', '7000' => '2000000', '8000' => '2500000', '12000' => '3000000', '16000' => '4000000'); } else { $pfb['pfs_mem'] = array( '0' => '200000', '1500' => '300000', '2000' => '400000', '2500' => '500000', '3000' => '800000', '4000' => '1200000', '5000' => '2000000', '6000' => '3000000', '7000' => '4000000', '8000' => '5000000', '12000' => '6000000', '16000' => '8000000'); } foreach ($pfb['pfs_mem'] as $pfb_mem => $domain_max) { if ($pfs_memory >= $pfb_mem) { $pfb['domain_max_cnt'] = $domain_max; } } ################################################# # Clear Removed Lists from Masterfiles # ################################################# $pfb['sync_master'] = TRUE; // Process to keep IP Masterfiles in sync with valid Lists from config.conf file $pfb['remove'] = FALSE; // Flag to execute pfctl and rules ordering or reload of DNSBL domains $pfb['summary'] = FALSE; // Execute final summary as a list was removed // Don't execute this function when pfBlockerNG is disabled and 'keep blocklists' is enabled. if ($pfb['enable'] == '' && $pfb['keep'] == 'on') { $pfb['sync_master'] = FALSE; } if ($pfb['sync_master']) { // Find all enabled Continents lists foreach ($pfb['continents'] as $continent => $pfb_alias) { if (isset($config['installedpackages']['pfblockerng' . strtolower(str_replace(' ', '', $continent))]['config']) && $pfb['enable'] == 'on') { $continent_config = $config['installedpackages']['pfblockerng' . strtolower(str_replace(' ', '', $continent))]['config'][0]; if ($continent_config['action'] != 'Disabled') { foreach ($cont_types as $c_type => $vtype) { if (!empty($continent_config[$c_type])) { // Force 'Alias Native' setting to any Alias with 'Advanced Inbound/Outbound -Invert src/dst' settings. // This will bypass Deduplication and Reputation features. if ($continent_config['autoaddrnot_in'] == 'on' || $continent_config['autoaddrnot_out'] == 'on') { $pfb['existing']['native'][] = "{$pfb_alias}{$vtype}"; } else { if (strpos($continent_config['action'], 'Match') !== FALSE) { $pfb['existing']['match'][] = "{$pfb_alias}{$vtype}"; } elseif (strpos($continent_config['action'], 'Permit') !== FALSE) { $pfb['existing']['permit'][] = "{$pfb_alias}{$vtype}"; } elseif (strpos($continent_config['action'], 'Deny') !== FALSE) { $pfb['existing']['deny'][] = "{$pfb_alias}{$vtype}"; } elseif ($continent_config['action'] == 'Alias_Native') { $pfb['existing']['native'][] = "{$pfb_alias}{$vtype}"; } } } } } } } // Find all enabled IPv4/IPv6 lists and DNSBL lists // Find all enabled IPv4 'Custom List' header names and check if 'Emerging Threats Update' and 'Custom List Update' needs force updating $list_types = array( 'pfblockernglistsv4' => '_v4', 'pfblockernglistsv6' => '_v6', 'pfblockerngdnsbl' => '_v4' ); $pfb_invalid = FALSE; foreach ($list_types as $ltype => $vtype) { $lists = array(); if (!empty($config['installedpackages'][$ltype]['config']) && $pfb['enable'] == 'on') { foreach ($config['installedpackages'][$ltype]['config'] as $list) { // If only the 'customlist' is defined. Remove the 'List row' data. if (isset($list['row']) && empty($list['row'][0]['url'])) { unset($list['row']); } if (!empty($list['custom'])) { $list['row'][] = array( 'header' => "{$list['aliasname']}_custom", 'custom' => $list['custom'], 'state' => 'Enabled', 'url' => 'custom' ); } $lists[] = $list; } } // ADD DNSBL IP if ($ltype == 'pfblockernglistsv4' && $pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && $pfb['dnsbl_ip'] != 'Disabled') { $list = array(); $list['action'] = "{$pfb['dnsbl_ip']}"; $list['row'][] = array('format' => 'auto', 'state' => 'Enabled', 'url' => "{$pfb['dbdir']}/DNSBLIP{$vtype}.txt", 'header' => 'DNSBLIP'); $lists[] = $list; } if (!empty($lists)) { foreach ($lists as $key => $list) { // Remove any spaces or special characters in existing Aliasnames if (preg_match("/\W/", $list['aliasname'])) { $pfb_invalid = TRUE; $config['installedpackages'][$ltype]['config'][$key]['aliasname'] = preg_replace("/\W/", '', $list['aliasname']); } if (isset($list['row']) && $list['action'] != 'Disabled') { // Force 'Alias Native' setting to any Alias with 'Advanced Inbound/Outbound -Invert src/dst' settings. // This will bypass Deduplication and Reputation features. if ($list['action'] != 'unbound' && ($list['autoaddrnot_in'] == 'on' || $list['autoaddrnot_out'] == 'on')) { $list['action'] = 'Alias_Native'; } foreach ($list['row'] as $hkey => $row) { // Remove any spaces or special characters in existing Header names if (preg_match("/\W/", $row['header'])) { $pfb_invalid = TRUE; $row['header'] = preg_replace("/\W/", '', $row['header']); $config['installedpackages'][$ltype]['config'][$key]['row'][$hkey]['header'] = $row['header']; } if ($ltype == 'pfblockerngdnsbl') { $header = "{$row['header']}"; } else { $header = "{$row['header']}{$vtype}"; } // Collect enabled lists if (!empty($row['url']) && $row['state'] != 'Disabled') { if (strpos($list['action'], 'Match') !== FALSE) { $pfb['existing']['match'][] = "{$header}"; } elseif (strpos($list['action'], 'Permit') !== FALSE) { $pfb['existing']['permit'][] = "{$header}"; } elseif (strpos($list['action'], 'Deny') !== FALSE) { $pfb['existing']['deny'][] = "{$header}"; } elseif ($list['action'] == 'Alias_Native') { $pfb['existing']['native'][] = "{$header}"; } elseif ($list['action'] == 'unbound') { $pfb['existing']['dnsbl'][] = "{$header}"; } } } } } } } // Save any existing Alias/Header names that have spaces or special characters if ($pfb_invalid) { write_config('pfBlockerNG: Remove spaces/special characters in Alias/Header names'); } // If 'TLD' enabled and TLD Blacklists are defined, add to enabled DNSBL lists if ($pfb['dnsbl_tld']) { $tld_blacklist = pfbng_text_area_decode($pfb['dnsblconfig']['tldblacklist'], TRUE, FALSE, TRUE); if (!empty($tld_blacklist)) { $pfb['existing']['dnsbl'][] = 'DNSBL_TLD'; } } // Add 'Reputation - ccwhite Action' if found if ($pfb['ccwhite'] == 'match' && file_exists("{$pfb['matchdir']}/matchdedup_v4.txt")) { $pfb['existing']['match'][] = 'matchdedup_v4'; } // Add enabled 'DNSBL Blacklist categories' if (isset($pfb['blconfig']) && $pfb['blconfig']['blacklist_enable'] != 'Disable' && !empty($pfb['blconfig']['blacklist_selected'])) { $selected = array_flip(explode(',', $pfb['blconfig']['blacklist_selected'])) ?: array(); foreach ($pfb['blconfig']['item'] as $item) { if (isset($selected[$item['xml']]) && !empty($item['selected'])) { $categories = explode(',', $item['selected']) ?: array(); foreach ($categories as $category) { if (!empty($category)) { $pfb['existing']['dnsbl'][] = "{$item['title']}_{$category}"; } } } } } // Collect all .txt file names for each list type $list_types = array( 'match' => $pfb['matchdir'], 'permit' => $pfb['permitdir'], 'deny' => $pfb['denydir'], 'native' => $pfb['nativedir'], 'dnsbl' => $pfb['dnsdir']); // Collect all previouly downloaded filename headers foreach ($list_types as $pftype => $pfbfolder) { $pfb_files = glob("{$pfbfolder}/*.txt"); foreach ($pfb_files as $pfb_list) { $pfb['actual'][$pftype][] = basename($pfb_list, '.txt'); } $results = array_diff($pfb['actual'][$pftype], $pfb['existing'][$pftype]); if (empty($results)) { continue; // No changes required } $f_result = implode(',', $results); $log = "\n[ Removing '{$pftype}' \tList(s) : {$f_result} ]"; pfb_logger("{$log}", 1); // Process to remove lists from IP Masterfile/DB folder if they are not referenced any longer switch ($pftype) { case 'deny': // Script to Remove un-associated List(s) exec("{$pfb['script']} remove x x x {$f_result} {$elog}"); $pfb['summary'] = $pfb['remove'] = TRUE; break; case 'match': case 'permit': case 'native': foreach ($results as $pfb_result) { unlink_if_exists("{$pfbfolder}/{$pfb_result}.txt"); unlink_if_exists("{$pfb['origdir']}/{$pfb_result}.*"); } $pfb['summary'] = $pfb['remove'] = TRUE; break; case 'dnsbl': foreach ($results as $pfb_result) { unlink_if_exists("{$pfb['dnsorigdir']}/{$pfb_result}.*"); } rmdir_recursive("{$pfb['dnsdir']}"); safe_mkdir("{$pfb['dnsdir']}"); pfb_logger("\n ** DNSBL Changes found, Reloading...\n", 1); $pfb['reuse_dnsbl'] = 'on'; break; } // Allow rebuilding of changed Alias to purge 'SKIP' Lists (when pfBlockerNG is enabled) if ($pfb['enable'] == 'on' && $pftype != 'dnsbl') { foreach ($ip_types as $ltype => $vtype) { foreach ($results as $removed_header) { if (is_array($config['installedpackages'][$ltype]['config'])) { foreach ($config['installedpackages'][$ltype]['config'] as $list) { if (!empty($list['row'])) { foreach ($list['row'] as $row) { $removed = rtrim($removed_header, ','); if ($row['header'] == $removed) { $pfb['summary'] = $pfb['remove'] = TRUE; // Add Alias to update array $pfb_alias_lists[] = "pfB_{$list['aliasname']}{$vtype}"; $pfb_alias_lists_all[] = "pfB_{$list['aliasname']}{$vtype}"; } } } } } } } } } } ######################################################### # Clear Match/Pass/ET/Original Files/Folders # ######################################################### // When pfBlockerNG is Disabled and 'Keep Blocklists' is Disabled. if ($pfb['enable'] == '' && $pfb['keep'] == '' && !$pfb['install']) { $log = "\n Removing DB Files/Folders \n"; pfb_logger("{$log}", 1); pfb_clear_contents(); } ################################################# # Create IP Suppression Txt File # ################################################# if ($pfb['enable'] == 'on' && $pfb['supp'] == 'on') { pfb_create_suppression_file(); } ######################################### # DNSBL - Processes # ######################################### if (!$pfb['save']) { $log = "\n===[ DNSBL Process ]================================================\n"; pfb_logger("{$log}", 1); } $dnsbl_error = FALSE; if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && !$pfb['save']) { // Terminate if DNSBL VIP is empty if (empty($pfb['dnsbl_vip']) || empty($pfb['dnsbl_port']) || empty($pfb['dnsbl_port_ssl'])) { $log = "\n\n===[ DNSBL Virtual IP and/or Ports are not defined. Exiting ]======\n"; pfb_logger("{$log}", 1); $dnsbl_error = TRUE; } } if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && !$pfb['save'] && !$dnsbl_error) { if (isset($config['installedpackages']['pfblockerngdnsbl']['config'])) { $dnsbl_missing = FALSE; // Collect existing DNSBL group statistics // SQLite3 Database format [ group name, updated timestamp, total domain count, total blocked count ] $pfb['dnsbl_info_stats'] = array(); if (file_exists("{$pfb['dnsbl_info']}")) { pfb_logger("\n Loading DNSBL Statistics...", 1); $db_handle = pfb_open_sqlite(1, 'Reading DNSBL database'); if ($db_handle) { $result = $db_handle->query("SELECT * FROM dnsbl;"); if ($result) { while ($res = $result->fetchArray(SQLITE3_ASSOC)) { $pfb['dnsbl_info_stats'][] = $res; } } } else { pfb_logger(" FAILED", 1); unlink_if_exists("{$pfb['dnsbl_info']}"); $dnsbl_missing = TRUE; } pfb_close_sqlite($db_handle); pfb_logger(" completed", 1); } else { $dnsbl_missing = TRUE; } if ($pfb['dnsbl_py_blacklist']) { // When DNSBL python blocking mode is enabled, zone should not exist, and data should exist if (!$pfb['dnsbl_tld']) { if (file_exists($pfb['unbound_py_zone'])) { $dnsbl_missing = TRUE; } if (!file_exists($pfb['unbound_py_data'])) { $dnsbl_missing = TRUE; } } // When DNSBL python blocking mode is enabled, atleast one 'data or zone' file must exist else { $found = FALSE; if (file_exists($pfb['unbound_py_data'])) { $found = TRUE; } if (file_exists($pfb['unbound_py_zone'])) { $found = TRUE; } if (!$found) { $dnsbl_missing = TRUE; } } } // Check if DNSBL database is missing, when DNSBL python blocking mode is not enabled if (!$pfb['dnsbl_py_blacklist'] && !file_exists("{$pfb['dnsbl_file']}.conf")) { $dnsbl_missing = TRUE; } if ($dnsbl_missing) { $log = "\n Missing DNSBL stats and/or Unbound DNSBL files - Rebuilding\n"; pfb_logger("{$log}", 1); $pfb['reuse_dnsbl'] = 'on'; touch("{$pfb['dnsbl_file']}.reload"); } // SafeSearch pfb_logger("\n Loading DNSBL SafeSearch...", 1); $safesearch_hosts = array(); $pfb['safesearch_tlds'] = array(); $safesearch_types = array( array( 'safesearch_enable', 'Enable', 'safesearch'), array( 'safesearch_youtube', 'Strict', 'youtube_restrict'), array( 'safesearch_youtube', 'Moderate', 'youtube_restrictmoderate'), array( 'safesearch_doh', 'Enable', 'doh') ); $safesearch_update = $pfb_found = FALSE; // Remove deprecated firefox conf file if (file_exists("{$pfb['dnsbldir']}/pfb_dnsbl.firefoxdoh.conf")) { unlink_if_exists("{$pfb['dnsbldir']}/pfb_dnsbl.firefoxdoh.conf"); $safesearch_update = TRUE; } foreach ($safesearch_types as $safe_type) { if ($pfb[$safe_type[0]] == $safe_type[1]) { $pfb_found = TRUE; // SafeSearch DoH validation (Add/remove comment '#' to DoH entries) if ($safe_type[0] == 'safesearch_doh') { $doh_file = file($pfb["dnsbl_{$safe_type[2]}"]); if (!empty($doh_file)) { $doh_file_final = ''; foreach ($doh_file as $host) { if (strpos($host, '#') !== FALSE) { $host = ltrim(str_replace('#', '', $host)); } $doh_found = FALSE; if (!empty($pfb['safesearch_doh_list'])) { foreach ($pfb['safesearch_doh_list'] as $doh) { if (strpos($host, $doh) !== FALSE) { $doh_found = TRUE; break; } } } if (!$doh_found) { $doh_file_final .= "# {$host}"; } else { $doh_file_final .= "{$host}"; } } if (!empty($doh_file_final)) { @file_put_contents($pfb["dnsbl_{$safe_type[2]}"], $doh_file_final, LOCK_EX); } } } if ($pfb['dnsbl_py_blacklist']) { unlink_if_exists("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf"); // Remove Unbound mode file $safesearch_file = file($pfb["dnsbl_{$safe_type[2]}"]); if (!empty($safesearch_file)) { foreach ($safesearch_file as $host) { if (substr($host, 0, 1) == '#') { continue; } $line = str_replace('"', '', strstr($host, '"', False)); $host_ip = trim(str_replace('A ', '', strstr($line, 'A ', FALSE))); $domain = strstr($line, ' ', TRUE); if (substr($domain, 0, 4) == 'www.') { $domain = substr($domain, 4); } if (strpos($line, ' A ') !== FALSE) { $safesearch_hosts[$domain]['A'] = $host_ip; } elseif (strpos($line, ' AAAA ') !== FALSE) { $safesearch_hosts[$domain]['AAAA'] = $host_ip; } elseif (strpos($line, ' CNAME ') !== FALSE) { $cname = trim(str_replace('CNAME ', '', strstr($line, 'CNAME', FALSE))); $safesearch_hosts[$domain]['A'] = 'cname'; $safesearch_hosts[$domain]['AAAA'] = $cname; } elseif (strpos($line, 'always_nxdomain') !== FALSE) { $safesearch_hosts[$domain]['nxdomain'] = ''; } } } } else { unlink_if_exists("/var/unbound/pfb_py_ss.*"); // Remove Python mode file // Copy file if not exists or has been updated if (!file_exists("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf") || @md5_file($pfb["dnsbl_{$safe_type[2]}"]) !== @md5_file("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf")) { @copy($pfb["dnsbl_{$safe_type[2]}"], "{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf"); $safesearch_update = TRUE; } // Collect SafeSearch domains for wildcard whitelisting exec("cut -d '\"' -f2 {$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf | cut -d ' ' -f1", $safesearch_hosts); // Collect all Domains/TLDs for 'TLD Blacklist' validation (Unbound mode only. Cannot have duplicate zones) if (!$pfb['dnsbl_py_blacklist'] && !empty($safesearch_hosts)) { foreach ($safesearch_hosts as $host) { $safesearch_tlds[$host] = ''; for ($i= substr_count($host, '.'); $i > 0; $i--) { $host = ltrim(strstr($host, '.', FALSE), '.'); $pfb['safesearch_tlds'][$host] = ''; } } } } } elseif (file_exists("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf")) { unlink("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf"); $safesearch_update = TRUE; } } // Python mode create a CSV list of SafeSearch hosts if ($pfb['dnsbl_py_blacklist'] && !empty($safesearch_hosts)) { foreach ($safesearch_hosts as $host => $data) { if (isset($data['nxdomain'])) { $line = "{$host},nxdomain,nxdomain\n"; } else { $line = "{$host}," . ($data['A'] ?: '') . ',' . ($data['AAAA'] ?: '') . "\n" . "www.{$host}," . ($data['A'] ?: '') . ',' . ($data['AAAA'] ?: '') . "\n"; } @file_put_contents('/var/unbound/pfb_py_ss.raw', $line, FILE_APPEND); } // Copy file if not exists or has been updated if (!file_exists("{$pfb['unbound_py_ss']}") || @md5_file("{$pfb['unbound_py_ss']}") !== @md5_file('/var/unbound/pfb_py_ss.raw')) { @rename('/var/unbound/pfb_py_ss.raw', "{$pfb['unbound_py_ss']}"); $safesearch_update = TRUE; touch("{$pfb['dnsbl_file']}.reload"); } unlink_if_exists('/var/unbound/pfb_py_ss.raw'); } else { unlink_if_exists("/var/unbound/pfb_py_ss.*"); } // Wildcard whitelist SafeSearch domains (Unbound mode only) $pfb_whitelist = ''; if (!$pfb['dnsbl_py_blacklist'] && !empty($safesearch_hosts)) { $safesearch_hosts = array_unique($safesearch_hosts); foreach ($safesearch_hosts as $line) { if (!empty($line)) { // Remove 'www.' prefix if (substr($line, 0, 4) == 'www.') { $line = substr($line, 4); } if ($pfb['dnsbl_py_blacklist']) { $pfb_whitelist .= ".{$line},,\n,{$line},,\n"; } else { $pfb_whitelist .= ".{$line} 60\n\"{$line} 60\n"; } } } } $s_log = ' disabled'; if ($pfb_found) { $s_log = ' enabled'; } pfb_logger(" {$s_log}", 1); // Collect Whitelist, create string, and save to file (for grep -vF -f cmd) pfb_logger("\n Loading DNSBL Whitelist...", 1); $pfb_white = pfbng_text_area_decode($pfb['dnsblconfig']['suppression'], TRUE, FALSE, TRUE); if (!empty($pfb_white)) { foreach ($pfb_white as $line) { if (!empty($line)) { $wildcard = FALSE; if (substr($line, 0, 1) == '.') { $line = ltrim($line, '.'); $wildcard = TRUE; } // Remove 'www.' prefix if (substr($line, 0, 4) == 'www.') { $line = substr($line, 4); } if ($wildcard) { if ($pfb['dnsbl_py_blacklist']) { $pfb_whitelist .= ".{$line},,\n,{$line},,\n"; } else { $pfb_whitelist .= ".{$line} 60\n\"{$line} 60\n"; } } else { if ($pfb['dnsbl_py_blacklist']) { $pfb_whitelist .= ",{$line},,\n,www.{$line},,\n"; } else { $pfb_whitelist .= "\"{$line} 60\n\"www.{$line} 60\n"; } } } } } // Added due to SWC Feed if ($pfb['dnsbl_py_blacklist']) { $pfb_whitelist .= ",localhost.localdomain,,\n"; } else { $pfb_whitelist .= "\"localhost.localdomain 60\n"; } @file_put_contents("{$pfb['dnsbl_supptxt']}", $pfb_whitelist, LOCK_EX); pfb_logger(" completed", 1); if (!$pfb['dnsbl_py_blacklist'] && $safesearch_update) { $log = "\n DNSBL - SafeSearch changes found - Rebuilding!\n"; pfb_logger("{$log}", 1); $pfb['reuse_dnsbl'] = 'on'; touch("{$pfb['dnsbl_file']}.reload"); } // Call TOP1M whitelist process if ($pfb['dnsbl_alexa'] == 'on') { pfb_logger("\n Loading TOP1M Whitelist...", 1); // Check if TOP1M database exists if (!file_exists("{$pfb['dbdir']}/top-1m.csv")) { // Check if TOP1M download already in progress exec('/bin/ps -wax', $result_cron); if (!preg_grep("/pfblockerng[.]php\s+al/", $result_cron)) { $log = "\n TOP1M Database downloading ( approx 21MB ) ... Please wait ...\n"; pfb_logger("{$log}", 1); exec('/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php al'); } else { $log = "\n TOP1M download already in process...\n"; pfb_logger("{$log}", 1); } } // Process TOP1M database if (!file_exists("{$pfb['dbdir']}/pfbalexawhitelist.txt") || file_exists("{$pfb['dbdir']}/top-1m.update")) { pfblockerng_top1m(); $pfb['reuse_dnsbl'] = 'on'; touch("{$pfb['dnsbl_file']}.reload"); pfb_logger("\n DNSBL - TOP1M changes found - Rebuilding!\n", 1); } pfb_logger(" completed", 1); } pfb_logger("\n", 1); // List of invalid Domains to skip parsed failed logging function $dnsbl_skip = array_flip (array('broadcasthost', 'local', 'localhost', '
',
							'Vault',
							'Site',
							'list',
							'::1',
							':',
							'ip6-localhost',
							'ip6-localnet',
							'ip6-mcastprefix',
							'ip6-allnodes',
							'ip6-allrouters',
							'ip6-allhosts'
							));

			// List of Alienvault OTX Indicator Types
			$alienvault_types = array_flip(array('domain', 'hostname', 'URL'));

			// Collect feeds and custom list configuration and format into one array ($lists).
			$lists = array();

			// Add DNSBL Category to '$lists array'
			if (isset($pfb['blconfig']) &&
			    $pfb['blconfig']['blacklist_enable'] != 'Disable' &&
			    !empty($pfb['blconfig']['blacklist_selected'])) {

				$bl_count = 0;
				$bl_validate = FALSE;

				$selected = array_flip(explode(',', $pfb['blconfig']['blacklist_selected'])) ?: array();
				foreach ($pfb['blconfig']['item'] as $item) {
					$type = "{$item['xml']}";

					if (isset($selected[$type]) && !empty($item['selected'])) {

						$bl_count++;
						$categories = explode(',', $item['selected']) ?: array();

						$list			= array();
						$list['aliasname']	= "{$item['title']}";
						$list['action']		= 'unbound';
						$list['logging']	= $pfb['blconfig']['blacklist_logging'] ?: 'enabled';
						$list['filter_alexa']	= '';

						$feedname	= strtolower($item['title']);
						$update_flag	= "{$pfb['dbdir']}/{$feedname}/{$feedname}.update";

						foreach ($categories as $category) {
							if (!empty($category)) {
								$list['row'][] = array(	'format'	=> 'auto',
											'state'		=> 'Enabled',
											'url'		=> "{$pfb['dbdir']}/{$type}/{$type}_{$category}",
											'header'	=> "{$item['title']}_{$category}"
											);

								// If update available set Update flag for each selected Category
								if (file_exists("{$update_flag}")) {
									touch("{$pfb['dnsdir']}/{$item['title']}_{$category}.update");
								}
							}
						}
						unlink_if_exists("{$update_flag}");

						if (isset($list['row'])) {
							$lists[] = $list;
						}

						// Check if Blacklist database has not been previously downloaded
						if (is_dir("{$pfb['dbdir']}/{$type}") && (count(scandir("{$pfb['dbdir']}/{$type}")) > 2)) {
							$bl_validate[$type] = 'exists';
						} else {
							$bl_validate[$type] = $item['size'];
						}
					}
				}

				// Download Blacklist databases that are not previously downloaded
				if ($bl_validate) {

					// Create commandline arguments for download script
					$bl_string = $bl_sources = '';
					foreach ($bl_validate as $type => $size) {
						if ($size != 'exists') {
							$bl_string	.= ",{$type}";
							$bl_sources	.= " {$type} (~{$size}MB) |";
						}
					}

					if (!empty($bl_string)) {
						$bl_string	= ltrim($bl_string, ',');
						$bl_sources	= rtrim($bl_sources, ' |');

						// Check if Blacklist download already in progress
						exec('/bin/ps -wax', $result_cron);
						if (!preg_grep("/pfblockerng[.]php\s+?(bl|bls)/", $result_cron)) {

							$log = "\nDownloading Blacklist Database(s) [{$bl_sources} ] ... Please wait ...\n";
							pfb_logger("{$log}", 1);
							exec("/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php bls {$bl_string} 2>&1", $pfb_return);

							if (is_array($pfb_return)) {
								foreach ($pfb_return as $key => $return_output) {

									pfb_logger("{$return_output}\n", 1);

									// On download failure, remove associated Blacklist category configuration
									if (strpos($return_output, 'Failed') !== FALSE) {
										unset($lists[$key]);
									}
								}
							}
						}
						else {
							$log = "\nBlacklist Database download already in process... Try again later...\n";
							pfb_logger("{$log}", 1);

							// Remove Blacklist Category updates until database download is completed
							while ($bl_count != 0) {
								array_pop($lists);
								$bl_count--;
							}
						}
					}
				}
				else {
					pfb_logger("Blacklist database(s) ... exist.\n", 1);
				}
			}

			if (is_array($config['installedpackages']['pfblockerngdnsbl']['config'])) {
				foreach ($config['installedpackages']['pfblockerngdnsbl']['config'] as $list) {
					// If only the 'customlist' is defined. Remove the 'List row' data.
					if (isset($list['row']) && empty($list['row'][0]['url'])) {
						unset($list['row']);
					}

					if (!empty($list['custom'])) {
						$list['row'][] = array( 'header'	=> "{$list['aliasname']}_custom",
									'custom'	=> $list['custom'],
									'state'		=> 'Enabled',
									'url'		=> 'custom'
									);
					}

					// Move DNSBL Group to primary position before the Blacklist settings
					if ($list['order'] == 'primary') {
						$list_primary = array();
						$list_primary[] = $list;
						$lists = array_merge($list_primary, $lists);
					} else {
						$lists[] = $list;
					}
				}
			}

			// Define DNSBL arrays and variables
			$pfb['alias_dnsbl_all']		= array();	// Array of all DNSBL aliases
			$pfb['tld_update']		= array();	// Array of all DNSBL Aliases/Feeds used for TLD Function
			$pfb['domain_update']		= FALSE;	// Flag to signal update Unbound
			$pfb['updateip']		= FALSE;	// Flag to signal updates to DNSBL IP lists

			foreach ($lists as $list) {

				// Reset variables once per alias
				$lists_dnsbl_current	= array();		// Array of all active Lists in current alias
				$pfb['aliasupdate']	= FALSE;		// Flag to signal changes to alias
				$pfb['domain_clear']	= FALSE;		// Flag to signal no Aliases defined or all Aliases disabled.
				$alias_cnt		= 0;

				if ($list['action'] != 'Disabled' && isset($list['row'])) {
					$alias				= "DNSBL_{$list['aliasname']}";
					$pfb['alias_dnsbl_all'][]	= "{$alias}";

					foreach ($list['row'] as $key => $row) {
						if (!empty($row['url']) && $row['state'] != 'Disabled') {

							$header		= "{$row['header']}";
							$liteparser	= FALSE;	// Minimal DNSBL Parser
							$rev_format	= FALSE;	// Host style format is reversed
							$domain_data_ip	= array();	// Array of IPs found in feed
							$domain_data	= '';		// List of Domains found in feed

							// If row is a custom_list, set flag.
							if (isset($row['custom'])) {
								$custom = TRUE;
							} else {
								$custom = FALSE;
							}

							// Global DNSBL Logging/Blocking mode
							if (!empty($pfb['dnsbl_global_log'])) {
								$list['logging'] = $pfb['dnsbl_global_log'];
							}

							// Force Unbound mode Logging for 'disabled_log' to 'enabled'
							if (!$pfb['dnsbl_py_blacklist'] && $list['logging'] == 'disabled_log') {
								$list['logging'] = 'enabled';
							}

							// If Null Blocking mode is selected, use '0.0.0.0|::0', otherwise utilize DNSBL WebServer/DNSBL VIP
							if ($list['logging'] == 'disabled') {
								$sinkhole_type4 = '0.0.0.0';
								$sinkhole_type6 = '::0';
								$logging_type	= '2';	// Null Blocking no logging
							}
							elseif ($list['logging'] == 'disabled_log') {
								$logging_type	= '0';	// Null Blocking logging
							} else {
								$sinkhole_type4 = "{$pfb['dnsbl_vip']}";
								$sinkhole_type6 = "::{$pfb['dnsbl_vip']}";
								$logging_type	= '1';	// DNSBL VIP logging
							}

							// Determine 'list' details (return array $pfbarr)
							pfb_determine_list_detail($list['action'], $header, 'pfblockerngdnsblsettings', '0');
							$pfbadv		= $pfbarr['adv'];
							$pfbfolder	= $pfbarr['folder'];
							$pfborig	= $pfbarr['orig'];
							$pfbreuse	= $pfbarr['reuse'];
							$logtab		= $pfbarr['logtab'];

							// Empty header field validation check
							if (empty($header)) {
								$log = "\n[ {$row['url']} ]{$logtab} Header Field cannot be empty. *Skipping* \n";
								pfb_logger("{$log}", 2);
								continue;
							}

							if (file_exists("{$pfbfolder}/{$header}.txt") &&
							    !file_exists("{$pfbfolder}/{$header}.update") &&
							    !file_exists("{$pfbfolder}/{$header}.fail") &&
							    $pfbreuse == '') {


								if ($row['state'] == 'Hold') {
									$log = "\n[ {$header} ]{$logtab} static hold. [ NOW ]";
								} else {
									$log = "\n[ {$header} ]{$logtab} exists. [ NOW ]";
								}
								pfb_logger("{$log}", 1);

								// Collect existing list stats
								$lists_dnsbl_all[]	= "{$row['header']}.txt";
								$lists_dnsbl_current[]	= "{$row['header']}";
								$list_cnt		= exec("{$pfb['grep']} -c ^ {$pfbfolder}/{$header}.txt");
								$alias_cnt		= $alias_cnt + $list_cnt;
							}
							else {
								if ($pfbreuse == 'on' && file_exists("{$pfborig}/{$header}.orig")) {
									$log = "\n[ {$header} ]{$logtab} Reload [ NOW ]";
								} else {
									$log = "\n[ {$header} ]{$logtab} Downloading update [ NOW ]";
								}
								pfb_logger("{$log}", 1);
								$file_dwn = "{$pfborig}/{$header}";

								if (!$custom) {
									pfb_logger(' .', 1);

									// Allow cURL SSL downgrade 'Flex' if user configured.
									$pflex = FALSE;
									if ($row['state'] == 'Flex') {
										$pflex = TRUE;
									}

									// Determine if list needs to be downloaded or reuse previously downloaded file.
									if ($pfbreuse == 'on' && file_exists("{$file_dwn}.orig")) {
										// File exists/reuse
										pfb_logger(' completed .', 1);
									} else {
										// Download file
										if (!pfb_download($row['url'], $file_dwn, $pflex, $header,
											$row['format'], 1, '', '', '', '', '', $srcint)) {

											// Determine reason for download failure
											pfb_download_failure($alias, $header, $pfbfolder, $row['url']);

											// Utilize previously download file (If 'fail' marker exists)
											if (file_exists("{$pfbfolder}/{$header}.fail") &&
											    file_exists("{$file_dwn}.orig")) {
												pfb_logger("\n  Restoring previously downloaded file\n ", 2);
											} else {
												continue;
											}
										}
										else {
											// Clear any previous download fail marker
											unlink_if_exists("{$pfbfolder}/{$header}.fail");
										}
									}
								}
								else {
									// Collect custom list data.
									$custom_list = pfbng_text_area_decode($row['custom'], FALSE, TRUE, TRUE);
									@file_put_contents("{$file_dwn}.orig", $custom_list, LOCK_EX);
									unset($custom_list);
									$liteparser = TRUE;
								}

								// Variables for Easylists
								$easylist = $validate_header = FALSE;
								$e_replace = array( '||', '.^', '^' );

								$run_once = $csv_parser = FALSE;
								$csv_type = '';
								$ipcount = $ip_cnt = 0;

								// Parse downloaded file for Domain names
								if (($fhandle = @fopen("{$file_dwn}.orig", 'r')) !== FALSE) {
									if (($dhandle = @fopen("{$pfbfolder}/{$header}.bk", 'w')) !== FALSE) {
										while (($line = @fgets($fhandle)) !== FALSE) {

											// Collect original line
											$oline = $line;

											// Validate EasyList/AdBlock/uBlock/ADGuard Feeds
											if (!$validate_header) {
												if (strpos($line, '[Adblock Plus ') !== FALSE ||
												    strpos($line, '[uBlock Origin') !== FALSE ||
												    strpos($line, '! Title: AdGuard') !== FALSE) {
													$easylist = $validate_header = TRUE;
													continue;
												}
												elseif (substr($line, 0, 1) === '!') {
													continue;
												}
												else {
													$validate_header = TRUE;
												}
											}

											// Remove any '^M' characters
											if (strpos($line, "\r") !== FALSE) {
												$line = rtrim($line, "\x00..\x1F");
											}

											// Remove invalid charaters
											$line = trim($line, " \t\n\r\0\x0B\xC2\xA0");

											if ($easylist) {
												if (substr($line, 0, 2) !== '||' ||
												    substr($line, -1) !== '^' ||
												    strpos($line, '$') !== FALSE ||
												    strpos($line, '*') !== FALSE ||
												    strpos($line, '/') !== FALSE) {
													continue;
												}

												$lite = TRUE;
												$line = str_replace($e_replace, '', $line);
											}
											else {
												// If 'tab' character found, replace with whitespace
												if (strpos($line, "\x09") !== FALSE) {
													$line = str_replace("\x09", ' ', $line);
												}

												// If '%20' found, remove.
												if (strpos($line, '%20') !== FALSE) {
													$line = str_replace('%20', '', $line);
												}

												// Remove comment lines and special format considerations
												if (substr($line, 0, 1) == '#') {
													// Exit (hpHosts) when end of domain names found.
													if (strpos($line, 'Append critical updates below') !== FALSE) {
														break;
													}

													// Spamhaus format validation
													if (strpos($line, 'The Spamhaus Project Ltd') !== FALSE) {
														$rev_format = TRUE;
													}

													if ($line == '#family,type,url,status,first_seen,'
															. 'first_active,last_active,last_update') {
														$csv_type	= 'h3x';
														$csv_parser	= TRUE;
													}
													continue;
												}

												// Remove slash comment lines
												if (substr($line, 0, 2) == '//') {
													continue;
												}

												// Remove any 'End of line' comments (Some contains commas)
												if (strpos($line, ' #') !== FALSE) {
													$line = strstr($line, ' #', TRUE);
												}

												// Convert CSV line into array
												if ($csv_parser) {
													$csvline = str_getcsv($line, ',', '', '"');
												}
												elseif (!$run_once) {
													if (substr_count($line, ',') >= 2) {
														$csvline = str_getcsv($line, ',', '', '"');
														$csv_parser = TRUE;
													}
													$run_once = TRUE;
												}
											}

											// Remove blank lines
											if (empty($line)) {
												continue;
											}

											// CSV parser
											if ($csv_parser) {

												$csv_found = FALSE;
												$csv_count = count($csvline);

												switch ($csv_type) {
													case 'pt':
														if ($csv_count == 8) {
															if (strpos($csvline[1], ' ') !== FALSE) {
																$line = str_replace(' ', '', $csvline[1]);
															} else {
																$line = $csvline[1];
															}
															$csv_found = TRUE;
														}
														break;
													case 'bbc':
														if ($csv_count == 4) {
															$line		= $csvline[0];
															$csv_found	= TRUE;
														}
														break;
													case 'h3x':
														if ($csv_count == 8) {
															$line		= $csvline[2];
															if (strpos($line, 'btc://') !== FALSE) {
																continue 2;
															}
															$csv_found	= TRUE;
														}
														break;
													case 'otx':
														if ($csv_count == 3) {
															if (isset($alienvault_types[$csvline[0]])) {
																$line		= $csvline[1];
																$csv_found	= TRUE;
															} else {
																continue 2;
															}
														}
														break;
													case 'pon':
														if ($csv_count == 9) {
															$line		= $csvline[2];
															$csv_found	= TRUE;

															// Collect additional IP csv entry
															if (is_ipaddrv4($csvline[0]) &&
															    $pfb['dnsbl_ip'] != 'Disabled') {
																$parsed = sanitize_ipaddr($line, $custom, 'Disabled');
																if (validate_ipv4($parsed)) {
																	$domain_data_ip[] = $parsed;
																	$pfb['updateip'] = TRUE;
																	$ipcount++;
																}
															}
														}
														break;
													case 'et':
														if ($csv_count == 3) {
															$line		= $csvline[0];
															$csv_found	= TRUE;
														}
														break;
													default:

														// Parse Phishtank Feed
														if (strpos($line, 'phish_id,url,'
																. 'phish_detail_url') !== FALSE) {
															$csv_type = 'pt';
															continue 2;
														}

														// Parse Bambenek Consulting Feed
														elseif (strpos($csvline[3], 'osint.'
															. 'bambenekconsulting.com') !== FALSE) {
															$csv_type	= 'bbc';
															$line		= $csvline[0];
															$csv_found	= TRUE;
															$liteparser	= TRUE;
														}

														// Parse Alienvault OTX pulse Feed
														elseif ($line == 'Indicator type,Indicator,'
																. 'Description') {
															$csv_type	= 'otx';
															$liteparser	= FALSE;
															continue 2;
														}

														// Parse Ponomocup Feed
														elseif (strpos($csvline[0], 'timestamp') !== FALSE) {
															$csv_type	= 'pon';
															$liteparser	= TRUE;
															continue 2;
														}

														// Parse Proofpoint/ET IQRisk IPRep Feed
														elseif ($line == 'domain, category, score') {
															$csv_type	= 'et';
															$liteparser	= TRUE;
															continue 2;
														}

														// Reset variables for CSV determination
														else {
															$csv_parser = $run_once = FALSE;
														}
														break;
												}

												// Record Failed CSV Parse event
												if (!$csv_found || empty($csv_type)) {
													pfb_parsed_fail($header, '', $oline, $pfb['dnsbl_parse_err']);
													continue;
												}
											}
											$line = trim($line);

											if (!$easylist) {

												// Typical Host Feed format - Remove characters before space
												if (!$rev_format && strpos($line, ' ') !== FALSE) {
													$line = trim(strstr($line, ' ', FALSE));
												}

												// Remove characters after space
												if (strpos($line, ' ') !== FALSE) {
													$line = strstr($line, ' ', TRUE);
												}

												// Determine if line contains only an alpha-numeric Domain name
												if (!$liteparser) {

													$lite = FALSE;
													if (strpos($line, '.') !== FALSE &&
													    ctype_alnum(str_replace('.', '', $line))) {
														$lite = TRUE;
													}
												}
												else {
													$lite = TRUE;
												}
											}

											if (!$lite) {

												// If 'http|https|telnet|ftp://' found, remove
												if (strpos($line, '://') !== FALSE) {
													$line = substr($line, strpos($line, '://') + 3);
												}

												// If '/' character found, remove characters after '/'
												if (strpos($line, '/') !== FALSE) {
													$line = strstr($line, '/', TRUE);
												}

												// If '#' character found, remove characters after '#'
												if (strpos($line, '#') !== FALSE) {
													$line = strstr($line, '#', TRUE);
												}

												// If '?' character found, remove characters after '?'
												if (strpos($line, '?') !== FALSE) {
													$line = strstr($line, '?', TRUE);
												}

												// If special characters found, parse line for host
												if (strpos($line, ';') !== FALSE) {
													$host = parse_url($line);
													if (isset($host['host'])) {
														$line = $host['host'];
													} else {
														$line = strstr($line, ';', TRUE);
													}
												}

												// Remove any Port numbers at end of line
												if (strpos($line, ':') !== FALSE) {
													$line = preg_replace("/:[0-9]{1,5}$/", '', $line);
												}
											}
											$line = trim($line);

											// Collect any IPs found in domain feed
											if (is_ipaddrv4($line)) {
												if ($pfb['dnsbl_ip'] != 'Disabled') {
													$parsed = sanitize_ipaddr($line, $custom, 'Disabled');
													if (validate_ipv4($parsed)) {
														$domain_data_ip[] = $parsed;
														$pfb['updateip'] = TRUE;
														$ipcount++;
													}
												}
												continue;
											}

											// Convert IDN (Unicode domains) to ASCII (punycode)
											if (!ctype_print($line)) {

												// Convert encodings to UTF-8
												$line = mb_convert_encoding($line, 'UTF-8',
													mb_detect_encoding($line, 'UTF-8, ASCII, ISO-8859-1'));

												$log = "\n  IDN converted: [ {$line} ]\t";
												$line = idn_to_ascii($line);
												if (!empty($line)) {
													pfb_logger("{$log} [ {$line} ]", 1);
												}
												else {
													// Record failed parsed line
													pfb_parsed_fail($header, '', $oline, $pfb['dnsbl_parse_err']);
													continue;
												}
											}

											// Domain Validation
											$dom_validate = str_replace(array('_', '-', '.', '@', ':'), '', $line);
											$tld_validate = substr($line, strrpos($line, '.') + 1);

											if (substr($line, -1) === '.' ||
											    substr($line, 0, 1) === '.' ||
											    strpos($line, '.') === FALSE ||
											    strpos($line, '..') !== FALSE ||
											    is_numeric($tld_validate) ||
											    !ctype_alnum($dom_validate) ||
											    !ctype_alnum(str_replace('-', '', $tld_validate))) {

												// Reset lite parser
												$liteparser = FALSE;

												// Skip yHost '@' prefixed lines
												if (substr($line, 0, 1) == '@') {
													continue;
												}

												// Log invalid Domains
												if (!isset($dnsbl_skip[$line])) {
													pfb_parsed_fail($header, $line, $oline, $pfb['dnsbl_parse_err']);
												}
												continue;
											}

											// For DNSBL python, save domain and Logging type
											if ($pfb['dnsbl_py_blacklist']) {
												$domain_data = ',' . strtolower($line)
													. ",,{$logging_type},{$header},{$alias}\n";
											}
											else {
												$ipv6_dnsbl = "\n";
												if ($pfb['dnsbl_v6'] == 'on' && !$pfb['dnsbl_tld']) {
													$ipv6_dnsbl = " local-data: \"" . strtolower($line)
															. " 60 IN AAAA {$sinkhole_type6}\"\n";
												}
												$domain_data = "local-data: \"" . strtolower($line)
														. " 60 IN A {$sinkhole_type4}\"{$ipv6_dnsbl}";
											}
											@fwrite($dhandle, $domain_data);
										}
									}
									@fclose($dhandle);
								}
								@fclose($fhandle);
								unset($csvline);

								// Remove duplicates and save any IPs found in domain feed
								if (!empty($domain_data_ip)) {
									$domain_data_ip = implode("\n", array_unique($domain_data_ip)) . "\n";
									@file_put_contents("{$pfbfolder}/{$header}_v4.ip", $domain_data_ip, LOCK_EX);
									$ip_cnt = exec("{$pfb['grep']} -c ^ {$pfbfolder}/{$header}_v4.ip");
								}
								else {
									// Remove previous IP feed
									unlink_if_exists("{$pfbfolder}/{$header}_v4.ip");
								}

								// Validate feed with Unbound-checkconf
								if (!empty($domain_data)) {
									$conf  = "server:\n";
									$conf .= "chroot: {$pfb['dnsbldir']}\n";
									$conf .= "username: \"unbound\"\n";
									$conf .= "directory: \"{$pfb['dnsbldir']}\"\n";
									$conf .= "pidfile: \"/var/run/unbound.pid\"\n";
									$conf .= "server:include: {$pfbfolder}/{$header}.bk";
									@file_put_contents("{$pfb['dnsbldir']}/check.conf", $conf, LOCK_EX);

									pfb_logger(".\n", 1);

									// Bypass TOP1M whitelist, if user configured
									$pfb_alexa = 'Disabled';
									if ($pfb['dnsbl_alexa'] == 'on' &&
									    $list['filter_alexa'] == 'on' &&
									    file_exists("{$pfb['dbdir']}/pfbalexawhitelist.txt")) {
										$pfb_alexa = 'on';
									}

									// DNSBL python requires a different deduplication process
									$dup_mode = '';
									if ($pfb['dnsbl_py_blacklist']) {
										$dup_mode = 'python';
									}

									// Call script to process DNSBL 'De-Duplication / Whitelisting / TOP1M Whitelisting'
									exec("{$pfb['script']} dnsbl_scrub {$header} {$pfb_alexa} {$dup_mode} {$elog}");

									if ($ip_cnt > 0) {
										pfb_logger("  IPv4 count={$ip_cnt}\n", 1);
									}

									if (!$pfb['dnsbl_py_blacklist']) {
										$result = array();
										exec("/usr/local/sbin/unbound-checkconf {$pfb['dnsbldir']}/check.conf 2>&1", $result);
									} else {
										$result = array('unbound-checkconf: no errors');
									}
									unlink_if_exists("{$pfb['dnsbldir']}/check.conf");
								}
								else {
									$log = "\n No Domains Found! Ensure only domain based Feeds are used for DNSBL!\n";
									pfb_logger("{$log}", 1);

									// Copy downloaded file to /tmp for debugging
									$ts = date('M_j', time());
									@copy("{$file_dwn}.orig", "/tmp/Error_{$header}_{$ts}.orig");

									unlink_if_exists("{$pfbfolder}/{$header}.bk");
									$result = array('unbound-checkconf: no errors');
								}

								// If parse error found, use previously downloaded file if available
								if (!$pfb['dnsbl_py_blacklist'] && !preg_grep("/unbound-checkconf: no errors/", $result)) {
									unlink_if_exists("{$pfbfolder}/{$header}.bk");

									pfb_logger("\n  DNSBL FAIL - Skipped! Use previous data, if found:\n", 2);
									$log = htmlspecialchars(implode("\n", $result));
									pfb_logger("{$log}\n", 1);

									// Create failed marker file
									touch("{$pfbfolder}/{$header}.fail");
								}

								// Save DNSBL feed info for next steps
								$pfb['domain_update']	= $pfb['aliasupdate'] = $pfb['summary'] = TRUE;
								$lists_dnsbl_all[]	= "{$row['header']}.txt";
								$lists_dnsbl_current[]	= "{$row['header']}";

								// Rename newly downloaded file to final location
								if (file_exists("{$pfbfolder}/{$header}.bk")) {
									@rename("{$pfbfolder}/{$header}.bk", "{$pfbfolder}/{$header}.txt");
								}

								// Create empty placeholder file
								if (!file_exists("{$pfbfolder}/{$header}.txt")) {
									touch("{$pfbfolder}/{$header}.txt");
								}

								$list_cnt	= exec("{$pfb['grep']} -c ^ {$pfbfolder}/{$header}.txt");
								$alias_cnt	= $alias_cnt + $list_cnt;

								// Remove update file indicator
								unlink_if_exists("{$pfbfolder}/{$header}.update");
							}
						}
					}

					// If changes found update DNSBL alias and TLD disabled, call function to update DNSBL alias
					if ($pfb['aliasupdate'] && !$pfb['dnsbl_tld']) {
						dnsbl_alias_update('update', $alias, $pfbfolder, $lists_dnsbl_current, $alias_cnt);
					}

					// Collect Alias/Feeds for post TLD function
					if ($pfb['dnsbl_tld']) {
						if (!is_array($pfb['tld_update'][$alias])) {
							$pfb['tld_update'][$alias] = array();
						}
						$pfb['tld_update'][$alias]['feeds']	= $lists_dnsbl_current;
						$pfb['tld_update'][$alias]['count']	= $alias_cnt;
					}
				}
				else {
					dnsbl_alias_update('disabled', $alias, '', '', '');
				}
			}

		}

		// Remove any unused DNSBL aliases
		$daliases = glob("{$pfb['dnsalias']}/*");
		if (!empty($daliases)) {
			foreach ($daliases as $dlist) {
				if (!in_array(basename($dlist), $pfb['alias_dnsbl_all'])) {
					unlink_if_exists ("{$dlist}");
				}
			}
		}

		// Add DNSBL Python options to widget statistics
		if ($pfb['dnsbl_mode'] == 'dnsbl_python') {
			if ($pfb['dnsbl_idn'] == 'on') {
				$idn_cnt = 1;
				$pfb['alias_dnsbl_all'][] = 'DNSBL_IDN';
				dnsbl_alias_update('update', 'DNSBL_IDN', '', '', $idn_cnt);
			}
			if ($pfb['dnsbl_pytld'] == 'on') {
				$pytld_cnt = 0;
				foreach (array('gtld', 'cctld', 'itld', 'bgtld') as $pytld) {
					if (isset($pfb['dnsblconfig']['pfb_pytlds_' . $pytld]) && !empty($pfb['dnsblconfig']['pfb_pytlds_' . $pytld])) {
						$p_cnt = count(explode(',', $pfb['dnsblconfig']['pfb_pytlds_' . $pytld]));
						if (is_numeric($p_cnt) && $p_cnt > 0) {
							$pytld_cnt += $p_cnt;
						}
					}
				}

				$pfb['alias_dnsbl_all'][] = 'DNSBL_TLD_Allow';
				dnsbl_alias_update('update', 'DNSBL_TLD_Allow', '', '', $pytld_cnt);
			}
			if ($pfb['dnsbl_regex'] == 'on') {
				$regex_cnt = 0;
				if (isset($pfb['dnsbl_regex_list'])) {
					$regex_cnt = count(pfbng_text_area_decode($pfb['dnsbl_regex_list'], TRUE, FALSE, FALSE)) ?: 0;
				}
				$pfb['alias_dnsbl_all'][] = 'DNSBL_Regex';
				dnsbl_alias_update('update', 'DNSBL_Regex', '', '', $regex_cnt);
			}
		}

		// Save DNSBL Alias statistics (Not for TLD mode)
		if ($pfb['domain_update'] && !$pfb['dnsbl_tld']) {
			dnsbl_save_stats();
		}
	}

	// Collect all DNSBL IP feeds (IPv4 only) into DNSBLIP_v4.txt
	if ($pfb['dnsbl_ip'] != 'Disabled' && ($pfb['updateip'] || !file_exists("{$pfb['dbdir']}/DNSBLIP_v4.txt"))) {

		$dnsbl_ip = glob("{$pfb['dnsdir']}/*_v4.ip");
		if (!empty($dnsbl_ip)) {
			$pfb_ips = @fopen("{$pfb['dbdir']}/DNSBLIP_v4.txt", 'w');
			foreach ($dnsbl_ip as $d_ip) {
				if (($handle = @fopen("{$d_ip}", 'r')) !== FALSE) {
					while (($line = @fgets($handle)) !== FALSE) {
						@fwrite($pfb_ips, $line);
					}
				}
				@fclose($handle);
			}
			@fclose($pfb_ips);
		}

		// Add empty placeholder IP
		else {
			@file_put_contents("{$pfb['dbdir']}/DNSBLIP_v4.txt", "{$pfb['ip_ph']}\n", LOCK_EX);
		}
		touch("{$pfb['denydir']}/DNSBLIP_v4.update");
	}

	// Remove DNSBL IP feed, if disabled
	if ($pfb['dnsbl_ip'] == 'Disabled') {
		unlink_if_exists("{$pfb['dbdir']}/DNSBLIP_v4.txt");
		unlink_if_exists("{$pfb['denydir']}/DNSBLIP_v4.*");
	}

	#########################################
	#	UPDATE Unbound DNS Database	#
	#########################################

	if ($pfb['domain_update']) {
		if (!empty($lists_dnsbl_all)) {
			pfb_logger("\n------------------------------------------------------------------------\n", 1);

			pfb_logger('Assembling DNSBL database...', 1);
			unlink_if_exists("{$pfb['dnsbl_file']}.raw");
			$pfb_output = @fopen("{$pfb['dnsbl_file']}.raw", 'w');
			foreach ($lists_dnsbl_all as $current_list) {
				if (($handle = @fopen("{$pfb['dnsdir']}/{$current_list}", 'r')) !== FALSE) {
					while (($line = @fgets($handle)) !== FALSE) {
						@fwrite($pfb_output, $line);
					}
				}
				@fclose($handle);
			}
			@fclose($pfb_output);
			pfb_logger("... completed [ NOW ]", 1);

			// DNSBL Python blocking mode, if TLD is not enabled
			if ($pfb['dnsbl_py_blacklist'] && !$pfb['dnsbl_tld']) {
				unlink_if_exists($pfb['unbound_py_data']);
				unlink_if_exists($pfb['unbound_py_zone']);
				unlink_if_exists($pfb['unbound_py_count']);
				rename("{$pfb['dnsbl_file']}.raw", $pfb['unbound_py_data']);
			}
		}

		else {
			$log = "\nDNSBL not Updated!\n";
			pfb_logger("{$log}", 1);
		}
	}
	else {
		if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on') {

			// When DNSBL is enabled and no Aliases are defined, or all Aliases are Disabled
			if (empty($lists_dnsbl_all) && !$pfb['save']) {
				pfb_logger("\nClearing all DNSBL Feeds", 1);
				$pfb['domain_clear'] = TRUE;

				// Clear out Unbound pfb_dnsbl.conf file
				if (!$pfb['dnsbl_py_blacklist']) {
					$pfb_output = @fopen("{$pfb['dnsbl_file']}.conf", 'w');
					@fwrite($pfb_output, '');
					@fclose($pfb_output);
				}

				// Remove DNSBL Python files
				else {
					unlink_if_exists($pfb['unbound_py_data']);
					unlink_if_exists($pfb['unbound_py_zone']);
					unlink_if_exists($pfb['unbound_py_wh']);
					unlink_if_exists($pfb['unbound_py_count']);
				}
			}
		}
		else {
			foreach (array("{$pfb['dnsbl_file']}.conf", $pfb['unbound_py_data'], $pfb['unbound_py_zone']) as $pfb_file) {
				if (file_exists($pfb_file)) {
					$pfb['domain_clear'] = TRUE;
					@unlink($pfb_file);
				}
			}
		}
	}

	#################################
	#	UNBOUND INTEGRATION	#
	#################################

	$pfbupdate = $pfbpython = FALSE;
	if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && $pfb['unbound_state'] == 'on') {
		$mode = 'enabled';
	}
	elseif (($pfb['enable'] == '' || $pfb['dnsbl'] == '') && !$pfb['install']) {
		$mode = 'disabled';
	}

	// Modify Unbound python configuration and mount lib/bin folders, as required
	$pfbpython = pfb_unbound_python($mode);

	// Modify Unbound.conf file, as required
	$pfbupdate = pfb_unbound_dnsbl($mode);

	// Modify DNSBL NAT and VIP and lighttpd web server conf, as required.
	pfb_create_dnsbl($mode);

	// Load new DNSBL updates to Unbound Resolver, as required
	if ($pfb['domain_update'] || $pfbupdate || $pfbpython ||$pfb['domain_clear'] || $safesearch_update) {

		// Create backup of existing DNSBL Unbound database
		if (!$pfb['dnsbl_py_blacklist'] && file_exists("{$pfb['dnsbl_file']}.conf")) {
			@copy("{$pfb['dnsbl_file']}.conf", "{$pfb['dnsbl_file']}.bk");
		}

		pfb_update_unbound($mode, $pfbupdate, $pfbpython);
	}


	#################################
	#	Assign Countries	#
	#################################

	if (!$pfb['save']) {
		$log = "\n\n===[  GeoIP Process  ]============================================\n";
		pfb_logger("{$log}", 1);
	}

	// Download MaxMind Databases if not found
	$maxmind_verify = FALSE;
	if (!empty($pfb['maxmind_key'])) {

		$maxmind_verify = TRUE;
		if (!file_exists("{$pfb['geoipshare']}/GeoLite2-Country.mmdb") ||
		    !file_exists("{$pfb['geoipshare']}/GeoLite2-Country-Blocks-IPv4.csv") ||
		    !file_exists("{$pfb['dbdir']}/geoip.txt") ||
		    !file_exists("{$pfb['ccdir']}/Top_Spammers_v4.info")) {

			// Check if MaxMind download already in progress
			exec('/bin/ps -wax', $result_cron);
			if (!preg_grep("/pfblockerng[.]php\s+dc/", $result_cron)) {
				$log = "\nMaxMind Database downloading and processing ( approx 4MB ) ... Please wait ...\n";
				pfb_logger("{$log}", 1);
				exec("/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php dc >> {$pfb['log']} 2>&1");
				restart_service('pfb_filter');
			}
			else {
				$log = "\nMaxMind download already in process...\n";
				pfb_logger("{$log}", 1);
			}
		}
	}

	$maxmind_run_once = TRUE;
	foreach ($pfb['continents'] as $continent => $pfb_alias) {
		if (isset($config['installedpackages']['pfblockerng' . strtolower(str_replace(' ', '', $continent))]['config'])) {
			$continent_config = $config['installedpackages']['pfblockerng' . strtolower(str_replace(' ', '', $continent))]['config'][0];
			$cc_name = 'pfblockerng' . strtolower(str_replace(' ', '', $continent));
			if (isset($continent_config['action']) && $continent_config['action'] != 'Disabled' && $pfb['enable'] == 'on') {

				// Maxmind License Key verification and user notification
				if ($maxmind_run_once && !$maxmind_verify) {
					$mmsg = 'MaxMind now requires a License Key! Review the IP tab: MaxMind settings for more information.';
					pfb_logger("\n\nURGENT:\n    {$mmsg}\n", 1);
					file_notice('pfBlockerNG MaxMind', $mmsg, 'pfBlockerNG', '/pfblockerng/pfblockerng_ip.php', 2);
					$maxmind_run_once = FALSE;
				}

				$urlvalue = '';	// Firewall: Aliases value field

				// Determine if Continent lists require action (IPv4 and IPv6)
				foreach ($cont_types as $c_type => $vtype) {

					$cc_alias = "{$pfb_alias}{$vtype}";

					// Determine 'list' details (return array $pfbarr)
					pfb_determine_list_detail($continent_config['action'], "{$cc_alias}", $cc_name, '0');
					$pfbadv		= $pfbarr['adv'];
					$pfbdescr	= $pfbarr['descr'];
					$pfbfolder	= $pfbarr['folder'];
					$pfborig	= $pfbarr['orig'];
					$logtab		= $pfbarr['logtab'];

					if (!empty($continent_config[$c_type])) {

						// Collect selected GeoIP ISOs
						if (($pfb_output = @fopen("{$pfb['geoip_tmp']}", 'w')) !== FALSE) {
							foreach (explode(',', $continent_config[$c_type]) as $iso) {

								$urlvalue .= "{$iso},";
								$isofile = "{$pfb['ccdir']}/{$iso}{$vtype}.txt";
								if (($handle = @fopen("{$isofile}", 'r')) !== FALSE) {
									while (($line = @fgets($handle)) !== FALSE) {
										@fwrite($pfb_output, $line);
									}
								}
								else {
									pfb_logger("\nCould not open ISO [ {$iso}{$vtype} ]\n", 1);
								}
								@fclose($handle);
							}
						}
						else {
							pfb_logger("\n[ {$cc_alias} ] Could not create GeoIP file handle\n", 1);
						}
						@fclose($pfb_output);

						// Collect md5 of new Continent data
						$continent		= 'md5_0';
						if (file_exists("{$pfb['geoip_tmp']}")) {
							$continent	= @md5_file("{$pfb['geoip_tmp']}");
						}

						// Collect md5 of existing Continent data
						$continent_ex		= 'md5_1';
						if (file_exists("{$pfborig}/{$cc_alias}.orig")) {
							$continent_ex	= @md5_file("{$pfborig}/{$cc_alias}.orig");
						}

						// Check if pfBlockerNG pfctl Continent tables are empty (pfBlockerNG was disabled w/ "keep", then re-enabled)
						$pfctlck = exec("{$pfb['pfctl']} -vvsTables | {$pfb['grep']} -A1 {$cc_alias} | {$pfb['awk']} '/Addresses/ {s+=$2}; END {print s}'");

						if (empty($pfctlck) && file_exists("{$pfbfolder}/{$cc_alias}.txt")) {
							@copy("{$pfbfolder}/{$cc_alias}.txt", "{$pfb['aliasdir']}/{$cc_alias}.txt");
							// Collect updated alias lists ('Reputation' disabled)
							$pfb_alias_lists[] = "{$cc_alias}";
						}

						// Collect active alias lists (Used for pfctl update when 'Reputation' is enabled).
						$pfb_alias_lists_all[] = "{$cc_alias}";

						// Compare existing (original file) and new Continent data
						if ($continent == $continent_ex && !empty($pfctlck)
						    && file_exists("{$pfbfolder}/{$cc_alias}.txt") && $pfb['reuse'] == '') {
							if (!$pfb['save']) {
								$log = "\n[ {$cc_alias} ]{$logtab} exists. [ NOW ]";
								pfb_logger("{$log}", 1);
							}
						} else {
							// Do not proceed with changes on user 'save'
							if (!$pfb['save']) {
								$log = "\n[ {$cc_alias} ]{$logtab} Changes found... Updating\n";
								pfb_logger("{$log}", 1);

								// Execute Reputation functions, when changes are found.
								if ($pfbadv && $vtype == '_v4') {
									$pfb['repcheck'] = TRUE;
								}

								// Collect updated alias lists ('Reputation' disabled)
								$pfb_alias_lists[] = "{$cc_alias}";

								if ($continent != 'md5_0') {
									@rename("{$pfb['geoip_tmp']}", "{$pfborig}/{$cc_alias}.orig");
									@copy("{$pfborig}/{$cc_alias}.orig", "{$pfbfolder}/{$cc_alias}.txt");

									// Call Aggregate process
									if ($pfb['agg'] == 'on' && $vtype == '_v4') {
										exec("{$pfb['script']} cidr_aggregate {$cc_alias} {$pfbfolder} {$elog}");
									}

									// Call Duplication process
									if ($pfb['dup'] == 'on' && $vtype == '_v4' && $pfbadv) {
										exec("{$pfb['script']} continent {$cc_alias} {$elog}");
									}

									// Save Continent data to aliastable folder
									@copy("{$pfbfolder}/{$cc_alias}.txt", "{$pfb['aliasdir']}/{$cc_alias}.txt");
								}

								// Check if file exists and is > 0 in size and save alias file
								$file_chk = 0;
								$cont_chk = "{$pfbfolder}/{$cc_alias}.txt";
								if (file_exists($cont_chk) && @filesize($cont_chk) > 0) {
									$file_chk = exec("{$pfb['grep']} -cv '^#\|^$' {$cont_chk}");
								}

								if ($file_chk <= 1) {
									if ($vtype == '_v6') {
                                                                                $p_ip = "::{$pfb['ip_ph']}";
                                                                        } else {
                                                                                $p_ip = $pfb['ip_ph'];
                                                                        }

									@file_put_contents("{$pfbfolder}/{$cc_alias}.txt", "{$p_ip}\n", LOCK_EX);
									@copy("{$pfbfolder}/{$cc_alias}.txt", "{$pfb['aliasdir']}/{$cc_alias}.txt");
									$log = "[ {$cc_alias} ] Found no unique IPs, adding '{$p_ip}' to avoid empty file\n";
									pfb_logger("{$log}", 1);
								}
							}
						}

						if (file_exists("{$pfbfolder}/{$cc_alias}.txt")) {
							// Create alias config
							$new_aliases_list[] = "{$cc_alias}";
							$new_aliases[] = array( 'name'		=> "{$cc_alias}",
										'url'		=> pfb_url_value($urlvalue, $cc_alias),
										'updatefreq'	=> '32',
										'address'	=> '',
										'descr'		=> "pfBlockerNG {$pfbdescr} GeoIP Alias",
										'type'		=> 'urltable',
										'detail'	=> 'DO NOT EDIT THIS ALIAS'
										);

							// Define firewall rule settings
							pfb_firewall_rule($continent_config['action'], $cc_alias, $vtype, $continent_config['aliaslog'],
							    $pfbarr['agateway_in'], $pfbarr['agateway_out'], $pfbarr['aaddrnot_in'], $pfbarr['aaddr_in'],
							    $pfbarr['aports_in'], $pfbarr['aproto_in'], $pfbarr['anot_in'], $pfbarr['aaddrnot_out'],
							    $pfbarr['aaddr_out'], $pfbarr['aports_out'], $pfbarr['aproto_out'], $pfbarr['anot_out']);
						}
						else {
							// unlink Continent list
							unlink_if_exists("{$pfb['aliasdir']}/{$cc_alias}.txt");
						}
					}
				}
			}
		}
	}

	// Remove temp file
	unlink_if_exists("{$pfb['geoip_tmp']}");

	#################################################
	#	Download and Collect IPv4/IPv6 lists	#
	#################################################

	// IPv4 REGEX Definitions
	$pfb['range']	= '/((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))-((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))/';
	$pfb['ipv4']	= '/(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)((\/(3[012]|[12]?[0-9]))?(?![-0-9a-zA-Z]))/';

	// IPv6 REGEX Definitions - Reference: http://labs.spritelink.net/regex
	$pfb['ipv6'] = '/((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?(\/[0-9][0-9]?|1([01][0-9]|2[0-8]))?/';

	if ($pfb['enable'] == 'on' && !$pfb['save']) {

		$pfb['supp_update'] = FALSE;
		$runonce_v4 = $runonce_v6 = TRUE;
		$lists = array();

		// Collect lists and custom list configuration and format into one array ($lists).
		foreach	($ip_types as $ip_type	=> $vtype) {
			if (!empty($config['installedpackages'][$ip_type]['config'])) {
				foreach	($config['installedpackages'][$ip_type]['config'] as $key => $list) {
					if (!is_array($list)) {
						$list = array();
					}
					if (!is_array($list['row'])) {
						$list['row'] = array();
					}

					$list['vtype']	= "{$vtype}";	// Collect list IP type
					$list['key']	= "{$key}";	// Collect list array key location

					// If only the 'customlist' is defined. Remove the 'List row' data.
					if (isset($list['row']) && empty($list['row'][0]['url'])) {
						unset($list['row']);
					}

					if (!empty($list['custom'])) {
						$list['row'][] = array(	'header'	=> "{$list['aliasname']}_custom",
									'custom'	=> $list['custom'],
									'state'		=> 'Enabled',
									'url'		=> 'custom'
									);
					}
					$lists[] = $list;
				}
			}

			// Add DNSBLIP, if configured (IPv4 only)
			if ($pfb['dnsbl'] == 'on' && $pfb['dnsbl_ip'] != 'Disabled' && $vtype == '_v4') {

				$list = array(	'aliasname'	=> 'DNSBLIP',
						'vtype'		=> "{$vtype}",
						'key'		=> 0,
						'dnsblip'	=> '',
						'action'	=> "{$pfb['dnsbl_ip']}",
						);

				$list['row'][] = array(	'format'	=> 'auto',
							'state'		=> 'Enabled',
							'url'		=> "{$pfb['dbdir']}/DNSBLIP{$vtype}.txt",
							'header'	=> 'DNSBLIP');
				$lists[] = $list;
			}
		}

		$maxmind_run_once = TRUE;
		foreach	($lists	as $list) {
			if ($runonce_v4 && $list['vtype'] == '_v4') {
				$runonce_v4 = FALSE;
				$log = "\n\n===[  IPv4 Process  ]=================================================\n";
				pfb_logger("{$log}", 1);
			} elseif ($runonce_v6 && $list['vtype'] == '_v6') {
				$runonce_v6 = FALSE;
				$log = "\n\n===[  IPv6 Process  ]=================================================\n";
				pfb_logger("{$log}", 1);
			}

			if ($list['action'] != 'Disabled' && isset($list['row'])) {
				$alias = "pfB_{$list['aliasname']}{$list['vtype']}";	// Capture Alias name

				foreach	($list['row'] as $row) {
					if (!empty($row['url']) && $row['state'] != 'Disabled') {
						$header = "{$row['header']}{$list['vtype']}";	// Capture Header/Label name

						// If row is a custom_list, set	flag.
						if (isset($row['custom'])) {
							$custom	= TRUE;
						} else {
							$custom	= FALSE;
						}

						// Maxmind License Key verification
						if ($maxmind_run_once && $row['format'] == 'geoip') {
							$mmsg = 'MaxMind now requires a License Key! Review the IP tab: MaxMind settings for more information.';
							if (empty($pfb['maxmind_key'])) {
								pfb_logger("\n\nURGENT:\n    {$mmsg}.\n", 1);
								file_notice('pfBlockerNG MaxMind', $mmsg, 'pfBlockerNG', '/pfblockerng/pfblockerng_ip.php', 2);
							}
							$maxmind_run_once = FALSE;
						}

						// IPv4 Advanced Tunables
						$pfbcidr = 'Disabled';
						if (isset($list['suppression_cidr'])) {
							$pfbcidr = "{$list['suppression_cidr']}";
						}
						
						// cURL Source Interface (sets CURLOPT_INTERFACE)
						$srcint = $list['srcint'] ?: FALSE;

						// IP v4/6 Advanced Tunable - (Pre/Post Script processing)
						$pfb_script_pre = FALSE;
						if (isset($list['script_pre']) && !empty($list['script_pre'])) {
							$pfb_script_pre = "/usr/local/pkg/pfblockerng/{$list['script_pre']}";
						}

						$pfb_script_post = FALSE;
						if (isset($list['script_post']) && !empty($list['script_post'])) {
							$pfb_script_post = "/usr/local/pkg/pfblockerng/{$list['script_post']}";
						}

						// Determine 'list' details (return array $pfbarr)
						if (isset($list['dnsblip'])) {
							$list_type = 'pfblockerngdnsblsettings';
						} else {
							$list_type = "{$ip_type}";
						}

						pfb_determine_list_detail($list['action'], $header, $list_type, $list['key']);
						$pfbadv		= $pfbarr['adv'];
						$pfbfolder	= $pfbarr['folder'];
						$pfborig	= $pfbarr['orig'];
						$pfbreuse	= $pfbarr['reuse'];
						$logtab		= $pfbarr['logtab'];

						// Collect active alias list (Used for pfctl update when 'Reputation' is enabled.
						$pfb_alias_lists_all[] = "{$alias}";

						if (file_exists("{$pfbfolder}/{$header}.txt") &&
						    !file_exists("{$pfbfolder}/{$header}.update") &&
						    !file_exists("{$pfbfolder}/{$header}.fail") &&
						    $pfbreuse == '') {

							if ($row['state'] == 'Hold') {
								$log = "\n[ {$header} ]{$logtab} static hold. [ NOW ]";
							} else {
								$log = "\n[ {$header} ]{$logtab} exists. [ NOW ]";
							}
							pfb_logger("{$log}", 1);
						}
						else {
							if ($pfbreuse == 'on' && file_exists("{$pfborig}/{$header}.orig")) {
								$log = "\n[ {$header} ]{$logtab} Reload [ NOW ]";
							} else {
								$log = "\n[ {$header} ]{$logtab} Downloading update [ NOW ]";
							}
							pfb_logger("{$log}", 1);
							$file_dwn = "{$pfborig}/{$header}";

							// Force 'Alias Native' setting to any Alias with 'Advanced Inbound/Outbound -Invert src/dst' settings.
							// This will bypass Deduplication and Reputation features.
							if ($pfbarr['aaddrnot_in'] == 'on' || $pfbarr['aaddrnot_out'] == 'on') {
								pfb_logger("Using Alias Native\n", 1);
							}

							if (!$custom) {
								pfb_logger(' .', 1);

								// Allow cURL SSL downgrade 'Flex' if user configured.
								$pflex = FALSE;
								if ($row['state'] == 'Flex') {
									$pflex = TRUE;
								}

								// Adjust 'geoip' format to GeoIP path location
								if ($row['format'] == 'geoip') {
									if (strpos($row['url'], ' ') !== FALSE) {
										$row['url'] = strstr($row['url'], ' ', TRUE);
									}
									$row['url'] = "/usr/local/share/GeoIP/cc/{$row['url']}{$list['vtype']}.txt";
								}

								// Remove 'whois' source field description
								elseif ($row['format'] == 'asn') {
									if (strpos($row['url'], ' ') !== FALSE) {
										$row['url'] = strstr($row['url'], ' ', TRUE);
									}
								}

								// Determine if	list needs to be downloaded or reuse previously downloaded file.
								if ($pfbreuse == 'on' && file_exists("{$file_dwn}.orig")) {
									// File exists/reuse

									// Process Emerging Threats IQRisk if required
									if (strpos($row['url'], 'iprepdata.txt') !== FALSE) {
										if (file_exists("{$file_dwn}.raw")) {
											exec("/usr/bin/gunzip -c {$file_dwn}.raw > {$file_dwn}.orig");
										}
										exec("{$pfb['script']} et {$header} x x x x x {$pfb['etblock']} {$pfb['etmatch']} {$elog}");
									}
								}
								else {
									// Download list
									if (!pfb_download($row['url'], $file_dwn, $pflex, $header, $row['format'],
										1, $list['vtype'], '', '', '', '', $srcint)) {

										// Determine reason for download failure
										pfb_download_failure($alias, $header, $pfbfolder, $row['url']);

										// Utilize previously download file (If 'fail' marker exists)
										if (file_exists("{$pfbfolder}/{$header}.fail") &&
										    file_exists("{$file_dwn}.orig")) {
											pfb_logger("\n  Restoring previously downloaded file contents...", 2);
										}
										else {
											if ($pfbadv) {
												// Script to Remove failed lists from masterfile
												exec("{$pfb['script']} remove x x x {$header} {$elog}");
											}
											continue;
										}
									}
									else {
										// Clear any previous download fail marker
										unlink_if_exists("{$pfbfolder}/{$header}.fail");
										pfb_logger('.', 1);
									}
								}
								pfb_logger(' completed .', 1);
							}
							else {
								if ($list['whois_convert'] == 'on') {
									// Process Domain/AS based custom list
									$custom_list = str_replace("\n", ',', pfbng_text_area_decode($list['custom'], FALSE, TRUE, TRUE));
									exec("{$pfb['script']} whoisconvert {$header} {$list['vtype']} {$custom_list} {$elog}");
								}
								else {
									// Process IP based custom list
									$custom_list = pfbng_text_area_decode($list['custom'], FALSE, TRUE, FALSE);
									@file_put_contents("{$file_dwn}.orig", $custom_list, LOCK_EX);
								}
								pfb_logger(' . completed .', 1);
							}

							$ip_data = '';		// IPs collected from feed
							$parse_fail = 0;	// Failed parsed lines from feed
							pfb_logger('.', 1);

							// Set 'auto' format for all lists, except for lists that require 'regex' parsing.
							if ($row['format'] == 'regex') {
								$pftype = 'regex';
							}
							else {
								$url = pathinfo($row['url']);

								// Strip any text after '?'
								if (strpos($url['extension'], '?') !== FALSE) {
									$url['extension'] = strstr($url['extension'], '?', TRUE);
								}

								// Determine if list is an IBlock list
								if (strpos($url['dirname'], 'iblocklist') !== FALSE) {
									$url['extension'] = 'iblock';
								}

								// Use 'regex' IP parser for non-standard IP lists.
								if (in_array($url['extension'], array('html', 'htm', 'php', 'aspx', 'cgi', 'csv', 'rules', ''))) {
									$pftype = 'regex';
								} else {
									$pftype = 'auto';
								}
							}

							// IPv4/6 Advanced Tunable - (Pre Script processing)
							if ($pfb_script_pre && file_exists("{$pfb_script_pre}")) {
								pfb_logger("\nExecuting pre-script: {$list['script_pre']}\n", 1);
								exec("{$pfb_script_pre} {$file_dwn}.orig {$list['vtype']} {$elog}");
							}

							if (($fhandle = @fopen("{$file_dwn}.orig", 'r')) !== FALSE) {
								while (($line = @fgets($fhandle)) !== FALSE) {
									// Record original line for regex matching, if required.
									$oline = $line;

									// Remove any leading/trailing whitespaces
									$line = trim($line);

									// Remove commentlines and blank lines
									if (substr($line, 0, 1) == '#' || empty($line)) {
										continue;
									}

									$parse_error = FALSE;
									if ($list['vtype'] == '_v4' && $pftype == 'auto') {

										// IBlock - parser sample ( JKS Media, LLC:4.53.2.12-4.53.2.15 )
										// Remove leading domain name details
										if (strpos($line, '-') !== FALSE && strpos($line, ':') !== FALSE) {
											$line = str_replace(':', '', strstr($line, ':', FALSE));
										}

										// If 'space' character found, remove characters after space
										if (strpos($line, ' ') !== FALSE) {
											$line = strstr($line, ' ', TRUE);
										}

										// If '#' character found, remove characters after '#'
										if (strpos($line, '#') !== FALSE) {
											$line = str_replace('#', '', strstr($line, '#', TRUE));
										}

										// Remove any leading/trailing whitespaces
										$line = trim($line);

										// Range parser
										if (strpos($line, '-') !== FALSE) {
											$matches = explode('-', $line);
											if (count($matches) == 2) {
												$a_cidr = ip_range_to_subnet_array($matches[0],$matches[1]);
												if (!empty($a_cidr)) {
													foreach ($a_cidr as $cidr) {
														$cidr = sanitize_ipaddr($cidr, $custom, $pfbcidr);
														if (!empty($cidr)) {
															if (validate_ipv4($cidr)) {
																$ip_data .= $cidr . "\n";
															}
															else {
																$parse_error = TRUE;
															}
														}
													}
													if (!$parse_error) {
														continue;
													}
												}
											}
											else {
												$parse_error = TRUE;
											}
										}

										if (!$parse_error) {
											// Single address parser
											$parsed = sanitize_ipaddr($line, $custom, $pfbcidr);
											if (validate_ipv4($parsed)) {
												$ip_data .= $parsed . "\n";
												continue;
											}
											else {
												$parse_error = TRUE;
											}
										}
									}

									if ($list['vtype'] == '_v4' && ($pftype == 'regex' || $parse_error)) {

										// Use regex as last alternative.

										if (strpos($oline, '-') !== FALSE) {
											// Network range 192.168.0.0-192.168.0.254
											if (preg_match($pfb['range'], $oline, $matches)) {
												$a_cidr = ip_range_to_subnet_array($matches[1], $matches[2]);
												if (!empty($a_cidr)) {
													foreach ($a_cidr as $cidr) {
														$cidr = sanitize_ipaddr($cidr, $custom, $pfbcidr);
														if (validate_ipv4($cidr)) {
															$ip_data .= $cidr . "\n";
														}
														else {
															$parse_fail++;
														}
													}
												}
												continue;
											}
										}

										// IPv4/CIDR format 192.168.0.0 | 192.168.0.0/16
										if (preg_match_all($pfb['ipv4'], $oline, $matches)) {
											$matches = array_unique($matches[0]);
											foreach ($matches as $match) {

												// Workaround to skip cloudflare error pages
												if (strpos($oline, 'cf-footer-item') === FALSE) {
													$parsed = sanitize_ipaddr($match, $custom, $pfbcidr);
													if (validate_ipv4($parsed)) {
														$ip_data .= $parsed . "\n";
													}
												}
											}
											continue;
										}
									}

									if ($list['vtype'] == '_v6') {
										// Auto IPv6 parser
										if ($pftype == 'auto') {
											if (strpos($line, ':') !== FALSE) {

												// Remove any comments
												if (strpos($line, '#') !== FALSE) {
													$line = str_replace('#', '', strstr($line, '#', TRUE));
												}

												if (is_ipaddrv6($line) || is_subnetv6($line)) {
													$ip_data .= $line . "\n";
													continue;
												}
											}
										}

										// IPv6 Regex parser
										if (preg_match_all($pfb['ipv6'], $oline, $matches)) {
											$matches = array_unique($matches[0]);
											foreach ($matches as $match) {

												// Remove any comments
												if (strpos($match, '#') !== FALSE) {
													$match = str_replace('#', '', strstr($match, '#', TRUE));
												}

												// Workaround to skip cloudflare error pages
												if (strpos($oline, 'cf-footer-item') === FALSE) {
													if (is_ipaddrv6($match) || is_subnetv6($match)) {
														$ip_data .= $match . "\n";
													}
												}
											}
										}
									}
								}

								// Check for parse failures
								if (!empty($line) && !preg_match('/[a-zA-Z,;|\"\'?]/', $line)) {
									$parse_fail++;
									$log = "[!] Parse Errors [ {$parse_fail} ]\n";
									pfb_logger("{$log}", 2);
								}
							}
							@fclose($fhandle);
							pfb_logger("\n", 1);

							// IP v4/6 Advanced Tunable - (Post Script processing)
							if ($pfb_script_post && file_exists("{$pfb_script_post}")) {
								pfb_logger("\nExecuting post-script: {$list['script_pre']}\n", 1);
								exec("{$pfb_script_post} {$file_dwn}.orig {$list['vtype']} {$elog}");
							}

							if (!$custom) {
								// Check to see if list actually failed download or has no IPs listed.
								$file_chk = '';
								if (file_exists("{$file_dwn}.orig") && @filesize("{$file_dwn}.orig") > 0) {
									$file_chk = exec("{$pfb['grep']} -cv '^#\|^$' {$file_dwn}.orig");
								}

								if ($file_chk == 0) {
									if ($list['vtype'] == '_v6') {
										$p_ip = "::{$pfb['ip_ph']}";
									} else {
										$p_ip = $pfb['ip_ph'];
									}

									$ip_data	= "{$p_ip}\n";
									$log		= "  Empty file, Adding '{$p_ip}' to avoid download failure.\n";
									pfb_logger("{$log}", 1);
								}
							}

							if (!empty($ip_data)) {
								// Save List to '.txt' format in appropriate folder
								@file_put_contents("{$pfbfolder}/{$header}.txt", "{$ip_data}", LOCK_EX);

								// Call 'shell script' functions (Deny Actions only)
								if ($pfbadv && $list['vtype'] == '_v4') {
									$args = '';
									// Call Process255
									if ($pfb['dup'] == 'on' || $pfb['agg'] == 'on') {
										$args  = '_255';
									}
									// Call Aggregate process
									if ($pfb['agg'] == 'on') {
										$args .= '_agg';
									}
									// Call Reputation Max process
									if ($pfb['rep'] == 'on') {
										$args .= '_rep';
									}
									// Call Duplication process
									if ($pfb['dup'] == 'on') {
										$args .= '_dup';
									}
									if (!empty($args)) {
										exec("{$pfb['script']} {$args} {$header} {$pfb['max']} {$pfb['drep']} {$pfb['ccexclude']} {$pfb['ccwhite']} {$pfb['ccblack']} {$elog}");
									}
								}

								if (!$pfbadv && $list['vtype'] == '_v4') {
									// Call Aggregate process
									if ($pfb['agg'] == 'on') {
										exec("{$pfb['script']} cidr_aggregate {$header} {$pfbfolder} {$elog}");
									}
								}

								// Collect updated alias lists ('Reputation' disabled)
								$pfb_alias_lists[] = "{$alias}";

								if ($pfbadv && $list['vtype'] == '_v4') {
									// Execute Reputation functions, when changes are found.
									$pfb['repcheck'] = TRUE;

									// Enable suppression process due to updates
									if ($pfb['supp'] == 'on') {
										$pfb['supp_update'] = TRUE;
									}
								}
							} else {
								if (!$custom) {
									$log = "[ {$alias} {$header} ] No IPs found! Ensure only IP based Feeds are used! ]\n";
								} else {
									$log = "[ {$alias} {$header} ] Custom List: No IPs found! Ensure only IP based Feeds are used! ]\n";
								}
								pfb_logger("{$log}", 1);
							}
							unset($ip_data);

							// Remove update file indicator
							unlink_if_exists("{$pfbfolder}/{$header}.update");
						}
					}
				}
			}
		}
	}


	#################################
	#	REPUTATION PROCESSES	#
	#################################

	// IP Reputation processes (pMax and dMax)
	if ($pfb['prep'] == 'on' && $pfb['repcheck'] && !$pfb['save'] && $pfb['enable'] == 'on') {
		// Script to run prep process
		exec("{$pfb['script']} pmax x {$pfb['pmax']} {$elog}");
	}
	if ($pfb['drep'] == 'on' && $pfb['repcheck'] && !$pfb['save'] && $pfb['enable'] == 'on') {
		// Script to run drep process
		exec("{$pfb['script']} dmax x {$pfb['dmax']} {$pfb['drep']} {$pfb['ccexclude']} {$pfb['ccwhite']} {$pfb['ccblack']} {$elog}");
	}

	#################################################
	#	CONFIGURE ALIASES AND FIREWALL RULES	#
	#################################################

	foreach ($ip_types as $ip_type => $vtype) {
		$lists = array();

		if (!empty($config['installedpackages'][$ip_type]['config'])) {
			$lists = $config['installedpackages'][$ip_type]['config'];
		}

		// Add DNSBLIP, if configured (IPv4 only)
		if ($pfb['dnsbl'] == 'on' && $pfb['dnsbl_ip'] != 'Disabled' && $vtype == '_v4') {

			$list = array(	'aliasname'	=> 'DNSBLIP',
					'vtype'		=> "{$vtype}",
					'key'		=> 0,
					'dnsblip'	=> '',
					'action'	=> "{$pfb['dnsbl_ip']}",
					'aliaslog'	=> "{$pfb['dnsblconfig']['aliaslog']}");

			$list['row'][] = array( 'format'	=> 'auto',
						'state'		=> 'Enabled',
						'url'		=> "{$pfb['dbdir']}/DNSBLIP{$vtype}.txt",
						'header'	=> 'DNSBLIP');
			$lists[] = $list;
		}

		if (!empty($lists) && $pfb['enable'] == 'on') {
			$pfbrunonce = TRUE;
			foreach ($lists as $key => $list) {
				$alias = "pfB_{$list['aliasname']}{$vtype}";

				// Skip any Alias that are 'enabled' but Lists/customlists are not defined.
				if (empty($list['row'][0]['url']) && empty($list['custom'])) {
					exec("{$pfb['pfctl']} -t {$alias} -T kill 2>&1", $result);
					continue;
				}

				if (isset($list['dnsblip'])) {
					$list_type = 'pfblockerngdnsblsettings';
				} else {
					$list_type = "{$ip_type}";
				}

				// Determine 'list' details (return array $pfbarr)
				pfb_determine_list_detail($list['action'], '', $list_type, $key);
				$pfbadv		= $pfbarr['adv'];
				$pfbdescr	= $pfbarr['descr'];
				$pfbfolder	= $pfbarr['folder'];

				// Only Save aliases that have been updated.
				// When 'Reputation' is used, all aliases need to be updated.
				$final_alias = array();
				if ($pfb['drep'] == 'on' || $pfb['prep'] == 'on') {
					if (!empty($pfb_alias_lists_all)) {
						$final_alias = array_unique($pfb_alias_lists_all);
					}
				}
				else {
					if (!empty($pfb_alias_lists)) {
						$final_alias = array_unique($pfb_alias_lists);
					}
				}

				if ($list['action'] != 'Disabled') {
					$pfbupdate	= FALSE;
					$alias_ips	= '';	// IP Collection of all Lists in the Alias
					$urlvalue	= '';	// Firewall: Aliases value field

					if (isset($list['row'])) {
						foreach ($list['row'] as $row) {
							if (!empty($row['url']) && $row['state'] != 'Disabled') {

								$header = "{$row['header']}{$vtype}";
								$urlvalue .= "{$header},";
								$pfctlck = exec("{$pfb['pfctl']} -vvsTables | {$pfb['grep']} -A1 {$alias} | {$pfb['awk']} '/Addresses/ {s+=$2}; END {print s}'");

								// Update alias if list file exists and its been updated or if the alias URL table is empty.
								if (file_exists("{$pfbfolder}/{$header}.txt") && (in_array($alias, $final_alias) || empty($pfctlck))) {
									// Script to run suppression process (print header only)
									if ($pfbrunonce && $pfb['supp'] == 'on' && $vtype == '_v4' && $pfb['supp_update']) {
										exec("{$pfb['script']} suppress x x x suppressheader {$elog}");
										$pfbrunonce = FALSE;
									}
									// Script to run suppression process (body)
									if ($pfb['supp'] == 'on' && $vtype == '_v4' && $pfb['supp_update'] && $pfbadv) {
										if ($pfb['dup'] == 'on') {
											exec("{$pfb['script']} suppress x x x {$header}\|{$pfbfolder}/ {$elog}");
										} else {
											exec("{$pfb['script']} suppress x x off {$header}\|{$pfbfolder}/ {$elog}");
										}
									}
									$alias_ips .= file_get_contents("{$pfbfolder}/{$header}.txt");
									$pfbupdate = TRUE;
								}
							}
						}
					}

					// check custom network list
					$aliasname = "{$list['aliasname']}_custom{$vtype}";

					// Update alias if list file exists and its been updated or if the alias URL table is empty.
					$pfctlck = exec("{$pfb['pfctl']} -vvsTables | {$pfb['grep']} -A1 {$alias} | {$pfb['awk']} '/Addresses/ {s+=$2}; END {print s}'");
					if (!empty($list['custom'])) {
						$urlvalue .= "{$aliasname},";
						if (file_exists("{$pfbfolder}/{$aliasname}.txt") && in_array($alias, $final_alias) ||
						    file_exists("{$pfbfolder}/{$aliasname}.txt") && empty($pfctlck)) {
							$alias_ips .= file_get_contents("{$pfbfolder}/{$aliasname}.txt");
							$pfbupdate = TRUE;
						}
					}

					// Determine validity of alias URL tables/rules. ie: Don't create empty URL tables or aliases
					if (empty($alias_ips) && empty($pfctlck)) {
						unlink_if_exists("{$pfb['aliasdir']}/{$alias}.txt");
					}
					else {
						// Save only aliases that have been updated.
						if ($pfbupdate) {
							@file_put_contents("{$pfb['aliasdir']}/{$alias}.txt", $alias_ips, LOCK_EX);
						}

						// Add '[s]' to Alias descriptions (Bypass States removal feature)
						$adescr = "pfBlockerNG {$pfbdescr} Alias";
						if ($list['stateremoval'] == 'disabled') {
							$adescr = "pfBlockerNG {$pfbdescr} Alias [s]";
						}

						// Create alias
						$new_aliases_list[] = "{$alias}";
						$new_aliases[] = array(	'name'		=> "{$alias}",
									'url'		=> pfb_url_value($urlvalue, $alias),
									'updatefreq'	=> '32',
									'address'	=> '',
									'descr'		=> "{$adescr}",
									'type'		=> 'urltable',
									'detail'	=> 'DO NOT EDIT THIS ALIAS'
									);

						// Define firewall rule settings
						pfb_firewall_rule($list['action'], $alias, $vtype, $list['aliaslog'], $pfbarr['agateway_in'], $pfbarr['agateway_out'],
						    $pfbarr['aaddrnot_in'], $pfbarr['aaddr_in'], $pfbarr['aports_in'], $pfbarr['aproto_in'], $pfbarr['anot_in'],
						    $pfbarr['aaddrnot_out'], $pfbarr['aaddr_out'], $pfbarr['aports_out'], $pfbarr['aproto_out'], $pfbarr['anot_out']);
					}
				}
				else {
					// unlink previous pfblockerNG alias list
					unlink_if_exists("{$pfb['aliasdir']}/{$alias}.txt");
				}
			}
		}
	}
	// Clear variables
	$alias_ips = '';

	// Define DNSBL VIP Ports alias
	if ($pfb['dnsbl_rule'] != 'Disabled' && !empty($pfb['dnsbl_port']) && !empty($pfb['dnsbl_port_ssl'])
	    && !empty($pfb['dnsblconfig']['dnsbl_allow_int']) && isset($pfb['dnsbl_vip'])) {

		$new_aliases_list[] = 'pfB_DNSBL_Ports';
		$new_aliases[] = array( 'name'		=> 'pfB_DNSBL_Ports',
					'address'	=> $pfb['dnsbl_iface'] != 'lo0' ? "{$pfb['dnsbl_port']} {$pfb['dnsbl_port_ssl']}" : '80 443',
					'descr'		=> 'pfBlockerNG DNSBL VIP Ports',
					'type'		=> 'port',
					'detail'	=> 'DO NOT EDIT THIS PORT||DO NOT EDIT THIS PORT'
					);

		if ($pfb['dnsbl_v6'] == 'on') {
			$new_aliases_list[] = 'pfB_DNSBL_VIPs';
			$new_aliases[] = array(	'name'		=> 'pfB_DNSBL_VIPs',
						'address'	=> "{$pfb['dnsbl_vip']} ::{$pfb['dnsbl_vip']}",
						'descr'		=> 'pfBlockerNG DNSBL VIPs',
						'type'		=> 'host',
						'detail'	=> 'DO NOT EDIT THIS HOST||DO NOT EDIT THIS HOST'
						);
		}
	}

	#########################################
	#	UPDATE pfSense ALIAS TABLES	#
	#########################################

	// Reload config.xml to get any recent changes
	$config = parse_config(true);

	if (!is_array($config['aliases'])) {
		$config['aliases'] = array();
	}

	$exist_aliases = $config['aliases']['alias'];
	if (is_array($config['aliases']['alias'])) {
		foreach ($config['aliases']['alias'] as $cbalias) {

			if (substr($cbalias['name'], 0, 4) == 'pfB_') {
				// Remove unreferenced pfB aliastable files
				if (!in_array($cbalias['name'], $new_aliases_list)) {
					unlink_if_exists("{$pfb['aliasdir']}/{$cbalias['name']}.*");
				}
			}
			else {
				$new_aliases[] = $cbalias;
			}
		}
	}

	// Update config.xml, if changes required
	if ($exist_aliases != $new_aliases) {
		$config['aliases']['alias'] = $new_aliases;
		write_config('pfBlockerNG: saving Aliases');
	}
	unset($new_aliases, $exist_aliases);

	#########################
	#	Assign Rules	#
	#########################

	// Only execute if autorules are defined or if an alias has been removed.
	if ($pfb['autorules'] || $pfb['enable'] == '' || $pfb['remove']) {
		$message = '';
		if (!empty($pfb['deny_inbound']) || !empty($pfb['permit_inbound']) || !empty($pfb['match_inbound'])) {
			if (empty($pfb['inbound_interfaces'])) {
				$message = " Unable to apply rules. Inbound interface option not configured.";
			}
		}
		if (!empty($pfb['deny_outbound']) || !empty($pfb['permit_outbound']) || !empty($pfb['match_outbound'])) {
			if (empty($pfb['outbound_interfaces'])) {
				$message .= "\n Unable to apply rules. Outbound interface option not configured.";
			}
		}

		if (empty($message)) {
			$new_rules = $permit_rules = $match_rules = $other_rules = $fpermit_rules = $fmatch_rules = $fother_rules = array();

			// Reload config.xml to get any recent changes
			$config = parse_config(true);

			// New vs old rules array comparison
			$orig_rules_nocreated = $new_rules_nocreated = array();

			// Collect all existing rules
			init_config_arr(array('filter', 'rule'));
			$rules = $config['filter']['rule'];

			// Collect existing pfSense rules 'pass', 'match' and 'other' pfSense rules into new arrays.
			if (!empty($rules)) {
				foreach ($rules as $rule) {

					// Remove all existing rules that start with 'pfB_' in the Rule Description
					if (substr($rule['descr'], 0, 4) != 'pfB_') {

						// Upgrade previous IPv4 pfBlockerNG 'alias type' aliasnames to new '_v4' suffix format
						foreach (array('source', 'destination') as $rtype) {
							if (substr($rule[$rtype]['address'], 0, 4) == 'pfB_' &&
							    substr($rule[$rtype]['address'], -3) != '_v4' &&
							    $rule['ipprotocol'] == 'inet') {

								// Add '_v4' suffix
								$rule[$rtype]['address'] = "{$rule[$rtype]['address']}_v4";
							}
						}

						// Floating rules collection 'Floating Pass/Match', balance to 'other'
						if ($pfb['float'] == 'on') {
							if ($pfb['order'] == 'order_0' && $rule['floating'] == 'yes') {
								$fother_rules[] = $rule;
							}
							else {
								if ($rule['type'] == 'pass' && $rule['floating'] == 'yes') {
									$fpermit_rules[] = $rule;
								} elseif ($rule['type'] == 'match' && $rule['floating'] == 'yes') {
									$fmatch_rules[] = $rule;
								} elseif ($rule['floating'] == 'yes') {
									$fother_rules[] = $rule;
								} else {
									$other_rules[] = $rule;
								}
							}
						} else {
							// Collect only 'selected inbound and outbound interfaces'. balance to 'other'
							if (in_array($rule['interface'], $pfb['inbound_interfaces']) ||
							    in_array($rule['interface'], $pfb['outbound_interfaces'])) {
								// Floating rules 'off'. Collect 'floating other', pass, balance to 'other'
								if ($rule['floating'] == 'yes') {
									$fother_rules[] = $rule;
								} elseif ($rule['type'] == 'pass' || isset($rule['associated-rule-id'])) {
									if ($pfb['order'] == 'order_0') {
										$other_rules[] = $rule;
									} else {
										$permit_rules[] = $rule;
									}
								} else {
									$other_rules[] = $rule;
								}
							} else {
								if ($rule['floating'] == 'yes') {
									$fother_rules[] = $rule;
								} else {
									$other_rules[] = $rule;
								}
							}
						}
					}

					// Remove 'created' tag
					if (isset($rule['created'])) {
						unset($rule['created']);
					}
					$orig_rules_nocreated[] = $rule;
				}
			}

			#################################################################################
			#			IP FIREWALL RULES ORDER					#
			#  ORDER 0 |	pfB (p/m/b/r)	| All other	|				#
			#  ORDER 1 |	pfSense (p/m)	| pfB (p/m)	| pfB (b/r) 	| pfSense (b/r)	#
			#  ORDER 2 |	pfB (p/m)	| pfSense (p/m) | pfB (b/r)	| pfSense (b/r)	#
			#  ORDER 3 |	pfB (p/m)	| pfB (b/r) 	| pfSense (p/m)	| pfSense (b/r)	#
			#  ORDER 4 |	pfB (p/m)	| pfB (b/r)	| pfSense (b/r)	| pfSense (p/m)	#
			#################################################################################


			if ($pfb['float'] == '' && $pfb['order'] == 'order_1' && !empty($fother_rules)) {
				foreach ($fother_rules as $cb_rules) {
					$new_rules[] = $cb_rules;
				}
			}
			if ($pfb['float'] == 'on' && $pfb['order'] == 'order_1') {
				foreach (array($fpermit_rules, $fmatch_rules) as $rtype) {
					if (!empty($rtype)) {
						foreach ($rtype as $cb_rules) {
							$new_rules[] = $cb_rules;
						}
					}
				}
			}

			// Define DNSBL 'Floating' pass rule for selected 'OPT' segments to be able to access the LAN DNSBL VIP
			if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && $pfb['dnsbl_rule'] != 'Disabled'
			    && !empty($pfb['dnsbl_port']) && !empty($pfb['dnsbl_port_ssl'])
			    && !empty($pfb['dnsblconfig']['dnsbl_allow_int']) && isset($pfb['dnsbl_vip'])) {

				$rule			= $pfb['base_rule_float'];
				$rule['tracker']	= pfb_tracker('pfB_DNSBL_Ping', '', '');
				$rule['type']		= 'pass';
				$rule['direction']	= 'any';
				$rule['interface']	= "{$pfb['dnsblconfig']['dnsbl_allow_int']}";
				$rule['ipprotocol']	= ($pfb['dnsbl_v6'] == 'on' ? $rule['ipprotocol'] = 'inet46' : $rule['ipprotocol'] = 'inet');
				$rule['descr']		= "pfB_DNSBL_Ping{$pfb['suffix']}";
				$rule['protocol']	= 'icmp';
				$rule['icmptype']	= 'echoreq';
				$rule['source']		= array('any' => '');
				$rule['destination']	= array('address' => ($pfb['dnsbl_v6'] == 'on' ? 'pfB_DNSBL_VIPs' : $pfb['dnsbl_vip']));
				$rule['created']	= array('time' => (int)microtime(true), 'username' => 'Auto');
				$new_rules[]		= $rule;

				$rule			= $pfb['base_rule_float'];
				$rule['tracker']	= pfb_tracker('pfB_DNSBL_Permit', '', '');
				$rule['type']		= 'pass';
				$rule['direction']	= 'any';
				$rule['interface']	= "{$pfb['dnsblconfig']['dnsbl_allow_int']}";
				$rule['ipprotocol']	= ($pfb['dnsbl_v6'] == 'on' ? $rule['ipprotocol'] = 'inet46' : $rule['ipprotocol'] = 'inet');
				$rule['descr']		= "pfB_DNSBL_Permit{$pfb['suffix']}";
				$rule['protocol']	= 'tcp/udp';
				$rule['source']		= array('any' => '');
				$rule['destination']	= array('address' => ($pfb['dnsbl_v6'] == 'on' ? 'pfB_DNSBL_VIPs' : $pfb['dnsbl_vip']),
								 'port' => 'pfB_DNSBL_Ports');
				$rule['created']	= array('time' => (int)microtime(true), 'username' => 'Auto');
				$new_rules[]		= $rule;
			}

			// Define inbound interface rules
			if (!empty($pfb['inbound_interfaces'])) {
				$pfbrunonce = TRUE;
				foreach ($pfb['inbound_interfaces'] as $inbound_interface) {
					if ($pfb['order'] == 'order_1' && !empty($permit_rules)) {
						foreach ($permit_rules as $cb_rules) {
							if ($cb_rules['interface'] == $inbound_interface) {
								$new_rules[] = $cb_rules;
							}
						}
					}
					if (!empty($pfb['permit_inbound'])) {
						foreach ($pfb['permit_inbound'] as $cb_rules) {
							$cb_rules['interface'] = $inbound_interface;
							$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $inbound_interface, 'permit_in');
							$new_rules[] = $cb_rules;
						}
					}
					// Match inbound rules defined as floating only.
					if ($pfbrunonce && !empty($pfb['match_inbound'])) {
						foreach ($pfb['match_inbound'] as $cb_rules) {
							$cb_rules['interface'] = $pfb['inbound_floating'];
							$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $inbound_interface, 'match_in');
							$new_rules[] = $cb_rules;
							$pfbrunonce = FALSE;
						}
					}
					if ($pfb['order'] == 'order_2') {
						foreach (array($fpermit_rules, $fmatch_rules) as $rtype) {
							if (!empty($rtype)) {
								foreach ($rtype as $cb_rules) {
									$new_rules[] = $cb_rules;
								}
							}
						}
						if (!empty($permit_rules)) {
							foreach ($permit_rules as $cb_rules) {
								if ($cb_rules['interface'] == $inbound_interface) {
									$new_rules[] = $cb_rules;
								}
							}
						}
					}
					if (!empty($pfb['deny_inbound'])) {
						foreach ($pfb['deny_inbound'] as $cb_rules) {
							$cb_rules['interface'] = $inbound_interface;
							$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $inbound_interface, 'deny_in');
							$new_rules[] = $cb_rules;
						}
					}
				}
			}

			// Define outbound interface rules
			if (!empty($pfb['outbound_interfaces'])) {
				$pfbrunonce = TRUE;
				foreach ($pfb['outbound_interfaces'] as $outbound_interface) {
					if ($pfb['order'] == 'order_1' && !empty($permit_rules)) {
						foreach ($permit_rules as $cb_rules) {
							if ($cb_rules['interface'] == $outbound_interface) {
								$new_rules[] = $cb_rules;
							}
						}
					}
					if (!empty($pfb['permit_outbound'])) {
						foreach ($pfb['permit_outbound'] as $cb_rules) {
							$cb_rules['interface'] = $outbound_interface;
							$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $outbound_interface, 'permit_out');
							$new_rules[] = $cb_rules;
						}
					}
					// Match outbound rules defined as floating only.
					if ($pfbrunonce && !empty($pfb['match_outbound'])) {
						foreach ($pfb['match_outbound'] as $cb_rules) {
							$cb_rules['interface'] = $pfb['outbound_floating'];
							$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $outbound_interface, 'match_out');
							$new_rules[] = $cb_rules;
							$pfbrunonce = FALSE;
						}
					}
					if ($pfb['order'] == 'order_2' && !empty($permit_rules)) {
						foreach ($permit_rules as $cb_rules) {
							if ($cb_rules['interface'] == $outbound_interface) {
								$new_rules[] = $cb_rules;
							}
						}
					}
					if (!empty($pfb['deny_outbound'])) {
						foreach ($pfb['deny_outbound'] as $cb_rules) {
							$cb_rules['interface'] = $outbound_interface;
							$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $outbound_interface, 'deny_out');
							$new_rules[] = $cb_rules;
						}
					}
				}
			}

			if ($pfb['float'] == 'on' && in_array($pfb['order'], array('order_0', 'order_3', 'order_4'))) {
				if ($pfb['order'] != 'order_3') {
					$rule_order = array($fother_rules, $fpermit_rules, $fmatch_rules);
				} else {
					$rule_order = array($fpermit_rules, $fmatch_rules, $fother_rules);
				}
				foreach ($rule_order as $rtype) {
					if (!empty($rtype)) {
						foreach ($rtype as $cb_rules) {
							$new_rules[] = $cb_rules;
						}
					}
				}
			}
			if ($pfb['float'] == 'on' && in_array($pfb['order'], array('order_1', 'order_2')) && !empty($fother_rules)) {
				foreach ($fother_rules as $cb_rules) {
					$new_rules[] = $cb_rules;
				}
			}
			if ($pfb['float'] == '' && $pfb['order'] != 'order_1' && !empty($fother_rules)) {
				foreach ($fother_rules as $cb_rules) {
					$new_rules[] = $cb_rules;
				}
			}
			if ($pfb['order'] == 'order_4' && !empty($other_rules)) {
				foreach ($other_rules as $cb_rules) {
					$new_rules[] = $cb_rules;
				}
			}
			if ($pfb['order'] == 'order_4' && !empty($permit_rules)) {
				foreach ($permit_rules as $cb_rules) {
					$new_rules[] = $cb_rules;
				}
			}
			if ($pfb['order'] == 'order_3' && !empty($permit_rules)) {
				foreach ($permit_rules as $cb_rules) {
					$new_rules[] = $cb_rules;
				}
			}
			if ($pfb['order'] != 'order_4' && !empty($other_rules)) {
				foreach ($other_rules as $cb_rules) {
					$new_rules[] = $cb_rules;
				}
			}

			unset($pfb['permit_inbound'], $pfb['permit_outbound'], $pfb['deny_inbound'],
			    $pfb['deny_outbound'], $pfb['match_inbound'], $pfb['match_outbound']);
			unset($cb_rules, $other_rules, $fother_rules, $permit_rules, $fpermit_rules, $match_rules, $fmatch_rules);

			// Remove 'created' tag (New vs old rules array comparison)
			foreach ($new_rules as $rule) {
				if (isset($rule['created'])) {
					unset($rule['created']);
				}
				$new_rules_nocreated[] = $rule;
			}

			// Update config.xml, if changes required
			if ($orig_rules_nocreated != $new_rules_nocreated) {
				$config['filter']['rule'] = $new_rules;
				write_config('pfBlockerNG: saving Firewall rules');
			}
		}
		else {
			$log = "\n\n{$message}\n";
			pfb_logger("{$log}", 1);
		}
	}

	#################################
	#	pfSense Integration	#
	#################################

	// If 'Rule Changes' are found, utilize the 'filter_configure()' function, if not, utilize 'pfctl replace' command
	if ($pfb['autorules'] && $orig_rules_nocreated != $new_rules_nocreated || $pfb['enable'] == '' || $pfb['remove']) {

		if (!$pfb['save']) {
			$log = "\n===[  Aliastables / Rules  ]================================\n\n";
			pfb_logger("{$log}", 1);

			$log = "Firewall rule changes found, applying Filter Reload\n";
			syslog(LOG_NOTICE, "[pfBlockerNG] {$log}");
			pfb_logger("{$log}", 1);
		}

		// Remove all pfB aliastables
		exec("{$pfb['pfctl']} -s Tables | {$pfb['grep']} '^pfB_'", $pfb_tables);
		if (isset($pfb_tables)) {
			foreach ($pfb_tables as $pfb_table) {
				exec("{$pfb['pfctl']} -t {$pfb_table} -T kill 2>&1", $result);
			}
		}

		$pfb['filter_configure'] = TRUE;		// Set flag for filter_configure which will create the pfctl tables

		// Call function for Ramdisk processes.
		pfb_aliastables('update');
	}
	else {
		// Don't execute on user 'save'
		if (!$pfb['save']) {
			$log = "\n\n===[  Aliastables / Rules  ]==========================================\n\n";
			pfb_logger("{$log}", 1);

			$log = "No changes to Firewall rules, skipping Filter Reload\n";
			syslog(LOG_NOTICE, "[pfBlockerNG] {$log}");
			pfb_logger("{$log}", 1);

			// Remove Alerts IP unlock file and force Reload of all Aliastables
			if (file_exists("{$pfb['ip_unlock']}")) {
				unlink_if_exists("{$pfb['ip_unlock']}");
				$pfb['repcheck'] = TRUE;
			}

			// Only Save Aliases that have been updated.
			// When 'Reputation' is used, all aliases need to be updated when any alias has been updated.
			$final_alias = array();
			if ($pfb['repcheck'] && ($pfb['drep'] == 'on' || $pfb['prep'] == 'on')) {
				if (!empty($pfb_alias_lists_all)) {
					$final_alias = array_unique($pfb_alias_lists_all);
				}
			} else {
				if (!empty($pfb_alias_lists)) {
					$final_alias = array_unique($pfb_alias_lists);
				}
			}

			if (!empty($final_alias)) {
				foreach ($final_alias as $final) {
					$log = "\n Updating: {$final}\n";
					pfb_logger("{$log}", 1);
					$result = '';
					if (file_exists("{$pfb['aliasdir']}/{$final}.txt")) {
						exec("{$pfb['pfctl']} -t {$final} -T replace -f {$pfb['aliasdir']}/{$final}.txt 2>&1", $result);
						$log = implode($result);
					} else {
						$log = "Aliastable file not found\n";
					}
					pfb_logger("{$log}", 1);
				}
				pfb_logger("\n", 1);

				// Call function for Ramdisk processes.
				pfb_aliastables('update');
			} else {
				$log = "No Changes to Aliases, Skipping pfctl Update\n";
				pfb_logger("{$log}", 1);
			}
		}
	}
	unset($rules, $new_rules, $orig_rules_nocreated, $new_rules_nocreated);


	#################################
	#	SAVE CONFIGURATION	#
	#################################

	// Uncheck reusing existing downloads check box
	if (!$pfb['save'] && $pfb['enable'] == 'on' && $pfb['config']['pfb_reuse'] == 'on') {
		$pfb_config['installedpackages']['pfblockerng']['config'][0]['pfb_reuse'] = '';
		$pfb['conf_mod'] = TRUE;
	}

	// Only save config.xml, if changes are found.
	if ($pfb['conf_mod'] && isset($pfb_config)) {
		// Reload config.xml to get any recent changes and merge/save changes.
		$config = parse_config(true);
		$config = array_replace_recursive($config, $pfb_config);
		write_config('pfBlockerNG: save settings');
	}


	// Query NAT Rules for previous IPv4 pfBlockerNG aliasnames which are not in the new '_v4' suffix format
	$pfb_found	= FALSE;
	$config		= parse_config(true);

	if (is_array($config['nat']['rule'])) {
		$nat_config = &$config['nat']['rule'];

		foreach ($nat_config as $key => $ex_nat) {
			foreach (array('source', 'destination') as $rtype) {
				if (isset($ex_nat[$rtype]['address']) && substr($ex_nat[$rtype]['address'], 0, 4) == 'pfB_') {
					$pfb_suffix = substr($ex_nat[$rtype]['address'], -3);

					if ($pfb_suffix == '_v6') {
						continue;
					}

					// Add '_v4' suffix if missing
					elseif ($pfb_suffix != '_v4') {
						$nat_config[$key][$rtype]['address'] = "{$ex_nat[$rtype]['address']}_v4";
						$pfb_found = TRUE;
					}
				}
			}
		}
	}

	if ($pfb_found) {
		write_config("pfBlockerNG: update NAT rule(s) aliasnames to include '_v4' suffix");
	}

	#################################
	#  Call filter_configure once	#
	#################################

	$log = '';
	if ($pfb['filter_configure']) {

		// Remove any IPs in Alerts unlock feature
		unlink_if_exists("{$pfb['ip_unlock']}");

		require_once('filter.inc');
		filter_configure();

		// Stop/Restart pfb_filter service for new rule changes
		if ($pfb['enable'] == '') {
			if (is_service_running('pfb_filter')) {
				$log = 'Stopping firewall filter daemon';
				stop_service('pfb_filter');
			}
		}
		else {
			$log = 'Restarting firewall filter daemon';
			restart_service('pfb_filter');
		}
	}
	else {
		// Stop/Start Firewall filter daemon
		if ($pfb['enable'] == '' && is_service_running('pfb_filter')) {
			$log = 'Stopping firewall filter daemon';
			stop_service('pfb_filter');
		}
		elseif ($pfb['enable'] == 'on' && !is_service_running('pfb_filter')) {
			$log = 'Starting firewall filter daemon';
			start_service('pfb_filter');
		}
	}

	if (!empty($log)) {
		pfb_logger("\n\n** {$log} **\n", 1);
		syslog(LOG_NOTICE, "[pfBlockerNG] {$log}");
	}


	#################################
	#	KILL STATES		#
	#################################

	if (!$pfb['save'] && $pfb['kstates'] && !$pfb['filter_configure']) {
		pfb_remove_states();
	}


	#########################################
	#	XMLRPC - sync process		#
	#########################################

	if (!platform_booting() && !$g['pfblockerng_install'] && $pfb['conf_mod'] && isset($pfb_config)) {
		pfblockerng_sync_on_changes();
	}

	#########################################
	#	Define/Apply CRON Jobs		#
	#########################################

	// Replace CRON job with any user changes to $pfb_min
	if ($pfb['enable'] == 'on' && $pfb['interval'] != 'Disabled') {
		// Define pfBlockerNG CRON job
		$pfb_cmd = "/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php cron >> {$pfb['log']} 2>&1";
		// $pfb['min'] ( User defined variable. Variable defined at start of script )

		// Define CRON hour (CRON interval & start hour)
		if ($pfb['interval'] == 1) {
			$pfb_hour = '*';
		} elseif ($pfb['interval'] == 24) {
			$pfb_hour = $pfb['24hour'];
		} else {
			$pfb_hour = implode(',', pfb_cron_base_hour($pfb['interval']));
		}

		$pfb_mday	= '*';
		$pfb_month	= '*';
		$pfb_wday	= '*';
		$pfb_who	= 'root';

		// Determine if CRON job requires updating
		if (!pfblockerng_cron_exists($pfb_cmd, $pfb['min'], $pfb_hour, $pfb_mday, $pfb_wday)) {
			install_cron_job($pfb_cmd, true, $pfb['min'], $pfb_hour, $pfb_mday, $pfb_month, $pfb_wday, $pfb_who);
		}
	}
	else {
		// Clear any existing pfBlockerNG CRON jobs
		install_cron_job('pfblockerng.php cron', false);
	}

	if ($pfb['enable'] == 'on' && !empty($pfb['maxmind_key'])) {
		// Define pfBlockerNG MaxMind CRON job
		$pfb_gcmd = "/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php dcc >> {$pfb['extraslog']} 2>&1";
		// MaxMind GeoIP CRON hour is randomized between 0-23 Hour to minimize effect on MaxMind website
		$pfb_gmin	= '0';
		$pfb_ghour	= rand(0,23);
		$pfb_gmday	= '4-16';
		$pfb_gmonth	= '*';
		$pfb_gwday	= '*';
		$pfb_gwho	= 'root';

		// Determine if CRON job requires updating
		if (!pfblockerng_cron_exists($pfb_gcmd, $pfb_gmin, 'random', $pfb_gmday, $pfb_gwday)) {
			install_cron_job($pfb_gcmd, true, $pfb_gmin, $pfb_ghour, $pfb_gmday, $pfb_gmonth, $pfb_gwday, $pfb_gwho);
		}
	}
	else {
		// Clear any existing pfBlockerNG CRON jobs
		install_cron_job('pfblockerng.php dcc', false);
	}


	// Define pfBlockerNG Blacklist CRON job
	if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && $pfb['blconfig'] &&
	    $pfb['blconfig']['blacklist_enable'] != 'Disable' &&
	    $pfb['blconfig']['blacklist_freq'] != 'Never' &&
	    !empty($pfb['blconfig']['blacklist_selected']) &&
	    isset($pfb['blconfig']['item'])) {

		$bl_string = '';
		$selected = array_flip(explode(',', $pfb['blconfig']['blacklist_selected'])) ?: array();
		foreach ($pfb['blconfig']['item'] as $item) {
			if (isset($selected[$item['xml']]) && !empty($item['selected'])) {
				$bl_string .= ",{$item['xml']}";
			}
		}
		$bl_string = ltrim($bl_string, ',');

		if (!empty($bl_string)) {
			$pfb_bcmd = "/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php bl {$bl_string} >> {$pfb['extraslog']} 2>&1";

			$pfb_bmin	= '0';
			$pfb_bhour	= rand(0,23);
			$pfb_bmday	= '*';
			$pfb_bmonth	= '*';
			$pfb_bwday	= ($pfb['blconfig']['blacklist_freq'] == 'Weekly' ? '7' : '*');
			$pfb_bwho	= 'root';

			// Determine if CRON job requires updating
			if (!pfblockerng_cron_exists($pfb_bcmd, $pfb_bmin, 'random', $pfb_bmday, $pfb_bwday)) {
				install_cron_job('pfblockerng.php bl', false);
				install_cron_job($pfb_bcmd, true, $pfb_bmin, $pfb_bhour, $pfb_bmday, $pfb_bmonth, $pfb_bwday, $pfb_bwho);
			}
		}
	}
	else {
		// Clear any existing pfBlockerNG Blacklist CRON jobs
		install_cron_job('pfblockerng.php bl', false);
	}

	// Define pfBlockerNG clear [ dnsbl and/or IP ] counter CRON job
	if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && $pfb['config_global']) {
		foreach (array( 'clearip', 'cleardnsbl') as $wtype) {
			if (isset($pfb['config_global']['widget-' . $wtype]) &&
			    $pfb['config_global']['widget-' . $wtype] != 'never') {

				$pfb_cmd = "/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php {$wtype} >/dev/null 2>&1";

				$pfb_day = '*';
				if ($pfb['wglobal']['widget-' . $wtype] == 'weekly') {
					$pfb_day = '7';
				}

				if (!pfblockerng_cron_exists($pfb_cmd, '0', '0', '*', $pfb_day)) {
					install_cron_job("pfblockerng.php {$wtype}", false);
					install_cron_job($pfb_cmd, true, '0', '0', '*', '*', $pfb_day, 'root');
				}
			}
			else {
				install_cron_job("pfblockerng.php {$wtype}", false);
			}
		}
	}
	else {
		// Clear any existing pfBlockerNG counter CRON job
		install_cron_job("pfblockerng.php clearip", false);
		install_cron_job("pfblockerng.php cleardnsbl", false);
	}

	#################################
	#	FINAL REPORTING		#
	#################################

	// Only run with CRON or Force invoked process
	if ((!$pfb['save'] && $pfb['repcheck'] && $pfb['enable'] == 'on') || $pfb['summary']) {
		// Script to run final script processes.
		exec("{$pfb['script']} closing {$pfb['dup']} {$elog}");
	}

	if ($pfb['enable'] == 'on' && !$pfb['save'] || $pfb['summary']) {
		$log = "\n UPDATE PROCESS ENDED [ NOW ]\n";
		pfb_logger("{$log}", 1);
	}
}


// Function to De-Install pfBlockerNG
function pfblockerng_php_pre_deinstall_command() {
	require_once('config.inc');
	global $g, $config, $pfb;

	// Set these two variables to disable pfBlockerNG on de-install
	$pfb['save'] = $pfb['install'] = TRUE;

	update_status("Removing pfBlockerNG...");
	sync_package_pfblockerng();

	// Maintain pfBlockerNG settings and database files if $pfb['keep'] is ON.
	if ($pfb['keep'] != 'on') {
		update_status(" Removing all customizations/data...");
		// Remove pfBlockerNG log and DB folder
		rmdir_recursive("{$pfb['dbdir']}");
		rmdir_recursive("{$pfb['logdir']}");

		// Remove all pfB aliastables
		exec("{$pfb['pfctl']} -s Tables | {$pfb['grep']} '^pfB_'", $pfb_tables);
		if (isset($pfb_tables)) {
			foreach ($pfb_tables as $pfb_table) {
				exec("{$pfb['pfctl']} -t {$pfb_table} -T kill 2>&1", $result);
			}
		}

		// Remove aliastables archive and earlyshellcmd if found.
		@unlink_if_exists("{$pfb['aliasarchive']}");
		if (isset($config['system']['earlyshellcmd'])) {
			$a_earlyshellcmd = &$config['system']['earlyshellcmd'];
			if (preg_grep("/pfblockerng.sh aliastables/", $a_earlyshellcmd)) {
				$a_earlyshellcmd = preg_grep("/pfblockerng.sh aliastables/", $a_earlyshellcmd, PREG_GREP_INVERT);
			}
		}
		if (is_array($config['installedpackages']['shellcmdsettings']['config'])) {
			foreach ($config['installedpackages']['shellcmdsettings']['config'] as $key => $shellcmd) {
				if (strpos($shellcmd['cmd'], 'pfblockerng.sh aliastables') !== FALSE) {
					unset($config['installedpackages']['shellcmdsettings']['config'][$key]);
				}
			}
		}

		// Remove settings from config.xml
		pfb_remove_config_settings();

		unlink_if_exists("{$pfb['dnsbl_conf']}");
		unlink_if_exists("{$pfb['dnsbl_cert']}");
		unlink_if_exists("{$pfb['aliasarchive']}");
	}
	else {
		update_status(" All customizations/data will be retained...");
	}

	unlink_if_exists('/usr/local/sbin/lighttpd_pfb');
	unlink_if_exists('/usr/local/bin/php_pfb');
	unlink_if_exists('/usr/local/sbin/clog_pfb');
	unlink_if_exists('/usr/bin/tail_pfb');
	unlink_if_exists('/usr/local/etc/rc.d/pfb_filter.sh');
	unlink_if_exists('/usr/local/etc/rc.d/pfb_dnsbl.sh');

	unlink_if_exists("{$pfb['dnsbl_info']}");
	unlink_if_exists("{$pfb['dnsbl_resolver']}");
	unlink_if_exists("{$pfb['dnsbl_cache']}");
	unlink_if_exists("/var/tmp/unbound_cache");
	unlink_if_exists("{$pfb['ip_cache']}");

	unlink_if_exists("{$g['unbound_chroot_path']}/pfb_unbound.py");
	unlink_if_exists("{$g['unbound_chroot_path']}/pfb_unbound_include.inc");
	unlink_if_exists("{$g['unbound_chroot_path']}/pfb_py_hsts.txt");

	// Remove incorrect xml setting
	if (isset($config['installedpackages']['pfblockerngantartica'])) {
		unset($config['installedpackages']['pfblockerngantartica']);
	}

	// Remove widget (code from Snort deinstall)
	$pfb['widgets'] = $config['widgets']['sequence'];
	if (!empty($pfb['widgets'])) {
		$widgetlist = explode(',', $pfb['widgets']);
		foreach ($widgetlist as $key => $widget) {
			if (strpos($widget, 'pfblockerng') !== FALSE) {
				unset($widgetlist[$key]);
			}
		}
		$config['widgets']['sequence'] = implode(',', $widgetlist);
		write_config('pfBlockerNG: Remove widget');
	}

	update_status(" done.\n");
}


// Remove settings from config.xml
function pfb_remove_config_settings() {
	global $config, $pfb;

	foreach (array(	'pfblockerng',
			'pfblockerngglobal',
			'pfblockerngsync',
			'pfblockerngreputation',
			'pfblockerngipsettings',
			'pfblockernglistsv4',
			'pfblockernglistsv6',
			'pfblockerngdnsbl',
			'pfblockerngdnsblsettings',
			'pfblockerngsafesearch',
			'pfblockerngblacklist',
			'pfblockerngafrica',
			'pfblockerngantarctica',
			'pfblockerngasia',
			'pfblockerngeurope',
			'pfblockerngnorthamerica',
			'pfblockerngoceania',
			'pfblockerngsouthamerica',
			'pfblockerngtopspammers',
			'pfblockerngproxyandsatellite' ) as $type) {

		if (isset($config['installedpackages'][$type])) {
			unset($config['installedpackages'][$type]);
		}
	}
}


/* Uses XMLRPC to synchronize the changes to a remote node */
function pfblockerng_sync_on_changes() {
	global $config;

	// Create array of sync settings and exit if sync is disabled.
	if (isset($config['installedpackages']['pfblockerngsync']['config'][0])) {
		$pfb_sync = $config['installedpackages']['pfblockerngsync']['config'][0];
		if ($pfb_sync['varsynconchanges'] == 'disabled' || empty($pfb_sync['varsynconchanges'])) {
			return;
		}
		$synctimeout = $pfb_sync['varsynctimeout'] ?: 150;
	} else {
		return;
	}

	pfb_logger("\n===[  XMLRPC Sync ]===================================================\n", 1);
	syslog(LOG_NOTICE, '[pfBlockerNG] XMLRPC sync is starting.');

	if (isset($config['installedpackages']['pfblockerngsync']['config'])) {
		switch ($pfb_sync['varsynconchanges']) {
			case 'manual':
				if (isset($pfb_sync['row'])) {
					$rs = $pfb_sync['row'];
				} else {
					log_error('[pfBlockerNG] Manual XMLRPC sync is enabled but there are no replication targets configured.');
					return;
				}
				break;
			case 'auto':
				if (isset($config['hasync'])) {
					$system_carp			= $config['hasync'];
					$rs[0]['varsyncipaddress']	= $system_carp['synchronizetoip'];
					$rs[0]['varsyncusername']	= $system_carp['username'];
					$rs[0]['varsyncpassword']	= $system_carp['password'];
					$rs[0]['varsyncdestinenable']	= FALSE;

					// XMLRPC sync is currently only supported over connections using the same protocol and port as this system
					if ($config['system']['webgui']['protocol'] == 'http') {
						$rs[0]['varsyncprotocol']	= 'http';
						$rs[0]['varsyncport']		= $config['system']['webgui']['port'] ?: '80';
					} else {
						$rs[0]['varsyncprotocol']	= 'https';
						$rs[0]['varsyncport']		= $config['system']['webgui']['port'] ?: '443';
					}

					if (empty($system_carp['synchronizetoip'])) {
						log_error('[pfBlockerNG] Auto XMLRPC sync is enabled but there is no sync IP address configured.');
						return;
					} else {
						$rs[0]['varsyncdestinenable']	= TRUE;
					}
				} else {
					log_error('[pfBlockerNG] Auto XMLRPC sync is enabled but there are no replication targets configured.');
					return;
				}
				break;
			default:
				return;
				break;
		}
		if (isset($rs)) {
			foreach ($rs as $sh) {
				// Only sync enabled replication targets
				if ($sh['varsyncdestinenable']) {
					$sync_to_ip	= $sh['varsyncipaddress'];
					$port		= $sh['varsyncport'];
					$password	= $sh['varsyncpassword'];
					$protocol	= $sh['varsyncprotocol'];
					$username	= $sh['varsyncusername'] ?: 'admin';

					$validate = TRUE;
					$error = '| ';

					if (empty($password)) {
						$error .= 'Password parameter missing. | ';
						$validate = FALSE;
					}
					if (!is_ipaddr($sync_to_ip) && !is_hostname($sync_to_ip) && !is_domain($sync_to_ip)) {
						$error .= 'Mis-configured Target IP/Host address. | ';
						$validate = FALSE;
					}
					if (!is_port($port)) {
						$error .= 'Mis-configured Target Port setting. |';
						$validate = FALSE;
					}

					if ($validate) {
						pfb_logger("\n Sync with [ {$protocol}://{$sync_to_ip}:{$port} ] ...", 1);
						$success = pfblockerng_do_xmlrpc_sync($sync_to_ip, $port, $protocol, $username, $password, $synctimeout);

						if ($success) {
							pfb_logger(" done.\n", 1);
							syslog(LOG_NOTICE, "[pfBlockerNG] XMLRPC sync to [ {$sync_to_ip}:{port} ] completed successfully.");
						} else {
							pfb_logger(" Failed!\n", 1);
						}
					} else {
						pfb_logger(" terminated due to the following error(s): {$error}", 1);
						log_error("[pfBlockerNG] XMLRPC sync to [ {$sync_to_ip}:{port} ] terminated due to the following error(s): {$error}");
					}
				}
			}
		}
	}
	pfb_logger("\n======================================================================\n", 1);
}


/* Do the actual XMLRPC sync */
function pfblockerng_do_xmlrpc_sync($sync_to_ip, $port, $protocol, $username, $password, $synctimeout) {
	global $config, $g;
	$success = TRUE;

	// Take care of IPv6 literal address
	if (is_ipaddrv6($sync_to_ip)) {
		$sync_to_ip = "[{$sync_to_ip}]";
	}

	/* xml will hold the sections to sync */
	$xml = array();

	// If User Disabled, remove 'General/IP/DNSBL Tab Customizations' from Sync
	if ($config['installedpackages']['pfblockerngsync']['config'][0]['syncinterfaces'] != 'on') {
		if (isset($config['installedpackages']['pfblockerng'])) {
			$xml['pfblockerng']		= $config['installedpackages']['pfblockerng'];
		}
		if (isset($config['installedpackages']['pfblockerngipsettings'])) {
			$xml['pfblockerngipsettings']	= $config['installedpackages']['pfblockerngipsettings'];
		}
		if (isset($config['installedpackages']['pfblockerngdnsblsettings'])) {
			$xml['pfblockerngdnsblsettings']= $config['installedpackages']['pfblockerngdnsblsettings'];

			// Increase CARP Advskew value, see https://redmine.pfsense.org/issues/11964
			if (isset($xml['pfblockerngdnsblsettings']['config'][0]['pfb_dnsvip_skew'])) {
				$advskew = intval($xml['pfblockerngdnsblsettings']['config'][0]['pfb_dnsvip_skew']);
				$advskew += 100;
				if ($advskew > 254) {
					$advskew = 254;
				}
				$xml['pfblockerngdnsblsettings']['config'][0]['pfb_dnsvip_skew'] = $advskew;
			}
		}
	}

	if (isset($config['installedpackages']['pfblockernglistsv4']))
		$xml['pfblockernglistsv4']		= $config['installedpackages']['pfblockernglistsv4'];
	if (isset($config['installedpackages']['pfblockernglistsv6']))
		$xml['pfblockernglistsv6']		= $config['installedpackages']['pfblockernglistsv6'];
	if (isset($config['installedpackages']['pfblockerngreputation']))
		$xml['pfblockerngreputation']		= $config['installedpackages']['pfblockerngreputation'];
	if (isset($config['installedpackages']['pfblockerngtopspammers']))
		$xml['pfblockerngtopspammers']		= $config['installedpackages']['pfblockerngtopspammers'];
	if (isset($config['installedpackages']['pfblockerngafrica']))
		$xml['pfblockerngafrica']		= $config['installedpackages']['pfblockerngafrica'];
	if (isset($config['installedpackages']['pfblockerngantarctica']))
		$xml['pfblockerngantarctica']		= $config['installedpackages']['pfblockerngantarctica'];
	if (isset($config['installedpackages']['pfblockerngasia']))
		$xml['pfblockerngasia']			= $config['installedpackages']['pfblockerngasia'];
	if (isset($config['installedpackages']['pfblockerngeurope']))
		$xml['pfblockerngeurope']		= $config['installedpackages']['pfblockerngeurope'];
	if (isset($config['installedpackages']['pfblockerngnorthamerica']))
		$xml['pfblockerngnorthamerica']		= $config['installedpackages']['pfblockerngnorthamerica'];
	if (isset($config['installedpackages']['pfblockerngoceania']))
		$xml['pfblockerngoceania']		= $config['installedpackages']['pfblockerngoceania'];
	if (isset($config['installedpackages']['pfblockerngsouthamerica']))
		$xml['pfblockerngsouthamerica']		= $config['installedpackages']['pfblockerngsouthamerica'];
	if (isset($config['installedpackages']['pfblockerngproxyandsatellite']))
		$xml['pfblockerngproxyandsatellite']	= $config['installedpackages']['pfblockerngproxyandsatellite'];
	if (isset($config['installedpackages']['pfblockerngdnsbl']))
		$xml['pfblockerngdnsbl']		= $config['installedpackages']['pfblockerngdnsbl'];
	if (isset($config['installedpackages']['pfblockerngblacklist']))
		$xml['pfblockerngblacklist']		= $config['installedpackages']['pfblockerngblacklist'];
	if (isset($config['installedpackages']['pfblockerngglobal']))
		$xml['pfblockerngglobal']		= $config['installedpackages']['pfblockerngglobal'];
	if (isset($config['installedpackages']['pfblockerngsafesearch']))
		$xml['pfblockerngsafesearch']		= $config['installedpackages']['pfblockerngsafesearch'];


	// Execute applicable XMLRPC code as per pfSense version
	if (substr(trim(file_get_contents('/etc/version')), 0, 3) < '2.4') {

		require_once('xmlrpc.inc');
		require_once('xmlrpc_client.inc');

		$url = "{$protocol}://{$sync_to_ip}";

		/* assemble xmlrpc payload */
		$params = array(XML_RPC_encode($password), XML_RPC_encode($xml));

		/* set a few variables needed for sync code borrowed from filter.inc */
		$msg = new XML_RPC_Message('pfsense.merge_installedpackages_section_xmlrpc', $params);
		$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
		$cli->setCredentials($username, $password);
		if ($g['debug']) {
			$cli->setDebug(1);
		}

		/* send our XMLRPC message and timeout after defined sync timeout value */
		$resp = $cli->send($msg, $synctimeout);

		if (!$resp) {
			log_error("[pfBlockerNG] XMLRPC communications error occurred while attempting sync with {$url}:{$port}.");
			file_notice('pfBlockerNG Sync settings', $error, 'pfBlockerNG', '/pfblockerng/pfblockerng_sync.php', 2);
			$success = FALSE;
		} elseif ($resp->faultCode()) {
			$cli->setDebug(1);
			$resp = $cli->send($msg, $synctimeout);
			log_error("[pfBlockerNG] XMLRPC errors syncing with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString());
			file_notice('pfBlockerNG Sync settings', $error, 'pfBlockerNG', '/pfblockerng/pfblockerng_sync.php', 2);
			$success = FALSE;
		}
		return $success;
	}
	else {
		require_once('xmlrpc_client.inc');

		// xmlrpc cannot encode NULL objects/arrays
		foreach ($xml as $xmlkey => $xmlvalue) {
			if (gettype($xmlvalue) == 'NULL') {
				$xml[$xmlkey] = array();
			}
		}

		$synctimeout = intval($synctimeout);
		$rpc_client = new pfsense_xmlrpc_client();
		$rpc_client->setConnectionData($sync_to_ip, $port, $username, $password, $protocol);
		$resp = $rpc_client->xmlrpc_method('merge_installedpackages_section', $xml, $synctimeout);

		if (!isset($resp)) {
			return FALSE;
		} else {
			return TRUE;
		}
	}
}
?>