<?php
/*
	pfBlockerNG.inc

	pfBlockerNG
	Copyright (C) 2014 BBcan177@gmail.com
	All rights reserved.

	part of the Postfix package for pfSense
	Copyright (C) 2010 Erik Fonnesbeck
	Based upon pfBlocker by
	Copyright (C) 2011-2012 Marcello Coutinho
	All rights reserved.

	Redistribution and use in source and binary forms, with or without
	modification, are permitted provided that the following conditions are met:


	1. Redistributions of source code must retain the above copyright notice,
	   this list of conditions and the following disclaimer.

	2. Redistributions in binary form must reproduce the above copyright
	   notice, this list of conditions and the following disclaimer in the
	   documentation and/or other materials provided with the distribution.


	THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
	AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
	AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
	OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
	POSSIBILITY OF SUCH DAMAGE.

*/

//error_reporting(E_ALL);

require_once("util.inc");
require_once("functions.inc");
require_once("pkg-utils.inc");
require_once("pfsense-utils.inc");
require_once("globals.inc");
require_once("services.inc");

# [ $pfb ] pfBlockerNG Global Array for Paths and Variables. This needs to be called to get the Updated Settings.
function pfb_global() {

	global $g,$config,$pfb;

	# Folders
	$pfb['dbdir']		= "{$g['vardb_path']}/pfblockerng";
	$pfb['aliasdir']	= "{$g['vardb_path']}/aliastables";
	$pfb['logdir']		= "{$g['varlog_path']}/pfblockerng";
	$pfb['etdir']		= "{$pfb['dbdir']}/ET";
	$pfb['ccdir']		= "{$pfb['dbdir']}/cc";
	$pfb['nativedir']	= "{$pfb['dbdir']}/native";
	$pfb['denydir']		= "{$pfb['dbdir']}/deny";
	$pfb['matchdir']	= "{$pfb['dbdir']}/match";
	$pfb['permitdir']	= "{$pfb['dbdir']}/permit";
	$pfb['origdir']		= "{$pfb['dbdir']}/original";

	# Create Folders if not Exist.
	$folder_array = array ("{$pfb['dbdir']}","{$pfb['logdir']}","{$pfb['ccdir']}","{$pfb['origdir']}","{$pfb['nativedir']}","{$pfb['denydir']}","{$pfb['matchdir']}","{$pfb['permitdir']}","{$pfb['aliasdir']}");
	foreach ($folder_array as $folder) {
		safe_mkdir ("{$folder}",0755);
	}

	# Files
	$pfb['master']	= "{$pfb['dbdir']}/masterfile";
	$pfb['errlog']	= "{$pfb['logdir']}/error.log";
	$pfb['geolog']	= "{$pfb['logdir']}/geoip.log";
	$pfb['log']	= "{$pfb['logdir']}/pfblockerng.log";
	$pfb['supptxt']	= "{$pfb['dbdir']}/pfbsuppression.txt";
	$pfb['script']	= 'sh /usr/local/pkg/pfblockerng/pfblockerng.sh';

	# Collect pfSense Version
	$pfb['pfsenseversion'] = substr(trim(file_get_contents("/etc/version")),0,3);

	# General Variables
	$pfb['config']	= $config['installedpackages']['pfblockerng']['config'][0];

	# Enable/Disable of pfBlockerNG
	$pfb['enable']	= $pfb['config']['enable_cb'];
	# Keep Blocklists on pfBlockerNG Disable
	$pfb['keep']	= $pfb['config']['pfb_keep'];
	# Enable Suppression
	$pfb['supp']	= $pfb['config']['suppression'];
	# Max Lines in pfblockerng.log file
	$pfb['logmax']	= $pfb['config']['log_maxlines'];
	$pfb['iplocal']	= $config['interfaces']['lan']['ipaddr'];
	# Disable Country Database CRON Updates
	$pfb['cc']	= $pfb['config']['database_cc'];

	# Set pfBlockerNG to Disabled on 'Re-Install'
	if (isset($pfb['install']) && $pfb['install']) {
		$pfb['enable'] = "";
		$pfb['install'] = FALSE;
	}
}

pfb_global();

# Set Max PHP Memory Setting
$uname = posix_uname();
if ($uname['machine'] == 'amd64')
	ini_set('memory_limit', '256M');


# Function to decode to Alias Custom Entry Box.
function pfbng_text_area_decode($text) {
	return preg_replace('/\r\n/', "\n",base64_decode($text));
}


# Manage Log File Line Limit
function pfb_log_mgmt() {
	global $pfb;
	pfb_global();

	if ($pfb['logmax'] == "nolimit") {
		# Skip Log Mgmt
	} else {
		exec("/usr/bin/tail -n {$pfb['logmax']} {$pfb['log']} > /tmp/pfblog; /bin/mv -f /tmp/pfblog {$pfb['log']}");
	}
}


# Record Log Messsages to pfBlockerNG Log File and/or Error Log File.
function pfb_logger($log, $type) {
	global $g,$pfb,$pfbarr;

	$now = date("m/d/y G:i:s", time());

	# Only log timestamp if new
	if (preg_match("/NOW/", $log)) {
		if ($now == $pfb['pnow']) {
			$log = str_replace("[ NOW ]", "", "{$log}");
		} else {
			$log = str_replace("NOW", $now, "{$log}");
		}
		$pfb['pnow'] = "{$now}";
	}

	if ($type == 2) {
		@file_put_contents("{$pfb['log']}", "{$log}", FILE_APPEND);
		@file_put_contents("{$pfb['errlog']}", "{$log}", FILE_APPEND);
	} elseif ($type == 3) {
		@file_put_contents("{$pfb['geolog']}", "{$log}", FILE_APPEND);
	} else {
		@file_put_contents("{$pfb['log']}", "{$log}", FILE_APPEND);
	}
}


# Determine Folder Location for 'List'
function pfb_determine_list_detail($list) {
	global $g,$pfb,$pfbarr;
	$pfbarr = array();

	if (in_array($list,array('Match_Both','Match_Inbound','Match_Outbound','Alias_Match'))) {
		$pfbarr['skip']		= FALSE;
		$pfbarr['folder']	= "{$pfb['matchdir']}";
	} elseif (in_array($list,array('Permit_Both','Permit_Inbound','Permit_Outbound','Alias_Permit'))) {
		$pfbarr['skip']		= FALSE;
		$pfbarr['folder']	= "{$pfb['permitdir']}";
	} elseif ($list == "Alias_Native") {
		$pfbarr['skip']		= FALSE;
		$pfbarr['folder']	= "{$pfb['nativedir']}";
	} else {
		# Deny
		$pfbarr['skip']		= TRUE;
		$pfbarr['folder']	= "{$pfb['denydir']}";
	}

	// Collect proper Alias Table Description (Alias Only vs AutoRules)
	if (preg_match("/Alias/", $list)) {
		$pfbarr['descr'] = "";
	} else {
		$pfbarr['descr'] = " Auto ";
	}

	return $pfbarr;
}

# Create Suppression Alias
function pfb_create_suppression_alias() {
	global $config;

	// Collect existing pfsense alias(s)
	if (is_array($config['aliases']['alias'])) {
		foreach($config['aliases']['alias'] as $exalias) {
			$new_aliases[] = $exalias;
		}
	}
	// Create New pfBlockerNGSuppress Alias
	$new_aliases[] = array( "name"		=> "pfBlockerNGSuppress",
				"address"	=> "",
				"descr"		=> "pfBlockerNG Suppression List (24|32 CIDR only)",
				"type"		=> "network",
				"detail"	=> ""
				);
	$config['aliases']['alias'] = $new_aliases;
	write_config();
}


# Create Suppression file from Alias
function pfb_create_suppression_file() {
	global $config,$pfb;

	// Find pfBlockerNGSuppress Array ID Number
	$pfb['found'] = FALSE;
	if (is_array($config['aliases']['alias'])) {
		$pfb_id = 0;
		foreach ($config['aliases']['alias'] as $alias) {
			if ($alias['name'] == "pfBlockerNGSuppress") {
				$pfb['found'] = TRUE;
				break;
			}
			$pfb_id++;
		}

		if ($pfb['found']) {
			$pfb_suppress = str_replace(" ", "\n", $config['aliases']['alias'][$pfb_id]['address']);
			if (!empty($pfb_suppress))
				@file_put_contents("{$pfb['supptxt']}",$pfb_suppress, LOCK_EX);
		} else {
			# Delete Suppression File if Alias is Empty.
			unlink_if_exists("{$pfb['supptxt']}");
		}
	}

	// Call Function to Create Suppression Alias.
	if (!$pfb['found'])
		pfb_create_suppression_alias();
}


# Main pfBlockerNG Function
function sync_package_pfblockerng($cron = "") {

	global $g,$config,$pfb,$pfbarr;
	pfb_global();

	# Detect Boot Process or Update via CRON
	if (isset($_POST) && $cron == "") {
		if (!preg_match("/\w+/",$_POST['__csrf_magic'])) {
			log_error("[pfBlockerNG] Sync terminated during boot process.");
			return;
		}
	}
	log_error("[pfBlockerNG] Starting sync process.");

	# Start of pfBlockerNG Logging to 'pfblockerng.log'
	if ($pfb['enable'] == "on" && !$pfb['save']) {
		$log = " UPDATE PROCESS START [ NOW ]\n";
	} else {
		$log = "\n**Saving Configuration [ NOW ] ...\n";
	}
	pfb_logger("{$log}","1");

	# TBC if Required ! (Fetch Timeout in 2.2)

	#apply fetch timeout to pfsense-utils.inc
	$pfsense_utils = file_get_contents('/etc/inc/pfsense-utils.inc');
	$new_pfsense_utils = preg_replace("/\/usr\/bin\/fetch -q/","/usr/bin/fetch -T 5 -q",$pfsense_utils);
	if ($new_pfsense_utils != $pfsense_utils) {
		@file_put_contents('/etc/inc/pfsense-utils.inc',$new_pfsense_utils, LOCK_EX);
	}

	# Collect pfSense Max Table Size Entry
	$pfb['table_limit'] = ($config['system']['maximumtableentries'] != "" ? $config['system']['maximumtableentries'] : "2000000");

	# If Table limit not defined, set Default to 2M
	$config['system']['maximumtableentries'] = "{$pfb['table_limit']}";

	# Collect local web gui configuration
	$pfb['weblocal'] = ($config['system']['webgui']['protocol'] != "" ? $config['system']['webgui']['protocol'] : "http");
	$pfb['port'] = $config['system']['webgui']['port'];
	if ($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['config']['inbound_deny_action'] != "" ? $pfb['config']['inbound_deny_action'] : "block");
	$pfb['deny_action_outbound'] = ($pfb['config']['outbound_deny_action'] != "" ? $pfb['config']['outbound_deny_action'] : "reject");

	# Validation check to see if the Original pfBlocker package is Enabled
	$pfb['validate']= $pfb['config']['pfblocker_cb'];
	# User Defined CRON Start Minute
	$pfb['min']	= $pfb['config']['pfb_min'];
	# Reloads Existing Blocklists without Downloading New Lists
	$pfb['reuse']	= $pfb['config']['pfb_reuse'];
	# Enable OpenVPN AutoRules
	$pfb['openvpn']	= $pfb['config']['openvpn_action'];
	# Enable/Disable Floating Auto-Rules
	$pfb['float']	= $pfb['config']['enable_float'];
	# Enable Remove of Duplicate IPs utilizing Grepcidr
	$pfb['dup']	= $pfb['config']['enable_dup'];
	# Order of the Auto-Rules
	$pfb['order']	= $pfb['config']['pass_order'];
	# Suffix used for Auto-Rules
	$pfb['suffix']	= $pfb['config']['autorule_suffix'];

	# Reputation Variables
	$pfb['config_rep'] = $config['installedpackages']['pfblockerngreputation']['config'][0];

	# Enable/Disable Reputation
	$pfb['rep']	= $pfb['config_rep']['enable_rep'];
	# Enable/Disable 'pDup'
	$pfb['pdup']	= $pfb['config_rep']['enable_pdup'];
	# Enable/Disable 'dDup'
	$pfb['dedup']	= ($pfb['config_rep']['enable_dedup'] != "" ? $pfb['config_rep']['enable_dedup'] : "x");
	# 'Max' variable setting for Reputation
	$pfb['max']	= ($pfb['config_rep']['p24_max_var'] != "" ? $pfb['config_rep']['p24_max_var'] : "x");
	# 'dMax' variable setting for Reputation
	$pfb['dmax']	= ($pfb['config_rep']['p24_dmax_var'] != "" ? $pfb['config_rep']['p24_dmax_var'] : "x");
	# 'pMax' variable setting for Reputation
	$pfb['pmax']	= ($pfb['config_rep']['p24_pmax_var'] != "" ? $pfb['config_rep']['p24_pmax_var'] : "x");
	# Action for Whitelist Country Category
	$pfb['ccwhite']	= $pfb['config_rep']['ccwhite'];
	# Action for Blacklist Country Category
	$pfb['ccblack']	= $pfb['config_rep']['ccblack'];
	# List of Countries in the Whitelist Category
	$pfb['ccexclude']= ($pfb['config_rep']['ccexclude'] != "" ? $pfb['config_rep']['ccexclude'] : "x");
	# Emerging Threats IQRisk Block Categories
	$pfb['etblock']	= ($pfb['config_rep']['etblock'] != "" ? $pfb['config_rep']['etblock'] : "x");
	# Emerging Threats IQRisk Match Categories
	$pfb['etmatch']	= ($pfb['config_rep']['etmatch'] != "" ? $pfb['config_rep']['etmatch'] : "x");
	# Perform a Force Update on ET Categories
	$pfb['etupdate']= $pfb['config_rep']['et_update'];

	# Variables

	# Starting Variable to Skip rep, pdup and dedeup functions if no changes are required
	$pfb['dupcheck'] = FALSE;
	## $pfb['save'] is used to determine if User pressed "Save" Button to avoid Collision with CRON.
	## This is defined in each pfBlockerNG XML Files

	# Validation Check to ensure pfBlocker and pfBlockerNG are not running at the same time.
	if ($pfb['validate'] == "") {
		# Collect pfBlocker Enabled Status from config file
		$pfb['validate_chk'] = $config['installedpackages']['pfblocker']['config'][0]['enable_cb'];
		if ($pfb['validate_chk'] == "on") {
			$log = "\n The Package 'pfBlocker' is currently Enabled. Either Disable pfBlocker, or 'Disable Validation Check' in pfBlockerNG \n";
			pfb_logger("{$log}","1");
			return;
		}
	}


	#############################################
	#            Configure ARRAYS               #
	#############################################

	$continents = array (	"Africa"	=> "pfB_Africa",
				"Antartica"	=> "pfB_Antartica",
				"Asia"		=> "pfB_Asia",
				"Europe"	=> "pfB_Europe",
				"North America"	=> "pfB_NAmerica",
				"Oceania"	=> "pfB_Oceania",
				"South America"	=> "pfB_SAmerica",
				"Top Spammers"	=> "pfB_Top"
				);

	#create rules vars and arrays
	# Array used to Collect Changes to Aliases to be saved to Config
	$new_aliases		= array();
	$new_aliases_list	= array();
	$continent_existing	= array();
	$continent_new		= array();
	$permit_inbound		= array();
	$permit_outbound	= array();
	$deny_inbound		= array();
	$deny_outbound		= array();
	# An Array of all Aliases (Active and non-Active)
	$aliases_list		= array();
	# This is an Array of Aliases that Have Updated Lists via CRON/Force Update when 'Reputation' disabled.
	$pfb_alias_lists	= array();
	# This is an Array of All Active Aliases used when 'Reputation' enabled
	$pfb_alias_lists_all = array();

	# Base Rule Array
	$base_rule_reg = array( "id"		=> "",
				"tag"		=> "",
				"tagged"	=> "",
				"max"		=> "",
				"max-src-nodes"	=> "",
				"max-src-conn"	=> "",
				"max-src-states"=> "",
				"statetimeout"	=> "",
				"statetype"	=> "keep state",
				"os"		=> ""
				);

	# Floating Rules, Base Rule Array
	$base_rule_float = array("id"		=> "",
				"tag"		=> "",
				"tagged"	=> "",
				"quick"		=> "yes",
				"floating"	=> "yes",
				"max"		=> "",
				"max-src-nodes"	=> "",
				"max-src-conn"	=> "",
				"max-src-states"=> "",
				"statetimeout"	=> "",
				"statetype"	=> "keep state",
				"os"		=> ""
				);


	#############################################
	#          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;
	$pfb['found'] = FALSE;
	foreach ($continents as $continent => $pfb_alias) {
		if (is_array($config['installedpackages']['pfblockerng' . strtolower(preg_replace('/ /','',$continent))]['config'])) {
			$continent_config = $config['installedpackages']['pfblockerng' . strtolower(preg_replace('/ /','',$continent))]['config'][0];
			if ($continent_config['action'] != "Disabled" && in_array($continent_config['action'],array('Deny_Both','Deny_Inbound','Deny_Outbound','Match_Both','Match_Inbound','Match_Outbound','Permit_Both','Permit_Inbound','Permit_Outbound'))) {
				$pfb['autorules'] = TRUE;
				$pfb['found'] = TRUE;
				break;
			}
		}
	}

	$list_type = array ("pfblockernglistsv4", "pfblockernglistsv6");
	foreach ($list_type as $ip_type) {
		if ($config['installedpackages'][$ip_type]['config'] != "" && !$pfb['found']) {
			foreach($config['installedpackages'][$ip_type]['config'] as $list) {
				if ($list['action'] != "Disabled" && in_array($list['action'],array('Deny_Both','Deny_Inbound','Deny_Outbound','Match_Both','Match_Inbound','Match_Outbound','Permit_Both','Permit_Inbound','Permit_Outbound'))) {
					$pfb['autorules'] = TRUE;
					break;
				}
			}
		}
	}

	#Configure Auto Rule Suffix. pfBlockerNG must be disabled to change Suffix and to avoid Duplicate Rules
	# Count Number of Rules with 'pfB_'
	$count = 0;
	if (is_array($config['filter']['rule'])) {
		foreach ($config['filter']['rule'] as $rule) {
			# Collect any pre-existing Suffix
			if (preg_match("/pfB_\w+(\s.*)/",$rule['descr'], $pfb_suffix_real) && $count == 0) {
				$pfb_suffix_match = $pfb_suffix_real[1];
			}
			# Query for Existing pfB Rules
			if (preg_match("/pfB_/",$rule['descr'])) {
				$count++;
				break;
			}
		}
	}

	# Change Suffix only if No pfB Rules Found and Auto Rules are Enabled.
	if ($pfb['autorules'] && $count == 0) {
		switch ($pfb['suffix']) {
			case "autorule":
				$pfb['suffix'] = " auto rule";
				break;
			case "standard":
				$pfb['suffix'] = "";
				break;
			case "ar":
				$pfb['suffix'] = " AR";
				break;
		}
	} else {
		if ($pfb['autorules']) {
			# Use existing Suffix Match
			$pfb['suffix'] = $pfb_suffix_match;
		} else {
			# Leave Rule Suffix 'Blank'
			$pfb['suffix'] = "";
		}
	}


	#############################################
	#   Configure INBOUND/OUTBOUND INTERFACES   #
	#############################################

	# Collect pfSense Interface Order
	$ifaces = get_configured_interface_list();

	if (!empty($pfb['config']['inbound_interface'])) {
		# Sort Interface Array to match pfSense Interface order to allow Floating Rules to populate.
		$selected_interfaces = explode(",",$pfb['config']['inbound_interface']);
		# Sort pfBlockerNG Interface order to pfSense Interface Order
		$sort_interfaces = array_intersect($ifaces, $selected_interfaces);
		$implode_interfaces = ltrim(implode(",",$sort_interfaces), ",");
		# CSV String for Inbound Interfaces for 'pfB_' Match Rules 
		$pfb['inbound_floating'] = $implode_interfaces;
		$pfb['inbound_interfaces_float'] = explode(" ",$implode_interfaces);

		# Assign Inbound Base Rule/Interfaces
		if ($pfb['float'] == "on") {
			# Define Base Firewall Floating Rules Settings
			$base_rule = $base_rule_float;
			$pfb['inbound_interfaces'] = $pfb['inbound_interfaces_float'];
		} else {
			# Define Base Firewall Rules Settings
			$base_rule = $base_rule_reg;
			$pfb['inbound_interfaces'] = explode(",",$pfb['config']['inbound_interface']);
		}
	} else {
		# Define Empty Variable/Array
		$pfb['inbound_interfaces_float'] = "";
		$pfb['inbound_interfaces'] = array();
	}

	if (!empty($pfb['config']['outbound_interface'])) {
		# Sort Interface Array to match pfSense Interface order to allow Floating Rules to populate.
		$selected_interfaces = explode(",",$pfb['config']['outbound_interface']);
		# Sort pfBlockerNG Interface order to pfSense Interface Order
		$sort_interfaces = array_intersect($ifaces, $selected_interfaces);
		// If OpenVPN Interfaces are not in dropdown menu
		if ($pfb['openvpn'] == "on" && $config['openvpn']['openvpn-server'] || $pfb['openvpn'] == "on" && $config['openvpn']['openvpn-client'])
			if (!in_array("openvpn",$sort_interfaces))
				array_push($sort_interfaces, "openvpn");
		$implode_interfaces = ltrim(implode(",",$sort_interfaces), ",");
		# CSV String for Outbound Interfaces for 'pfB_' Match Rules
		$pfb['outbound_floating'] = $implode_interfaces;
		$pfb['outbound_interfaces_float'] = explode(" ",$implode_interfaces);

		# Assign Outbound Base Rule/Interfaces
		if ($pfb['float'] == "on") {
			$base_rule = $base_rule_float;
			$pfb['outbound_interfaces'] = $pfb['outbound_interfaces_float'];
		} else {
			$base_rule = $base_rule_reg;
			$pfb['outbound_interfaces'] = explode(",",$pfb['config']['outbound_interface']);
			// If OpenVPN Interfaces are not in dropdown menu
			if ($pfb['openvpn'] == "on" && $config['openvpn']['openvpn-server'] || $pfb['openvpn'] == "on" && $config['openvpn']['openvpn-client'])
				if (!in_array("openvpn",$sort_interfaces))
					array_push($pfb['outbound_interfaces'], "openvpn");
		}
	} else {
		# Define Empty Variable/Array
		$pfb['outbound_interfaces_float'] = "";
		$pfb['outbound_interfaces'] = array();
	}


	#############################################
	#   Clear Removed Lists from Masterfiles    #
	#############################################

	# Process to keep Masterfiles in Sync with Valid Lists from config.conf file.
	$pfb['sync_master'] = TRUE;

	# 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']) {
		$pfb['existing']['match']['type']	= "match";
		$pfb['existing']['permit']['type']	= "permit";
		$pfb['existing']['deny']['type']	= "deny";
		$pfb['existing']['native']['type']	= "native";
		$pfb['existing']['match']['folder']	= "{$pfb['matchdir']}";
		$pfb['existing']['permit']['folder']	= "{$pfb['permitdir']}";
		$pfb['existing']['deny']['folder']	= "{$pfb['denydir']}";
		$pfb['existing']['native']['folder']	= "{$pfb['nativedir']}";
		$pfb['actual']['match']['type']		= "match";
		$pfb['actual']['permit']['type']	= "permit";
		$pfb['actual']['deny']['type']		= "deny";
		$pfb['actual']['native']['type']	= "native";
		$pfb['actual']['match']['folder']	= "{$pfb['matchdir']}";
		$pfb['actual']['permit']['folder']	= "{$pfb['permitdir']}";
		$pfb['actual']['deny']['folder']	= "{$pfb['denydir']}";
		$pfb['actual']['native']['folder']	= "{$pfb['nativedir']}";

		// Find all Enabled Continents Lists
		foreach ($continents as $continent => $pfb_alias) {
			if (is_array($config['installedpackages']['pfblockerng' . strtolower(preg_replace('/ /','',$continent))]['config']) && $pfb['enable'] == "on") {
				$continent_config = $config['installedpackages']['pfblockerng' . strtolower(preg_replace('/ /','',$continent))]['config'][0];
				if ($continent_config['action'] != "Disabled") {
					$cont_type = array ("countries4" => "_v4", "countries6" => "_v6");
					foreach ($cont_type as $c_type => $vtype) {
						if ($continent_config[$c_type] != "") {
							# Set Parameters for 'Match', 'Permit', 'Native' and 'Deny'
							if (in_array($continent_config['action'],array('Match_Both','Match_Inbound','Match_Outbound','Alias_Match'))) {
								$pfb['existing']['match'][] = "{$pfb_alias}{$vtype}";
							} elseif (in_array($continent_config['action'],array('Permit_Both','Permit_Inbound','Permit_Outbound','Alias_Permit'))){
								$pfb['existing']['permit'][] = "{$pfb_alias}{$vtype}";
							} elseif ($continent_config['action'] == "Alias_Native") {
								$pfb['existing']['native'][] = "{$pfb_alias}{$vtype}";
							} else {
								$pfb['existing']['deny'][] = "{$pfb_alias}{$vtype},";	// Add Trailing ','
							}
						}
					}
				}
			}
		}

		# Find all Enabled IPv4/IPv6 Lists
		$list_type = array ("pfblockernglistsv4" => "_v4", "pfblockernglistsv6" => "_v6");
		foreach ($list_type as $ip_type => $vtype) {
			if ($config['installedpackages'][$ip_type]['config'] != "" && $pfb['enable'] == "on") {
				foreach ($config['installedpackages'][$ip_type]['config'] as $list) {
					if (is_array($list['row']) && $list['action'] != "Disabled") {
						foreach ($list['row'] as $row) {
							if ($vtype == "_v4") {
								$pfb_alias = "{$row['header']}";
							} else {
								$pfb_alias = "{$row['header']}_v6";
							}
							# Collect Enabled Lists
							if ($row['url'] != "" && $row['state'] != "Disabled") {
								# Set Parameters for 'Match', 'Permit', 'Native' and 'Deny'
								if (in_array($list['action'],array('Match_Both','Match_Inbound','Match_Outbound','Alias_Match'))) {
									$pfb['existing']['match'][] = "{$pfb_alias}";
								} elseif (in_array($list['action'],array('Permit_Both','Permit_Inbound','Permit_Outbound','Alias_Permit'))) {
									$pfb['existing']['permit'][] = "{$pfb_alias}";
								} elseif ($list['action'] == "Alias_Native") {
									$pfb['existing']['native'][] = "{$pfb_alias}";
								} else {
									$pfb['existing']['deny'][] = "{$pfb_alias},";	// Add Trailing ','
								}
							}
						}
					}
				}
			}
		}

		# Find all Enabled IPv4 'Custom List' Header Names and Check if 'Emerging Threats Update' and 'Custom List Update' Needs Force Updating
		$list_type = array ("pfblockernglistsv4" => "_v4", "pfblockernglistsv6" => "_v6");
		foreach ($list_type as $ip_type => $vtype) {
			if ($config['installedpackages'][$ip_type]['config'] != "" && $pfb['enable'] == "on") {
				$count = -1;
				foreach ($config['installedpackages'][$ip_type]['config'] as $list) {
					if (is_array($list['row']) && $list['action'] != "Disabled") {
						$count++;
						# Check if 'Emerging Threats Update' Needs Updating before next CRON Event.
						if (is_array($list['row']) && $row['state'] != "Disabled" && $pfb['etupdate'] == "enabled" && $vtype == "_v4") {
							foreach ($list['row'] as $row) {
								$aliasname = $row['header'];
								if ($row['format'] == "et") {
									unlink_if_exists("{$pfb['denydir']}/{$aliasname}.txt");
									$config['installedpackages']['pfblockerngreputation']['config'][0]['et_update'] = "disabled";
									break;
								}
							}
						}
					}

					# Collect Enabled Custom List Box Aliases
					if (pfbng_text_area_decode($list['custom']) != "") {
						if ($vtype == "_v4") {
							$pfb_alias = "{$list['aliasname']}_custom";
						} else {
							$pfb_alias = "{$list['aliasname']}_custom_v6";
						}
						# Determine Folder Location for 'List'
						if (in_array($list['action'],array('Match_Both','Match_Inbound','Match_Outbound','Alias_Match'))) {
							$pfb['existing']['match'][] = "{$pfb_alias}";
							$pfbfolder = "{$pfb['matchdir']}";
						} elseif (in_array($list['action'],array('Permit_Both','Permit_Inbound','Permit_Outbound','Alias_Permit'))) {
							$pfb['existing']['permit'][] = "{$pfb_alias}";
							$pfbfolder = "{$pfb['permitdir']}";
						} elseif ($list['action'] == "Alias_Native") {
							$pfb['existing']['native'][] = "{$pfb_alias}";
							$pfbfolder = "{$pfb['nativedir']}";
						} else {
							$pfb['existing']['deny'][] = "{$pfb_alias},";	// Add Trailing ','
							$pfbfolder = "{$pfb['denydir']}";
						}
						# Determine if 'Custom List' Needs Force Updating before next CRON Event.
						if ($list['custom_update'] == "enabled") {
							unlink_if_exists("{$pfbfolder}/{$pfb_alias}.txt");
							# Uncheck 'Enabled' in List 'Custom_update' Setting
							$config['installedpackages'][$ip_type]['config'][$count]['custom_update'] = "disabled";
						}
					}
				}
			}
		}

		# Collect all .txt file Names for each List Type
		$list_types = array('match' => $pfb['matchdir'], 'permit' => $pfb['permitdir'], 'deny' => $pfb['denydir'], 'native' => $pfb['nativedir']);
		foreach ($list_types as $type => $pfbfolder) {
			$pfb_files = glob("$pfbfolder/*.txt");
			foreach ($pfb_files as $pfb_list) {
				$pfb_file = basename($pfb_list,".txt");
				if ($type == "deny") {
					$pfb['actual'][$type][] = "{$pfb_file},";	// Add Trailing ','
				} else {
					$pfb['actual'][$type][] = "{$pfb_file}";
				}
			}
		}

		# Flag to execute pfctl and Rules Ordering
		$pfb['remove'] = FALSE;
		# Execute Final Summary as a List was Removed
		$pfb['summary'] = FALSE;

		# Process to Remove Lists from Masterfile/DB Folder if they do not Exist
		if (isset($pfb['existing'])) {
			foreach ($pfb['existing'] as $pfb_exist) {
				$existing_type = $pfb_exist['type'];
				$pfbfolder = $pfb_exist['folder'];
				foreach ($pfb['actual'] as $pfb_act) {
					$actual_type = $pfb_act['type'];
					if ($existing_type == $actual_type) {
						switch ($existing_type) {
							case "deny":
								$results = array_diff($pfb_act, $pfb_exist);
								$f_result = implode($results);
								if ($f_result != "") {
									$log = "[ Removing List(s) : {$f_result} ]\n";
									pfb_logger("{$log}","1");
									# Script to Remove un-associated Lists
									exec ("{$pfb['script']} remove x x x {$f_result} >> {$pfb['log']} 2>&1");
									$pfb['summary'] = TRUE;
									$pfb['remove'] = TRUE;
								}
								break;
							case "match":
							case "permit":
							case "native":
								$results = array_diff($pfb_act, $pfb_exist);
								# This variable ($f_result) used in next section below.
								$f_result = implode($results);
								if (!empty($results)) {
									foreach ($results as $pfb_results) {
										$log = "[ Removing List(s) : {$pfb_results} ]\n";
										pfb_logger("{$log}","1");
										unlink_if_exists("{$pfbfolder}/{$pfb_results}.txt");
									}
									$pfb['summary'] = TRUE;
									$pfb['remove'] = TRUE;
								}
								break;
						}

						# Allow Rebuilding of Changed Aliase to purge 'SKIP' Lists (when pfBlockerNG is Enabled)
						$list_type = array ("pfblockernglistsv4" => "_v4", "pfblockernglistsv6" => "_v6");
						foreach ($list_type as $ip_type => $vtype) {
							if ($f_result != "" && $pfb['enable'] == "on") {
								foreach ($results as $removed_header) {
									if ($config['installedpackages'][$ip_type]['config'] != "" && $pfb['enable'] == "on") {
										foreach ($config['installedpackages'][$ip_type]['config'] as $list) {
											$alias = "pfB_" . preg_replace("/\W/","",$list['aliasname']);
											if (is_array($list['row'])) {
												foreach ($list['row'] as $row) {
													$removed = rtrim($removed_header, ',');
													if ($row['header'] == $removed) {
														$pfb['summary'] = TRUE;
														$pfb['remove'] = TRUE;
														# Add Alias to Update Array
														$pfb_alias_lists[] = "{$alias}";
														$pfb_alias_lists_all[] = "{$alias}";
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}

	##############################################
	# 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");

		unlink_if_exists("{$pfb['dbdir']}/masterfile");
		unlink_if_exists("{$pfb['dbdir']}/mastercat");
		unlink_if_exists("{$pfb['supptxt']}");
		rmdir_recursive("{$pfb['origdir']}");
		rmdir_recursive("{$pfb['matchdir']}");
		rmdir_recursive("{$pfb['permitdir']}");
		rmdir_recursive("{$pfb['denydir']}");
		rmdir_recursive("{$pfb['nativedir']}");
		rmdir_recursive("{$pfb['etdir']}");
	}


	#############################################
	#       Create Suppression Txt File         #
	#############################################

	if ($pfb['enable'] == "on" && $pfb['supp'] == "on")
		pfb_create_suppression_file();


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

	foreach ($continents as $continent => $pfb_alias) {
		if (is_array($config['installedpackages']['pfblockerng' . strtolower(preg_replace('/ /','',$continent))]['config'])) {
			$continent_config = $config['installedpackages']['pfblockerng' . strtolower(preg_replace('/ /','',$continent))]['config'][0];
			if ($continent_config['action'] != "Disabled" && $pfb['enable'] == "on") {

				# Determine Folder Location for Alias (return array $pfbarr)
				pfb_determine_list_detail($continent_config['action']);
				$pfb['skip']	= $pfbarr['skip'];
				$pfb_descr	= $pfbarr['descr'];
				$pfbfolder	= $pfbarr['folder'];

				// Determine if Continent Lists require Action (IPv4 and IPv6)
				$cont_type = array ("countries4" => "_v4", "countries6" => "_v6");
				foreach ($cont_type as $c_type => $vtype) {

					$continent = "";
					if ($continent_config[$c_type] != "") {

						// Collect Selected ISO Country Files
						foreach (explode(",", $continent_config[$c_type]) as $iso) {
							if ($iso != "" && file_exists($pfb['ccdir'] .'/' . $iso . $vtype . '.txt')) {
								$continent .= file_get_contents ($pfb['ccdir'] . '/' . $iso . $vtype . '.txt');
							}
						}

						if (file_exists($pfb['origdir'] . '/' . $pfb_alias . $vtype . '.orig'))
							$continent_existing = preg_replace('/\s/', '', file ($pfb['origdir'] . '/' . $pfb_alias . $vtype . '.orig'));

						// Collect New Continent Data for comparison. Cleanup Array for Comparison
						$continent_new = preg_split ('/$\R?^/m', $continent);
						$line = count ( $continent_new ) - 1;
						$match = $continent_new[$line];
						$continent_new[$line] = rtrim($match, "\n");

						# Check if pfBlockerNG pfctl Continent Tables are Empty (pfBlockerNG was Disabled w/ "keep", then Re-enabled)
						$pfctlck = exec ("/sbin/pfctl -vvsTables | grep -A1 {$pfb_alias}{$vtype} | awk '/Addresses/ {s+=$2}; END {print s}'");
						if (empty($pfctlck) && file_exists($pfbfolder . '/' . $pfb_alias . $vtype . '.txt')) {
							$file_cont = file_get_contents($pfbfolder . '/' . $pfb_alias . $vtype . '.txt');
							@file_put_contents($pfb['aliasdir'] . '/' . $pfb_alias . $vtype . '.txt',$file_cont, LOCK_EX);
							# PFCTL - Update Only Aliases that have been updated. ('Reputation' Disabled)
							$pfb_alias_lists[] = "{$pfb_alias}{$vtype}";
						}

						# Collect Active Alias Lists (Used for pfctl Update when 'Reputation' is enabled).
						$pfb_alias_lists_all[] = "{$pfb_alias}{$vtype}";

						// Compare Existing (Original File) and New Continent Data
						if ($continent_new === $continent_existing && !empty($pfctlck) && file_exists($pfbfolder . '/' . $pfb_alias . $vtype . '.txt') && $pfb['reuse'] == "") {
							# Format Log into clean Tab Spaces
							$string_final = "{$pfb_alias}{$vtype}";
							if (strlen($string_final) > 10) {
								$log_tab = "\t";
							} else {
								$log_tab = "\t\t";
							}

							if (!$pfb['save']) {
								$log = "\n[ {$pfb_alias}{$vtype} ] {$log_tab} exists, Reloading File [ NOW ]\n";
								pfb_logger("{$log}","1");
							}
						} else {
							// Do not proceed with Changes on User 'Save'
							if (!$pfb['save']) {
								$log = "\n[ {$pfb_alias}{$vtype} ] {$log_tab} Changes Found... Updating \n";
								pfb_logger("{$log}","1");

								# Test to Skip d-dup and p-dup functions when changes are found.
								$pfb['dupcheck'] = TRUE;

								$pfb_alias_lists[] = "{$pfb_alias}{$vtype}";

								// Script to call Duplication Check Process only on IPv4
								if ($pfb['dup'] == "on" && $pfb['skip'] && $vtype == "_v4") {
									// Copy Continent Data to 'lists' folder for duplication processing
									@file_put_contents($pfb['origdir'] . '/' . $pfb_alias . $vtype . '.orig',$continent, LOCK_EX);
									@file_put_contents($pfb['denydir'] . '/' . $pfb_alias . $vtype . '.txt',$continent, LOCK_EX);
									exec ("{$pfb['script']} continent {$pfb_alias}{$vtype} >> {$pfb['log']} 2>&1");
									$continent = file_get_contents($pfbfolder . '/' . $pfb_alias . $vtype . '.txt');
									@file_put_contents($pfb['aliasdir'] . '/' . $pfb_alias . $vtype . '.txt',$continent, LOCK_EX);
								} else {
									@file_put_contents($pfbfolder . '/' . $pfb_alias . $vtype . '.txt',$continent, LOCK_EX);
									@file_put_contents($pfb['origdir'] . '/' . $pfb_alias . $vtype . '.orig',$continent, LOCK_EX);
									@file_put_contents($pfb['aliasdir'] . '/' . $pfb_alias . $vtype . '.txt',$continent, LOCK_EX);
								}

								# Check if File Exists and is >0 in Size and Save alias file
								$file_chk = "0";
								$cont_chk = "{$pfbfolder}/{$pfb_alias}{$vtype}.txt";
								if (file_exists($cont_chk) && @filesize($cont_chk) >0)
									$file_chk = exec ("/usr/bin/grep -cv '^#\|^$' {$cont_chk}");

								if ($file_chk == "0" || $file_chk == "1") {
									$new_file = "1.1.1.1\n";
									@file_put_contents($pfbfolder . '/' . $pfb_alias . $vtype . '.txt', $new_file, LOCK_EX);
									@file_put_contents($pfb['aliasdir'] . "/" . $pfb_alias . $vtype . ".txt", $new_file, LOCK_EX);
									$log = "[ {$pfb_alias}{$vtype} ] Found no Unique IPs, Adding '1.1.1.1' to avoid Empty File\n";
									pfb_logger("{$log}","1");
								}
							}
						}


						if (file_exists($pfbfolder . '/' . $pfb_alias . $vtype . '.txt')) {
							#Create alias config
							$new_aliases_list[] = "{$pfb_alias}{$vtype}";

							$pfb_contlog = $continent_config['aliaslog'];

							$new_aliases[] = array( "name"		=> "{$pfb_alias}{$vtype}",
										"url"		=> "{$pfb['weblocal']}?pfb={$pfb_alias}{$vtype}",
										"updatefreq"	=> "32",
										"address"	=> "",
										"descr"		=> "pfBlockerNG {$vtype} {$pfb_descr} Country Alias",
										"type"		=> "urltable",
										"detail"	=> "DO NOT EDIT THIS ALIAS"
										);

							#Create rule if action permits
							switch ($continent_config['action']) {
								case "Deny_Both":
								case "Deny_Outbound":
									$rule = $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}{$vtype}{$pfb['suffix']}";
									$rule['source'] = array("any" => "");
									$rule['destination'] = array ("address" => "{$pfb_alias}{$vtype}");
									if ($pfb['config']['enable_log'] == "on" || $pfb_contlog == "enabled")
										$rule['log'] = "";
									$deny_outbound[] = $rule;
									if ($continent_config['action'] != "Deny_Both")
										break;
								case "Deny_Inbound":
									$rule = $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}{$vtype}{$pfb['suffix']}";
									$rule['source'] = array("address" => "{$pfb_alias}{$vtype}");
									$rule['destination'] = array ("any" => "");
									if ($pfb['config']['enable_log'] == "on" || $pfb_contlog == "enabled")
										$rule['log'] = "";
									$deny_inbound[] = $rule;
									break;
								case "Permit_Both":
								case "Permit_Outbound":
									$rule = $base_rule;
									$rule['type'] = "pass";
									if ($vtype == "_v6")
										$rule['ipprotocol'] = "inet6";
									if ($pfb['float'] == "on")
										$rule['direction'] = "any";
									$rule['descr'] = "{$pfb_alias}{$vtype}{$pfb['suffix']}";
									$rule['source'] = array ("any" => "");
									$rule['destination'] = array("address" => "{$pfb_alias}{$vtype}");
									if ($pfb['config']['enable_log'] == "on" || $pfb_contlog == "enabled")
										$rule['log'] = "";
									$permit_outbound[] = $rule;
									if ($continent_config['action'] != "Permit_Both")
										break;
								case "Permit_Inbound":
									$rule = $base_rule;
									$rule['type'] = "pass";
									if ($vtype == "_v6")
										$rule['ipprotocol'] = "inet6";
									if ($pfb['float'] == "on")
										$rule['direction'] = "any";
									$rule['descr'] = "{$pfb_alias}{$vtype}{$pfb['suffix']}";
									$rule['source'] = array("address"=> "{$pfb_alias}{$vtype}");
									$rule['destination'] = array ("any" => "");
									if ($pfb['config']['enable_log'] == "on" || $pfb_contlog == "enabled")
										$rule['log'] = "";
									$permit_inbound[] = $rule;
									break;
								case "Match_Both":
								case "Match_Outbound":
									$rule = $base_rule_float;
									$rule['type'] = "match";
									if ($vtype == "_v6")
										$rule['ipprotocol'] = "inet6";
									$rule['direction'] = "any";
									$rule['descr'] = "{$pfb_alias}{$vtype}{$pfb['suffix']}";
									$rule['source'] = array ("any" => "");
									$rule['destination'] = array ("address" => "{$pfb_alias}{$vtype}");
									if ($pfb['config']['enable_log'] == "on" || $pfb_contlog == "enabled")
										$rule['log'] = "";
									$match_outbound[] = $rule;
									if ($list['action'] != "Match_Both")
										break;
								case "Match_Inbound":
									$rule = $base_rule_float;
									$rule['type'] = "match";
									if ($vtype == "_v6")
										$rule['ipprotocol'] = "inet6";
									$rule['direction'] = "any";
									$rule['descr'] = "{$pfb_alias}{$vtype}{$pfb['suffix']}";
									$rule['source'] = array ("address" => "{$pfb_alias}{$vtype}");
									$rule['destination'] = array ( "any" => "");
									if ($pfb['config']['enable_log'] == "on" || $pfb_contlog == "enabled")
										$rule['log'] = "";
									$match_inbound[] = $rule;
									break;
							}
						} else {
							#unlink continent list if any
							unlink_if_exists($pfb['aliasdir'] . '/' . $pfb_alias . $vtype . '.txt');
						}
					}
				}
			}
			#mark pfctl aliastable for cleanup
			if (!in_array($pfb_alias, $aliases_list)) {
				$aliases_list[] = "{$pfb_alias}{$vtype}";
			}
		}
	}
	# UNSET variables
	unset ($continent, $continent_existing, $continent_new);

	#############################################
	#    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['block']	= '/(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]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.([0]{1})\s+/';
	$pfb['cidr']	= '/(?:(?: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]?)?\/[0-9]{2}/';
	$pfb['single']	= '/(?:(?: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]?)\s+/';
	$pfb['s_html']	= '/(?:(?: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]?)/';

	# IPv4 preg_replace Regex Filter array
	$pfb_ipreg = array();
	$pfb_ipreg[0] = '/\b0+(?=\d)/'; # Remove any Leading Zeros in each Octet
	$pfb_ipreg[1] = '/\s/'; # Remove any Whitespaces
	$pfb_ipreg[2] = '/127\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/'; # Remove any Loopback Addresses 127/8
	$pfb_ipreg[3] = '/0\.0\.0\.0/'; # Remove 0.0.0.0

	# IPv6 REGEX Definitions  -- ** Still Needs some Adjustment on Regex Definition for IPv6 **
	# https://mebsd.com/coding-snipits/php-regex-ipv6-with-preg_match.html
	$pattern1 = '([A-Fa-f0-9]{1,4}:){7}[A-Fa-f0-9]{1,4}';
	$pattern2 = '([A-Fa-f0-9]{1,4}::([A-Fa-f0-9]{1,4}:){0,5}[A-Fa-f0-9]{1,4}';
	$pattern3 = '([A-Fa-f0-9]{1,4}:){2}:([A-Fa-f0-9]{1,4}:){0,4}[A-Fa-f0-9]{1,4}';
	$pattern4 = '([A-Fa-f0-9]{1,4}:){3}:([A-Fa-f0-9]{1,4}:){0,3}[A-Fa-f0-9]{1,4}';
	$pattern5 = '([A-Fa-f0-9]{1,4}:){4}:([A-Fa-f0-9]{1,4}:){0,2}[A-Fa-f0-9]{1,4}';
	$pattern6 = '([A-Fa-f0-9]{1,4}:){5}:([A-Fa-f0-9]{1,4}:){0,1}[A-Fa-f0-9]{1,4}';
	$pattern7 = '([A-Fa-f0-9]{1,4}:){6}:[A-Fa-f0-9]{1,4}';
	$pfb['ipv6'] = "/^($pattern1)$|^($pattern2)$|^($pattern3)$|^($pattern4)$|^($pattern5)$|^($pattern6)$|^($pattern7)$/";

	$pfb['supp_update'] = FALSE;
	$list_type = array ("pfblockernglistsv4" => "_v4", "pfblockernglistsv6" => "_v6");
	foreach ($list_type as $ip_type => $vtype) {
		if ($config['installedpackages'][$ip_type]['config'] != "") {
			foreach ($config['installedpackages'][$ip_type]['config'] as $list) {
				if ($list['action'] != "Disabled" && $pfb['enable'] == "on" && !$pfb['save'] && is_array($list['row'])) {
					# Capture Alias Name
					$alias = "pfB_" . preg_replace("/\W/","",$list['aliasname']);
					foreach ($list['row'] as $row) {
						if ($row['url'] != "" && $row['state'] != "Disabled") {

							# Determine Folder Location for Alias (return array $pfbarr)
							pfb_determine_list_detail($list['action']);
							$pfb['skip']	= $pfbarr['skip'];
							$pfbfolder	= $pfbarr['folder'];

							if ($vtype == "_v4") {
								$header_url = "{$row['header']}";
							} else {
								$header_url = "{$row['header']}_v6";
							}

							# Format Log into clean Tab Spaces
							if (strlen($header_url) > 10) {
								$log_tab = "\t";
							} else {
								$log_tab = "\t\t";
							}

							# Collect Active Alias List (Used for pfctl Update when 'Reputation' is enabled.
							$pfb_alias_lists_all[] = "{$alias}";

							// Empty Header Field Validation Check
							if (empty($header_url)) {
								$log = "\n [ {$row['url']} ] {$log_tab} Header Field cannot be Empty. *Skipping* \n";
								pfb_logger("{$log}","2");
								continue;
							}

							if (file_exists($pfbfolder . '/' . $header_url . '.txt') && $pfb['reuse'] == "") {
								if ($row['state'] == "Hold") {
									$log = "\n[ {$header_url} ] {$log_tab} Static Hold [ NOW ]\n";
								} else {
									$log = "\n[ {$header_url} ] {$log_tab} exists, Reloading File [ NOW ]\n";
								}
								pfb_logger("{$log}","1");
							} else {
								if ($pfb['reuse'] == "on" && file_exists($pfb['origdir'] . '/' . $header_url . '.orig')) {
									$log = "\n[ {$header_url} ] {$log_tab} Using Previously Downloaded File [ NOW ]\n";
								} else {
									$log = "\n[ {$header_url} ] {$log_tab} Downloading New File [ NOW ]\n";
								}  
								pfb_logger("{$log}","1");

								# Perform Remote URL Date/Time Stamp checks
								$host = @parse_url($row['url']);
								$list_url = "{$row['url']}";
								if ($row['format'] != "rsync" || $row['format'] != "html") {
									if ($host['host'] == "127.0.0.1" || $host['host'] ==  $pfb['iplocal'] || empty($host['host'])) {
										$remote_tds = "local";
									} else {
										$remote_tds = @implode(preg_grep("/Last-Modified/", get_headers($list_url)));
										$remote_tds = preg_replace("/^Last-Modified: /","", $remote_tds);
									}
								}

								$url_list = array();
								if ($row['format'] == "gz" || $row['format'] == "gz_2") {
									$file_dwn = "{$pfb['origdir']}/{$header_url}.gz";
									if ($pfb['reuse'] == "on" && file_exists($file_dwn)) {
										# File Exists/Reuse
									} else {
										$url_gz = "{$row['url']}";
										$file_gz = @file_get_contents($url_gz);
										@file_put_contents($file_dwn, $file_gz, LOCK_EX);
										if ($remote_tds == "local")
											$remote_tds = gmdate ("D, d M Y H:i:s T", filemtime($file_dwn));
										$remote_stamp = strtotime($remote_tds);
										if (!empty($remote_stamp) && file_exists($file_dwn))
											touch ($file_dwn, $remote_stamp);
									}
									$url_list = @gzfile($file_dwn);
								}

								# IBlock Large Files mixed with IPs and Domains. PHP mem of 256M can't handle very large Files.
								if ($row['format'] == "gz_lg") {
									$file_dwn = "{$pfb['origdir']}/{$header_url}.gz";
									if ($pfb['reuse'] == "on" && file_exists($file_dwn)) {
										# File Exists/Reuse
									} else {
										$url_gz = "{$row['url']}";
										$file_gz = @file_get_contents($url_gz);
										@file_put_contents($file_dwn, $file_gz, LOCK_EX);
										exec ("/usr/bin/gunzip -c {$file_dwn} | /usr/bin/sed 's/^.*://' | /usr/bin/grep -v '[a-zA-Z]\|^$\|^#' > {$pfb['origdir']}/{$header_url}.orig");
										if ($remote_tds == "local")
											$remote_tds = gmdate ("D, d M Y H:i:s T", filemtime($file_dwn));
										$remote_stamp = strtotime($remote_tds);
										if (!empty($remote_stamp) && file_exists($file_dwn))
											touch ($file_dwn, $remote_stamp);
									}
									$url_list = @file($pfb['origdir'] . '/' . $header_url . '.orig');
								}

								elseif ($row['format'] == "zip") {
									$file_dwn = "{$pfb['origdir']}/{$header_url}.zip";
									if ($pfb['reuse'] == "on" && file_exists($file_dwn)) {
										# File Exists/Reuse
									} else {
										$url_zip = "{$row['url']}";
										if (!$file_zip = @file_get_contents($url_zip)) {
											$error = error_get_last();
											$log = "\n [ {$header_url} ] {$error['message']} \n";
											pfb_logger("{$log}","2");
										} else {
											@file_put_contents($file_dwn, $file_zip, LOCK_EX);
											if ($remote_tds == "local")
												$remote_tds = gmdate ("D, d M Y H:i:s T", filemtime($file_dwn));
											$remote_stamp = strtotime($remote_tds);
											if (!empty($remote_stamp) && file_exists($file_dwn))
												touch ($file_dwn, $remote_stamp);
										}
									}
									$zip_out = "{$pfb['origdir']}/{$header_url}.orig";
									exec ("/usr/bin/tar -xOf {$file_dwn} | tr ',' '\n' > {$zip_out}");
									$url_list = @file($zip_out);
								}

								elseif ($row['format'] == "et") {
									$file_dwn = "{$pfb['origdir']}/{$header_url}.gz";
									# Script to Call ET IQRISK Process
									if ($pfb['reuse'] == "on" && file_exists($file_dwn)) {
										# File Exists/Reuse
									} else {
										$url_et = "{$row['url']}";
										$file_et = @file_get_contents($url_et);
										@file_put_contents($file_dwn, $file_et, LOCK_EX);
										if ($remote_tds == "local")
											$remote_tds = gmdate ("D, d M Y H:i:s T", filemtime($file_dwn));
										$remote_stamp = strtotime($remote_tds);
										if (!empty($remote_stamp) && file_exists($file_dwn))
											touch ($file_dwn, $remote_stamp);
									}
									exec ("{$pfb['script']} et {$header_url} x x x x x {$pfb['etblock']} {$pfb['etmatch']} >> {$pfb['log']} 2>&1");
									$url_list = @file($pfb['origdir'] . '/' . $header_url . '.orig');
								}

								elseif ($row['format'] == "xlsx") {
									$file_dwn = "{$pfb['origdir']}/{$header_url}.zip";
									# Script to Call XLSX Process
									if ($pfb['reuse'] == "on" && file_exists($file_dwn)) {
										# File Exists/Reuse
									} else {
										$url_xlsx = "{$row['url']}";
										$file_xlsx = @file_get_contents($url_xlsx);
										@file_put_contents($file_dwn, $file_xlsx, LOCK_EX);
										if ($remote_tds == "local")
											$remote_tds = gmdate ("D, d M Y H:i:s T", filemtime($file_dwn));
										$remote_stamp = strtotime($remote_tds);
										if (!empty($remote_stamp) && file_exists($file_dwn))
											touch ($file_dwn, $remote_stamp);
									}
									exec ("{$pfb['script']} xlsx {$header_url} >> {$pfb['log']} 2>&1");
									$url_list = @file($pfb['origdir'] . '/' . $header_url . '.orig');
								}

								elseif ($row['format'] == "txt") {
									$file_dwn = "{$pfb['origdir']}/{$header_url}.orig";
									if ($pfb['reuse'] == "on" && file_exists($file_dwn)) {
										$url_list = @file($file_dwn);
									} else {
										$url_other = @file($row['url']);
										$url_list = $url_other;
										@file_put_contents($file_dwn, $url_other, LOCK_EX);
										if ($remote_tds == "local")
											$remote_tds = gmdate ("D, d M Y H:i:s T", filemtime($file_dwn));
										$remote_stamp = strtotime($remote_tds);
										if (!empty($remote_stamp) && file_exists($file_dwn))
											touch ($file_dwn, $remote_stamp);
									}
								}

								elseif ($row['format'] == "html" || $row['format'] == "block") {
									$file_dwn = "{$pfb['origdir']}/{$header_url}.raw";
									if ($pfb['reuse'] == "on" && file_exists($file_dwn)) {
										# File Exists/Reuse
										$return = 0;
									} else {
										$url_html = "{$row['url']}";
										exec ("/usr/bin/fetch -v -o {$file_dwn} -T 20 {$url_html}",$output,$return);
									}
									if ($return == 0)
										$url_list = @file($file_dwn);
								}

								elseif ($row['format'] == "rsync") {
									$file_dwn = "{$pfb['origdir']}/{$header_url}.orig";
									if ($pfb['reuse'] == "on" && file_exists($file_dwn)) {
										# File Exists/Reuse
									} else {
										$url_rsync = "{$row['url']}";
										exec ("/usr/local/bin/rsync --timeout=5 {$url_rsync} {$file_dwn}");
									}
									$url_list = @file($file_dwn);
								}

								#extract range lists
								$new_file = "";
								if (!empty($url_list)) {
									if ($row['format'] == "gz" && $vtype == "_v4") {
										foreach ($url_list as $line) {
											# Network range 192.168.0.0-192.168.0.254
											if (preg_match($pfb['range'],$line,$matches)) {
												$a_cidr = ip_range_to_subnet_array($matches[1],$matches[2]);
												if (!empty($a_cidr)) {
													foreach ($a_cidr as $cidr) {
														$new_file .= preg_replace($pfb_ipreg,'',$cidr) . "\n";
													}
												}
											}
										}
									}

									elseif ($row['format'] == "block" && $vtype == "_v4") {
										foreach ($url_list as $line) {
											# Block Type '218.77.79.0	218.77.79.255   24'
											if (preg_match($pfb['block'],$line,$matches)) {
												$new_file .= preg_replace($pfb_ipreg, '',$matches[0]) . "/24\n";
											}
										}
									}

									elseif ($row['format'] == "html" && $vtype == "_v4") {
										foreach ($url_list as $line) {
											# CIDR format 192.168.0.0/16
											if (preg_match($pfb['cidr'],$line,$matches)) {
												$new_file .= preg_replace($pfb_ipreg, '',$matches[0]) . "\n";
											}
											# Single ip addresses
											elseif (preg_match($pfb['s_html'],$line,$matches)) {
												$new_file .= preg_replace($pfb_ipreg, '',$matches[0]) . "\n";
											}
										}
									} 

									elseif ($vtype == "_v6") {
										foreach ($url_list as $line) {
											# IPv6 Regex Match
											if (preg_match($pfb['ipv6'],$line,$matches)) {
												$new_file .= preg_replace($pfb_ipreg, '',$matches[0]) . "\n";
											}
										}
									}

									else {
										foreach ($url_list as $line) {
											# CIDR format 192.168.0.0/16
											if (preg_match($pfb['cidr'],$line,$matches)) {
												$new_file .= preg_replace($pfb_ipreg, '',$matches[0]) . "\n";
											}
											# Single ip addresses
											elseif (preg_match($pfb['single'],$line,$matches)) {
												$new_file .= preg_replace($pfb_ipreg, '',$matches[0]) . "\n";
											}
										}
									}
								}

								# Check to see if Blocklist actually Failed Download or has no IPs listed.
								if ($row['format'] == "html" || $row['format'] == "block") {
									$url_chk = $file_dwn;
								} else {
									$url_chk = "{$pfb['origdir']}/{$header_url}.orig";
								}

								# Check if File Exists and is >0 in Size
								$file_chk = "";
								if (file_exists($url_chk) && @filesize($url_chk) >0)
									$file_chk = exec ("/usr/bin/grep -cv '^#\|^$' {$url_chk}");

								if ($file_chk == "0") {
									$new_file = "1.1.1.1\n";
									$url_other = $new_file;
									$log = "[ {$header_url} ] Found no IPs, Adding '1.1.1.1' to avoid Download FAIL\n";
									pfb_logger("{$log}","1");
								}

								if ($new_file != "") {
									if ($row['format'] == "gz" || $row['format'] == "gz_2" || $row['format'] == "html" || $row['format'] == "block") {
										# Re-Save these formats as original file
										$url_other = $new_file;
										@file_put_contents($pfb['origdir'] . '/' . $header_url . '.orig',$url_other, LOCK_EX);
									}

									# Save List to '.txt' format in appropriate Folder
									@file_put_contents($pfbfolder . '/' .$header_url . '.txt',$new_file, LOCK_EX);

									if ($pfb['rep'] == "on" && $pfb['skip'] && $vtype == "_v4") {
										# Script to Call p24 Process
										exec ("{$pfb['script']} p24 {$header_url} {$pfb['max']} {$pfb['dedup']} {$pfb['ccexclude']} {$pfb['ccwhite']} {$pfb['ccblack']} >> {$pfb['log']} 2>&1");
									}

									if ($pfb['dup'] == "on" && $pfb['skip'] && $vtype == "_v4") {
										# Script to call Duplication Check Process
										exec ("{$pfb['script']} duplicate {$header_url} >> {$pfb['log']} 2>&1");
									}

									# PFCTL - Update Only Aliases that have been updated only.
									$pfb_alias_lists[] = "{$alias}";
									# Launch d-dup and p-dup functions when changes are found.
									if ($pfb['skip'] && $vtype == "_v4")
										$pfb['dupcheck'] = TRUE;
									# Enable Suppression Process due to Updates
									if ($pfb['supp'] == "on" && $vtype == "_v4")
										$pfb['supp_update'] = TRUE;

								} else {
									# Log FAILED Downloads and Check if Firewall or Snort/Suricata is Blocking Host
									$log = "\n [ {$alias} {$header_url} ] Download FAIL [ NOW ]\n";
									pfb_logger("{$log}","2");

									# Rebuild Previous List File from contents of Masterfile
									if ($pfb['skip'] && $vtype == "_v4") {
										# Search with trailing Whitespace to match exact Header in Masterfile
										$header_url2 = $header_url . "[[:space:]]";
										$file_chk = exec ("/usr/bin/grep {$header_url2} {$pfb['master']} | grep -c ^");

										if (!file_exists($pfbfolder . '/' . $header_url . '.txt') && @$file_chk > 0 && file_exists($pfb['master'])) {
											$log = " [ {$alias} {$header_url} ] Found: {$file_chk} Line(s), Restoring previous List from Master \n";
											pfb_logger("{$log}","2");
											exec ("/usr/bin/grep {$header_url2} {$pfb['master']} | cut -d' ' -f2 > {$pfbfolder}/{$header_url}.txt");
										}
									}
									# A "Space" string Variable
									$sp = " ";
									$ip = @gethostbyname($host['host']);
									$ip2 = preg_replace("/(\d{1,3})\.(\d{1,3}).(\d{1,3}).(\d{1,3})/", "\"^$1\.$2\.$3\.\"", $ip);

									# Only Perform these Checks if they are not "localfiles"
									if ($host['host'] == "127.0.0.1" || $host['host'] ==  $pfb['iplocal'] || empty($host['host'])) {
										$log = " [ {$alias} {$header_url} ] Local File Failure \n";
										pfb_logger("{$log}","2");
									} else {
										# only perform these steps if an 'IP' is found.
										if (!empty($ip)) {
											// Query for Exact IP Match
											$result_b1 = array();
											$pfb_b1 = exec ("/usr/bin/grep ^{$ip} {$pfbfolder}/*", $result_b1);
											// Query for First Three IP Octet Matches
											$result_b2 = array();
											$pfb_b2 = exec ("/usr/bin/grep {$ip2} {$pfbfolder}/*", $result_b2);
											// Query Snort/Suricata snort2c IP Block Table
											$snort_pfb = exec("/sbin/pfctl -t snort2c -T show | grep {$ip}");

											# If an exact IP Match is not found report any First Three IP Octets.
											if (!empty($result_b1)) {
												$final_b1 = implode("\n ", $result_b1);
												$log = " [ {$alias} {$header_url}, {$ip} ] Firewall IP Block Found in : \n{$sp}{$final_b1}\n";
												pfb_logger("{$log}","2");
											} else {
												if (!empty($result_b2)) {
													$final_b2 = implode("\n ", $result_b2);
													$log = " [ {$alias} {$header_url}, {$ip} ] *Potential* Firewall IP Block Found in : \n{$sp}{$final_b2}\n";
													pfb_logger("{$log}","2");
												}
											}
											if (!empty($snort_pfb)) {
												$log = " [ {$alias} {$header_url}, {$ip} ] snort2c IP Block Found in : [ {$snort_pfb} ]\n";
												pfb_logger("{$log}","2");
											}
										} else {
											$log = " [ {$alias} {$header_url} ] No host IP found \n";
											pfb_logger("{$log}","2");
										}	
									}
								}
								# UNSET variables
								unset ($file_gz,$file_zip,$file_et,$file_xlsx,$url_other,$url_list);
							}
						}
					}
					#check custom network list
					if (pfbng_text_area_decode($list['custom']) != "") {

						if ($vtype == "_v4") {
							$aliascustom = "{$list['aliasname']}_custom";
						} else {
							$aliascustom = "{$list['aliasname']}_custom_v6";
						}

						# Format Log into clean Tab Spaces
						if (strlen($aliascustom) > 10) {
							$log_tab = "\t";
						} else {
							$log_tab = "\t\t";
						}

						# Collect Active Alias List (Used for pfctl Update when 'Reputation' is enabled.
						$pfb_alias_lists_all[] = "{$alias}";

						# Determine Folder Location for Alias (return array $pfbarr)
						pfb_determine_list_detail($list['action']);
						$pfb['skip'] = $pfbarr['skip'];
						$pfbfolder   = $pfbarr['folder'];

						if (file_exists($pfbfolder . '/' . $aliascustom . '.txt') && $pfb['reuse'] == "") {
							$log = "\n[ {$aliascustom} ] {$log_tab} exists, Reloading File [ NOW ]\n";
							pfb_logger("{$log}","1");
						} else {
							$url_list = array();
							$log = "\n[ {$aliascustom} ] {$log_tab} Loading Custom File [ NOW ]\n";
							pfb_logger("{$log}","1");

							$custom_list = pfbng_text_area_decode($list['custom']) . "\n";
							@file_put_contents($pfb['origdir'] . '/' . $aliascustom . '.orig', $custom_list, LOCK_EX);
							$url_list = @file($pfb['origdir'] . '/' . $aliascustom . '.orig');

							$new_file = "";
							if (!empty($url_list)) {
								foreach ($url_list as $line) {
									if ($vtype == "_v4") {
										# CIDR format 192.168.0.0/16
										if (preg_match($pfb['cidr'],$line,$matches)) {
											$new_file .= preg_replace($pfb_ipreg, '',$matches[0]) . "\n";
										}
										# Single ip addresses
										elseif (preg_match($pfb['s_html'],$line,$matches)) {
											$new_file .= preg_replace($pfb_ipreg, '',$matches[0]) . "\n";
										}
										# Network range 192.168.0.0-192.168.0.254
										elseif (preg_match($pfb['range'],$line,$matches)) {
											$a_cidr = ip_range_to_subnet_array($matches[1],$matches[2]);
											if (!empty($a_cidr)) {
												foreach ($a_cidr as $cidr) {
													$new_file .= preg_replace($pfb_ipreg, '',$cidr) . "\n";
												}
											}
										}
									} else {
										# IPv6 Regex
										if (preg_match($pfb['ipv6'],$line,$matches)) {
											$new_file .= preg_replace($pfb_ipreg, '',$matches[0]) . "\n";
										}
									}
								}

							}
							if ($new_file != "") {	
								# PFCTL - Collect Only Aliases that have been updated only.
								$pfb_alias_lists[] = "{$alias}";
								# Collect Updated lists for Suppression Process
								@file_put_contents($pfbfolder . '/'. $aliascustom . '.txt',$new_file, LOCK_EX);
								# Enable Suppression Process due to Updates
								if ($pfb['supp'] == "on" && $vtype == "_v4")
									$pfb['supp_update'] = TRUE;
								if ($pfb['rep'] == "on" && $pfb['skip'] && $vtype == "_v4") {
									# Script to Call p24 Process
									exec ("{$pfb['script']} p24 {$aliascustom} {$pfb['max']} {$pfb['dedup']} {$pfb['ccexclude']} {$pfb['ccwhite']} {$pfb['ccblack']} >> {$pfb['log']}  2>&1");
								}
								if ($pfb['dup'] == "on" && $pfb['skip'] && $vtype == "_v4") {
									# Script to call Duplication Check Process
									exec ("{$pfb['script']} duplicate {$aliascustom} >> {$pfb['log']} 2>&1");
								}
							} else {
								$log = "[ {$aliascustom} ] Custom List Error ]\n";
								pfb_logger("{$log}","1");
							}
						}
					}
				}
			}
		}
	}


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

	# IP Reputation processes (pdup and ddup)
	if ($pfb['pdup'] == "on" && $pfb['dupcheck'] && !$pfb['save'] && $pfb['enable'] == "on") {
		# Script to run pdup process
		exec ("{$pfb['script']} pdup x {$pfb['pmax']}  >> {$pfb['log']} 2>&1");
	}
	if ($pfb['dedup'] == "on" && $pfb['dupcheck'] && !$pfb['save'] && $pfb['enable'] == "on") {
		# Script to run dedup process
		exec ("{$pfb['script']} dedup x {$pfb['dmax']} {$pfb['dedup']} {$pfb['ccexclude']} {$pfb['ccwhite']} {$pfb['ccblack']} >> {$pfb['log']} 2>&1");
	}

	#############################################
	#            CONFIGURE ALIASES              #
	#############################################

	$list_type = array ("pfblockernglistsv4" => "_v4", "pfblockernglistsv6" => "_v6");
	foreach ($list_type as $ip_type => $vtype) {
		if ($config['installedpackages'][$ip_type]['config'] != "" && $pfb['enable'] == "on") {
			$runonce = 0;
			foreach ($config['installedpackages'][$ip_type]['config'] as $list) {
				$alias = "pfB_" . preg_replace("/\W/","",$list['aliasname']);

				# Determine Folder Location for Alias (return array $pfbarr)
				pfb_determine_list_detail($list['action']);
				$pfb['skip']	= $pfbarr['skip'];
				$pfb_descr	= $pfbarr['descr'];
				$pfbfolder	= $pfbarr['folder'];

				// Re-Save Only Aliases that have been updated only.
				// When 'Reputation' is used, all Aliases need to be Updated.
				$final_alias = array();
				if ($pfb['dedup'] == "on" || $pfb['pdup'] == "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") {
					#remove empty lists files if any
					if (is_array($list['row'])) {
						$update = 0;
						${$alias} = "";
						foreach ($list['row'] as $row) {
							if ($row['url'] != "" && $row['state'] != "Disabled") {
								if ($vtype == "_v4") {
									$header_url = "{$row['header']}";
								} else {
									$header_url = "{$row['header']}_v6";
								}
								$pfctlck = exec ("/sbin/pfctl -vvsTables | grep -A1 {$alias} | 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_url . ".txt") && in_array($alias, $final_alias) || file_exists($pfbfolder . "/" . $header_url . ".txt") && empty($pfctlck)) {
									# Script to run Suppression process (Print Header Only)
									if ($pfb['supp'] == "on" && $vtype == "_v4" && $runonce == 0 && $pfb['supp_update']) {
										exec ("{$pfb['script']} suppress x x x suppressheader >> {$pfb['log']} 2>&1");
										$runonce++;
									}
									# Script to run Suppression Process (Body)
									if ($pfb['supp'] == "on" && $vtype == "_v4" && $pfb['supp_update']) {
										if ($pfb['dup'] == "on" || !$pfb['skip']) {
											# Execute if Duplication Process is Enabled or List is Permit or Match
											exec ("{$pfb['script']} suppress x x x {$header_url}\|{$pfbfolder}/ >> {$pfb['log']} 2>&1");
										} else {
											# Execute if Duplication Process is Disabled
											exec ("{$pfb['script']} suppress x x off {$header_url}\|{$pfbfolder}/ >> {$pfb['log']} 2>&1");
										}
									}
									${$alias} .= file_get_contents($pfbfolder . '/' . $header_url . '.txt');
									$update++;
								}
							}
						}
					}

					#check custom network list
					if ($vtype == "_v4") {
						$aliasname = "{$list['aliasname']}_custom";
					} else {
						$aliasname = "{$list['aliasname']}_custom_v6";
					}

					# Update Alias if List File Exists and its been updated or if the Alias URL Table is Empty.
					$pfctlck = exec ("/sbin/pfctl -vvsTables | grep -A1 {$alias} | awk '/Addresses/ {s+=$2}; END {print s}'");

					if (pfbng_text_area_decode($list['custom']) != "") {
						if (file_exists($pfbfolder . "/" . $aliasname . ".txt") && in_array($alias, $final_alias) || file_exists($pfbfolder . "/" . $aliasname . ".txt") && empty($pfctlck)) {
							${$alias} .= file_get_contents($pfbfolder . '/' . $aliasname . '.txt');
							$update++;
						}
					}
					# Determine Validity of Alias URL Tables/Rules. ie: Don't create Empty URL Tables or Aliases
					if (${$alias} == "" && empty($pfctlck)) {
						unlink_if_exists($pfb['aliasdir'] . '/' . $alias. '.txt');
					} else {
						// Save Only Aliases that have been updated.
						if ($update > 0) {
							@file_put_contents($pfb['aliasdir'] . '/' . $alias. '.txt',${$alias}, LOCK_EX);
						}

						$alias_log = $list['aliaslog'];
						#create alias
						$new_aliases_list[] = "{$alias}";

						$new_aliases[] = array(	"name"		=> "{$alias}",
									"url"		=> "{$pfb['weblocal']}?pfb={$alias}",
									"updatefreq"	=> "32",
									"address"	=> "",
									"descr"		=> "pfBlockerNG {$pfb_descr} List Alias",
									"type"		=> "urltable",
									"detail"	=> "DO NOT EDIT THIS ALIAS"
									);

						#Create rule if action permits
						switch ($list['action']) {
							case "Deny_Both":
							case "Deny_Outbound":
								$rule = $base_rule;
								$rule['type'] = "{$pfb['deny_action_outbound']}";
								if ($vtype == "_v6")
									$rule['ipprotocol'] = "inet6";
								if ($pfb['float'] == "on")
									$rule['direction'] = "any";
								$rule['descr'] = "{$alias}{$pfb['suffix']}";
								$rule['source'] = array ("any" => "");
								$rule['destination'] = array ("address" => "{$alias}");
								if ($pfb['config']['enable_log'] == "on" || $alias_log == "enabled")
									$rule['log'] = "";
								$deny_outbound[] = $rule;
								if ($list['action'] != "Deny_Both")
									break;
							case "Deny_Inbound":
								$rule = $base_rule;
								$rule['type'] = "{$pfb['deny_action_inbound']}";
								if ($vtype == "_v6")
									$rule['ipprotocol'] = "inet6";
								if ($pfb['float'] == "on")
									$rule['direction'] = "any";
									$rule['descr'] = "{$alias}{$pfb['suffix']}";
								$rule['source'] = array("address" => "{$alias}");
								$rule['destination'] = array ("any" => "");
								if ($pfb['config']['enable_log'] == "on" || $alias_log == "enabled")
									$rule['log'] = "";
								$deny_inbound[] = $rule;
								break;
							case "Permit_Both":
							case "Permit_Outbound":
								$rule = $base_rule;
								$rule['type'] = "pass";
								if ($vtype == "_v6")
									$rule['ipprotocol'] = "inet6";
								if ($pfb['float'] == "on")
									$rule['direction'] = "any";
								$rule['descr'] = "{$alias}{$pfb['suffix']}";
								$rule['source'] = array ("any" => "");
								$rule['destination'] = array ("address" => "{$alias}");
								if ($pfb['config']['enable_log'] == "on" || $alias_log == "enabled")
									$rule['log'] = "";
								$permit_outbound[] = $rule;
								if ($list['action'] != "Permit_Both")
									break;
							case "Permit_Inbound":
								$rule = $base_rule;
								$rule['type'] = "pass";
								if ($vtype == "_v6")
									$rule['ipprotocol'] = "inet6";
								if ($pfb['float'] == "on")
									$rule['direction'] = "any";
								$rule['descr'] = "{$alias}{$pfb['suffix']}";
								$rule['source'] = array ("address" => "{$alias}");
								$rule['destination'] = array ("any" => "");
								if ($pfb['config']['enable_log'] == "on" || $alias_log == "enabled")
									$rule['log'] = "";
								$permit_inbound[] = $rule;
								break;
							case "Match_Both":
							case "Match_Outbound":
								$rule = $base_rule_float;
								$rule['type'] = "match";
								if ($vtype == "_v6")
									$rule['ipprotocol'] = "inet6";
								$rule['direction'] = "any";
								$rule['descr'] = "{$alias}{$pfb['suffix']}";
								$rule['source'] = array ("any" => "");
								$rule['destination'] = array ("address" => "{$alias}");
								if ($pfb['config']['enable_log'] == "on" || $alias_log == "enabled")
									$rule['log'] = "";
								$match_outbound[] = $rule;
								if ($list['action'] != "Match_Both")
									break;
							case "Match_Inbound":
								$rule = $base_rule_float;
								$rule['type'] = "match";
								if ($vtype == "_v6")
									$rule['ipprotocol'] = "inet6";
								$rule['direction'] = "any";
								$rule['descr'] = "{$alias}{$pfb['suffix']}";
								$rule['source'] = array ("address" => "{$alias}");
								$rule['destination'] = array ("any" => "");
								if ($pfb['config']['enable_log'] == "on" || $alias_log == "enabled")
									$rule['log'] = "";
								$match_inbound[] = $rule;
								break;
						}
					}
					#mark pfctl aliastable for cleanup
					if (!in_array($alias, $aliases_list)) {
						$aliases_list[] = "{$alias}";
					}
				} else {
					#unlink previous pfblockerNG alias list if any
					unlink_if_exists($pfb['aliasdir'] . '/' . $alias . '.txt');
				}
			}
		}
	}
	# Clear Variables
	${$alias} = "";


	#############################################
	#       UPDATE PfSENSE ALIAS TABLES         #
	#############################################

	#update pfsense alias table
	if (is_array($config['aliases']['alias'])) {
		foreach ($config['aliases']['alias'] as $cbalias) {
			if (preg_match("/pfB_/",$cbalias['name'])) {
				#mark pfctl aliastable for cleaning
				if (!in_array($cbalias['name'], $aliases_list)) {
					$aliases_list[] = $cbalias['name']; #mark aliastable for cleaning
				}
				#remove previous aliastable file if alias is not defined any more
				if (!in_array($cbalias['name'], $new_aliases_list)) {
					unlink_if_exists($pfb['aliasdir'] . '/' . $cbalias['name'] . ".txt");
				}
			} else {
				$new_aliases[] = $cbalias;

				# Check Table Size
				if (file_exists($pfb['aliasdir'] . '/' . $alias . '.txt') && $message == "") {
					preg_match("/(\d+)/",exec("/usr/bin/grep -c ^ " . $pfb['aliasdir'] . '/' . $alias . '.txt'),$matches);
				}
				if (($matches[1] * 2.1) >= $pfb['table_limit']) {
					#alias table too large
					$message = "{$alias} alias table is too large. Reduce networks in list or increase 'Firewall Maximum Table Entries' value to at least " . (int)($matches[1] * 2.1) . ' in "system - advanced - Firewall/NAT" . ';
				}
			}
		}
	}

	#apply new alias table to xml
	if ($message == "") {
		$config['aliases']['alias'] = $new_aliases;
	}
	# UNSET Variables
	unset($new_aliases, $cbalias);


	#############################################
	#              Assign rules                 #
	#############################################

	# Only Execute if AutoRules are defined or if an Alias has been removed.
	if ($pfb['autorules'] || $pfb['enable'] == "" || $pfb['remove']) {
		if (count($deny_inbound) > 0 || count($permit_inbound) > 0 || count($match_inbound) > 0) {
			if ($pfb['inbound_interfaces'] == "") {
				$message = "Unable to apply rules. Inbound Interface option not configured.";
			}
		}
		if (count($deny_outbound) > 0 || count($permit_outbound) > 0 || count($match_outbound) > 0) {
			if ($pfb['outbound_interfaces'] == "") {
				$message = "Unable to apply rules. Outbound Interface option not configured.";
			}
		}

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

			# Collect All Existing Rules
			$rules = $config['filter']['rule'];
			# Collect Existing pfSense Rules 'Pass', 'Match' and 'Other' pfSense rules into new Arrays.
			if (!empty($rules)) {
				foreach ($rules as $rule) {
					if (!preg_match("/pfB_.*" . $pfb['suffix'] . "/",$rule['descr'])) {
						// Floating rules collection 'Floating Pass/Match'. Balance to 'other'
						if ($pfb['float'] == "on") {
							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', Balance to 'Other'
								if ($rule['floating'] == "yes") {
									$fother_rules[] = $rule;
								} elseif ($rule['type'] == "pass") { 
									if ($pfb['order'] == "order_0") {
										$other_rules[] = $rule;
									} else {
										$permit_rules[] = $rule;
									}
								} elseif ($rule['type'] == "match") {
									if ($pfb['order'] == "order_0") {
										$other_rules[] = $rule;
									} else {
										$match_rules[] = $rule;
									}
								} else {
									$other_rules[] = $rule;
								}
							} else {
								if ($rule['floating'] == "yes") {
									$fother_rules[] = $rule;
								} else {
									$other_rules[] = $rule;
								}
							}
						}
					}
				}
			}

			#################################################################################
			#			PASS/MATCH RULES ORDER(p/m)				#
			#  ORDER 0 - pfBlockerNG / All other Rules					#
			#  ORDER 1 - pfSense (p/m) / pfBlockerNG (p/m) / pfBlockerNG Block/Reject	#
			#  ORDER 2 - pfBlockerNG (p/m) / pfSense (p/m) / pfBlockerNG Block/Reject	#
			#  ORDER 3 - pfBlockerNG (p/m) / pfBlockerNG Block/Reject / pfSense (p/m)	#
			#################################################################################

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

			# Define Inbound Interface Rules
			if (!empty($pfb['inbound_interfaces'])) {
				$counter = 0;
				foreach ($pfb['inbound_interfaces'] as $inbound_interface) {
					if (!empty($permit_rules) && $pfb['order'] == "order_1") {
						foreach ($permit_rules as $cb_rules) {
							if ($cb_rules['interface'] == $inbound_interface)
								$new_rules[] = $cb_rules;
						}
					}
					if (!empty($match_rules) && $pfb['order'] == "order_1") {
						foreach ($match_rules as $cb_rules) {
							if ($cb_rules['interface'] == $inbound_interface)
								$new_rules[] = $cb_rules;
						}
					}
					# Match Inbound Rules defined as Floating Only.
					if (!empty($match_inbound) && $counter == 0) {
						foreach ($match_inbound as $cb_rules) {
							$cb_rules['interface'] = $pfb['inbound_floating'];
							$new_rules[] = $cb_rules;
							$counter ++;
						}
					}
					if (!empty($permit_inbound)) {
						foreach ($permit_inbound as $cb_rules) {
							$cb_rules['interface'] = $inbound_interface;
							$new_rules[] = $cb_rules;
						}
					}
					if (!empty($fpermit_rules) && $pfb['order'] == "order_2") {
						foreach ($fpermit_rules as $cb_rules) {
							$new_rules[] = $cb_rules;
						}
					}
					if (!empty($fmatch_rules) && $pfb['order'] == "order_2") {
						foreach ($fmatch_rules as $cb_rules) {
							$new_rules[] = $cb_rules;
						}
					}
					if (!empty($permit_rules) && $pfb['order'] == "order_2") {
						foreach ($permit_rules as $cb_rules) {
							if ($cb_rules['interface'] == $inbound_interface)
								$new_rules[] = $cb_rules;
						}
					}
					if (!empty($match_rules) && $pfb['order'] == "order_2") {
						foreach ($match_rules as $cb_rules) {
							if ($cb_rules['interface'] == $inbound_interface)
								$new_rules[] = $cb_rules;
						}
					}
					if (!empty($deny_inbound)) {
						foreach ($deny_inbound as $cb_rules) {
							$cb_rules['interface'] = $inbound_interface;
							$new_rules[] = $cb_rules;
						}
					}
				}
			}

			# Define Outbound Interface Rules
			if (!empty($pfb['outbound_interfaces'])) {
				$counter = 0;
				foreach ($pfb['outbound_interfaces'] as $outbound_interface) {
					if (!empty($permit_rules) && $pfb['order'] == "order_1") {
						foreach ($permit_rules as $cb_rules) {
							if ($cb_rules['interface'] == $outbound_interface)
								$new_rules[] = $cb_rules;
						}
					}
					if (!empty($match_rules) && $pfb['order'] == "order_1") {
						foreach ($match_rules as $cb_rules) {
							if ($cb_rules['interface'] == $outbound_interface)
								$new_rules[] = $cb_rules;
						}
					}
					# Match Outbound Rules defined as Floating Only.
					if (!empty($match_outbound) && $counter == 0) {
						foreach ($match_outbound as $cb_rules) {
							$cb_rules['interface'] = $pfb['outbound_floating'];
							$new_rules[] = $cb_rules;
							$counter++;
						}
					}
					if (!empty($permit_outbound)) {
						foreach ($permit_outbound as $cb_rules) {
							$cb_rules['interface'] = $outbound_interface;
							$new_rules[] = $cb_rules;
						}
					}
					if (!empty($permit_rules) && $pfb['order'] == "order_2") {
						foreach ($permit_rules as $cb_rules) {
							if ($cb_rules['interface'] == $outbound_interface)
								$new_rules[] = $cb_rules;
						}
					}
					if (!empty($match_rules) && $pfb['order'] == "order_2") {
						foreach ($match_rules as $cb_rules) {
							if ($cb_rules['interface'] == $outbound_interface)
								$new_rules[] = $cb_rules;
						}
					}
					if (!empty($deny_outbound)) {
						foreach ($deny_outbound as $cb_rules) {
							$cb_rules['interface'] = $outbound_interface;
							$new_rules[] = $cb_rules;
						}
					}
				}
			}

			if (!empty($fpermit_rules) && $pfb['order'] == "order_0") {
				foreach ($fpermit_rules as $cb_rules) {
					$new_rules[] = $cb_rules;
				}
			}
			if (!empty($fmatch_rules) && $pfb['order'] == "order_0") {
				foreach ($fmatch_rules as $cb_rules) {
					$new_rules[] = $cb_rules;
				}
			}
			if (!empty($fpermit_rules) && $pfb['order'] == "order_3") {
				foreach ($fpermit_rules as $cb_rules) {
					$new_rules[] = $cb_rules;
				}
			}
			if (!empty($fmatch_rules) && $pfb['order'] == "order_3") {
				foreach ($fmatch_rules as $cb_rules) {
					$new_rules[] = $cb_rules;
				}
			}
			if (!empty($permit_rules) && $pfb['order'] == "order_3") {
				foreach ($permit_rules as $cb_rules) {
					$new_rules[] = $cb_rules;
				}
			}
			if (!empty($match_rules) && $pfb['order'] == "order_3") {
				foreach ($match_rules as $cb_rules) {
					$new_rules[] = $cb_rules;
				}
			}
			if ($pfb['float'] == "on") {
				if (!empty($fother_rules)) {
					foreach ($fother_rules as $cb_rules) {
						$new_rules[] = $cb_rules;
					}
				}
			}
			if (!empty($other_rules)) {
				foreach ($other_rules as $cb_rules) {
					$new_rules[] = $cb_rules;
				}
			}

			# Save New Rule Order to Config
			$config['filter']['rule'] = $new_rules;
		}
		$log = "\n {$message} \n";
		pfb_logger("{$log}","1");

		# UNSET arrays 
		unset ($cb_rules,$permit_inbound,$permit_outbound,$deny_inbound,$deny_outbound,$match_inbound,$match_outbound);
		unset ($other_rules,$fother_rules,$permit_rules,$fpermit_rules,$match_rules,$fmatch_rules);
	}

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

	# Clear any existing pfBlockerNG Cron Jobs
	install_cron_job("pfblockerng.php cron", false);

	# Replace Cron job with any User Changes to $pfb_min
	if ($pfb['enable'] == "on") {
		# 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 )
		$pfb_hour	= "*";
		$pfb_mday	= "*";
		$pfb_month	= "*";
		$pfb_wday	= "*";
		$pfb_who	= "root";

		install_cron_job($pfb_cmd, true, $pfb['min'], $pfb_hour, $pfb_mday, $pfb_month, $pfb_wday, $pfb_who);
	}

	# Clear any existing pfBlockerNG MaxMind CRON Job
	install_cron_job("pfblockerng.php dc", false);

	if ($pfb['enable'] == "on") {
		# Define pfBlockerNG MaxMind CRON Job
		$pfb_gcmd = "/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php dc >> {$pfb['geolog']} 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	= "1,2,3,4,5,6,7";
		$pfb_gmonth	= "*";
		$pfb_gwday	= "2";
		$pfb_gwho	= "root";

		install_cron_job($pfb_gcmd, true, $pfb_gmin, $pfb_ghour, $pfb_gmday, $pfb_gmonth, $pfb_gwday, $pfb_gwho);
	}


	#############################################
	#             Closing Processes             #
	#############################################

	#uncheck Reusing Existing Downloads Check box
	if (!$pfb['save'] && $pfb['enable'] == "on")
		$config['installedpackages']['pfblockerng']['config'][0]['pfb_reuse'] = "";

	# Save all Changes to pfSense config file
	write_config();

	# If 'Rule Changes' are found, utilize the 'filter_configure()' function, if not, utilize 'pfctl replace' command
	if ($pfb['autorules'] && $rules != $new_rules || $pfb['enable'] == "" || $pfb['remove']) {
		require_once("filter.inc");

		$log = "\n===[  Aliastables / Rules  ]================================\n\n";
		pfb_logger("{$log}","1");

		$log = "Firewall Rule Changes Found, Applying Filter Reload \n";
		pfb_logger("{$log}","1");

		# Remove all pfBlockerNG Alias tables
		if (!empty($aliases_list)) {
			foreach ($aliases_list as $table) {
				exec ("/sbin/pfctl -t " . escapeshellarg($table) . " -T kill 2>&1", $pfb_null);
			}
		}

		#load filter file which will create the pfctl tables
		filter_configure();
	} else {
		# Don't Execute on User 'Save'
		if (!$pfb['save']) {

			$log = "\n===[  Aliastables / Rules  ]================================\n\n";
			pfb_logger("{$log}","1");

			$log = "No Changes to Firewall Rules, Skipping Filter Reload \n";
			pfb_logger("{$log}","1");

			// Re-Save Only Aliases that have been updated only.
			// When 'Reputation' is used, all Aliases Need to be Updated.
			$final_alias = array();
			if ($pfb['dedup'] == "on" || $pfb['pdup'] == "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_pfctl = "";
					exec ("/sbin/pfctl -t " . escapeshellarg($final) . " -T replace -f " . $pfb['aliasdir'] . "/" . escapeshellarg($final) . ".txt 2>&1", $result_pfctl);
					$log = implode($result_pfctl);
					pfb_logger("{$log}","1");
				}
			} else {
				$log = "\n No Changes to Aliases, Skipping pfctl Update \n";
				pfb_logger("{$log}","1");
			}
		}
	}
	# UNSET Variables
	unset($rules, $new_rules);

	#sync config
	pfblockerng_sync_on_changes();

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

	# Only run with CRON or Force Invoked Process
	if ((!$pfb['save'] && $pfb['dupcheck'] && $pfb['enable'] == "on") || $pfb['summary']) {
		# Script to run Final Script Processes.
		exec ("{$pfb['script']} closing {$pfb['dup']} >> {$pfb['log']} 2>&1");
	}

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


function pfblockerng_validate_input($post, &$input_errors) {
	global $config;
	foreach ($post as $key => $value) {
		if (empty($value))
			continue;
		if ($key == "message_size_limit" && !is_numeric($value))
			$input_errors[] = "Message size limit must be numeric.";
		if ($key == "process_limit" && !is_numeric($value))
			$input_errors[] = "Process limit must be numeric.";
		if ($key == "freq" && (!preg_match("/^\d+(h|m|d)$/",$value) || $value == 0))
			$input_errors[] = "A valid number with a time reference is required for the field 'Frequency'";
		if (substr($key, 0, 2) == "dc" && !is_hostname($value))
			$input_errors[] = "{$value} is not a valid host name.";
		if (substr($key, 0, 6) == "domain" && is_numeric(substr($key, 6))) {
			if (!is_domain($value))
				$input_errors[] = "{$value} is not a valid domain name.";
		} else if (substr($key, 0, 12) == "mailserverip" && is_numeric(substr($key, 12))) {
			if (empty($post['domain' . substr($key, 12)]))
				$input_errors[] = "Domain for {$value} cannot be blank.";
			if (!is_ipaddr($value) && !is_hostname($value))
				$input_errors[] = "{$value} is not a valid IP address or host name.";
		}
	}
}


function pfblockerng_php_install_command() {
	require_once("/usr/local/www/pfblockerng/pfblockerng.php");
	global $config,$pfb;
	pfb_global();

	# Uncompress Country Code File and delete Archive after extraction.
	exec("cd /{$pfb['ccdir']}; /usr/bin/tar -jxvf {$pfb['ccdir']}/countrycodes.tar.bz2");
	unlink_if_exists("{$pfb['ccdir']}/countrycodes.tar.bz2");
	# Download MaxMind Files and Create Country Code files and Build Continent XML Files
	update_output_window(gettext("Downloading MaxMind Country Databases. This may take a minute..."));
	exec("/bin/sh /usr/local/pkg/pfblockerng/geoipupdate.sh all >> {$pfb['geolog']} 2>&1");
	update_output_window(gettext("MaxMind Country Database downloads completed..."));
	update_output_window(gettext("Converting MaxMind Country Databases for pfBlockerNG. This may take a few minutes..."));
	pfblockerng_uc_countries();
	update_output_window(gettext("Creating pfBlockerNG Continenet XML Files..."));
	pfblockerng_get_countries();
	update_output_window(gettext("Completed Creating pfBlockerNG Continenet XML Files..."));

	# Add Widget to Dashboard
	update_output_window(gettext("Adding pfBlockerNG Widget to Dashboard."));
	if ($pfb['keep'] == "on" && !empty($pfb['widgets'])) {
		// Restore previous Widget setting if "Keep" is enabled.
		$config['widgets']['sequence'] = $pfb['widgets'];
	} else {
		$widgets = $config['widgets']['sequence'];
		if (!preg_match("/pfblockerng-container/", $widgets)) {
			if (empty($widgets)) {
				$config['widgets']['sequence'] = "pfblockerng-container:col2:show";
			} else {
				$config['widgets']['sequence'] .= ",pfblockerng-container:col2:show";
			}
		}
	}
}


function pfblockerng_php_deinstall_command() {
	require_once("config.inc");
	global $config,$pfb;

	# Set these two variables to Disable pfBlockerNG on De-Install
	$pfb['save'] = TRUE;
	$pfb['install'] = TRUE;
	sync_package_pfblockerng();
	rmdir_recursive("/usr/local/pkg/pfblockerng");
	rmdir_recursive("/usr/local/www/pfblockerng");

	# Maintain pfBlockerNG Settings and Database Files if $pfb['keep'] is ON.
	if ($pfb['keep'] != "on") {
		# Remove pfBlockerNG Log and DB Folder
		rmdir_recursive("{$pfb['dbdir']}");
		rmdir_recursive("{$pfb['logdir']}");

		# Remove Settings from Config
		if (is_array($config['installedpackages']['pfblockerng']))
			unset($config['installedpackages']['pfblockerng']);
		if (is_array($config['installedpackages']['pfblockerngglobal']))
			unset($config['installedpackages']['pfblockerngglobal']);
		if (is_array($config['installedpackages']['pfblockerngsync']))
			unset($config['installedpackages']['pfblockerngsync']);
		if (is_array($config['installedpackages']['pfblockerngreputation']))
			unset($config['installedpackages']['pfblockerngreputation']);
		if (is_array($config['installedpackages']['pfblockernglistsv4']))
			unset($config['installedpackages']['pfblockernglistsv4']);
		if (is_array($config['installedpackages']['pfblockernglistsv6']))
			unset($config['installedpackages']['pfblockernglistsv6']);
		if (is_array($config['installedpackages']['pfblockerngafrica']))
			unset($config['installedpackages']['pfblockerngafrica']);
		if (is_array($config['installedpackages']['pfblockerngantartica']))
			unset($config['installedpackages']['pfblockerngantartica']);
		if (is_array($config['installedpackages']['pfblockerngasia']))
			unset($config['installedpackages']['pfblockerngasia']);
		if (is_array($config['installedpackages']['pfblockerngeurope']))
			unset($config['installedpackages']['pfblockerngeurope']);
		if (is_array($config['installedpackages']['pfblockerngnorthamerica']))
			unset($config['installedpackages']['pfblockerngnorthamerica']);
		if (is_array($config['installedpackages']['pfblockerngoceania']))
			unset($config['installedpackages']['pfblockerngoceania']);
		if (is_array($config['installedpackages']['pfblockerngsouthamerica']))
			unset($config['installedpackages']['pfblockerngsouthamerica']);
		if (is_array($config['installedpackages']['pfblockerngtopspammers']))
			unset($config['installedpackages']['pfblockerngtopspammers']);
	}

	# 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 (strstr($widget, "pfblockerng-container")) {
				unset($widgetlist[$key]);
				break;
			}
		}
		$config['widgets']['sequence'] = implode(",", $widgetlist);
	}
	update_output_window(gettext("pfBlockerNG has been Uninstalled"));
}

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

	// Create Array of Sync Settings and exit if Sync is Disabled.
	if (is_array($config['installedpackages']['pfblockerngsync']['config'][0])) {
		$pfb_sync = $config['installedpackages']['pfblockerngsync']['config'][0];
		if ($pfb_sync['varsynconchanges'] == "disabled" || $pfb_sync['varsynconchanges'] == "")
			return;

		$synctimeout = $pfb_sync['varsynctimeout'];
	} else {
		return;
	}

	log_error("[pfBlockerNG] XMLRPC sync is starting.");

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

					// 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";
					} else {
						$rs[0]['varsyncprotocol'] = "https";
					}

					if ($system_carp['synchronizetoip'] == "") {
						log_error("[pfBlockerNG] XMLRPC sync is enabled but there are no replication targets configured.");
						return;
					}
				} else {
					log_error("[pfBlockerNG] XMLRPC sync is enabled but there are no replication targets configured.");
					return;
				}
				break;
			default:
				return;
				break;
		}
		if (is_array($rs)) {
			foreach ($rs as $sh) {
				// Only Sync Enabled Replication Targets
				if ($sh['varsyncdestinenable'] == "ON") {
					$sync_to_ip = $sh['varsyncipaddress'];
					$port = $sh['varsyncport'];
					$password = htmlspecialchars($sh['varsyncpassword']);
					$protocol = $sh['varsyncprotocol'];

					if (!empty($sh['varsyncusername'])) {
						$username = $sh['varsyncusername'];
					} else {
						$username = "admin";
					}

					pfblockerng_do_xmlrpc_sync($sync_to_ip, $port, $protocol, $username, $password, $synctimeout);
				}
			}
			if ($success)
				log_error("[pfBlockerNG] XMLRPC sync completed successfully.");
		}
	}
}


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

	/* Exit on missing parameters */
	if (empty($sync_to_ip) || empty($password)) {
		log_error("[pfBlockerNG] XMLRPC sync parameter missing (host IP or password) ... aborting xmlrpc sync");
		$success = FALSE;
		return $success;
	}

	/* Do not attempt a package sync while booting up or installing package */
	if ($g['booting'] || $g['pfblockerng_postinstall']) {
		log_error("[pfBlockerNG] XMLRPC sync to Replication targets terminated during boot up or during package reinstallation.");
		$success = FALSE;
		return $success;
	}

	// Validate Replication Target IP Address and Port Settings
	if (!is_ipaddr($sync_to_ip) || !is_port($port)) {
		log_error("[pfBlockerNG] XMLRPC sync terminated due to mis-configured Replication Target IP Address or Port settings.");
		$success = FALSE;
		return $success;
	}

	/* Test key variables and set defaults if empty */
	if (empty($synctimeout))
		$synctimeout = 150;

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

	if ($port == "") { $port = $config['system']['webgui']['port']; };
	/* If port is empty lets rely on the protocol selection */
	if ($port == "") {
		if ($config['system']['webgui']['protocol'] == "http") {
			$port = "80";
		} else {
			$port = "443";
		}
	}
	/* xml will hold the sections to sync */
	$xml = array();
	// If User Disabled, remove 'General Tab Customizations' from Sync
	if ($config['installedpackages']['pfblockerngsync']['config'][0]['syncinterfaces'] == "")
		$xml['pfblockerng'] = $config['installedpackages']['pfblockerng'];
	$xml['pfblockerngreputation'] = $config['installedpackages']['pfblockerngreputation'];
	$xml['pfblockernglistsv4'] = $config['installedpackages']['pfblockernglistsv4'];
	$xml['pfblockernglistsv6'] = $config['installedpackages']['pfblockernglistsv6'];
	$xml['pfblockerngtopspammers'] = $config['installedpackages']['pfblockerngtopspammers'];
	$xml['pfblockerngafrica'] = $config['installedpackages']['pfblockerngafrica'];
	$xml['pfblockerngantartica'] = $config['installedpackages']['pfblockerngantartica'];
	$xml['pfblockerngasia'] = $config['installedpackages']['pfblockerngasia'];
	$xml['pfblockerngeurope'] = $config['installedpackages']['pfblockerngeurope'];
	$xml['pfblockerngnorthamerica'] = $config['installedpackages']['pfblockerngnorthamerica'];
	$xml['pfblockerngoceania'] = $config['installedpackages']['pfblockerngoceania'];
	$xml['pfblockerngsouthamerica'] = $config['installedpackages']['pfblockerngsouthamerica'];

	/* 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 */
	log_error("[pfBlockerNG] XMLRPC syncing to {$url}:{$port}.");
	$method = 'pfsense.merge_installedpackages_section_xmlrpc';
	$msg = new XML_RPC_Message($method, $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);
	$error = "";
	if (!$resp) {
		log_error("[pfBlockerNG] XMLRPC communications error occurred while attempting sync with {$url}:{$port}.");
		file_notice("sync_settings", $error, "pfBlockerNG Settings Sync", "");
		$success = FALSE;
		return $success;
	} elseif ($resp->faultCode()) {
		$cli->setDebug(1);
		$resp = $cli->send($msg, $synctimeout);
		log_error("[pfBlockerNG] XMLRPC Error received while attempting sync with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString());
		file_notice("sync_settings", $error, "pfBlockerNG Settings Sync", "");
		$success = FALSE;
		return $success;
	} else {
		log_error("[pfBlockerNG] XMLRPC sync successfully completed with {$url}:{$port}.");
	}
	return $success;
}
?>