Project

General

Profile

Bug #14409 » pfblockerng.inc

Marcelo Cury, 10/01/2025 02:20 PM

 
1
<?php
2
/*
3
 * pfblockerng.inc
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2015-2025 Rubicon Communications, LLC (Netgate)
7
 * Copyright (c) 2015-2024 BBcan177@gmail.com
8
 * All rights reserved.
9
 *
10
 * Licensed under the Apache License, Version 2.0 (the "License");
11
 * you may not use this file except in compliance with the License.
12
 * You may obtain a copy of the License at
13
 *
14
 * http://www.apache.org/licenses/LICENSE-2.0
15
 *
16
 * Unless required by applicable law or agreed to in writing, software
17
 * distributed under the License is distributed on an "AS IS" BASIS,
18
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
 * See the License for the specific language governing permissions and
20
 * limitations under the License.
21
 */
22

    
23
require_once('config.lib.inc');
24
require_once('util.inc');
25
require_once('functions.inc');
26
require_once('pkg-utils.inc');
27
require_once('pfsense-utils.inc');
28
require_once('globals.inc');
29
require_once('services.inc');
30
require_once('service-utils.inc');
31
if (file_exists('/usr/local/pkg/pfblockerng/pfblockerng_extra.inc')) {
32
	require_once('/usr/local/pkg/pfblockerng/pfblockerng_extra.inc');	// 'include functions' not yet merged into pfSense
33
}
34

    
35
global $g, $pfb;
36
if (!is_array($pfb)) {
37
	$pfb = array();
38
}
39

    
40
// Folders
41
$pfb['dbdir']		= "{$g['vardb_path']}/pfblockerng";
42
$pfb['aliasdir']	= "{$g['vardb_path']}/aliastables";
43
$pfb['logdir']		= "{$g['varlog_path']}/pfblockerng";
44
$pfb['etdir']		= "{$pfb['dbdir']}/ET";
45
$pfb['nativedir']	= "{$pfb['dbdir']}/native";
46
$pfb['denydir']		= "{$pfb['dbdir']}/deny";
47
$pfb['matchdir']	= "{$pfb['dbdir']}/match";
48
$pfb['permitdir']	= "{$pfb['dbdir']}/permit";
49
$pfb['origdir']		= "{$pfb['dbdir']}/original";
50
$pfb['dnsdir']		= "{$pfb['dbdir']}/dnsbl";
51
$pfb['dnsorigdir']	= "{$pfb['dbdir']}/dnsblorig";
52
$pfb['dnsalias']	= "{$pfb['dbdir']}/dnsblalias";
53
$pfb['geoipshare']	= '/usr/local/share/GeoIP';
54
$pfb['ccdir']		= '/usr/local/share/GeoIP/cc';
55
$pfb['ccdir_tmp']	= '/tmp/geoip_cc';
56
$pfb['dnsbl_tmp']	= '/tmp/dnsbl_tmp';
57
$pfb['dnsbl_tmpdir']	= '/tmp/DNSBL_TMP';
58

    
59
// Application Paths
60
$pfb['grep']	= '/usr/bin/grep';
61
$pfb['ggrep']	= '/usr/local/bin/ggrep';	// textproc/gnugrep
62
$pfb['awk']	= '/usr/bin/awk';
63
$pfb['cut']	= '/usr/bin/cut';
64
$pfb['sed']	= '/usr/bin/sed';
65
$pfb['cat']	= '/bin/cat';
66
$pfb['ls']	= '/bin/ls';
67
$pfb['pfctl']	= '/sbin/pfctl';
68

    
69
// Folder Array
70
$pfb['folder_array'] = array(	"{$pfb['dbdir']}", "{$pfb['logdir']}", "{$pfb['ccdir']}", "{$pfb['origdir']}", "{$pfb['nativedir']}",
71
				"{$pfb['denydir']}", "{$pfb['matchdir']}","{$pfb['permitdir']}", "{$pfb['aliasdir']}",
72
				"{$pfb['dnsdir']}", "{$pfb['dnsorigdir']}", "{$pfb['dnsalias']}");
73

    
74
// Files
75
$pfb['log']		= "{$pfb['logdir']}/pfblockerng.log";
76
$pfb['dnslog']		= "{$pfb['logdir']}/dnsbl.log";
77
$pfb['dnsreplylog']	= "{$pfb['logdir']}/dns_reply.log";
78
$pfb['ip_blocklog']	= "{$pfb['logdir']}/ip_block.log";
79
$pfb['ip_permitlog']	= "{$pfb['logdir']}/ip_permit.log";
80
$pfb['ip_matchlog']	= "{$pfb['logdir']}/ip_match.log";
81
$pfb['unilog']		= "{$pfb['logdir']}/unified.log";
82
$pfb['errlog']		= "{$pfb['logdir']}/error.log";
83
$pfb['pyerrlog']	= "{$pfb['logdir']}/py_error.log";
84
$pfb['extraslog']	= "{$pfb['logdir']}/extras.log";
85
$pfb['dnsbl_parse_err']	= "{$pfb['logdir']}/dnsbl_parsed_error.log";
86

    
87
$pfb['master']		= "{$pfb['dbdir']}/masterfile";
88
$pfb['supptxt']		= "{$pfb['dbdir']}/pfbsuppression.txt";
89
$pfb['dnsbl_supptxt']	= "{$pfb['dbdir']}/pfbdnsblsuppression.txt";
90
$pfb['geoip_isos']	= "{$pfb['dbdir']}/geoip.txt";
91
$pfb['dnsbl_info']	= '/var/unbound/pfb_py_dnsbl.sqlite';
92
$pfb['dnsbl_resolver']	= '/var/unbound/pfb_py_resolver.sqlite';
93
$pfb['dnsbl_cache']	= '/var/unbound/pfb_py_cache.sqlite';
94
$pfb['asn_cache']	= "{$pfb['dbdir']}/asn_cache.sqlite";
95
$pfb['ip_cache']	= "{$pfb['dbdir']}/ip_cache.sqlite";
96

    
97
$pfb['script']		= '/usr/local/pkg/pfblockerng/pfblockerng.sh';
98
$pfb['feeds']		= '/usr/local/www/pfblockerng/pfblockerng_feeds.json';
99
$pfb['aliasarchive']	= '/usr/local/etc/aliastables.tar.bz2';
100

    
101
$pfb['dnsbl_tld_txt']	= "{$pfb['dnsdir']}/DNSBL_TLD.txt";
102
$pfb['dnsbl_tld_data']	= '/usr/local/pkg/pfblockerng/dnsbl_tld';
103
$pfb['dnsbl_conf']	= '/var/unbound/pfb_dnsbl_lighty.conf';
104
$pfb['dnsbl_cert']	= '/var/unbound/dnsbl_cert.pem';
105
$pfb['unbound_py_conf']	= '/var/unbound/pfb_unbound.ini';
106
$pfb['unbound_py_wh']	= '/var/unbound/pfb_py_whitelist.txt';
107
$pfb['unbound_py_data']	= '/var/unbound/pfb_py_data.txt';
108
$pfb['unbound_py_zone'] = '/var/unbound/pfb_py_zone.txt';
109
$pfb['unbound_py_ss']	= '/var/unbound/pfb_py_ss.txt';
110
$pfb['unbound_py_count']= '/var/unbound/pfb_py_count';
111

    
112
$pfb['dnsbl_safesearch']		= '/usr/local/pkg/pfblockerng/pfb_dnsbl.safesearch.conf';
113
$pfb['dnsbl_youtube_restrict']		= '/usr/local/pkg/pfblockerng/pfb_dnsbl.youtube_restrict.conf';
114
$pfb['dnsbl_youtube_restrictmoderate']	= '/usr/local/pkg/pfblockerng/pfb_dnsbl.youtube_restrictmoderate.conf';
115
$pfb['dnsbl_doh']			= '/usr/local/pkg/pfblockerng/pfb_dnsbl.doh.conf';
116

    
117
// tmp files
118
$pfb['geoip_tmp']		= '/tmp/pfb_continent';
119
$pfb['ip_unlock']		= '/tmp/ip_unlock';
120

    
121
$pfb['dnsbl_tld_remove']	= '/tmp/dnsbl_tld_remove';
122
$pfb['dnsbl_add']		= '/tmp/dnsbl_add';
123
$pfb['dnsbl_add_zone']		= '/tmp/dnsbl_add_zone';
124
$pfb['dnsbl_add_data']		= '/tmp/dnsbl_add_data';
125
$pfb['dnsbl_remove']		= '/tmp/dnsbl_remove';
126
$pfb['dnsbl_remove_zone']	= '/tmp/dnsbl_remove_zone';
127
$pfb['dnsbl_remove_data']	= '/tmp/dnsbl_remove_data';
128
$pfb['dnsbl_unlock']		= '/tmp/dnsbl_unlock';
129
$pfb['states_tmp']		= '/tmp/pfb_states';
130

    
131
// Unbound files and folders
132
$pfb['dnsbl_file']	= '/var/unbound/pfb_dnsbl';	// Filename Extension not referenced
133
$pfb['dnsbldir']	= '/var/unbound';
134

    
135
// Array definitions
136
$pfb['continents'] = array (	'Top Spammers'		=> 'pfB_Top',
137
				'Africa'		=> 'pfB_Africa',
138
				'Antarctica'		=> 'pfB_Antarctica',
139
				'Asia'			=> 'pfB_Asia',
140
				'Europe'		=> 'pfB_Europe',
141
				'North America'		=> 'pfB_NAmerica',
142
				'Oceania'		=> 'pfB_Oceania',
143
				'South America'		=> 'pfB_SAmerica',
144
				'Proxy and Satellite'	=> 'pfB_PS'
145
				);
146

    
147
$pfb['continent_list'] = array_flip(array('pfB_Africa', 'pfB_Antarctica', 'pfB_Asia', 'pfB_Europe', 'pfB_NAmerica', 'pfB_Oceania', 'pfB_SAmerica', 'pfB_Top'));
148

    
149
// Base rule array
150
$pfb['base_rule_reg'] = array('ipprotocol' => 'inet');
151

    
152
// Floating rules, base rule array
153
$pfb['base_rule_float'] = array('quick' => 'yes', 'floating' => 'yes', 'ipprotocol' => 'inet');
154

    
155
// Define Arrays for managing the IP mastefile
156
foreach (array('existing', 'actual') as $pftype) {
157
	$pfb[$pftype]['match']	= array();
158
	$pfb[$pftype]['permit']	= array();
159
	$pfb[$pftype]['deny']	= array();
160
	$pfb[$pftype]['native']	= array();
161
	$pfb[$pftype]['dnsbl']	= array();
162
}
163

    
164
// Default cURL options
165
$pfb['curl_defaults'] = array(  CURLOPT_USERAGENT	=> 'pfSense/pfBlockerNG cURL download agent-' . system_get_uniqueid(),
166
				CURLOPT_SSL_CIPHER_LIST	=> 'TLSv1.3, TLSv1.2',
167
				CURLOPT_FOLLOWLOCATION	=> true,
168
				CURLOPT_SSL_VERIFYPEER	=> true,
169
				CURLOPT_SSL_VERIFYHOST	=> true,
170
				CURLOPT_FRESH_CONNECT	=> true,
171
				CURLOPT_FILETIME	=> true,
172
				CURLOPT_TCP_NODELAY	=> true,
173
				CURLOPT_CONNECTTIMEOUT	=> 15,
174
				CURLOPT_AUTOREFERER	=> true,
175
				CURLOPT_MAXREDIRS	=> 10,
176
				CURLOPT_HTTP_VERSION	=> CURL_HTTP_VERSION_NONE,
177
				CURLOPT_FORBID_REUSE	=> true,
178
				CURLOPT_SSL_ENABLE_ALPN	=> true,
179
				CURLOPT_SSL_ENABLE_NPN	=> true,
180
				);
181

    
182
// RFC7231 HTTP response codes
183
$pfb['rfc7231'] = array(
184
			1 => 'Unsupported Protocol',		2 => 'Early Initilization failed',		3 => 'Malformed URL',
185
			4 => 'Requested feature not found',	5 => 'Could not Resolve Proxy',			6 => 'Could not Resolve Host',
186
			7 => 'Failed to connect',		8 => 'Failed to parse data',			9 => 'Denied access',
187
			10 => 'FTP failed',			11 => 'FTP password failure',			12 => 'FTP Accept timeout',
188
			13 => 'FTP PASV failure',		14 => 'FTP 227 Format failure',			15 => 'FTP Host lookup failure',
189
			16 => 'HTTP2 framing layer failure',	17 => 'FTP Could not set transfer mode',	18 => 'File Transfer failure',
190
			19 => 'FTP weird reply',		20 => 'Obsolete error',				21 => 'FTP quote command error',
191
			22 => 'HTTP Returned Error code',	23 => 'Write Error',				24 => 'Obsolete error',
192
			25 => 'FTP failted starting upload',	26 => 'Read Error',				27 => 'Memory allocation request error',
193
			28 => 'Operation timed out',		29 => 'Obsolete error',				30 => 'FTP Port command failure',
194
			31 => 'FTP REST failure',		32 => 'Obsolete error',				33 => 'Range Request failure',
195
			34 => 'HTTP Post error',		35 => 'SSL Connect error',			37 => 'Could not read file',
196
			38 => 'LDAP Cannot bind',		39 => 'LDAP Search failed',			40 => 'Obsolete error',
197
			41 => 'Function not found',		42 => 'Aborted by callback',			43 => 'Bad parameter',
198
			44 => 'Obsolete error',			45 => 'Interface failure',			46 => 'Obsolete error',
199
			47 => 'Too many redirects',		48 => 'Unknown option',				49 => 'Setopt Syntax error',
200
			50 => 'Obsolete error',			51 => 'Obsolete error',				52 => 'Curl got nothing',
201
			53 => 'SSL Engine not found',		54 => 'SSL Engine failure',			55 => 'Failed sending data',
202
			56 => 'Failure receving data',		57 => 'Obsolete error',				58 => 'Local client certificate error',
203
			59 => 'Cipher error',			60 => 'SSL certificate error',			61 => 'Transfer encoding error',
204
			62 => 'Obsolete error',			63 => 'Maximum file size exceeded',		64 => 'FTP SSL level failure',
205
			65 => 'Rewinding operation failed',	66 => 'SSL Engine failure',			67 => 'Login Denied',
206
			68 => 'TFTP server not found',		69 => 'TFTP permission error',			70 => 'Out of disk space on server',
207
			71 => 'Illegal TFTP operation',		72 => 'Unknown TFTP transfer ID',		73 => 'File already exists',
208
			74 => 'TFTP no such user',		75 => 'Obsolete error',				76 => 'Obsolete error',
209
			77 => 'SSL CA certificate error',	78 => 'Remote file not found',			79 => 'SSH connection failure',
210
			80 => 'SSH connection failure',		81 => 'Socket is not ready',			82 => 'CRL Bad File error',
211
			83 => 'SSL Issurer failure',		84 => 'FTP PRET failure',			85 => 'RTSP CSeq number error',
212
			86 => 'RTSP Session mismatch',		87 => 'FTP Bad file list',			88 => 'Chunk callback error',
213
			89 => 'No connection available',	90 => 'Pin Key match failure',			91 => 'Invalid Certificate status',
214
			92 => 'HTTP/2 framing error',		93 => 'Recursive API call error',		94 => 'Authentication function error',
215
			95 => 'HTTP/3 layer error',
216
			96 => 'QUIC connect error',		97 => 'Proxy handshake error',			98 => 'SSL Client certificate required',
217
			99 => 'Unrecoveranle Poll error',
218

    
219
			100 => '100 Continue',			101 => '101 Switching Protocols',		102 => '102 Processing',
220

    
221
			200 => '200 OK',			201 => '201 Created',				202 => '202 Accepted',
222
			203 => '203 Non-Authoritative Info',	204 => '204 No Content',			205 => '205 Reset Content',
223
			206 => '206 Partial Content',		207 => '207 Multi-Status',			208 => '208 Already Reported',
224
			226 => '226 IM Used',
225

    
226
			300 => '300 Multiple Choices',		301 => '301 Moved Permanently',			302 => '302 Found',
227
			303 => '303 See Other',			304 => '304 Not Modified',			305 => '305 Use Proxy',
228
			306 => '306 Switch Proxy',		307 => '307 Temporary Redirect',		308 => '308 Permanent Redirect',
229

    
230
			400 => '400 Bad Request',		401 => '401 Unauthorized',			402 => '402 Payment Required',
231
			403 => '403 Forbidden',			404 => '404 Not Found',				405 => '405 Method Not Allowed',
232
			406 => '406 Not Acceptable',		407 => '407 Proxy Authentication Required',	408 => '408 Request Timeout',
233
			409 => '409 Conflict',			410 => '410 Gone',				411 => '411 Length Required',
234
			412 => '412 Precondition Failed',	413 => '413 Request Entity Too Large',		414 => '414 Request-URI Too Long',
235
			415 => '415 Unsupported Media Type',	416 => '416 Requested Range Not Satisfiable',	417 => '417 Expectation Failed',
236
			418 => '418 Im a teapot',		419 => '419 Authentication Timeout',		420 => '420 Method Failure',
237
			421 => '421 Misdirected Request',	422 => '422 Unprocessable Entity',		423 => '423 Locked',
238
			424 => '424 Failed Dependency',		426 => '426 Upgrade Required',			428 => '428 Precondition Required',
239
			429 => '429 Too Many Requests',		431 => '431 Request Header Fields Large',	440 => '440 Login Timeout',
240
			444 => '444 No Response',		449 => '449 Retry With',			450 => '450 Blocked Windows Parental Controls',
241
			451 => '451 Unavailable Legal Reasons',	494 => '494 Request Header too Large',		495 => '495 Cert Error',
242
			496 => '496 No Cert',			497 => '497 HTTP to HTTPS',			498 => '498 Token expired/invalid',
243
			499 => '499 Client Closed Request',
244

    
245
			500 => '500 Internal Server Error',	501 => '501 Not Implemented',			502 => '502 Bad Gateway',
246
			503 => '503 Service Unavailable',	504 => '504 Gateway Timeout',			505 => '505 HTTP Version Not Supported',
247
			506 => '506 Variant Also Negotiates',	507 => '507 Insufficient Storage',		508 => '508 Loop Detected',
248
			509 => '509 Bandwidth Limit Exceeded',	510 => '510 Not Extended',			511 => '511 Network Authentication Required',
249
			521 => '521 Web Server is down',	598 => '598 Network read timeout error',	599 => '599 Network connect timeout error',
250

    
251
			520 => 'CF 520 Unknown Error',		521 => 'CF 521 Web Server is Down',		522 => 'CF 522 Connection Timed Out',
252
			523 => 'CF 523 Origin is Unreachable',	524 => 'CF 524 A Timeout Occured',		525 => 'CF 525 SSL Handshake Failed',
253
			526 => 'CF 526 Invalid SSL Certificate',527 => 'CF 527 Railgun Error'
254
			);
255

    
256
// File download Mime-Types
257
$pfb['mime_types'] = array_flip(array(	'inode/x-empty',
258
					'text/x-file',
259
					'text/plain',
260
					'text/html',
261
					'text/xml',
262
					'text/csv',
263
					'application/csv',
264
					'application/json',
265
					'application/x-ndjson',
266
					'application/x-tar',
267
					'application/gzip',
268
					'application/x-gzip',
269
					'application/x-bzip2',
270
					'application/zip'));
271

    
272
// pfb_filter constants
273
define('PFB_FILTER_HTML', 1);
274
define('PFB_FILTER_URL', 2);
275
define('PFB_FILTER_WORD', 3);
276
define('PFB_FILTER_WORD_DOT', 4);
277
define('PFB_FILTER_TLD', 5);
278
define('PFB_FILTER_DOMAIN', 6);
279
define('PFB_FILTER_HOSTNAME', 7);
280
define('PFB_FILTER_IPV4', 8);
281
define('PFB_FILTER_IP', 9);
282
define('PFB_FILTER_ALPHA', 10);
283
define('PFB_FILTER_ALNUM', 11);
284
define('PFB_FILTER_NUM', 12);
285
define('PFB_FILTER_CSV', 13);
286
define('PFB_FILTER_CSV_WHOIS', 14);
287
define('PFB_FILTER_CSV_CRON', 15);
288
define('PFB_FILTER_FILE_MIME_COMPARE', 16);
289
define('PFB_FILTER_FILE_MIME', 17);
290
define('PFB_FILTER_FILE_MIME_COMPRESSED', 18);
291
define('PFB_FILTER_ATYPE', 19);
292
define('PFB_FILTER_HEX_COLOR', 20);
293
define('PFB_FILTER_ON_OFF', 21);
294

    
295
// Function to filter/sanitize user input
296
function pfb_filter($input, $type, $reference='Unknown', $default='', $escape=FALSE) {
297
	global $pfb;
298

    
299
	$header = "\n PFB_FILTER - {$type} | {$reference} [ NOW ]";
300

    
301
	$return_type = $default;
302
	if (in_array($type, array(PFB_FILTER_URL, PFB_FILTER_FILE_MIME_COMPARE, PFB_FILTER_FILE_MIME, PFB_FILTER_FILE_MIME_COMPRESSED))) {
303
		$return_type = FALSE;
304
	}
305

    
306
	if (!in_array($type, array(PFB_FILTER_ON_OFF, PFB_FILTER_NUM))) {
307
		if (empty($input) || is_null($input)) {
308
			return $return_type;
309
		}
310
	}
311

    
312
	// Check for control characters
313
	if (is_array($input)) {
314
		foreach ($input as $vline) {
315
			if (preg_match("/[\p{C}]+/", $vline)) {
316
				pfb_logger("{$header} Control characters found [ " . htmlspecialchars($vline) . " ]", 6); 
317
				return $return_type;
318
			}
319
		}
320
	}
321
	else {
322
		if (preg_match("/[\p{C}]+/", $input)) {
323
			pfb_logger("{$header} Control characters found [ " . htmlspecialchars($input) . " ]", 6);
324
			return $return_type;
325
		}
326
	}
327

    
328
	$result = FALSE;
329
	switch ($type) {
330
		case PFB_FILTER_HTML:
331
			$result = htmlspecialchars(trim($input));
332
			break;
333
		case PFB_FILTER_URL:
334
			// Validate URL input
335
			$is_RSYNC = FALSE;
336
			if (strpos($input, '::') !== FALSE && !$escape) {
337
				$rsync = explode('::', $input);
338
				if (count($rsync) == 2 && !empty($rsync[0]) && filter_var($rsync[0], FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
339
					$is_RSYNC = TRUE;
340
				}
341
			}
342

    
343
			if ($is_RSYNC || filter_var($input, FILTER_VALIDATE_URL)) {
344
				if ($is_RSYNC) {
345
					$data = array('host' => $rsync[0], 'path' => $rsync[1]);
346
				} else {
347
					$data = parse_url($input);
348
					if (!in_array($data['scheme'], array('http', 'https', 'rsync', 'ftp'))) {
349
						pfb_logger("{$header} Invalid URL Scheme [ " . htmlspecialchars($input) . " ]", 6);
350
						return FALSE;
351
					}
352
				}
353

    
354
				$validate_list = '';
355
				if (!$data || !is_array($data)) {
356
					pfb_logger("{$header} Invalid URL (missing data) [ " . htmlspecialchars($input) . " ]", 6);
357
					return FALSE;
358
				} elseif (!isset($data['host']) || empty($data['host'])) {
359
					pfb_logger("{$header} Invalid URL (missing hostname) [ " . htmlspecialchars($input) . " ]", 6);
360
					return FALSE;
361
				} elseif (is_ipaddr($data['host'])) {
362
					$validate_list = array(array('type' => 'IP', 'data' => $data['host']));
363
				} else {
364
					$validate_list = resolve_host_addresses("{$data['host']}.");
365
				}
366

    
367
				$pfsense_configured = FALSE;
368
				if (!empty($validate_list) && is_array($validate_list)) {
369
					foreach ($validate_list as $validate) {
370
						if ($validate['type'] == 'CNAME' && !empty($validate['data'])) {
371

    
372
							if (is_ipaddr($validate['data'])) {
373
								$cname_list = resolve_host_addresses($validate['data']);
374
							} else {
375
								$cname_list = resolve_host_addresses("{$validate['data']}.");
376
							}
377

    
378
							if (!empty($cname_list) && is_array($cname_list)) {
379
								foreach ($cname_list as $cname) {
380
									if (!empty($cname['data']) && is_ipaddr_configured($cname['data'])) {
381
										$pfsense_configured = TRUE;
382
										break 2;
383
									}
384
								}
385
							}
386
						}
387
						if (!empty($validate['data']) && is_ipaddr_configured($validate['data'])) {
388
							$pfsense_configured = TRUE;
389
							break;
390
						}
391
					}
392
				}
393
				else {
394
					// Cannot resolve host
395
					pfb_logger("{$header} Invalid URL (cannot resolve) [ " . htmlspecialchars($input) . " ]", 6);
396
					return FALSE;
397
				}
398

    
399
				$path = pathinfo($data['path'], PATHINFO_DIRNAME);
400

    
401
				// Validate only pfSense URLS (localfile check and Alerts Tab refresh)
402
				if ($escape) {
403
					if ($pfsense_configured ||
404
					    ($data['host'] == '127.0.0.1') ||
405
					    ($data['host'] == '::1')) {
406

    
407
						// Allow '/usr/local/www'
408
						if ($path == '/') {
409
							return TRUE;
410
						}
411
					}
412

    
413
					if ($reference == 'alerts refresh') {
414
						pfb_logger("{$header} Invalid URL (alerts tab) [ " . htmlspecialchars($input) . " ]", 6);
415
					}
416
					return FALSE;
417
				}
418

    
419
				// pfSense URL
420
				if (($pfsense_configured) ||
421
				    ($data['host'] == '127.0.0.1') ||
422
				    ($data['host'] == '::1')) {
423

    
424
					// Allow '/usr/local/www'
425
					if ($path == '/') {
426
						return TRUE;
427
					}
428
					pfb_logger("{$header} Invalid URL (not allowed) [ " . htmlspecialchars($input) . " ]", 6);
429
					return FALSE;
430
				}
431

    
432
				// all other remote URLs no path validation
433
				return TRUE;
434
			}
435

    
436
			// Local file path validation
437
			else {
438
				$path		= pathinfo($input, PATHINFO_DIRNAME) . '/';
439
				$allowed_path	= array_flip(array(	'/var/db/pfblockerng/',
440
									'/var/db/pfblockerng/deny/',
441
									'/var/db/pfblockerng/permit/',
442
									'/var/db/pfblockerng/match/',
443
									'/var/db/pfblockerng/native/',
444
									'/var/db/pfblockerng/original/',
445
									'/var/db/pfblockerng/dnsbl/',
446
									'/var/db/pfblockerng/dnsblorig/',
447
									'/var/db/pfblockerng/ET/',
448
									'/var/db/pfblockerng/ut1/',
449
									'/var/db/pfblockerng/shallalist/',
450
									'/usr/local/www/',
451
									'/usr/local/share/GeoIP/',
452
									'/usr/local/share/GeoIP/cc/' ));
453
				if (isset($allowed_path[$path])) {
454
					return TRUE;
455
				}
456
			}
457
			pfb_logger("\n[PFB_FILTER - {$type}] Invalid URL (not allowed2) [ " . htmlspecialchars($input) . " ]", 6);
458
			return FALSE;
459
			break;
460
		case PFB_FILTER_WORD:
461
			// Validate for 'Any word character (letter, number, underscore)'
462
			if (!preg_match("/\W/", $input)) {
463
				$result = htmlspecialchars($input);
464
			}
465
			break;
466
		case PFB_FILTER_WORD_DOT:
467
			// Validate for '(letter, number, underscore, dash) dot (letter, number, underscore, dash)'
468
			if (preg_match("/^[a-zA-Z0-9_\-]+\.{1}[a-zA-Z0-9_\-]+$/", $input)) {
469
				$result = htmlspecialchars($input);
470
			}
471
			break;
472
		case PFB_FILTER_TLD:
473
			// Validate TLD
474
			if (preg_match("/^[a-zA-Z0-9_\.\-]+$/", $input)) {
475
				$result = htmlspecialchars($input);
476
			}
477
			break;
478
		case PFB_FILTER_DOMAIN:
479
			// Validate domain
480
			if ((strpos($input, '.') !== FALSE) &&			// Exclude no dots
481
			    (strpos($input, '..') === FALSE) &&			// Exclude double dot
482
			    (strlen($input) < 255) &&				// Validate length of domain (Max 255 chars)
483
			    (pfb_validate_domain_labels($input) !== FALSE) &&	// Validate length of labels (Max of 63 chars)
484
			    (preg_match("/^[a-zA-Z0-9_\.\-]+$/", $input))) {  	// Exclude any other characters
485
				$result = TRUE;
486
			}
487
			if ($result) {
488
				$result = htmlspecialchars($input);
489
			}
490
			break;
491
		case PFB_FILTER_HOSTNAME:
492
			// Validate hostname
493
			if (is_hostname($input)) {
494
				$result = htmlspecialchars($input);
495
			}
496
			break;
497
		case PFB_FILTER_IPV4:
498
			// Validate IPv4
499
			if (is_ipaddrv4($input)) {
500
				$result = htmlspecialchars($input);
501
			}
502
			break;
503
		case PFB_FILTER_IP:
504
			// Validate any IP address v4/v6
505
			if (is_ipaddr($input)) {
506
				$result = htmlspecialchars($input);
507
			}
508
			break;
509
		case PFB_FILTER_ALPHA:
510
			// Validate input is alphabetic only
511
			if (ctype_alpha($input)) {
512
				$result = htmlspecialchars($input);
513
			}
514
			break;
515
		case PFB_FILTER_ALNUM:
516
			// Validate input is alphanumeric only
517
			if (ctype_alnum($input)) {
518
				$result = htmlspecialchars($input);
519
			}
520
			break;
521
		case PFB_FILTER_NUM:
522
			// Validate input is number only
523
			if (preg_match("/^[0-9]+$/", $input)) {
524
				$result = htmlspecialchars($input);
525
			}
526
			break;
527
		case PFB_FILTER_CSV:
528
			// Validate CSV string
529
			if (preg_match("/^[a-zA-Z0-9,_-]+$/", $input)) {
530
				$result = htmlspecialchars($input);
531
			}
532
			break;
533
		case PFB_FILTER_CSV_WHOIS:
534
			// Validate Whoisconvert string
535
			if (preg_match("/^[a-zA-Z0-9,\._\-]+$/", $input)) {
536
				$result = htmlspecialchars($input);
537
			}
538
			break;
539
		case PFB_FILTER_CSV_CRON:
540
			// Validate CSV string (cron hour setting)
541
			if ($input == '*' || preg_match("/^[0-9,]+$/", $input)) {
542
				$result = htmlspecialchars($input);
543
			}
544
			break;
545
		case PFB_FILTER_FILE_MIME_COMPARE:
546
			// Validate mime-type
547
			// $input [0] path/file, [1] mime-type to be validated against
548
			if (isset($retval)) {
549
				unset($retval);
550
			}
551
			if (isset($output)) {
552
				unset($output);
553
			}
554
			if (!file_exists($input[0])) {
555
				pfb_logger("{$header} Invalid Mime-type (file missing): [" .  htmlspecialchars($input[0]) . "|" . htmlspecialchars($input[1]) . "]", 2);
556
				return FALSE;
557
			}
558
			exec("/usr/bin/file -b --mime-type " . escapeshellarg($input[0]) . " 2>&1", $output, $retval);
559
			if ($retval != 0 || empty($output[0]) || $output[0] !== $input[1]) {
560
				pfb_logger("{$header} Invalid Mime-type: [" .  htmlspecialchars($input[0]) . "|" . htmlspecialchars($input[1]) . "]", 2);
561
				return FALSE;
562
			}
563
			return TRUE;
564
			break;
565
		case PFB_FILTER_FILE_MIME:
566
			// Validate File Mime-types
567
			// $input [0] path/file escaped, [1] path/file [2] URL
568

    
569
			if (isset($retval)) {
570
				unset($retval);
571
			}
572
			if (isset($output)) {
573
				unset($output);
574
			}
575
			if (!file_exists($input[1])) {
576
				pfb_logger("{$header} Downloaded file not found: [" .  htmlspecialchars($input[0]) . "|" . htmlspecialchars($input[1]) . "]", 2);
577
				return FALSE;
578
			}
579
			exec("/usr/bin/file -b --mime-type {$input[0]} 2>&1", $output, $retval);
580
			if ($retval != 0 || empty($output[0]) || !isset($pfb['mime_types'][$output[0]])) {
581

    
582
				// Exceptions
583
				$hostname = parse_url($input[2], PHP_URL_HOST);
584
				if ($output[0] == 'text/x-asm' &&
585
				    ($hostname == 'easylist-downloads.adblockplus.org' || $hostname == 'easylist.to' )) {
586
					$output[0] = 'text/plain';
587
				}
588
				elseif ($output[0] == 'application/octet-stream' && $hostname == 'ipinfo.io') {
589
					$output[0] = 'text/plain';
590
				}
591
				else {
592
					pfb_logger("\n[PFB_FILTER - {$type}] Failed or invalid Mime Type: [" .  htmlspecialchars($output[0]) . "|" . htmlspecialchars($retval) . "]", 2);
593
					unlink_if_exists($input[1]);
594
					return FALSE;
595
				}
596
			}
597
			$result = htmlspecialchars($output[0]);
598
			break;
599
		case PFB_FILTER_FILE_MIME_COMPRESSED:
600
			// Validate File Mime-types in compressed files
601
			// $input [0] path/file escaped, [1] path/file [2] URL
602
			if (isset($retval)) {
603
				unset($retval);
604
			}
605
			if (isset($output)) {
606
				unset($output);
607
			}
608
			if (!file_exists($input[1])) {
609
				pfb_logger("{$header} Downloaded file not found: [" .  htmlspecialchars($input[0]) . "|" . htmlspecialchars($input[1]) . "]", 2);
610
				return FALSE;
611
			}
612
			exec("/usr/bin/file -bZ --mime-type {$input[0]} 2>&1", $output, $retval);
613
			if ($retval != 0 || empty($output[0]) || !isset($pfb['mime_types'][$output[0]])) {
614
				pfb_logger("{$header} Failed or invalid Mime Type Compressed: [" .  htmlspecialchars($output[0]) . "|" . htmlspecialchars($retval) . "]", 2);
615
				unlink_if_exists($input[1]);
616
				return FALSE;
617
			}
618
			$result = htmlspecialchars($output[0]);
619
			break;
620
		case PFB_FILTER_ATYPE:
621
			// Validate atype entry in category_edit.php
622
			if (preg_match("/^[a-zA-Z0-9\.|_]+$/", $input)) {
623
				$result = htmlspecialchars($input);
624
			}
625
			break;
626
		case PFB_FILTER_HEX_COLOR:
627
			// Alerts Tab - Hex code validation
628
			if ($input == 'none' || preg_match("/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/", $input)) {
629
				$result = htmlspecialchars($input);
630
			}
631
			break;
632
		case PFB_FILTER_ON_OFF:
633
			// Validate on or off
634
			if ($input == 'on' || $input == '') {
635
				$result = htmlspecialchars($input);
636
			} else {
637
				pfb_logger("{$header} Invalid on/off [ " . htmlspecialchars($input) . " ]", 6);
638
			}
639
			break;
640
		default:
641
			pfb_logger("{$header} Type invalid [ " . htmlspecialchars($input) . " ]", 6);
642
			break;
643
	}
644

    
645
	if ($result && $escape) {
646
		$result = escapeshellarg($result);
647
	}
648

    
649
	// Log validation errors
650
	if (!empty($input) && empty($result) &&
651
	    !in_array($type, array(PFB_FILTER_URL, PFB_FILTER_FILE_MIME_COMPARE, PFB_FILTER_FILE_MIME, PFB_FILTER_FILE_MIME_COMPRESSED, PFB_FILTER_ON_OFF, PFB_FILTER_NUM))) {
652

    
653
		// Exceptions
654
		if (in_array($reference, array(	'DNSBL_Download'))) {
655
			//
656
		} else {
657
			pfb_logger("{$header} Failed validation [ " . htmlspecialchars($input) . " ]", 6);
658
		}
659
	}
660

    
661
	return $result == FALSE ? $default : $result;
662
}
663

    
664

    
665
// Validate length of labels (Max of 63 chars)
666
function pfb_validate_domain_labels($input) {
667

    
668
	$labels = explode('.', $input);
669
	if (!empty($labels) && is_array($labels)) {
670
		foreach ($labels as $label) {
671
			if (strlen($label) > 63) {
672
				return FALSE;
673
			}
674
		}
675
		return TRUE;
676
	}
677
	return FALSE;
678
}
679

    
680

    
681
// [ $pfb ] pfBlockerNG global array. This needs to be called to get the updated settings.
682
function pfb_global() {
683
	global $g, $pfb;
684

    
685
	// Create folders if not exist.
686
	foreach ($pfb['folder_array'] as $folder) {
687
		safe_mkdir("{$folder}", 0755);
688
	}
689

    
690
	// Reload config.xml to get any recent changes
691
	config_read_file(false, true);
692

    
693
	// General variables
694
	$pfb['config']		= config_get_path('installedpackages/pfblockerng/config/0', []);
695
	$pfb['ipconfig']	= config_get_path('installedpackages/pfblockerngipsettings/config/0', []);
696
	$pfb['dnsblconfig']	= config_get_path('installedpackages/pfblockerngdnsblsettings/config/0', []);
697
	$pfb['blconfig']	= config_get_path('installedpackages/pfblockerngblacklist', []);
698
	$pfb['config_global']	= config_get_path('installedpackages/pfblockerngglobal', []);
699

    
700
	$pfb['enable']		= $pfb['config']['enable_cb'];			// Enable/Disable of pfBlockerNG
701
	$pfb['keep']		= $pfb['config']['pfb_keep'];			// Keep blocklists on pfBlockerNG Disable
702
	$pfb['interval']	= $pfb['config']['pfb_interval']	?: '1';	// Hour cycle for scheduler
703

    
704
	// Validate Cron settings
705
	if (!is_numeric($pfb['interval'])) {
706
		$pfb['interval'] = '1';
707
	}
708
        foreach (array( 'pfb_min'		=> 'min',			// User defined CRON start minute
709
			'pfb_hour'		=> 'hour',			// Start hour of the scheduler
710
			'pfb_dailystart'	=> '24hour'			// Start hour of the 'Once a day' schedule
711
		) as $conf_value => $pfb_value) {
712

    
713
		$pfb_variable = $pfb['config'][$conf_value] ?: '0';
714
		if (!is_numeric($pfb_variable)) {
715
			$pfb[$pfb_value] = '0';
716
		} else {
717
			$pfb[$pfb_value] = $pfb_variable;
718
		}
719
	}
720

    
721
	$pfb['supp']		= $pfb['ipconfig']['suppression'];			// Enable Suppression
722
	$pfb['cc']		= $pfb['ipconfig']['database_cc'];			// Disable Country database CRON updates
723
	$pfb['maxmind_locale']	= $pfb['ipconfig']['maxmind_locale']	?: 'en';	// MaxMind Localized Language setting
724
	$pfb['asn_reporting']	= $pfb['ipconfig']['asn_reporting']	?: 'disabled';	// ASN Reporting
725
	$pfb['asn_token']	= $pfb['ipconfig']['asn_token']		?: '';		// ASN Token (IPinfo)
726

    
727
	$pfb['maxmind_account']	= pfb_filter($pfb['ipconfig']['maxmind_account'], PFB_FILTER_WORD, 'pfb_global')	?: '';		// Maxmind Account ID
728
	$pfb['maxmind_key']	= pfb_filter($pfb['ipconfig']['maxmind_key'], PFB_FILTER_WORD, 'pfb_global')		?: '';		// Maxmind License Key
729

    
730
	$pfb['dnsbl']		= $pfb['dnsblconfig']['pfb_dnsbl'];			// Enabled state of DNSBL
731
	$pfb['dnsbl_vip_type']	= $pfb['dnsblconfig']['pfb_dnsvip_type'] ?: 'ipalias';	// Virtual IP type
732

    
733
	$pfb['dnsbl_vip_vhid']	= isset($pfb['dnsblconfig']['pfb_dnsvip_vhid']) ? $pfb['dnsblconfig']['pfb_dnsvip_vhid'] : '';		// Virtual IP Carp VHID
734
	$pfb['dnsbl_vip_base']	= isset($pfb['dnsblconfig']['pfb_dnsvip_base']) ? $pfb['dnsblconfig']['pfb_dnsvip_base'] : '';		// Virtual IP Carp advbase
735
	$pfb['dnsbl_vip_skew']	= isset($pfb['dnsblconfig']['pfb_dnsvip_skew']) ? $pfb['dnsblconfig']['pfb_dnsvip_skew'] : '';		// Virtual IP Carp skew
736
	$pfb['dnsbl_vip_pass']	= isset($pfb['dnsblconfig']['pfb_dnsvip_pass']) ? $pfb['dnsblconfig']['pfb_dnsvip_pass'] : '';		// Virtual IP Carp password (if required)
737

    
738
	$pfb['dnsbl_iface']	= $pfb['dnsblconfig']['dnsbl_interface']?: 'lo0';	// VIP Local Interface setting
739
	$pfb['dnsbl_vip']	= $pfb['dnsblconfig']['pfb_dnsvip']	?: '';		// Virtual IP local address
740
	$pfb['dnsbl_v6']	= $pfb['dnsblconfig']['pfb_dnsblv6']	?: '';		// Enable/Disable DNSBL IPv6
741
	$pfb['dnsbl_port']	= $pfb['dnsblconfig']['pfb_dnsport'];			// Lighttpd web server http port setting
742
	$pfb['dnsbl_port_ssl']	= $pfb['dnsblconfig']['pfb_dnsport_ssl'];		// Lighttpd web server https port setting
743
	$pfb['dnsbl_alexa']	= $pfb['dnsblconfig']['alexa_enable'];			// TOP1M whitelist
744
	$pfb['dnsbl_alexatype'] = $pfb['dnsblconfig']['alexa_type'] ?: 'tranco';	// TOP1M type (Tranco, Alexa or Cisco)
745
	$pfb['dnsbl_res_cache']	= $pfb['dnsblconfig']['pfb_cache'];			// DNSBL Option to backup/restore Resolver cache
746
	$pfb['dnsbl_sync']	= $pfb['dnsblconfig']['pfb_dnsbl_sync'];		// Live Updates to Resolver without a Reload
747
	$pfb['dnsbl_global_log']= $pfb['dnsblconfig']['global_log'] ?: '';		// Global Logging/Blocking mode
748

    
749
	$pfb['dnsbl_mode']	= $pfb['dnsblconfig']['dnsbl_mode'];			// DNSBL Mode (Unbound/python mode)
750
	$pfb['dnsbl_py_reply']	= $pfb['dnsblconfig']['pfb_py_reply'];			// DNSBL Resolver python DNS Reply logging
751
	$pfb['dnsbl_py_block']	= $pfb['dnsblconfig']['pfb_py_block'];			// DNSBL Resolver python blocking mode
752
	$pfb['dnsbl_hsts']	= $pfb['dnsblconfig']['pfb_hsts'];			// DNSBL Resolver python block HSTS via Null Block mode
753
	$pfb['dnsbl_idn']	= $pfb['dnsblconfig']['pfb_idn'];			// DNSBL Resolver python block IDN domains
754
	$pfb['dnsbl_regex']	= $pfb['dnsblconfig']['pfb_regex'];			// DNSBL Resolver python regex
755
	$pfb['dnsbl_regex_list']= $pfb['dnsblconfig']['pfb_regex_list'];		// DNSBL Resolver python regex list
756
	$pfb['dnsbl_cname']	= $pfb['dnsblconfig']['pfb_cname'];			// DNSBL Resolver python CNAME Validation
757
	$pfb['dnsbl_pytld']	= $pfb['dnsblconfig']['pfb_pytld'];			// DNSBL Resolver python TLD Allow option
758
	$pfb['dnsbl_py_nolog']	= $pfb['dnsblconfig']['pfb_py_nolog'];			// DNSBL Resolver python - Log events via DNSBL Webserver vs python
759
	$pfb['dnsbl_noaaaa']	= $pfb['dnsblconfig']['pfb_noaaaa'];			// DNSBL Resolver python no AAAA
760
	$pfb['dnsbl_noaaaa_list']=$pfb['dnsblconfig']['pfb_noaaaa_list'];		// DNSBL Resolver python no AAAA list
761

    
762
	$pfb['dnsbl_gp']		= $pfb['dnsblconfig']['pfb_gp'];		// DNSBL Resolver python - DNSBL Bypass
763
	$pfb['dnsbl_gp_bypass_list']	= $pfb['dnsblconfig']['pfb_gp_bypass_list'];	// DNSBL Resolver python - List of Local IPs to bypass DNSBL
764

    
765
	// DNSBL Resolver mode (Unbound/Python)
766
	$pfb['dnsbl_py_blacklist'] = FALSE;
767
	if ($pfb['dnsbl_mode'] == 'dnsbl_python' && $pfb['dnsbl_py_block'] == 'on') {
768
		$pfb['dnsbl_py_blacklist'] = TRUE;
769
	}
770

    
771
	// SafeSearch
772
	$pfb['safesearch_enable']	= config_get_path('installedpackages/pfblockerngsafesearch/safesearch_enable', 'Disable');
773
	$pfb['safesearch_youtube']	= config_get_path('installedpackages/pfblockerngsafesearch/safesearch_youtube', 'Disable');
774
	$pfb['safesearch_doh']		= config_get_path('installedpackages/pfblockerngsafesearch/safesearch_doh', 'Disable');
775
	$pfb['safesearch_doh_list']	= explode(',', config_get_path('installedpackages/pfblockerngsafesearch/safesearch_doh_list', ''));
776

    
777
	// DNSBL SafeSearch
778
	$pfb['dnsbl_safe_search'] = FALSE;
779
	if ($pfb['safesearch_enable'] !== 'Disable' ||
780
	    $pfb['safesearch_youtube'] !== 'Disable' ||
781
	    $pfb['safesearch_doh'] !== 'Disable') {
782
		$pfb['dnsbl_safe_search'] = TRUE;
783
	}
784

    
785
	// External DNS Server for TLD drill and CNAME Queries
786
	$pfb['extdns'] = pfb_filter($pfb['config_global']['pfbextdns'], PFB_FILTER_IPV4, 'pfb_global', '8.8.8.8');
787

    
788
	// Unbound chroot cmd
789
	$pfb['chroot_cmd'] = "/usr/sbin/chroot -u unbound -g unbound / /usr/local/sbin/unbound-control -c {$g['unbound_chroot_path']}/unbound.conf";
790

    
791
	// Define SQLite3 parameters
792
	$pfb['sqlite_timeout']	= 100000;
793

    
794
	// Max daily download failure threshold (default to '0' unlimited failures)
795
	$pfb['skipfeed']	= $pfb['config']['skipfeed'] != '' ? $pfb['config']['skipfeed'] : 0;
796

    
797
	if (config_path_enabled('unbound')) {
798
		$pfb['unbound_state'] = 'on';
799
	} else {
800
		$pfb['unbound_state'] = '';
801
	}
802

    
803
	// cURL - system proxy server setttings, if configured
804
	if (!empty(config_get_path('system/proxyurl'))) {
805
		$pfb['curl_defaults'][CURLOPT_PROXY]			= config_get_path('system/proxyurl');
806
		if (!empty(config_get_path('system/proxyport'))) {
807
			$pfb['curl_defaults'][CURLOPT_PROXYPORT]	= config_get_path('system/proxyport');
808
		}
809
		if (!empty(config_get_path('system/proxyuser')) && !empty(config_get_path('system/proxypass'))) {
810
			$pfb['curl_defaults'][CURLOPT_PROXYAUTH]	= 'CURLAUTH_ANY | CURLAUTH_ANYSAFE';
811
			$pfb['curl_defaults'][CURLOPT_PROXYUSERPWD]	= config_get_path('system/proxyuser') . ':' . config_get_path('system/proxypass');
812
		}
813
	}
814
	else {
815
		$pfb['curl_defaults'][CURLOPT_TCP_FASTOPEN]		= true;
816
	}
817

    
818
	// Set pfBlockerNG to disabled on 're-install'
819
	if (isset($pfb['install']) && $pfb['install']) {
820
		$pfb['enable'] = $pfb['dnsbl'] = '';
821
		$pfb['install']	= FALSE;
822
	}
823
}
824
pfb_global();
825

    
826
// Function to get pfBlockerNG package version
827
function pfb_pkg_ver() {
828
	$pkg_ver = 'v??';
829
	foreach (config_get_path('installedpackages/package', []) as $pkg_info_data) {
830
		if (strpos($pkg_info_data['name'], 'pfBlockerNG') !== FALSE) {
831
			$pkg_ver = 'v' . $pkg_info_data['version'];
832
			break;
833
		}
834
	}
835
	return $pkg_ver;
836
}
837

    
838

    
839
// Firewall Filter Service
840
function pfb_filter_service() {
841

    
842
	$rc		= array();
843
	$rc['file']	= 'pfb_filter.sh';
844
	$rc['start']	= <<<EOF
845

    
846
	# Check if pfBlockerNG is enabled
847
	pfbcheck="\$(/usr/local/sbin/read_xml_tag.sh string installedpackages/pfblockerng/config/enable_cb)"
848
	if [ "\${pfbcheck}" != 'on' ]; then
849
		exit
850
	fi
851

    
852
	# Ensure all processes are stopped
853
	rc_stop
854

    
855
	# clog is not required for pfSense 2.5 and above
856
	filter_type='tail_pfb'
857
	if [ -e "/usr/local/sbin/clog_pfb" ]; then
858
		filter_type='clog_pfb'
859
	fi
860

    
861
	# Compare php/php_pfb versions
862
	php_path='/usr/local/bin'
863

    
864
	phpver="\$(/usr/bin/stat \${php_path}/php | cut -d '/' -f1)"
865
	if [ -e "\${php_path}/php_pfb" ]; then
866
		phppfbver="\$(/usr/bin/stat \${php_path}/php_pfb | cut -d '/' -f1)"
867
	else
868
		phppfbver=''
869
	fi
870

    
871
	# Create new hard link for php_pfb on version mismatch
872
	if [ "\${phpver}" != "\${phppfbver}" ]; then
873
		if [ -e "\${php_path}/php_pfb" ]; then
874
			tmpfile="\$(/usr/bin/mktemp /tmp/pfb_php.XXXXXX)"
875
			/bin/mv "\${php_path}/php_pfb" "\${tmpfile}"
876
		fi
877

    
878
		/bin/ln "\${php_path}/php" "\${php_path}/php_pfb"
879
	fi
880

    
881
	# Start pfBlockerNG Firewall filter Daemon
882
	if [ -e '/var/log/filter.log' ]; then
883
		/usr/bin/logger -p daemon.info -t "\${filter_type}" "[pfBlockerNG] Firewall Filter Service started"
884

    
885
		if [ "\${filter_type}" == 'clog_pfb' ]; then
886
			/usr/local/sbin/clog_pfb -f /var/log/filter.log | /usr/local/bin/php_pfb -f /usr/local/pkg/pfblockerng/pfblockerng.inc filterlog &
887
		else
888
			/usr/bin/tail_pfb -n0 -F /var/log/filter.log | /usr/local/bin/php_pfb -f /usr/local/pkg/pfblockerng/pfblockerng.inc filterlog &
889
		fi
890
	fi
891

    
892
EOF;
893
	$rc['stop']	= <<<EOF
894

    
895
	# clog is not required for pfSense 2.5 and above
896
	filter_type='tail_pfb'
897
	if [ -e "/usr/local/sbin/clog_pfb" ]; then
898
		filter_type='clog_pfb'
899
	fi
900

    
901
	# Terminate pfBlockerNG Firewall filter Daemon (clog) and filter Daemon, if found
902
	/usr/bin/logger -p daemon.info -t "\${filter_type}" "[pfBlockerNG] Firewall Filter Service stopped"
903
	/usr/bin/logger -p daemon.info -t php_pfb "[pfBlockerNG] filterlog daemon stopped"
904
	pidnum="\$(/bin/ps -wax | /usr/bin/grep '[c]log_pfb -f /var/log/filter.log\|[t]ail_pfb -n0 -F /var/log/filter.log\|[p]fblockerng.inc filterlog' | /usr/bin/awk '{print \$1}')"
905
	if [ ! -z "\${pidnum}" ]; then
906
		for i in \${pidnum}; do
907
			/bin/kill -9 "\${i}"
908
		done
909
	fi
910

    
911
EOF;
912
	write_rcfile($rc);
913
}
914

    
915

    
916
// DNSBL Service
917
function pfb_dnsbl_service() {
918

    
919
	$rc		= array();
920
	$rc['file']	= 'pfb_dnsbl.sh';
921
	$rc['start']	= <<<EOF
922

    
923
	# Check if DNSBL is enabled
924
	dnsblcheck="\$(/usr/local/sbin/read_xml_tag.sh string installedpackages/pfblockerngdnsblsettings/config/pfb_dnsbl)"
925
	if [ "\${dnsblcheck}" != 'on' ]; then
926
		exit
927
	fi
928

    
929
	# Ensure all processes are stopped
930
	rc_stop
931

    
932
	# Start DNSBL Lighttpd webserver (Unbound/Python mode) and DNSBL HTTPS Daemon (Unbound mode only)
933
	if [ -e '/var/unbound/pfb_dnsbl_lighty.conf' ]; then
934
		/usr/bin/logger -p daemon.info -t lighttpd_pfb "[pfBlockerNG] DNSBL Webserver started"
935
		/usr/local/sbin/lighttpd_pfb -f /var/unbound/pfb_dnsbl_lighty.conf
936
	fi
937

    
938
	# Check if Unbound mode is enabled
939
	unbound_mode="\$(/usr/local/sbin/read_xml_tag.sh string installedpackages/pfblockerngdnsblsettings/config/dnsbl_mode)"
940

    
941
	# Start DNSBL Resolver queries Daemon
942
	if [ "\${unbound_mode}" == 'dnsbl_unbound' ]; then
943
		/usr/local/bin/php -f /usr/local/pkg/pfblockerng/pfblockerng.inc queries &
944
	elif [ ! -e '/var/unbound/pfb_unbound.py' ]; then
945
		/usr/bin/logger -p daemon.err -t php_pfb "[pfBlockerNG] DNSBL missing python script - forced reload required"
946
	fi
947

    
948
EOF;
949
	$rc['stop']	= <<<EOF
950

    
951
	# Terminate DNSBL Lighttpd webserver, if found
952
	/usr/bin/logger -p daemon.info -t lighttpd_pfb "[pfBlockerNG] DNSBL Webserver stopped"
953
	pidnum="\$(/bin/pgrep lighttpd_pfb)"
954
	if [ ! -z "\${pidnum}" ]; then
955
		/usr/bin/killall lighttpd_pfb
956
	fi
957

    
958
	# Terminate DNSBL queries Daemon, if found
959
	pidnum="\$(/bin/ps -wax | /usr/bin/grep '[p]fblockerng.inc queries' | /usr/bin/awk '{print \$1}')"
960
	if [ ! -z "\${pidnum}" ]; then
961
		for i in \${pidnum}; do
962
			/bin/kill -9 "\${i}"
963
		done
964
	fi
965

    
966
EOF;
967
	write_rcfile($rc);
968
}
969

    
970

    
971
// Create Firewall filter service
972
if (!file_exists('/usr/local/etc/rc.d/pfb_filter.sh')) {
973
	pfb_filter_service();
974
}
975

    
976
// Create DNSBL service
977
if (!file_exists('/usr/local/etc/rc.d/pfb_dnsbl.sh')) {
978
	pfb_dnsbl_service();
979
}
980

    
981
// clog is not required for pfSense 2.5 and above
982
if (substr(trim(file_get_contents('/etc/version')), 0, 3) > '2.5' && file_exists('/usr/local/sbin/clog_pfb')) {
983
	unlink_if_exists('/usr/local/sbin/clog_pfb');
984
	unlink_if_exists('/usr/bin/tail_pfb');
985
	link('/usr/bin/tail', '/usr/bin/tail_pfb');
986
	restart_service('pfb_filter');
987
}
988

    
989
// Commandline arguments for daemons
990
if (isset($argv[1])) {
991

    
992
	// DNSBL Lighttpd HTTPS daemon (Collects HTTPS events from the Lighttpd dnsbl_error.log)
993
	if ($argv[1] == 'dnsbl') {
994
			ignore_user_abort(TRUE);
995
			set_time_limit(0);
996
			pfb_daemon_dnsbl();
997
			exit;
998
	}
999

    
1000
	// DNSBL Lighttpd HTTP daemon (Collects HTTP events from the index.php script)
1001
	elseif ($argv[1] == 'index') {
1002
			ignore_user_abort(TRUE);
1003
			set_time_limit(0);
1004
			pfb_daemon_dnsbl_index();
1005
			exit;
1006
	}
1007

    
1008
	// DNSBL daemon to monitor Resolver queries and manage SQLite3 database
1009
	elseif ($argv[1] == 'queries') {
1010
			ignore_user_abort(TRUE);
1011
			set_time_limit(0);
1012
			pfb_daemon_queries();
1013
			exit;
1014
	}
1015

    
1016
	// IP filter daemon to convert filter.log to ip_block|ip_permit|ip_match log format
1017
	elseif ($argv[1] == 'filterlog') {
1018
			ignore_user_abort(TRUE);
1019
			set_time_limit(0);
1020
			if (!file_exists('/var/log/filter.log')) {
1021
				log_error('[pfBlockerNG] pfSense Firewall log missing');
1022
				exit;
1023
			}
1024
			pfb_daemon_filterlog();
1025
			exit;
1026
	}
1027
}
1028

    
1029

    
1030
// Function to convert string to lowercase (Not for comment line section)
1031
function pfb_strtolower($line) {
1032
	if (strpos($line, '#') === FALSE) {
1033
		return trim(strtolower($line));
1034
	}
1035
	return trim($line);
1036
}
1037

    
1038

    
1039
// Function to decode alias custom entry box.
1040
// Default (False, True): Return as string with comments
1041
function pfbng_text_area_decode($text, $mode=FALSE, $type=TRUE, $idn=FALSE) {
1042

    
1043
	if ($mode) {
1044
		$custom = array();
1045
	}
1046

    
1047
	$customlist = explode("\r\n", base64_decode($text));
1048
	if (!empty($customlist)) {
1049
		foreach ($customlist as $line) {
1050
			if (substr(trim($line), 0, 1) != '#' && !empty($line)) {
1051
				if ($idn && !ctype_print($line)) {
1052
					$line_old = $line;
1053
					// Convert encodings to UTF-8
1054
					$line = mb_convert_encoding($line, 'UTF-8',
1055
						mb_detect_encoding($line, 'UTF-8, ASCII, ISO-8859-1'));
1056
					if (strpos($line, '#') !== FALSE) {
1057
						$tmpline = preg_split('/(?=#)/', $line);
1058
						if (substr($tmpline[0], 0, 1) == '.') {
1059
							// idn_to_ascii() returns empty string if it starts with '.' 
1060
							$tmpline[0] = idn_to_ascii(ltrim($tmpline[0], '.'));
1061
							if (!empty($tmpline[0])) {
1062
								$tmpline[0] = '.' . $tmpline[0];
1063
							}
1064
						} else {
1065
							$tmpline[0] = idn_to_ascii($tmpline[0]);
1066
						}
1067
						if (empty($tmpline[0])) {
1068
							$log = "\nError converting IDN line '{$line_old}'\n";
1069
							pfb_logger($log, 2);
1070
							continue;
1071
						}
1072
						$line = implode(' ', $tmpline);
1073
					} else {
1074
						if (substr($line, 0, 1) == '.') {
1075
							$line = idn_to_ascii(ltrim($line, '.'));
1076
							if (!empty($line)) {
1077
								$line = '.' . $line;
1078
							}
1079
						} else {
1080
							$line = idn_to_ascii($line);
1081
						}
1082
						if (empty($line)) {
1083
							$log = "\nError converting IDN line '{$line_old}'\n";
1084
							pfb_logger($log, 2);
1085
							continue;
1086
						}
1087
					}
1088
				}
1089
				// '#' commentline found
1090
				if (strpos($line, '#') !== FALSE) {
1091
					if ($mode) {
1092
						if ($type) {
1093
							// Split line into two elements (array)
1094
							$custom[] = array_map('pfb_strtolower', preg_split('/(?=#)/', $line));
1095
						} else {
1096
							// Remove commentline
1097
							$custom[] = trim(strtolower(strstr($line, '#', TRUE)));
1098
						}
1099
					} else {
1100
						// Remove commentline
1101
						$custom .= trim(strtolower(strstr($line, '#', TRUE))) . "\n";
1102
					}
1103
				}
1104

    
1105
				// No '#' commentline found
1106
				else {
1107
					$line = trim(strtolower($line));
1108

    
1109
					if ($mode) {
1110
						if ($type) {
1111
							$custom[][0] = $line;
1112
						} else {
1113
							$custom[] = $line;
1114
						}
1115
					} else {
1116
						$custom .= "{$line}\n";
1117
					}
1118
				}
1119
			}
1120
		}
1121
		return $custom;
1122
	}
1123
}
1124

    
1125

    
1126
// Manage log files line limit
1127
function pfb_log_mgmt() {
1128
	global $g, $pfb;
1129
	pfb_global();
1130

    
1131
	$chroot_folder = '/var/unbound';
1132

    
1133
	foreach (array(	'log', 'errlog', 'extraslog', 'ip_blocklog', 'ip_permitlog', 'ip_matchlog',
1134
			'dnslog', 'dnsbl_parse_err', 'dnsreplylog', 'unilog') as $logtype) {
1135

    
1136
		// Max lines in Log file
1137
		$logmax = pfb_filter($pfb['config']['log_max_' . $logtype], PFB_FILTER_NUM, 'pfb_log_mgmt', 20000);
1138

    
1139
		if ($logmax != 'nolimit' && file_exists($pfb[$logtype])) {
1140
			if ($logtype == 'dnslog' || $logtype == 'dnsreplylog' || $logtype == 'unilog') {
1141

    
1142
				// Set DNSBL python logfile permissions using chroot folder
1143
				if (is_dir("{$chroot_folder}/var/log/pfblockerng")) {
1144
					$final_log_file = "{$chroot_folder}{$pfb[$logtype]}";
1145
					$temp = tempnam("{$chroot_folder}/var/log/pfblockerng", 'pfb_log_');
1146
				} else {
1147
					$final_log_file = $pfb[$logtype];
1148
					$temp = tempnam("{$g['tmp_path']}/", 'pfb_log_');
1149
				}
1150

    
1151
				if (file_exists($final_log_file)) {
1152
					exec("/usr/bin/tail -n " . escapeshellarg($logmax) . " " . escapeshellarg($final_log_file) . " > " . escapeshellarg($temp));
1153
					@chown($temp, 'unbound');
1154
					@chgrp($temp, 'unbound');
1155
					exec("/bin/mv -f " . escapeshellarg($temp) . " " . escapeshellarg($final_log_file));
1156
				}
1157
			}
1158
			else {
1159
				$temp = tempnam("{$g['tmp_path']}/", 'pfb_log_');
1160
				exec("/usr/bin/tail -n " . escapeshellarg($logmax) . " {$pfb[$logtype]} > " . escapeshellarg($temp));
1161
				exec("/bin/mv -f " . escapeshellarg($temp) . " {$pfb[$logtype]}");
1162
			}
1163
			unlink_if_exists($temp);
1164
		}
1165
	}
1166
}
1167

    
1168

    
1169
// Record log messsages to pfBlockerNG log file and/or error log file.
1170
function pfb_logger($log, $logtype) {
1171
	global $g, $pfb;
1172

    
1173
	$now = date('m/j/y H:i:s', time());
1174

    
1175
	// Only log timestamp if new
1176
	if (strpos($log, 'NOW') !== FALSE) {
1177
		$elog = str_replace('NOW', $now, "{$log}");	// Always report timestamp to errorlog
1178
		if ($now == $pfb['pnow']) {
1179
			$log = str_replace(' [ NOW ]', '', "{$log}");
1180
		} else {
1181
			$log = str_replace('NOW', $now, "{$log}");
1182
		}
1183
		$pfb['pnow'] = "{$now}";
1184
	}
1185
	else {
1186
		$elog = "{$log} [ {$now} ]";
1187
	} 
1188

    
1189
	switch ($logtype) {
1190

    
1191
		// Print to pfBlockerNG log
1192
		case 1:
1193
			@file_put_contents("{$pfb['log']}", "{$log}", FILE_APPEND);
1194
			break;
1195

    
1196
		// Print to pfBlockerNG log and Error log
1197
		case 2:
1198
			@file_put_contents("{$pfb['log']}", "{$log}", FILE_APPEND);
1199
			@file_put_contents("{$pfb['errlog']}", "{$elog}", FILE_APPEND);
1200
			break;
1201

    
1202
		// Print to Extras log
1203
		case 3:
1204
			@file_put_contents("{$pfb['extraslog']}", "{$log}", FILE_APPEND);
1205
			break;
1206

    
1207
		// Print to screen and Extras log
1208
		case 4:
1209
			if (!$g['pfblockerng_install'] && !$pfb['extras_update']) {
1210
				print "{$log}";
1211
			}
1212
			@file_put_contents("{$pfb['extraslog']}", "{$log}", FILE_APPEND);
1213
			break;
1214

    
1215
		// Print to debugger
1216
		case 5:
1217
			@file_put_contents("/tmp/pfb_debug", "{$now} | {$elog}", FILE_APPEND);
1218
			break;
1219

    
1220
		// Print to Error log
1221
		case 6:
1222
			@file_put_contents("{$pfb['errlog']}", "{$elog}", FILE_APPEND);
1223
			break;
1224
		default:
1225
			break;
1226
	}
1227
}
1228

    
1229

    
1230
// Record failed IP/DNSBL Feed parse errors
1231
function pfb_parsed_fail($header, $line='', $oline, $logfile) {
1232

    
1233
	$line   = $line ?: 'null';
1234
	$now	= date('m/j/y H:i:s', time());
1235

    
1236
	$log	= "{$now},{$header},{$line},{$oline}";
1237
	@file_put_contents("{$logfile}", "{$log}", FILE_APPEND);
1238
}
1239

    
1240

    
1241
// Determine 'list' details
1242
function pfb_determine_list_detail($list='', $header='', $confconfig='', $key='') {
1243
	global $pfb, $pfbarr;
1244
	$pfbarr = array();
1245

    
1246
	switch($list) {
1247
		case 'Deny_Both':
1248
		case 'Deny_Inbound':
1249
		case 'Deny_Outbound':
1250
		case 'Alias_Deny':
1251
			$pfbarr = array('adv' => TRUE, 'folder' => "{$pfb['denydir']}", 'orig' => "{$pfb['origdir']}", 'reuse' => "{$pfb['reuse']}");
1252
			break;
1253
		case 'unbound':
1254
			$pfbarr = array('adv' => FALSE, 'folder' => "{$pfb['dnsdir']}", 'orig' => "{$pfb['dnsorigdir']}", 'reuse' => "{$pfb['reuse_dnsbl']}");
1255
			break;
1256
		case 'Permit_Both':
1257
		case 'Permit_Inbound':
1258
		case 'Permit_Outbound':
1259
		case 'Alias_Permit':
1260
			$pfbarr = array('adv' => FALSE, 'folder' => "{$pfb['permitdir']}", 'orig' => "{$pfb['origdir']}", 'reuse' => "{$pfb['reuse']}");
1261
			break;
1262
		case 'Match_Both':
1263
		case 'Match_Inbound':
1264
		case 'Match_Outbound':
1265
		case 'Alias_Match':
1266
			$pfbarr = array('adv' => FALSE, 'folder' => "{$pfb['matchdir']}", 'orig' => "{$pfb['origdir']}", 'reuse' => "{$pfb['reuse']}");
1267
			break;
1268
		case 'Alias_Native':
1269
			$pfbarr = array('adv' => FALSE, 'folder' => "{$pfb['nativedir']}", 'orig' => "{$pfb['origdir']}", 'reuse' => "{$pfb['reuse']}");
1270
			break;
1271
	}
1272

    
1273
	// Collect proper alias table description (alias only vs autorules)
1274
	if (strpos($list, 'Alias') !== FALSE) {
1275
		$pfbarr['descr'] = '';
1276
	} else {
1277
		$pfbarr['descr'] = ' Auto ';
1278
	}
1279

    
1280
	// Determine length of header to format log output
1281
	$tabtype = strlen($header);
1282
	if ($tabtype > 27) {
1283
		$pfbarr['logtab'] = '';
1284
	} elseif ($tabtype > 19) {
1285
		$pfbarr['logtab'] = "\t";
1286
	} elseif ($tabtype > 11) {
1287
		$pfbarr['logtab'] = "\t\t";
1288
	} elseif ($tabtype < 4) {
1289
		$pfbarr['logtab'] = "\t\t\t\t";
1290
	} else {
1291
		$pfbarr['logtab'] = "\t\t\t";
1292
	}
1293

    
1294
	// Configure autoports/protocol and auto destination if required.
1295
	if (!empty($confconfig) && is_array(config_get_path("installedpackages/{$confconfig}/config/{$key}"))) {
1296

    
1297
		$conf_config	= config_get_path("installedpackages/{$confconfig}/config/{$key}");
1298
		$autotype	= array( 'autoports' => 'aliasports', 'autoaddr' => 'aliasaddr');
1299

    
1300
		foreach (array('_out', '_in') as $dir) {
1301

    
1302
			$pfbarr['aproto' . $dir]	= $conf_config['autoproto' . $dir];
1303
			$pfbarr['anot' . $dir]		= $conf_config['autonot' . $dir];
1304
			$pfbarr['aaddrnot' . $dir]	= $conf_config['autoaddrnot' . $dir];
1305
			$pfbarr['agateway' . $dir]	= $conf_config['agateway' . $dir];
1306

    
1307
			foreach ($autotype as $akey => $atype) {
1308
				if ($conf_config[$akey . $dir] == 'on') {
1309
					foreach (config_get_path('aliases/alias', []) as $palias) {
1310
						if ($palias['name'] == $conf_config[$atype . $dir]) {
1311
							if (!empty($palias['address'])) {
1312
								$dalias = "{$atype}{$dir}";
1313
								switch($akey) {
1314
									case 'autoports':
1315
										$ctype = "aports{$dir}";
1316
										$pfbarr[$ctype] = $conf_config[$dalias];
1317
										break;
1318
									case 'autoaddr':
1319
										$ctype = "aaddr{$dir}";
1320
										$pfbarr[$ctype] = $conf_config[$dalias];
1321
										break;
1322
								}
1323
							}
1324
						}
1325
					}
1326
				}
1327
			}
1328
		}
1329
	}
1330

    
1331
	// Force 'Alias Native' setting to any Alias with 'Advanced Inbound/Outbound -Invert src/dst' settings.
1332
	// This will bypass Deduplication and Reputation features.
1333
	if ($pfbarr['aaddrnot_in'] == 'on' || $pfbarr['aaddrnot_out'] == 'on') {
1334
		$pfbarr['adv'] = FALSE;
1335
		$pfbarr['folder'] = "{$pfb['nativedir']}";
1336
	}
1337

    
1338
	return $pfbarr;
1339
}
1340

    
1341

    
1342
// Determine if cron task requires updating
1343
function pfblockerng_cron_exists($pfb_cmd, $pfb_min, $pfb_hour, $pfb_mday, $pfb_wday) {
1344
	foreach (config_get_path('cron/item', []) as $item) {
1345
		if (strpos($item['command'], $pfb_cmd) !== FALSE) {
1346
			if ($item['command'] != $pfb_cmd) {
1347
				return FALSE;
1348
			}
1349
			if ($item['minute'] != $pfb_min) {
1350
				return FALSE;
1351
			}
1352
			if ($item['mday'] != $pfb_mday) {
1353
				return FALSE;
1354
			}
1355
			if ($item['wday'] != $pfb_wday) {
1356
				return FALSE;
1357
			}
1358
			if ($pfb_hour == 'random' && $item['hour'] != '') {
1359
				// MaxMind/Blacklist hour is randomized. Skip comparison.
1360
				return TRUE;
1361
			}
1362
			if ($item['hour'] != $pfb_hour) {
1363
				return FALSE;
1364
			}
1365
			return TRUE;
1366
		}
1367
	}
1368
	return FALSE;
1369
}
1370

    
1371

    
1372
// Calculate the cron task base hour setting
1373
function pfb_cron_base_hour($freq) {
1374
	global $pfb;
1375

    
1376
	switch($freq) {
1377
		case 'Disabled':
1378
		case 1:
1379
		case '01hour':
1380
			$j = 23; $k = 1;
1381
			break;
1382
		case 2:
1383
		case '02hours':
1384
			$j = 11; $k = 2;
1385
			break;
1386
		case 3:
1387
		case '03hours':
1388
			$j = 7; $k = 3;
1389
			break;
1390
		case 4:
1391
		case '04hours':
1392
			$j = 5; $k = 4;
1393
			break;
1394
		case 6:
1395
		case '06hours':
1396
			$j = 3; $k = 6;
1397
			break;
1398
		case 8:
1399
		case '08hours':
1400
			$j = 2; $k = 8;
1401
			break;
1402
		case 12:
1403
		case '12hours':
1404
			$j = 1; $k = 12;
1405
			break;
1406
		case 24:
1407
			return array($pfb['24hour']);
1408
			break;
1409
		default:
1410
			$pfb['interval'] = 1;
1411
			return [];
1412
	}
1413

    
1414
	$shour	= intval(substr($pfb['hour'], 0, 2));
1415
	$sch	= strval($shour);
1416

    
1417
	for ($i=0; $i < $j; $i++) {
1418
		$shour += $k;
1419
		if ($shour >= 24) {
1420
			$shour -= 24;
1421
		}
1422
		$sch .= ',' . strval($shour);
1423
	}
1424

    
1425
	$sch = explode(',', $sch);
1426
	sort($sch);
1427
	return $sch;
1428
}
1429

    
1430

    
1431
// Collect 'gateway(s)' and 'gateway group(s)' for Adv. In/Outbound customizations
1432
function pfb_get_gateways() {
1433
	$gateway = array();
1434
	$gateway['default'] = 'default';
1435

    
1436
	foreach (config_get_path('gateways/gateway_item', []) as $item) {
1437
		$gateway[$item['name']] = $item['name'];
1438
	}
1439
	foreach (config_get_path('gateways/gateway_group', []) as $item) {
1440
		$gateway[$item['name']] = $item['name'];
1441
	}
1442

    
1443
	return $gateway;
1444
}
1445

    
1446

    
1447
// Collect all Interfaces for General Tab and DNSBL Firewall Permit Rule
1448
function pfb_build_if_list($show_wan=FALSE, $show_groups=FALSE) {
1449
	$pfb_interfaces = array();
1450

    
1451
	foreach (get_configured_interface_with_descr() as $ifent => $ifdesc) {
1452
		if ($show_wan || $ifent != 'wan') {
1453
			$pfb_interfaces[$ifent] = $ifdesc;
1454
		}
1455
	}
1456

    
1457
	if ($show_groups) {
1458
		foreach (config_get_path('ifgroups/ifgroupentry', []) as $ifgen) {
1459
			$pfb_interfaces[$ifgen['ifname']] = $ifgen['ifname'];
1460
		}
1461
	}
1462

    
1463
	if (ipsec_enabled()) {
1464
		$pfb_interfaces['enc0'] = 'IPsec';
1465
	}
1466

    
1467
	if (config_get_path('openvpn/openvpn-server') || config_get_path('openvpn/openvpn-client')) {
1468
		$pfb_interfaces['openvpn'] = 'OpenVPN';
1469
	}
1470

    
1471
	if (config_get_path('l2tp/mode') == 'server') {
1472
		$pfb_interfaces['l2tp'] = 'L2TP VPN';
1473
	}
1474

    
1475
	if (function_exists('is_wg_enabled') && is_wg_enabled()) {
1476
		$pfb_interfaces['wireguard'] = 'WireGuard';
1477
	}
1478

    
1479
	return $pfb_interfaces;
1480
}
1481

    
1482

    
1483
// Create suppression file from suppression list
1484
function pfb_create_suppression_file() {
1485
	global $pfb;
1486

    
1487
	$v4suppression = pfbng_text_area_decode($pfb['ipconfig']['v4suppression'], FALSE, TRUE);
1488
	if (!empty($v4suppression)) {
1489
		@file_put_contents("{$pfb['supptxt']}", $v4suppression, LOCK_EX);
1490
	} else {
1491
		unlink_if_exists("{$pfb['supptxt']}");
1492
	}
1493
}
1494

    
1495

    
1496
// Function to update DNSBL aliases and widget stats
1497
function dnsbl_alias_update($mode, $alias, $pfbfolder, $lists_dnsbl_current, $alias_cnt) {
1498
	global $pfb;
1499

    
1500
	if ($mode == 'update') {
1501

    
1502
		// Create master alias file
1503
		if ($lists_dnsbl_current != '') {
1504
			$pfb_output = @fopen("{$pfb['dnsalias']}/{$alias}", 'w');
1505
			foreach ($lists_dnsbl_current as $clist) {
1506
				if (($handle = @fopen("{$pfbfolder}/{$clist}.txt", 'r')) !== FALSE) {
1507
					while (($line = @fgets($handle)) !== FALSE) {
1508
						@fwrite($pfb_output, $line);
1509
					}
1510
				}
1511
				@fclose($handle);
1512
			}
1513
			@fclose($pfb_output);
1514
		}
1515

    
1516
		// Update DNSBL alias statistics
1517
		$dns_now = date('M j H:i:s', time());
1518
		$pfbfound = FALSE;
1519

    
1520
		if (!empty($pfb['dnsbl_info_stats'])) {
1521
			foreach ($pfb['dnsbl_info_stats'] as $key => $line) {
1522

    
1523
				// Update existing alias stats
1524
				if ($line['groupname'] == "{$alias}") {
1525
					$pfbfound = TRUE;
1526
					$pfb['dnsbl_info_stats'][$key]['timestamp']	= "{$dns_now}";
1527
					$pfb['dnsbl_info_stats'][$key]['entries']	= "{$alias_cnt}";
1528
					break;
1529
				}
1530
			}
1531
		}
1532

    
1533
		if (!$pfbfound) {
1534
			$pfb['dnsbl_info_stats'][] = array ( 'groupname' => $alias, 'timestamp' => $dns_now, 'entries' => $alias_cnt, 'counter' => 0);
1535
		}
1536
	}
1537
	elseif ($mode == 'disabled') {
1538
		// Record disabled alias statistics
1539
		$pfbfound = FALSE;
1540
		if (!empty($pfb['dnsbl_info_stats'])) {
1541
			foreach ($pfb['dnsbl_info_stats'] as $line) {
1542
				if ($line['groupname'] == "{$alias}") {
1543
					$pfbfound = TRUE;
1544
					break;
1545
				}
1546
			}
1547
		}
1548

    
1549
		if (!$pfbfound) {
1550
			$dns_now = date('M j H:i:s', time());
1551
			$pfb['dnsbl_info_stats'][] = array ('groupname' => $alias, 'timestamp' => $dns_now, 'entries' => 'disabled', 'counter' => 0);
1552
		}
1553
	}
1554
}
1555

    
1556

    
1557
// Function to save DNSBL Group statistics
1558
function dnsbl_save_stats() {
1559
	global $pfb;
1560

    
1561
	// Save group statistics to SQLite3 database (Remove any feeds that are not referenced)
1562
	$db_update = $db_delete = '';
1563
	pfb_logger("\nSaving DNSBL statistics...", 1);
1564

    
1565
	// Collect existing SQL group names
1566
	$sql_groupnames = array();
1567
	$db_handle = pfb_open_sqlite(1, 'Collect Group');
1568
	if ($db_handle) {
1569
		$result = $db_handle->query("SELECT * FROM dnsbl;");
1570
		if ($result) {
1571
			while ($res = $result->fetchArray(SQLITE3_ASSOC)) {
1572
				$sql_groupnames[$res['groupname']] = '';
1573
			}
1574
		}
1575
	}
1576
	pfb_close_sqlite($db_handle);
1577

    
1578
	// Compare SQL database Group names to latest Group names
1579
	if (!empty($pfb['dnsbl_info_stats'])) {
1580

    
1581
		$db_handle = pfb_open_sqlite(1, 'Save DNSBL stats');
1582
		if ($db_handle) {
1583

    
1584
			foreach ($pfb['dnsbl_info_stats'] as $group) {
1585

    
1586
				// Keep row
1587
				$pfb_delete = FALSE;
1588
				if (in_array($group['groupname'], $pfb['alias_dnsbl_all'])) {
1589

    
1590
					// Update existing row
1591
					if (isset($sql_groupnames[$group['groupname']])) {
1592
						$db_update 	= "UPDATE dnsbl SET timestamp = :timestamp, entries = :entries"
1593
								. " WHERE groupname = :groupname;\n";
1594
					}
1595

    
1596
					// Add new row
1597
					else {
1598
						$db_update	= "INSERT INTO dnsbl (groupname, timestamp, entries, counter)"
1599
								. " VALUES (:groupname, :timestamp, :entries, 0);\n";
1600
					}
1601
				}
1602

    
1603
				// Remove row
1604
				else {
1605
					$db_update	= "DELETE FROM dnsbl WHERE groupname = :groupname;\n";
1606
					$pfb_delete	= TRUE;
1607
				}
1608

    
1609
				if (is_numeric($group['entries'])) {
1610
					$group['groupname'] = pfb_filter($group['groupname'], PFB_FILTER_HTML, 'dnsbl_save_stats');
1611

    
1612
					$stmt = $db_handle->prepare($db_update);
1613
					if ($stmt) {
1614
						$stmt->bindValue(':groupname', $group['groupname'], SQLITE3_TEXT);
1615
						if (!$pfb_delete) {
1616
							$group['timestamp'] = pfb_filter($group['timestamp'], PFB_FILTER_HTML, 'dnsbl_save_stats');
1617

    
1618
							$stmt->bindValue(':timestamp', $group['timestamp'], SQLITE3_TEXT);
1619
							$stmt->bindValue(':entries', $group['entries'], SQLITE3_TEXT);
1620
						}
1621
						$stmt->execute();
1622
					}
1623
				}
1624
			}
1625
		}
1626
		pfb_close_sqlite($db_handle);
1627
	}
1628
	else {
1629
		$db_delete = 'DROP TABLE dnsbl;';
1630
		$db_handle = pfb_open_sqlite(1, 'Delete table');
1631
		if ($db_handle) {
1632
			$db_handle->exec("BEGIN TRANSACTION;"
1633
				. "{$db_delete}"
1634
				. "END TRANSACTION;");
1635
		}
1636
		pfb_close_sqlite($db_handle);
1637
	}
1638
	pfb_logger(" completed [ NOW ]", 1);
1639
}
1640

    
1641

    
1642
// Function to create DNSBL Lighttpd configuration file
1643
function pfb_create_lighttpd() {
1644
	global $pfb;
1645

    
1646
	$lighttpd_bind = $pfb['dnsbl_iface'] != 'lo0' ? "127.0.0.1" : "{$pfb['dnsbl_vip']}";
1647
	$lighttpd_port = $pfb['dnsbl_iface'] != 'lo0' ? "{$pfb['dnsbl_port']}" : '80';
1648

    
1649
	$pfb_conf = '';
1650
	$pfb_conf = <<<EOF
1651
#
1652
#pfBlockerNG DNSBL Lighttpd configuration file
1653
#
1654
server.tag			= "pfBlockerNG DNSBL"
1655
server.bind			= "{$lighttpd_bind}"
1656
server.port			= "{$lighttpd_port}"
1657
server.event-handler		= "freebsd-kqueue"
1658
server.network-backend		= "freebsd-sendfile"
1659
server.dir-listing		= "disable"
1660
server.document-root		= "/usr/local/www/pfblockerng/www/"
1661
server.max-request-size		= "1"
1662
server.pid-file			= "/var/run/dnsbl.pid"
1663

    
1664
EOF;
1665

    
1666
if (!$pfb['dnsbl_py_blacklist'] || $pfb['dnsbl_py_nolog'] == 'on') {
1667
	$pfb_conf .= <<<EOF
1668
server.errorlog			= "|/usr/local/bin/php -f /usr/local/pkg/pfblockerng/pfblockerng.inc dnsbl"
1669

    
1670
EOF;
1671
}
1672

    
1673
	if (file_exists('/usr/local/lib/lighttpd/mod_openssl.so')) {
1674
		if (!$pfb['dnsbl_py_blacklist'] || $pfb['dnsbl_py_nolog'] == 'on') {
1675
			$pfb_conf .= 'server.modules			= ( "mod_access", "mod_auth", "mod_accesslog", "mod_fastcgi", "mod_rewrite", "mod_openssl" )';
1676
		} else {
1677
			$pfb_conf .= 'server.modules			= ( "mod_auth", "mod_fastcgi", "mod_rewrite", "mod_openssl" )';
1678
		}
1679
	} else {
1680
		if (!$pfb['dnsbl_py_blacklist'] || $pfb['dnsbl_py_nolog'] == 'on') {
1681
			$pfb_conf .= 'server.modules			= ( "mod_access", "mod_auth", "mod_accesslog", "mod_fastcgi", "mod_rewrite" )';
1682
		} else {
1683
			$pfb_conf .= 'server.modules			= ( "mod_auth", "mod_fastcgi", "mod_rewrite" )';
1684
		}
1685
	}
1686

    
1687
	$pfb_conf .= <<<EOF
1688

    
1689
index-file.names		= ( "index.php" )
1690
mimetype.assign			= ( ".html" => "text/html", ".gif" => "image/gif" )
1691
url.access-deny			= ( "~", ".inc" )
1692
fastcgi.server			= ( ".php" => ( "localhost" => ( "socket" => "/var/run/php-fpm.socket", "broken-scriptfilename" => "enable" ) ) )
1693

    
1694
EOF;
1695
	if (!$pfb['dnsbl_py_blacklist'] || $pfb['dnsbl_py_nolog'] == 'on') {
1696

    
1697
	$pfb_conf .= <<<EOF
1698
debug.log-condition-handling	= "enable"
1699
accesslog.use-syslog		= "disable"
1700
accesslog.format		= "INDEX!%r!%V!%h!%{Referer}i * %r * %{User-Agent}i"
1701
accesslog.filename		= "|/usr/local/bin/php -f /usr/local/pkg/pfblockerng/pfblockerng.inc index"
1702

    
1703
EOF;
1704
}
1705
	// Lighttpd v1.4.58+ conditional error log with 'ssl.verifyclient.activate' to collect the domain name
1706
	$lighty_ver = exec('/usr/local/sbin/lighttpd -v 2>&1');
1707
	if ((!$pfb['dnsbl_py_blacklist'] || $pfb['dnsbl_py_nolog'] == 'on') &&
1708
  	    (strpos($lighty_ver, 'lighttpd/1.4.58') !== FALSE ||
1709
	    strpos($lighty_ver, 'lighttpd/1.4.59') !== FALSE ||
1710
	    strpos($lighty_ver, 'lighttpd/1.4.60') !== FALSE ||
1711
	    strpos($lighty_ver, 'lighttpd/1.4.61') !== FALSE ||
1712
	    strpos($lighty_ver, 'lighttpd/1.4.67') !== FALSE ||
1713
	    strpos($lighty_ver, 'lighttpd/1.5') !== FALSE)) {
1714

    
1715
		$pfb_conf .= <<<EOF
1716
ssl.verifyclient.activate	= "enable"
1717

    
1718
EOF;
1719
	}
1720

    
1721
	$pfb_conf .= <<<EOF
1722

    
1723
\$HTTP["scheme"] == "http" {
1724
	url.rewrite-once = ( ".*" => "/index.php" )
1725
}
1726

    
1727
\$HTTP["remoteip"] =~ ".*" {
1728

    
1729
EOF;
1730

    
1731
	if ($pfb['dnsbl_iface'] != 'lo0') {
1732
		$pfb_conf .= <<<EOF
1733

    
1734
	\$SERVER["socket"] == "127.0.0.1:{$pfb['dnsbl_port_ssl']}" {
1735
		ssl.engine			= "enable"
1736
		ssl.pemfile			= "/var/unbound/dnsbl_cert.pem"
1737
		ssl.dh-file			= "/etc/dh-parameters.4096"
1738
		ssl.ec-curve			= "secp384r1"
1739
		ssl.honor-cipher-order		= "enable"
1740
		ssl.openssl.ssl-conf-cmd	= ("MinProtocol"	=> "TLSv1.2",
1741
					 	   "Options"		=> "-ServerPreference",
1742
						   "CipherString"	=> "EECDH+AESGCM:AES256+EECDH:CHACHA20:!SHA1:!SHA256:!SHA384")
1743
	}
1744

    
1745
	\$SERVER["socket"] == "{$pfb['dnsbl_vip']}:80" {
1746
		#
1747
	}
1748

    
1749
EOF;
1750
	}
1751
	$pfb_conf .= <<<EOF
1752

    
1753
	\$SERVER["socket"] == "{$pfb['dnsbl_vip']}:443" {
1754
		ssl.engine			= "enable"
1755
		ssl.pemfile			= "/var/unbound/dnsbl_cert.pem"
1756
		ssl.dh-file			= "/etc/dh-parameters.4096"
1757
		ssl.ec-curve			= "secp384r1"
1758
		ssl.honor-cipher-order		= "enable"
1759
		ssl.openssl.ssl-conf-cmd	= ("MinProtocol"	=> "TLSv1.2",
1760
                                                   "Options"		=> "-ServerPreference",
1761
                                                   "CipherString"	=> "EECDH+AESGCM:AES256+EECDH:CHACHA20:!SHA1:!SHA256:!SHA384")
1762
	}
1763

    
1764
EOF;
1765
	if ($pfb['dnsbl_iface'] != 'lo0') {
1766
		$pfb_conf .= <<<EOF
1767

    
1768
	\$SERVER["socket"] == "[::1]:{$pfb['dnsbl_port']}" {
1769
		#
1770
	}
1771

    
1772
	\$SERVER["socket"] == "[::1]:{$pfb['dnsbl_port_ssl']}" {
1773
		ssl.engine			= "enable"
1774
		ssl.pemfile			= "/var/unbound/dnsbl_cert.pem"
1775
		ssl.dh-file			= "/etc/dh-parameters.4096"
1776
		ssl.ec-curve			= "secp384r1"
1777
		ssl.honor-cipher-order 		= "enable"
1778
		ssl.openssl.ssl-conf-cmd	= ("MinProtocol"	=> "TLSv1.2",
1779
						   "Options"		=> "-ServerPreference",
1780
						   "CipherString"	=> "EECDH+AESGCM:AES256+EECDH:CHACHA20:!SHA1:!SHA256:!SHA384")
1781
	}
1782

    
1783
EOF;
1784
	}
1785
	if ($pfb['dnsbl_v6'] == 'on') {
1786
		$pfb_conf .= <<<EOF
1787

    
1788
	\$SERVER["socket"] == "[::{$pfb['dnsbl_vip']}]:80" {
1789
		#
1790
	}
1791

    
1792
	\$SERVER["socket"] == "[::{$pfb['dnsbl_vip']}]:443" {
1793
		ssl.engine			= "enable"
1794
		ssl.pemfile			= "/var/unbound/dnsbl_cert.pem"
1795
		ssl.dh-file			= "/etc/dh-parameters.4096"
1796
		ssl.ec-curve			= "secp384r1"
1797
		ssl.honor-cipher-order		= "enable"
1798
		ssl.openssl.ssl-conf-cmd	= ("MinProtocol"	=> "TLSv1.2",
1799
						   "Options"		=> "-ServerPreference",
1800
						   "CipherString"	=> "EECDH+AESGCM:AES256+EECDH:CHACHA20:!SHA1:!SHA256:!SHA384")
1801
	}
1802

    
1803
EOF;
1804
	}
1805
	$pfb_conf .= <<<EOF
1806

    
1807
	\$HTTP["host"] =~ ".*" {
1808
		url.rewrite-once = ( ".*" => "/index.php" )
1809
	}
1810
}
1811

    
1812
EOF;
1813
	return $pfb_conf;
1814
}
1815

    
1816

    
1817
// Function to create DNSBL SSL certificate
1818
function pfb_create_dnsbl_cert() {
1819
	global $pfb, $cert_strict_values;
1820

    
1821
	$cert		= array();
1822
	$cert['refid']	= uniqid();
1823
	$cert['descr']	= sprintf(gettext("pfBlockerNG DNSBL (%s)"), $cert['refid']);
1824
	$cert_hostname	= config_get_path('system/hostname') . "-pfBNG-DNSBL-{$cert['refid']}";
1825

    
1826
	$dn = array(
1827
		'organizationName'	=> "pfBlockerNG DNSBL Self-Signed Certificate",
1828
		'commonName'		=> $cert_hostname,
1829
		'subjectAltName'	=> "DNS:{$cert_hostname}");
1830

    
1831
	$old_err_level = error_reporting(0); /* otherwise openssl_ functions throw warnings directly to a page screwing menu tab */
1832
	if (!cert_create($cert, null, 2048, $cert_strict_values['max_server_cert_lifetime'] ?: 398, $dn, 'self-signed', 'sha256')) {
1833
		while ($ssl_err = openssl_error_string()) {
1834
			log_error(sprintf(gettext("Error creating pfBlockerNG DNSBL Certificate: openssl library returns: %s"), $ssl_err));
1835
		}
1836
		error_reporting($old_err_level);
1837
		return null;
1838
	}
1839
	error_reporting($old_err_level);
1840
	$privatekey	= base64_decode($cert['prv']);
1841
	$publickey	= base64_decode($cert['crt']);
1842
 
1843
	@file_put_contents("{$pfb['dnsbl_cert']}", "{$privatekey}{$publickey}", LOCK_EX);
1844
}
1845

    
1846

    
1847
// Create DNSBL VIP and NAT rules, lighttpd conf and services
1848
function pfb_create_dnsbl($mode) {
1849
	global $pfb;
1850
	pfb_global();
1851

    
1852
	// Reload config.xml to get any recent changes
1853
	config_read_file(false, true);
1854

    
1855
	$new_nat = $new_vip = $pfb_ex_nat = $pfb_ex_vip = $dnsbl_ex_nat = $dnsbl_ex_vip = array();
1856
	$pfb['dnsbl_vip_changed'] = $pfbupdate = FALSE;
1857
	$iface = escapeshellarg(get_real_interface($pfb['dnsbl_iface']));
1858

    
1859
	if ((!empty($pfb['dnsbl_port']) && !empty($pfb['dnsbl_port_ssl']) && !empty($pfb['dnsbl_vip']) && $mode == 'enabled') || $mode == 'disabled') {
1860

    
1861
		// DNSBL NAT rules generation
1862
		$pfbfound = FALSE;
1863
		// Collect existing pfSense NAT rules
1864
		foreach (config_get_path('nat/rule', []) as $ex_nat) {
1865
			if (strpos($ex_nat['descr'], 'pfB DNSBL') !== FALSE) {
1866
				// Collect DNSBL NAT rules
1867
				$dnsbl_ex_nat[] = $ex_nat;
1868
				$pfbfound = TRUE;
1869
			} else {
1870
				// Collect all 'other' NAT rules
1871
				$pfb_ex_nat[] = $ex_nat;
1872
			}
1873
		}
1874

    
1875
		if ($mode == 'enabled') {
1876

    
1877
			// Generate new DNSBL NAT per DNSBL listening ports except for 'localhost' interface setting.
1878
			$dnsbl_new_nat = array();
1879
			if ($pfb['dnsbl_iface'] != 'lo0') {
1880
				$selected_ports = array("{$pfb['dnsbl_port']}" => '80', "{$pfb['dnsbl_port_ssl']}" => '443');
1881
				foreach ($selected_ports as $port => $lport) {
1882

    
1883
					$dnsbl_new_nat[] =	array ( 'source'		=> array('any'  => ''),
1884
									'destination'		=> array('address' => "{$pfb['dnsbl_vip']}", 'port' => "{$lport}"),
1885
									'protocol'		=> 'tcp',
1886
									'target'		=> '127.0.0.1',
1887
									'local-port'		=> "{$port}",
1888
									'interface'		=> "{$pfb['dnsbl_iface']}",
1889
									'descr'			=> 'pfB DNSBL - DO NOT EDIT',
1890
									'associated-rule-id'	=> 'pass',
1891
									'natreflection'		=> 'purenat'
1892
									);
1893

    
1894
					// Add DNSBL IPv6 NAT Rules
1895
					if ($pfb['dnsbl_v6'] == 'on') {
1896
						$dnsbl_new_nat[] = array (	'source'		=> array('any'  => ''),
1897
										'destination'		=> array('address' => "::{$pfb['dnsbl_vip']}",
1898
														'port' => "{$lport}"),
1899
										'protocol'		=> 'tcp',
1900
										'target'		=> '::1',
1901
										'local-port'		=> "{$port}",
1902
										'interface'		=> "{$pfb['dnsbl_iface']}",
1903
										'ipprotocol'		=> 'inet6',
1904
										'descr'			=> 'pfB DNSBL - DO NOT EDIT',
1905
										'associated-rule-id'	=> 'pass',
1906
										'natreflection'		=> 'purenat'
1907
										);
1908
					}
1909
				}
1910
			}
1911

    
1912
			// Compare existing to new and if they are not identical update
1913
			if ($dnsbl_ex_nat !== $dnsbl_new_nat) {
1914
				$pfbupdate = TRUE;
1915
				$new_nat = array_merge($pfb_ex_nat, $dnsbl_new_nat);
1916
			} else {
1917
				$new_nat = array_merge($pfb_ex_nat, $dnsbl_ex_nat);
1918
			}
1919
		} else {
1920
			$new_nat = array_merge($pfb_ex_nat, $new_nat);
1921
			// Update when DNSBL NAT found but is now disabled.
1922
			if ($pfbfound) {
1923
				$pfbupdate = TRUE;
1924
			}
1925
		}
1926

    
1927
		// DNSBL VIP generation
1928
		$dnsbl_new_vip				= array();
1929
		$dnsbl_new_vip[0]			= array();
1930
		$dnsbl_new_vip[0]['interface']		= "{$pfb['dnsbl_iface']}";
1931
		$dnsbl_new_vip[0]['descr']		= 'pfB DNSBL - DO NOT EDIT';
1932
		$dnsbl_new_vip[0]['type']		= 'single';
1933
		$dnsbl_new_vip[0]['subnet_bits']	= '32';
1934
		$dnsbl_new_vip[0]['subnet']		= "{$pfb['dnsbl_vip']}";
1935

    
1936
		if ($pfb['dnsbl_vip_type'] == 'carp') {
1937
			$dnsbl_new_vip[0]['mode']	= 'carp';
1938
			$dnsbl_new_vip[0]['vhid']	= "{$pfb['dnsbl_vip_vhid']}";
1939
			$dnsbl_new_vip[0]['advskew']	= "{$pfb['dnsbl_vip_skew']}";
1940
			$dnsbl_new_vip[0]['advbase']	= "{$pfb['dnsbl_vip_base']}";
1941
			$dnsbl_new_vip[0]['password']	= "{$pfb['dnsbl_vip_pass']}";
1942
		} else {
1943
			$dnsbl_new_vip[0]['mode']	= 'ipalias';
1944
		}
1945

    
1946
		// Add DNSBL IPv6 VIP
1947
		if ($pfb['dnsbl_v6'] == 'on') {
1948
			$dnsbl_new_vip[1]			= array();
1949
			$dnsbl_new_vip[1]['interface']		= "{$pfb['dnsbl_iface']}";
1950
			$dnsbl_new_vip[1]['descr']		= 'pfB DNSBL - DO NOT EDIT';
1951
			$dnsbl_new_vip[1]['type']		= 'single';
1952
			$dnsbl_new_vip[1]['subnet_bits']	= '128';
1953
			$dnsbl_new_vip[1]['subnet']		= "::{$pfb['dnsbl_vip']}";
1954

    
1955
			if ($pfb['dnsbl_vip_type'] == 'carp') {
1956
				$dnsbl_new_vip[1]['mode']	= 'carp';
1957
				$dnsbl_new_vip[1]['vhid']	= "{$pfb['dnsbl_vip_vhid']}";
1958
				$dnsbl_new_vip[1]['advskew']	= "{$pfb['dnsbl_vip_skew']}";
1959
				$dnsbl_new_vip[1]['advbase']	= "{$pfb['dnsbl_vip_base']}";
1960
				$dnsbl_new_vip[1]['password']	= "{$pfb['dnsbl_vip_pass']}";
1961
			} else {
1962
				 $dnsbl_new_vip[1]['mode']	= 'ipalias';
1963
			}
1964
		}
1965

    
1966
		$vip_count = 0;
1967
		$pfbfound = FALSE;
1968
		// Collect existing pfSense VIPs
1969
		foreach (config_get_path('virtualip/vip', []) as $ex_vip) {
1970
			if (strpos($ex_vip['descr'], 'pfB DNSBL') !== FALSE) {
1971
				// Collect DNSBL VIP
1972
				$dnsbl_ex_vip[] = $ex_vip;
1973
				$pfbfound = TRUE;
1974
				$vip_count++;
1975
			} else {
1976
				// Collect all 'other' VIPs
1977
				$pfb_ex_vip[] = $ex_vip;
1978
			}
1979
		}
1980
		
1981
		if (isset($dnsbl_ex_vip[0]) && !isset($dnsbl_ex_vip[0]['uniqid'])) {
1982
			$dnsbl_new_vip[0]['uniqid'] = uniqid();
1983
		}
1984
		if (isset($dnsbl_ex_vip[1]) && !isset($dnsbl_ex_vip[1]['uniqid'])) {
1985
			$dnsbl_new_vip[1]['uniqid'] = uniqid();
1986
		}
1987

    
1988
		if ($mode == 'enabled') {
1989
			// Compare existing to new and if they are not identical update
1990
			if ($dnsbl_ex_vip !== $dnsbl_new_vip) {
1991
				$pfb['dnsbl_vip_changed'] = TRUE;
1992
				$pfbupdate = TRUE;
1993
				$new_vip = array_merge($pfb_ex_vip, $dnsbl_new_vip);
1994
			} else {
1995
				$new_vip = array_merge($pfb_ex_vip, $dnsbl_ex_vip);
1996
			}
1997
		} else {
1998
			$new_vip = array_merge($pfb_ex_vip, $new_vip);
1999
			// Update when DNSBL NAT found but is now disabled.
2000
			if ($pfbfound) {
2001
				$pfbupdate = TRUE;
2002
			}
2003
		}
2004

    
2005
		// Compare previous Lighttpd conf
2006
		$pfb_lighty_conf = pfb_create_lighttpd();
2007
		$pfb_lighty_conf_ex = @file_get_contents($pfb['dnsbl_conf']);
2008
		if ($pfb_lighty_conf !== $pfb_lighty_conf_ex) {
2009
			$pfbupdate = TRUE;
2010
			@file_put_contents($pfb['dnsbl_conf'], $pfb_lighty_conf, LOCK_EX);
2011
			$log = "\nSaving new DNSBL web server configuration to port [ {$pfb['dnsbl_port']} and {$pfb['dnsbl_port_ssl']} ]";
2012
			pfb_logger("{$log}", 1);
2013
		}
2014

    
2015
		// Validate DNSBL VIP address(es)
2016
		$result = array();
2017
		foreach (array("inet {$pfb['dnsbl_vip']}", "inet6 ::{$pfb['dnsbl_vip']}") as $g_vip) {
2018
			$g_vip = escapeshellarg($g_vip);
2019
			exec("/sbin/ifconfig {$iface} | {$pfb['grep']} {$g_vip} 2>&1", $result, $retval);
2020
		}
2021
		if (count($result) != $vip_count) {
2022
			$pfbupdate = TRUE;
2023
		}
2024

    
2025
		// Update config.xml, if changes required
2026
		if ($pfbupdate) {
2027
			config_set_path('nat/rule', $new_nat);
2028
			config_set_path('virtualip/vip', $new_vip);
2029
			write_config('pfBlockerNG: saving DNSBL changes');
2030
		}
2031

    
2032
		if ($mode == 'enabled' && $pfbupdate) {
2033

    
2034
			// Execute ifconfig to enable VIP address
2035
			if (!empty($iface) && !empty($pfb['dnsbl_vip'])) {
2036

    
2037
				if (is_service_running('pfb_dnsbl')) {
2038
					pfb_logger("\nStop Service DNSBL", 1);
2039
					stop_service('pfb_dnsbl');
2040
				}
2041

    
2042
				foreach (array("{$pfb['dnsbl_vip']}", "::{$pfb['dnsbl_vip']}") as $vip) {
2043

    
2044
					$mask = '32';
2045
					$inet = 'inet';
2046
					if (strpos($vip, '::') !== FALSE) {
2047
						$mask = '128';
2048
						$inet = 'inet6';
2049
					}
2050

    
2051
					$g_vip	= escapeshellarg("{$inet} {$vip}");
2052
					$vip	= escapeshellarg($vip);
2053

    
2054
					// Clear any existing VIP
2055
					$result = exec("/sbin/ifconfig {$iface} | {$pfb['grep']} {$g_vip} 2>&1");
2056
					if (!empty($result)) {
2057
						exec("/sbin/ifconfig {$iface} {$inet} {$vip} -alias 2>&1");
2058
					}
2059

    
2060
					if ($inet == 'inet6' && $pfb['dnsbl_v6'] != 'on') {
2061
						break;
2062
					}
2063

    
2064
					// Configure VIP type (ipalias or carp)
2065
					if ($pfb['dnsbl_vip_type'] == 'carp') {
2066
						$vhid		= escapeshellarg("vhid {$pfb['dnsbl_vip_vhid']}");
2067
						$advbase	= escapeshellarg("advbase {$pfb['dnsbl_vip_base']}");
2068
						$advskew	= escapeshellarg("advskew {$pfb['dnsbl_vip_skew']}");
2069
						if (config_path_enabled('', 'virtualip_carp_maintenancemode')) {
2070
							$advskew = 'advskew 254';
2071
						}
2072

    
2073
						$password = '';
2074
						if (!empty($pfb['dnsbl_vip_pass'])) {
2075
							$password = 'pass ' . escapeshellarg(addslashes(str_replace(' ', '', $pfb['dnsbl_vip_pass'])));
2076
						}
2077

    
2078
						exec("/sbin/ifconfig {$iface} {$inet} {$vhid} {$advskew} {$advbase} {$password} 2>&1");
2079
						exec("/sbin/ifconfig {$iface} {$inet} '{$vip}/{$mask}' alias {$vhid} 2>&1");
2080
					}
2081
					else {
2082
						exec("/sbin/ifconfig {$iface} {$inet} '{$vip}/{$mask}' alias 2>&1");
2083
					}
2084

    
2085
					// Validate 'tentative' interface state
2086
					for ($i=10; $i > 0; $i--) {
2087
						$result = exec("/sbin/ifconfig {$iface} | {$pfb['grep']} {$g_vip} | {$pfb['grep']} 'tentative' 2>&1");
2088
						if (!empty($result)) {
2089
							pfb_logger('.', 1);
2090
							usleep(500000);
2091
						} else {
2092
							break;
2093
						}
2094
					}
2095
				}
2096

    
2097
				$log = "\nVIP address(es) configured";
2098
				pfb_logger("{$log}", 1);
2099
				$pfb['filter_configure'] = TRUE;
2100
			} else {
2101
				$log = "DNSBL ifconfig error : Interface:{$iface}, VIP:{$pfb['dnsbl_iface']}\n";
2102
				pfb_logger("{$log}", 1);
2103
			}
2104
		}
2105
	}
2106

    
2107
	// Save settings, restart services as required
2108
	if ($mode == 'enabled') {
2109

    
2110
		// Create DNSBL SSL certificate
2111
		if (!file_exists("{$pfb['dnsbl_cert']}")) {
2112
			pfb_create_dnsbl_cert();
2113

    
2114
			$log = "\nNew DNSBL certificate created";
2115
			pfb_logger("{$log}", 1);
2116
		}
2117

    
2118
		if ($pfbupdate || !is_service_running('pfb_dnsbl')) {
2119

    
2120
			// Remove any existing and create link for DNSBL lighttpd executable
2121
			unlink_if_exists('/usr/local/sbin/lighttpd_pfb');
2122
			link('/usr/local/sbin/lighttpd', '/usr/local/sbin/lighttpd_pfb');
2123

    
2124
			$log = "\nRestarting DNSBL Service";
2125
			pfb_logger("{$log}", 1);
2126
			restart_service('pfb_dnsbl');
2127
		}
2128
	}
2129
	else {
2130
		if (is_service_running('pfb_dnsbl')) {
2131
			pfb_logger("Stop Service DNSBL\n", 1);
2132
			stop_service('pfb_dnsbl');
2133
		}
2134

    
2135
		// Remove DNSBL VIP address
2136
		if (!empty($iface) && !empty($pfb['dnsbl_vip'])) {
2137
			foreach (array("{$pfb['dnsbl_vip']}" => 'inet', "::{$pfb['dnsbl_vip']}" => 'inet6') as $vip => $inet) {
2138

    
2139
				$g_vip	= escapeshellarg("{$inet} {$vip}");
2140
				$vip	= escapeshellarg($vip);
2141

    
2142
				$result = exec("/sbin/ifconfig {$iface} | {$pfb['grep']} {$g_vip} 2>&1");
2143
				if (!empty($result)) {
2144
					exec("/sbin/ifconfig {$iface} {$inet} {$vip} -alias 2>&1");
2145
					$pfb['filter_configure'] = TRUE;
2146
				}
2147

    
2148
				// Validate 'tentative' interface state
2149
				for ($i=10; $i > 0; $i--) {
2150
					$result = exec("/sbin/ifconfig {$iface} | {$pfb['grep']} {$g_vip} | {$pfb['grep']} 'tentative' 2>&1");
2151
					if (!empty($result)) {
2152
						pfb_logger('.', 1);
2153
						usleep(500000);
2154
					} else {
2155
						break;
2156
					}
2157
				}
2158
			}
2159
		}
2160
	}
2161
}
2162

    
2163

    
2164
// Define DNSBL Unbound include settings (config.xml)
2165
function pfb_unbound_dnsbl($mode) {
2166
	global $g, $pfb;
2167
	pfb_global();
2168

    
2169
	// Reload config.xml to get any recent changes
2170
	config_read_file(false, true);
2171

    
2172
	$pfbupdate = FALSE;
2173
	$unbound_include = "server:include: {$pfb['dnsbl_file']}.*conf";
2174

    
2175
	// Collect Unbound custom option pfSense conf line
2176
	$pfb['unboundconfig']	= config_get_path('unbound/custom_options');
2177

    
2178
	if (!empty($pfb['unboundconfig'])) {
2179
		$unbound_custom = base64_decode($pfb['unboundconfig']);
2180
	} else {
2181
		$unbound_custom = '';
2182
	}
2183

    
2184
	// Determine if DNSBL include line exists
2185
	if (!empty($unbound_custom)) {
2186
		// Append DNSBL Unbound pfSense conf integration
2187
		if (!strstr($unbound_custom, 'pfb_dnsbl.*conf')) {
2188
			if ($mode == 'enabled') {
2189
				if (!$pfb['dnsbl_py_blacklist']) {
2190
					$pfbupdate = TRUE;
2191
					$unbound_custom .= "\n{$unbound_include}";
2192
					$log = "\nAdding DNSBL Unbound mode (Resolver adv. setting)";
2193
				}
2194

    
2195
				// To be removed when SafeSearch CNAME python mode has been fixed
2196
				elseif ($pfb['safesearch_enable'] !== 'Disable') {
2197
					$pfbupdate = TRUE;
2198
					$unbound_custom .= "\n{$unbound_include}";
2199
					$log = "\nAdding DNSBL SafeSearch CNAME mode (Resolver adv. setting)";
2200
				}
2201
			}
2202
		}
2203
		else {
2204
			// Remove DNSBL Unbound pfSense conf integration when disabled
2205
			// or when DNSBL python mode is enabled but not when SafeSearch is enabled
2206
			if ($mode == 'disabled' || $pfb['dnsbl_py_blacklist']) {
2207
				$custom = explode ("\n", $unbound_custom);
2208
				foreach ($custom as $key => $line) {
2209
					if (strpos($line, 'pfb_dnsbl.*conf') !== FALSE) {
2210
						$pfbupdate = TRUE;
2211
						if (!$pfb['dnsbl_py_blacklist']) {
2212
							$log = "\nRemoving DNSBL SafeSearch mode (Resolver adv. setting)";
2213
							unset($custom[$key]);
2214
						}
2215

    
2216
						// To be removed when SafeSearch CNAME python mode has been fixed
2217
						elseif ($pfb['safesearch_enable'] !== 'Disable') {
2218
							//
2219
						}
2220

    
2221
						else {
2222
							$log = "\nRemoving DNSBL Unbound mode and/or DNSBL SafeSearch CNAME mode (Resolver adv. setting)";
2223
							unset($custom[$key]);
2224
						}
2225
					}
2226
				}
2227
				$unbound_custom = implode("\n", $custom);
2228
			}
2229
		}
2230
	}
2231
	else {
2232
		// Add DNSBL Unbound pfSense conf integration
2233
		if ($mode == 'enabled') {
2234
			if (!$pfb['dnsbl_py_blacklist']) {
2235
				$pfbupdate = TRUE;
2236
				$unbound_custom = "{$unbound_include}";
2237
				$log = "\nAdding DNSBL Unbound mode (Resolver adv. setting)";
2238
			}
2239

    
2240
			// To be removed when SafeSearch CNAME python mode has been fixed
2241
			elseif ($pfb['safesearch_enable'] !== 'Disable') {
2242
				$pfbupdate = TRUE;
2243
				$unbound_custom = "{$unbound_include}";
2244
				$log = "\nAdding DNSBL SafeSearch CNAME mode (Resolver adv. setting)";
2245
			}
2246
		}
2247
	}
2248

    
2249
	// Remove the previous include line, see Bug #6603
2250
	$custom = explode ("\n", $unbound_custom);
2251
	foreach ($custom as $key => $line) {
2252
		if (strpos($line, 'pfb_dnsbl.conf') !== FALSE) {
2253
			$pfbupdate = TRUE;
2254
			$log .= "\nDNSBL - Removing previous DNSBL Unbound custom option\n";
2255
			unset($custom[$key]);
2256
		}
2257
	}
2258
	$unbound_custom = implode("\n", $custom);
2259

    
2260
	// Update config.xml, if changes required
2261
	if ($pfbupdate) {
2262
		pfb_logger("{$log}", 1);
2263
		$unbound_custom = base64_encode(str_replace("\r\n", "\n", $unbound_custom));
2264
		$pfb['unboundconfig'] = "{$unbound_custom}";
2265
		config_set_path('unbound/custom_options', $pfb['unboundconfig']);
2266
		write_config('pfBlockerNG: saving Unbound custom options');
2267
	}
2268

    
2269
	// Modify unbound.conf file as required
2270
	if (file_exists("{$pfb['dnsbldir']}/unbound.conf")) {
2271
		$conf = file("{$pfb['dnsbldir']}/unbound.conf");
2272
		if (empty($conf)) {
2273
			pfb_logger("\nDNS Resolver configuration file missing or empty, Exiting!", 1);
2274
		}
2275

    
2276
		$unbound = FALSE;	// Unbound mode
2277
		$unbound_py = FALSE;	// Unbound python mode
2278

    
2279
		$u_update = FALSE;
2280
		$u_msg = '';
2281
		foreach ($conf as $key => $line) {
2282

    
2283
			if (empty($line)) {
2284
				continue;
2285
			}
2286

    
2287
			elseif (strpos($line, 'pfb_dnsbl.*conf') !== FALSE) {
2288
				if ($mode == 'enabled') {
2289
					if (!$pfb['dnsbl_py_blacklist']) {
2290
						$unbound = TRUE;
2291
					}
2292

    
2293
					// To be removed when SafeSearch CNAME python mode has been fixed
2294
					elseif ($pfb['safesearch_enable'] !== 'Disable') {
2295
						$unbound = TRUE;
2296
					}
2297

    
2298
					else {
2299
						$u_update = TRUE;
2300
						$u_msg .= "  Removed DNSBL SafeSearch mode\n";
2301
						unset($conf[$key]);
2302
					}
2303
				} else {
2304
					$u_update = TRUE;
2305
					$u_msg .= "  Removed DNSBL Unbound mode\n"; 
2306
					unset($conf[$key]);
2307
				}
2308
			}
2309

    
2310
			elseif (strpos($line, 'module-config:') !== FALSE) {
2311
				if ($mode == 'enabled' && $pfb['dnsbl_mode'] == 'dnsbl_python') {
2312
					if (strpos($line, 'module-config: "python') !== FALSE) {
2313
						$unbound_py = TRUE;
2314
					} else {
2315
						$u_update = TRUE;
2316
						$u_msg .= "  Added DNSBL Unbound Python mode\n";
2317
						$conf[$key] = str_replace('module-config: "', 'module-config: "python ', $line);
2318
					}
2319
				}
2320
				else {
2321
					// Only remove python module if script is 'pfb_unbound'
2322
					if (strpos($line, 'module-config: "python') !== FALSE && in_array("python-script: pfb_unbound.py\n", $conf)) {
2323
						$u_update = TRUE;
2324
						$u_msg .= "  Removed DNSBL Unbound Python mode\n";
2325
						$conf[$key] = str_replace('module-config: "python ', 'module-config: "', $line);
2326
					}
2327
				}
2328
			}
2329

    
2330
			// Remove any DNSBL VIPs added to unbound.conf on 'disable'
2331
			elseif ((strpos($line, 'interface:') !== FALSE) && ($mode == 'disabled') &&
2332
			    !empty($pfb['dnsbl_vip']) && (strpos($line, $pfb['dnsbl_vip']) !== FALSE)) { 
2333
				$u_update = TRUE;
2334
				$u_msg .= "  Removed DNSBL VIP from Unbound Interface settings\n";
2335
				unset($conf[$key]);
2336
			}
2337

    
2338
			elseif (strpos($line, 'python-script: pfb_unbound.py') !== FALSE) {
2339
				if ($mode == 'enabled' && $pfb['dnsbl_mode'] == 'dnsbl_python') {
2340
					$unbound_py = TRUE;
2341
				} else {
2342
					$u_update = TRUE;
2343
					$u_msg .= "  Removed DNSBL Unbound Python mode script\n";
2344
					unset($conf[$key-1]); // remove 'python:' line above
2345
					unset($conf[$key]);
2346
				}
2347
			}
2348
		}
2349

    
2350
		// Add Unbound include line
2351
		if (!$unbound && $mode == 'enabled') {
2352
			if (!$pfb['dnsbl_py_blacklist']) {
2353
				$u_update = TRUE;
2354
				$u_msg .= "  Added DNSBL Unbound mode\n";
2355
				$conf[] = "\nserver:include: {$pfb['dnsbl_file']}.*conf\n";
2356
			}
2357

    
2358
			// To be removed when SafeSearch CNAME python mode has been fixed
2359
			elseif ($pfb['safesearch_enable'] !== 'Disable') {
2360
				$u_update = TRUE;
2361
				$u_msg .= "  Added DNSBL SafeSearch CNAME mode\n";
2362
				$conf[] = "\nserver:include: {$pfb['dnsbl_file']}.*conf\n";
2363
			}
2364
		}
2365

    
2366
		// Add python script line
2367
		if (!$unbound_py && $mode == 'enabled' && $pfb['dnsbl_mode'] == 'dnsbl_python') {
2368
			$u_update = TRUE;
2369
			$u_msg .= "  Added DNSBL Unbound Python mode script\n";
2370
			$conf[] = "\npython:\npython-script: pfb_unbound.py\n";
2371
		}
2372

    
2373
		if ($mode == 'enabled' && $pfb['dnsbl_mode'] == 'dnsbl_python') {
2374
			if (!file_exists("{$g['unbound_chroot_path']}/pfb_unbound.py")) {
2375
				@copy("/usr/local/pkg/pfblockerng/pfb_unbound.py", "{$g['unbound_chroot_path']}/pfb_unbound.py");
2376
			}
2377
			if (!file_exists("{$g['unbound_chroot_path']}/pfb_unbound_include.inc")) {
2378
				@copy("/usr/local/pkg/pfblockerng/pfb_unbound_include.inc", "{$g['unbound_chroot_path']}/pfb_unbound_include.inc");
2379
			}
2380
			if (!file_exists("{$g['unbound_chroot_path']}/pfb_py_hsts.txt")) {
2381
				@copy("/usr/local/pkg/pfblockerng/pfb_py_hsts.txt", "{$g['unbound_chroot_path']}/pfb_py_hsts.txt");
2382
			}
2383
		} else {
2384
			unlink_if_exists("{$g['unbound_chroot_path']}/pfb_unbound.py");
2385
			unlink_if_exists("{$g['unbound_chroot_path']}/pfb_unbound_include.inc");
2386
			unlink_if_exists("{$g['unbound_chroot_path']}/pfb_py_hsts.txt");
2387
		}
2388

    
2389
		// Save changes to unbound.conf
2390
		if ($u_update) {
2391
			pfb_logger("\nDNS Resolver ( {$mode} ) unbound.conf modifications:\n{$u_msg}", 1);
2392
			@file_put_contents("{$pfb['dnsbldir']}/unbound.tmp", $conf, LOCK_EX);
2393
			@chown("{$pfb['dnsbldir']}/unbound.tmp", 'unbound');
2394
			@chgrp("{$pfb['dnsbldir']}/unbound.tmp", 'unbound');
2395
			return TRUE;
2396
		}
2397
	}
2398
	else {
2399
		pfb_logger("\n\n*** [ Unbound.conf file missing. Exiting! ] ***\n\n", 1);
2400
	}
2401

    
2402
	return FALSE;
2403
}
2404

    
2405

    
2406
// Create DNSBL Whitelist
2407
function pfb_unbound_python_whitelist($mode='') {
2408
	global $pfb;
2409
	pfb_global();
2410

    
2411
	$dnsbl_whitelist = '';
2412
	$dnsbl_white = pfbng_text_area_decode($pfb['dnsblconfig']['suppression'], TRUE, FALSE, TRUE);
2413
	if (!empty($dnsbl_white)) {
2414
		foreach ($dnsbl_white as $key => $line) {
2415
			if (!empty($line)) {
2416
				if (substr($line, 0, 4) == 'www.') {
2417
					$line = substr($line, 4);
2418
				}
2419

    
2420
				// Minimize the python whitelist queries to the smallest tld segment count
2421
				if (!isset($tld_segments)) {
2422
					$tld_segments = (substr_count($line, '.') +1);
2423
				}
2424
				$tld_segments = @min((array((substr_count($line, '.') +1), $tld_segments) ?: 1));
2425

    
2426
				if (substr($line, 0, 1) == '.') {
2427
					$line = ltrim($line, '.');
2428
					$dnsbl_whitelist .= "{$line},1\n";
2429
				} else {
2430
					$dnsbl_whitelist .= "{$line},0\n";
2431
				}
2432
			}
2433
		}
2434
	}
2435

    
2436
	if ($mode == 'alerts') {
2437
		@file_put_contents($pfb['unbound_py_wh'], $dnsbl_whitelist, LOCK_EX);
2438
	} else {
2439
		return $dnsbl_whitelist;
2440
	}
2441
}
2442

    
2443

    
2444
// Unbound python configuration file
2445
function pfb_unbound_python($mode) {
2446
	global $pfb;
2447
	pfb_global();
2448

    
2449
	// Reload config.xml to get any recent changes
2450
	config_read_file(false, true);
2451

    
2452
	$pfbpython = FALSE;
2453

    
2454
	// Ensure log file permissions are set as 'unbound:unbound'
2455
	foreach (array('dnsbl.log', 'dns_reply.log', 'unified.log') as $logfile) {
2456

    
2457
		if (!file_exists("{$pfb['logdir']}/{$logfile}")) {
2458
			touch("{$pfb['logdir']}/{$logfile}");
2459
		}
2460

    
2461
		@chown("{$pfb['logdir']}/{$logfile}", 'unbound');
2462
		@chgrp("{$pfb['logdir']}/{$logfile}", 'unbound');
2463
	}
2464

    
2465
	// Add python settings to DNS Resolver configuration
2466
	$python_enable = 'off';
2467
	if ($mode == 'enabled' && $pfb['dnsbl_mode'] == 'dnsbl_python') {
2468
		$python_enable = 'on';
2469

    
2470
		if (!config_path_enabled('unbound', 'python') ||
2471
		    config_get_path('unbound/python_script') != 'pfb_unbound') {
2472

    
2473
			config_set_path('unbound/python', '');
2474
			config_set_path('unbound/python_order', 'pre_validator');
2475
			config_set_path('unbound/python_script', 'pfb_unbound');
2476

    
2477
			$pfbpython = TRUE;
2478
			$log = 'Added DNSBL Unbound python integration settings';
2479
			pfb_logger("\n{$log}", 1);
2480
			write_config("pfBlockerNG: {$log}");
2481
		}
2482

    
2483
		// If DNSBL python blocking mode enabled
2484
		if ($pfb['dnsbl_py_blacklist']) {
2485

    
2486
			// Create DNSBL Whitelist
2487
			$dnsbl_whitelist = pfb_unbound_python_whitelist();
2488

    
2489
			// Compare previous DNSBL Whitelist to new Whitelist
2490
			$pfb_py_whitelist_ex = @file_get_contents($pfb['unbound_py_wh']);
2491
			if ($dnsbl_whitelist !== $pfb_py_whitelist_ex) {
2492
				$pfbpython = TRUE;
2493
				@file_put_contents($pfb['unbound_py_wh'], $dnsbl_whitelist, LOCK_EX);
2494
			}
2495
		}
2496

    
2497
		// Remove previous whitelist and reload
2498
		elseif (file_exists($pfb['unbound_py_wh'])) {
2499
			unlink_if_exists($pfb['unbound_py_wh']);
2500
			$pfbpython = TRUE;
2501
		}
2502

    
2503
		if (!isset($tld_segments)) {
2504
			$tld_segments = '1';
2505
		}
2506

    
2507
		$python_ipv6 = 'off';
2508
		if ($pfb['dnsbl_v6'] == 'on') {
2509
			$python_ipv6 = 'on';
2510
		}
2511

    
2512
		$python_reply = 'off';
2513
		if ($pfb['dnsbl_py_reply'] == 'on') {
2514
			$python_reply = 'on';
2515
		}
2516

    
2517
		$python_blocking = 'off';
2518
		if ($pfb['dnsbl_py_blacklist']) {
2519
			$python_blocking = 'on';
2520
		}
2521

    
2522
		$python_hsts = 'off';
2523
		if ($pfb['dnsbl_hsts'] == 'on') {
2524
			$python_hsts = 'on';
2525
		}
2526

    
2527
		$python_idn = 'off';
2528
		if ($pfb['dnsbl_idn'] == 'on') {
2529
			$python_idn = 'on';
2530
		}
2531

    
2532
		$python_cname = 'off';
2533
		if ($pfb['dnsbl_cname'] == 'on') {
2534
			$python_cname = 'on';
2535
		}
2536

    
2537
		$python_control = 'off';
2538
		if ($pfb['dnsbl_control'] == 'on') {
2539
			$python_control = 'on';
2540
		}
2541

    
2542
		$python_noaaaa = 'off';
2543
		if ($pfb['dnsbl_noaaaa'] == 'on') {
2544
			$python_noaaaa = 'on';
2545
		}
2546

    
2547
		$python_tld = 'off';
2548
		if ($pfb['dnsbl_pytld'] == 'on') {
2549
			$python_tlds = '';
2550
			if (!empty($pfb['dnsblconfig']['pfb_pytlds_gtld'])) {
2551
				$python_tld = 'on';
2552
				$python_tlds = $pfb['dnsblconfig']['pfb_pytlds_gtld'];
2553
			}
2554
			if (!empty($pfb['dnsblconfig']['pfb_pytlds_cctld'])) {
2555
				$python_tld = 'on';
2556
				$python_tlds .= ',' . $pfb['dnsblconfig']['pfb_pytlds_cctld'];
2557
			}
2558
			if (!empty($pfb['dnsblconfig']['pfb_pytlds_itld'])) {
2559
				$python_tld = 'on';
2560
				$python_tlds .= ',' . $pfb['dnsblconfig']['pfb_pytlds_itld'];
2561
			}
2562
			if (!empty($pfb['dnsblconfig']['pfb_pytlds_bgtld'])) {
2563
				$python_tld = 'on';
2564
				$python_tlds .= ',' . $pfb['dnsblconfig']['pfb_pytlds_bgtld'];
2565
			}
2566
			$python_tlds = ltrim($python_tlds, ',');
2567
		}
2568

    
2569
		$python_nolog = 'off';
2570
		if ($pfb['dnsbl_py_nolog'] == 'on') {
2571
			$python_nolog = 'on';
2572
		}
2573

    
2574
		$now = date('m/j/y H:i:s', time());
2575

    
2576
		$pfb_py_conf = <<<EOF
2577
; pfBlockerNG DNSBL Unbound python configuration file
2578
; pfb_unbound.ini [ File created: {$now} ]
2579
[MAIN]
2580
dnsbl_ipv4	= {$pfb['dnsbl_vip']}
2581
python_enable	= {$python_enable}
2582
python_ipv6	= {$python_ipv6}
2583
python_reply	= {$python_reply}
2584
python_blocking	= {$python_blocking}
2585
python_hsts	= {$python_hsts}
2586
python_idn	= {$python_idn}
2587
python_tld_seg	= {$tld_segments}
2588
python_tld	= {$python_tld}
2589
python_tlds	= {$python_tlds}
2590
python_nolog	= {$python_nolog}
2591
python_cname	= {$python_cname}
2592
python_control	= {$python_control}
2593

    
2594
EOF;
2595
		if ($pfb['dnsbl_regex'] == 'on' && isset($pfb['dnsbl_regex_list']) && !empty($pfb['dnsbl_regex_list'])) {
2596
			$regex = '';
2597
			$regex_list = pfbng_text_area_decode($pfb['dnsbl_regex_list'], TRUE, TRUE, FALSE);
2598
			if (!empty($regex_list)) {
2599
				$counter = 1;
2600
				$key_index = array();
2601
				foreach ($regex_list as $key => $list) {
2602
					if (!isset($list[1])) {
2603
						$list[1] = "Regex_{$counter}";
2604
					} else {
2605
						$list[1] = trim(ltrim($list[1], '#'));
2606
						$list[1] = preg_replace("/\W/", '', str_replace(' ', '_', $list[1]));
2607
					}
2608

    
2609
					// Check if key exists
2610
					if (!isset($key_index[$list[1]])) {
2611
						$regex .= "{$list[1]} = {$list[0]}\n";
2612
						$key_index[$list[1]] = '';
2613
					} else {
2614
						$regex .= "{$list[1]}_{$counter} = {$list[0]}\n";
2615
						$key_index["{$list[1]}_{$counter}"] = '';
2616
					}
2617
					$counter++;
2618
				}
2619
				$pfb_py_conf .= <<<EOF
2620

    
2621
[REGEX]
2622
{$regex}
2623
EOF;
2624
			}
2625
		}
2626

    
2627
		if ($pfb['dnsbl_noaaaa'] == 'on' && isset($pfb['dnsbl_noaaaa_list']) && !empty($pfb['dnsbl_noaaaa_list'])) {
2628
			$noaaaa = '';
2629
			$noaaaa_list = pfbng_text_area_decode($pfb['dnsbl_noaaaa_list'], TRUE, TRUE, TRUE);
2630
			if (!empty($noaaaa_list)) {
2631
				foreach ($noaaaa_list as $key => $list) {
2632
					if (substr($list[0], 0, 1) == '.') {
2633
						$list[0] = ltrim($list[0], '.');
2634
						$noaaaa .= "{$key} = {$list[0]},1\n";
2635
					} else {
2636
						$noaaaa .= "{$key} = {$list[0]},0\n";
2637
					}
2638
				}
2639
				$pfb_py_conf .= <<<EOF
2640

    
2641
[noAAAA]
2642
{$noaaaa}
2643
EOF;
2644
			}
2645
		}
2646

    
2647
		if ($pfb['dnsbl_gp'] == 'on' && isset($pfb['dnsbl_gp_bypass_list']) && !empty($pfb['dnsbl_gp_bypass_list'])) {
2648
			$gp_bypass = '';
2649
			$gp_bypass_list = pfbng_text_area_decode($pfb['dnsbl_gp_bypass_list'], TRUE, TRUE, FALSE);
2650
			if (!empty($gp_bypass_list)) {
2651
				foreach ($gp_bypass_list as $key => $list) {
2652
					$gp_bypass .= "{$key} = {$list[0]}\n";
2653
				}
2654
				$pfb_py_conf .= <<<EOF
2655

    
2656
[GP_Bypass_List]
2657
{$gp_bypass}
2658
EOF;
2659
			}
2660
		}
2661

    
2662
		// Compare previous ini file to new ini (bypass file timestamp string)
2663
		$pfb_py_conf_ex		= @file_get_contents($pfb['unbound_py_conf']);
2664
		$pfb_py_conf_ex2	= preg_replace("/File created:.* ]/", "File created: {$now} ]", $pfb_py_conf_ex);
2665

    
2666
		if ($pfb_py_conf !== $pfb_py_conf_ex2) {
2667
			$pfbpython = TRUE;
2668
			@file_put_contents($pfb['unbound_py_conf'], $pfb_py_conf, LOCK_EX);
2669
		}
2670
	}
2671

    
2672
	else {
2673
		$mode = 'disabled';
2674

    
2675
		// Remove python settings from DNS Resolver configuration
2676
		if (config_path_enabled('unbound', 'python') && config_get_path('unbound/python_script') == 'pfb_unbound') {
2677
			config_del_path('unbound/python');
2678
			config_set_path('unbound/python_order', '');
2679
			config_set_path('unbound/python_script', '');
2680

    
2681
			$log = 'Removing DNSBL Unbound python integration settings';
2682
			pfb_logger("\n{$log}", 1);
2683
			write_config("pfBlockerNG: {$log}");
2684
		}
2685
	}
2686

    
2687
	// Mount lib and bin folders
2688
	$base_py = '/usr/local';
2689

    
2690
	$log = '';
2691
	foreach (array('/bin', '/lib') as $dir) {
2692
		$validate = exec("/sbin/mount | {$pfb['grep']} " . escapeshellarg("{$pfb['dnsbldir']}{$base_py}{$dir}") . " 2>&1");
2693

    
2694
		if ($mode == 'enabled' && empty($validate)) {
2695
			if (!is_dir("{$pfb['dnsbldir']}{$base_py}{$dir}")) {
2696
				$log .= "\n  Creating: {$pfb['dnsbldir']}{$base_py}{$dir}";
2697
				safe_mkdir("{$pfb['dnsbldir']}{$base_py}{$dir}");
2698
			}
2699
			$pfbpython = TRUE;
2700
			$log .= "\n  Mounting: /usr/local{$dir}";
2701
			exec("/sbin/mount_nullfs -o ro " . escapeshellarg("/usr/local{$dir}") . ' '
2702
				. escapeshellarg("{$pfb['dnsbldir']}{$base_py}{$dir}") . " 2>&1", $output, $retval);
2703
			if ($retval != 0) {
2704
				$log .= "\n  Failed to mount [ /usr/local{$dir} ] to [ {$pfb['dnsbldir']}{$base_py}{$dir} ]!";
2705
			}
2706
		}
2707
	}
2708

    
2709
	if (!empty($log)) {
2710
		pfb_logger("\nAdding DNSBL Unbound python mounts:{$log}\n", 1);
2711
	}
2712

    
2713
	// Set flag to unmount python folders after the next reboot is completed to avoid crashing Unbound
2714
	if ($mode == 'disabled') {
2715
		$pfb['dnsbl_python_unmount'] = TRUE;
2716
	}
2717
	return $pfbpython;
2718
}
2719

    
2720

    
2721
// Unbound python unmount
2722
function pfb_unbound_python_unmount() {
2723
	global $pfb;
2724

    
2725
	// Unmount lib and bin folders
2726
	$base_py = '/usr/local';
2727

    
2728
	$log = '';
2729
	foreach (array('/bin', '/lib') as $dir) {
2730
		$validate = exec("/sbin/mount | {$pfb['grep']} " . escapeshellarg("{$pfb['dnsbldir']}{$base_py}{$dir}") . " 2>&1");
2731
		if (!empty($validate)) {
2732
			$log .= "\n  Unmounting: /usr/local{$dir}";
2733
			exec("/sbin/umount -t nullfs " . escapeshellarg("{$pfb['dnsbldir']}{$base_py}{$dir}") . " 2>&1", $output, $retval);
2734
			if ($retval != 0) {
2735
				$log .= "\n  Failed to unmount [ {$pfb['dnsbldir']}{$base_py}{$dir} ]!";
2736
			}
2737
		}
2738

    
2739
		foreach (array( "/usr/local{$dir}", '/usr/local', '/usr') as $folder) {
2740
			if (is_dir("{$pfb['dnsbldir']}{$folder}")) {
2741
				$log .= "\n  Removing: {$pfb['dnsbldir']}{$folder}";
2742
				@rmdir("{$pfb['dnsbldir']}{$folder}");
2743
			}
2744

    
2745
			// Delete remaining subfolders on next loop
2746
			if ($dir == '/bin') {
2747
				break;
2748
			}
2749
		}
2750
	}
2751

    
2752
	if (!empty($log)) {
2753
		pfb_logger("\nRemoving DNSBL Unbound python mounts:{$log}\n", 1);
2754
	}
2755
}
2756

    
2757

    
2758
// Search for TLD match
2759
function tld_search($tld, $dparts, $j, $k) {
2760
	global $tlds;
2761

    
2762
	$tld_query = implode('.', array_slice($dparts, -$j, $j, TRUE));
2763
	if (isset($tlds[$tld][$tld_query])) {
2764
		return implode('.', array_slice($dparts, -$k, $k, TRUE));
2765
	}
2766
	return NULL;
2767
}
2768

    
2769

    
2770
// Function to determine if each Domain is a Sub-Domain ('transparent' zone) or a whole Domain ('redirect' zone)
2771
function tld_analysis() {
2772
	global $pfb, $tlds;
2773

    
2774
	if (!file_exists("{$pfb['dnsbl_file']}.raw")) {
2775
		pfb_logger("\n\nTLD Analysis not required.", 1);
2776
		return;
2777
	}
2778

    
2779
	pfb_logger("\nTLD:\n", 1);
2780

    
2781
	$domain_cnt = 0;
2782
	$pfb_found = FALSE;				// Flag to determine if TLD 'redirect' zones found
2783

    
2784
	rmdir_recursive("{$pfb['dnsbl_tmpdir']}");
2785
	if (!$pfb['dnsbl_py_blacklist']) {
2786
		safe_mkdir("{$pfb['dnsbl_tmpdir']}");
2787
	}
2788
	unlink_if_exists("{$pfb['dnsbl_file']}.tsp");
2789
	unlink_if_exists("{$pfb['dnsbl_tld_txt']}.*");
2790
	unlink_if_exists("{$pfb['dnsbl_tld_remove']}.tsp");
2791
	unlink_if_exists("{$pfb['dnsbl_tld_remove']}");
2792
	unlink_if_exists("{$pfb['dnsbl_tmp']}.sup");
2793
	unlink_if_exists("{$pfb['dnsbl_tmp']}.adup");
2794

    
2795
	// Master TLD Domain list
2796
	if (($t_handle = @fopen("{$pfb['dnsbl_tld_data']}", 'r')) !== FALSE) {
2797
		while (($line = @fgets($t_handle)) !== FALSE) {
2798
			$line	= rtrim($line, "\x00..\x1F");
2799
			$tld	= substr($line, strrpos($line, '.') + 1);
2800

    
2801
			if (!empty($tld)) {
2802
				if (!is_array($tlds[$tld])) {
2803
					$tlds[$tld] = array();
2804
				}
2805
				$tlds[$tld][$line] = '';
2806
			}
2807
		}
2808
		@fclose($t_handle);
2809
	} else {
2810
		pfb_logger("\n ** TLD Master data file missing. Terminating TLD **\n", 1);
2811
		return;
2812
	}
2813

    
2814
	// DNSBL python - create file handles for data, zone, and remove files
2815
	if ($pfb['dnsbl_py_blacklist']) {
2816
		$p_data = @fopen("{$pfb['unbound_py_data']}.raw", 'w');
2817
		$p_zone = @fopen("{$pfb['unbound_py_zone']}.raw", 'w');
2818
		$p_tsp = @fopen("{$pfb['dnsbl_tld_remove']}", 'w');
2819

    
2820
		if ((get_resource_type($p_data) != 'stream') ||
2821
		    (get_resource_type($p_zone) != 'stream') ||
2822
		    (get_resource_type($p_tsp) != 'stream')) {
2823

    
2824
			pfb_logger("\nFailed to create DNSBL python data|zone|remove file handles! Exiting\n", 1);
2825
			foreach (array($p_data, $p_zone, $p_tsp) as $handlex) {
2826
				if ($handlex) {
2827
					@fclose($handlex);
2828
				}
2829
			}
2830
			return;
2831
		}
2832
	}
2833

    
2834
	// Collect TLD Blacklist(s). If configured the whole TLD will be blocked
2835
	$tld_blacklist = pfbng_text_area_decode($pfb['dnsblconfig']['tldblacklist'], TRUE, FALSE, TRUE);
2836
	if (!empty($tld_blacklist)) {
2837
		$tld_blacklist = array_flip($tld_blacklist);
2838
	}
2839

    
2840
	// Collect TLD Whitelist(s). If configured, create a 'static local-zone' Resolver entry (Not required for python mode blocking)
2841
	$whitelist = array();
2842
	if (!$pfb['dnsbl_py_blacklist']) {
2843
		$whitelist = pfbng_text_area_decode($pfb['dnsblconfig']['tldwhitelist'], TRUE, FALSE, TRUE);
2844
		$tld_whitelist = array();
2845
	}
2846

    
2847
	$extdns_esc = escapeshellarg("@{$pfb['extdns']}");
2848

    
2849
	if (!empty($tld_blacklist) && !empty($whitelist)) {
2850
		foreach ($whitelist as $domain) {
2851

    
2852
			// Use user-defined IP address
2853
			if (strpos($domain, '|') !== FALSE) {
2854
				list($domain, $resolved_host) = array_map('trim', explode('|', $domain));
2855
			}
2856

    
2857
			// Resolve Domain IP address
2858
			else {
2859
				$domain_esc = escapeshellarg($domain);
2860
				$resolved_host = exec("/usr/bin/drill {$extdns_esc} {$domain_esc} | grep -v '^;\|^\$' | head -1 | cut -f5 2>&1");
2861
			}
2862

    
2863
			$tld = '';
2864
			if (strpos($domain, '.') !== FALSE && is_ipaddr($resolved_host)) {
2865
				$dparts = explode('.', $domain);
2866
				$dcnt	= count($dparts);
2867
				$tld	= end($dparts);
2868

    
2869
				for ($i=($dcnt-1); $i > 0; $i--) {
2870
					$d_query = implode('.', array_slice($dparts, -$i, $i, TRUE));
2871
					if (isset($tlds[$tld][$d_query])) {
2872
						$tld = $d_query;
2873
						break;
2874
					}
2875
				}
2876
			}
2877

    
2878
			$resolved_host = pfb_filter($resolved_host, PFB_FILTER_IP, 'tld_analysis');
2879
			if (!empty($tld) && !empty($resolved_host)) {
2880
				if (!is_array($tld_whitelist[$tld])) {
2881
					$tld_whitelist[$tld] = array();
2882
				}
2883
				$tld_whitelist[$tld][] = array($domain, $resolved_host);
2884
				pfb_logger(" TLD Whitelist {$domain}|{$resolved_host}\n", 1);
2885
			} elseif (!empty($resolved_host)) {
2886
				pfb_logger("\n TLD Whitelist - Missing data | {$domain} | {$resolved_host} |\n", 1);
2887
			}
2888
		}
2889
	}
2890

    
2891
	// Process TLD Blacklist(s). If configured the whole TLD will be blocked
2892
	if (!empty($tld_blacklist)) {
2893

    
2894
		$tld_list	= '';
2895
		$tld_cnt	= 0;
2896
		$tld_segments	= 0;
2897
		pfb_logger(" Blocking full TLD/Sub-Domain(s)... |", 1);
2898

    
2899
		foreach ($tld_blacklist as $tld => $key) {
2900

    
2901
			unset($tld_blacklist[$tld]);			// Remove old entry
2902
			$tld = trim($tld, '.');				// Remove any leading/trailing dots
2903
			$tld_blacklist[$tld] = '';			// Add new TLD entry
2904

    
2905
			// DNSBL python - TLD Blacklist (Set logging type to enabled '1')
2906
			if ($pfb['dnsbl_py_blacklist']) {
2907
				$tld_cnt++;
2908
				$pfb_found = TRUE;
2909
				$tld_segments = @max((array((substr_count($tld, '.') +1), $tld_segments) ?: 1));
2910

    
2911
				pfb_logger("{$tld}|", 1);
2912
				@fwrite($p_zone, ",{$tld},,1,DNSBL_TLD,DNSBL_TLD\n");
2913

    
2914
				// Add TLD to remove file
2915
				@fwrite($p_tsp, ".{$tld},,\n");
2916

    
2917
				// Collect List of TLDs and save to DNSBL folder
2918
				$tld_list .= ",{$tld},,\n";
2919

    
2920
				// Remove any 'TLD Blacklists' from the 'TLD master list'
2921
				if (isset($tlds[$tld])) {
2922
					unset($tlds[$tld]);
2923
				}
2924
				continue;
2925
			}
2926

    
2927
			// (Unbound mode only. Cannot have duplicate zones defined
2928
			elseif (!empty($pfb['safesearch_tlds']) && isset($pfb['safesearch_tlds'][$tld])) {
2929
				pfb_logger("\n{$tld}(Removed due to SafeSearch conflict)", 1);
2930
				continue;
2931
			}
2932

    
2933
			$dnsbl_file = "{$pfb['dnsbl_tmpdir']}/DNSBL_{$tld}.txt";
2934
			if (!file_exists($dnsbl_file)) {
2935

    
2936
				$tld_cnt++;
2937
				$pfb_found = TRUE;
2938
				$tld_segments = @max((array((substr_count($tld, '.') +1), $tld_segments) ?: 1));
2939

    
2940
				// If a 'TLD Whitelist' exists, use 'static local-zone'
2941
				if (isset($tld_whitelist[$tld])) {
2942

    
2943
					pfb_logger("{$tld}(static)|", 1);
2944
					$dnsbl_line = "local-zone: \"{$tld}\" \"static\"\n";
2945
					$whitelist = $tld_whitelist[$tld];
2946

    
2947
					foreach ($whitelist as $list) {
2948
						$ip_type = is_ipaddr($list[1]);
2949
						$ip_esc = escapeshellarg($list[0]);
2950

    
2951
						switch ($ip_type) {
2952
							case 4:
2953
								$dnsbl_line .= "local-data: \"{$list[0]} A {$list[1]}\"\n";
2954
								$tld_list .= "{$tld} | {$list[0]} A {$list[1]}\n";
2955
								if ($pfb['dnsbl_v6'] == 'on') {
2956
									$r = exec("/usr/bin/drill {$extdns_esc} AAAA {$ip_esc} | grep -v '^;\|^\$' | head -1 | cut -f5 2>&1");
2957
									if (is_ipaddrv6($r)) {
2958
										$dnsbl_line .= "local-data: \"{$list[0]} AAAA {$r}\"\n";
2959
										$tld_list .= "{$list[0]} AAAA {$r}\"\n";
2960
									}
2961
								}
2962
								break;
2963
							case 6:
2964
								$r = exec("/usr/bin/drill {$extdns_esc} A {$ip_esc} | grep -v '^;\|^\$' | head -1 | cut -f5 2>&1");
2965
								if (is_ipaddrv4($r)) {
2966
									$dnsbl_line .= "local-data: \"{$list[0]} A {$r}\"\n";
2967
									$tld_list .= "{$tld} | {$list[0]} A {$r}\"\n";
2968
								}
2969
								$dnsbl_line .= "local-data: \"{$list[0]} AAAA {$list[1]}\"\n";
2970
								$tld_list .= "{$tld} | {$list[0]} AAAA {$list[1]}\"\n";
2971
								break;
2972
							default:
2973
								break;
2974
						}
2975
					}
2976
				}
2977

    
2978
				// Create 'redirect' zone for whole TLD
2979
				else {
2980
					pfb_logger("{$tld}|", 1);
2981

    
2982
					$ipv6_dnsbl = '';
2983
					if ($pfb['dnsbl_v6'] == 'on') {
2984
						$ipv6_dnsbl = " local-data: \"{$tld} 60 IN AAAA ::{$pfb['dnsbl_vip']}\"";
2985
					}
2986
					$dnsbl_line = "local-zone: \"{$tld}\" redirect local-data: \"{$tld} 60 IN A {$pfb['dnsbl_vip']}\"{$ipv6_dnsbl}\n";
2987

    
2988
					// Collect List of TLDs and save to DNSBL folder
2989
					$tld_list .= "{$tld}\n";
2990
				}
2991

    
2992
				@file_put_contents($dnsbl_file, $dnsbl_line, LOCK_EX);
2993

    
2994
				// Add TLD to remove file (To be removed from 'transparent' zone)
2995
				@file_put_contents("{$pfb['dnsbl_tld_remove']}.tsp", ".{$tld} 60\n", FILE_APPEND | LOCK_EX);
2996

    
2997
				// Remove any 'TLD Blacklists' from the 'TLD master list'
2998
				if (isset($tlds[$tld])) {
2999
					unset($tlds[$tld]);
3000
				}
3001
			}
3002
		}
3003

    
3004
		// Save a list of TLDs in DNSBL folder (DNSBL total line count verification)
3005
		if (!empty($tld_list)) {
3006
			@file_put_contents("{$pfb['dnsbl_tld_txt']}", $tld_list, LOCK_EX);
3007

    
3008
			// Add 'TLD' to Alias/Feeds array
3009
			if (!is_array($pfb['tld_update']['DNSBL_TLD'])) {
3010
				$pfb['tld_update']['DNSBL_TLD'] = array();
3011
			}
3012

    
3013
			$pfb['tld_update']['DNSBL_TLD']['feeds']	= array('DNSBL_TLD');
3014
			$pfb['tld_update']['DNSBL_TLD']['count']	= $tld_cnt;
3015
			$pfb['alias_dnsbl_all'][]			= 'DNSBL_TLD';
3016
		}
3017
		else {
3018
			unlink_if_exists("{$pfb['dnsbl_tld_txt']}");
3019
		}
3020
		pfb_logger(" completed\n", 1);
3021
	}
3022
	else {
3023
		unlink_if_exists("{$pfb['dnsbl_tld_txt']}");
3024
	}
3025

    
3026
	// Collect TLD Exclusion list and remove any 'TLD Exclusions' from the 'TLD master list'
3027
	$exclusion = pfbng_text_area_decode($pfb['dnsblconfig']['tldexclusion'], TRUE, FALSE, TRUE);
3028
	$tld_exclusion = array();
3029
	if (!empty($exclusion)) {
3030
		foreach ($exclusion as $key => $exclude) {
3031
			$exclude = trim($exclude, '.');		// Remove any leading/trailing dots
3032

    
3033
			// Collect exclusion
3034
			if (strpos($exclude, '.') !== FALSE) {
3035
				$tld_exclusion[$exclude] = '';
3036
			}
3037

    
3038
			// Remove Exclusion from TLDS array
3039
			if (isset($tlds[$exclude])) {
3040
				unset($tlds[$exclude]);
3041
			}
3042
		}
3043
	}
3044

    
3045
	pfb_logger("TLD analysis", 1);
3046

    
3047
	// [ $pfb['dnsbl_file']}.tsp	] Final DNSBL output file (using 'transparent' zone)
3048
	// [ $pfb['dnsbl_tld_remove']	] File of Sub-Domains to be removed (from 'redirect' zone)
3049

    
3050
	// DNSBL Unbound: Analyse DNSBL: 1) 'redirect' zone for whole Domain 2) 'transparent' zone only
3051
	// DNSBL python: Analyse DNSBL into local and zone files
3052

    
3053
	if (($fhandle = @fopen("{$pfb['dnsbl_file']}.raw", 'r')) !== FALSE) {
3054
		while (($line = @fgets($fhandle)) !== FALSE) {
3055
			if (empty($line)) {
3056
				continue;
3057
			}
3058

    
3059
			// Display progress indicator
3060
			if ($domain_cnt % 100000 == 0) {
3061
				// Memory limitation exceeded for 'redirect' zones
3062
				if ($domain_cnt >= $pfb['domain_max_cnt']) {
3063
					pfb_logger('x', 1);
3064
				} else {
3065
					pfb_logger('.', 1);
3066
				}
3067
			}
3068

    
3069
			// DNSBL python blocking mode
3070
			if ($pfb['dnsbl_py_blacklist']) {
3071
				$eparts = explode(',', $line, 3);
3072
				$domain = $eparts[1];
3073
				$dparts = explode('.', $domain);
3074
				$dcnt   = count($dparts);
3075
				$tld    = end($dparts);
3076
				$d_info = $eparts[2]; // Logging Type/Header/Alias group details
3077
				$dfound	= '';
3078
			}
3079

    
3080
			// DNSBL Unbound blocking mode
3081
			else {
3082
				$eparts = explode(' ', str_replace('"', '', $line), 3);
3083
				$domain = $eparts[1];
3084
				$s_info = trim($eparts[2]);
3085

    
3086
				if ($pfb['dnsbl_v6'] == 'on') {
3087
					// Determine if DNSBL Logging is disabled and switch to '::0'
3088
					if (strpos($s_info, ' A 0.0.0.0') !== FALSE) {
3089
						$s_info6 = str_replace(' A 0.0.0.0', ' AAAA ::0', $s_info);
3090
					} else {
3091
						$s_info6 = str_replace(' A ', ' AAAA ::', $s_info);
3092
					}
3093
				}
3094

    
3095
				$dparts = explode('.', $domain);
3096
				$dcnt	= count($dparts);
3097
				$tld	= end($dparts);
3098
				$dfound = '';
3099
			}
3100

    
3101
			// Determine if TLD exists in TLD Blacklist (skip for DNSBL python)
3102
			if (!$pfb['dnsbl_py_blacklist'] && !empty($tld_blacklist)) {
3103

    
3104
				// Determine minimum 'tld level' for loop efficiency
3105
				$min_cnt = @min(array($tld_segments, ($dcnt -1)));
3106

    
3107
				for ($i=1; $i <= $min_cnt; $i++) {
3108
					$d_query = implode('.', array_slice($dparts, -$i, $i, TRUE));
3109
					if (isset($tld_blacklist[$d_query])) {
3110
						continue 2;			// Whole TLD being blocked
3111
					}
3112
				}
3113
			}
3114

    
3115
			if ($domain_cnt <= $pfb['domain_max_cnt']) {
3116

    
3117
				// Search TLD master list (Levels 1-4)
3118
				// If Domain is a Sub-Domain, create 'transparent' zone. Otherwise create 'redirect' zone
3119
				switch($dcnt) {
3120
					case ($dcnt > 5):
3121
						break;
3122
					case '5':
3123
						$dfound = tld_search($tld, $dparts, 4, 5);
3124
						break;
3125
					case '4':
3126
						$dfound = tld_search($tld, $dparts, 3, 4);
3127
						break;
3128
					case '3':
3129
						$dfound = tld_search($tld, $dparts, 2, 3);
3130
						break;
3131
					case '2':
3132
						$dfound = implode('.', array_slice($dparts, -2, 2, TRUE));
3133
						break;
3134
				}
3135
			}
3136

    
3137
			// If Domain is in the TLD Exclusion(s), use 'transparent zone'
3138
			if (!empty($domain) && isset($tld_exclusion[$domain])) {
3139
				$dfound = '';
3140
			}
3141

    
3142
			// Create 'redirect' zone for Domain
3143
			if (!empty($dfound)) {
3144
				$pfb_found = TRUE;
3145

    
3146
				// DNSBL python blocking mode
3147
				if ($pfb['dnsbl_py_blacklist']) {
3148
					@fwrite($p_zone, ",{$dfound},{$d_info}");
3149

    
3150
					// TLD remove files - See below for description
3151
					@fwrite($p_tsp, ".{$dfound},,\n");
3152
				}
3153
				else {
3154
					$ipv6_dnsbl = '';
3155
					if ($pfb['dnsbl_v6'] == 'on') {
3156
						$ipv6_dnsbl = " local-data: \"{$dfound} {$s_info6}\"";
3157
					}
3158
					$domain_line = "local-zone: \"{$dfound}\" redirect local-data: \"{$dfound} {$s_info}\"{$ipv6_dnsbl}\n";
3159
					@file_put_contents("{$pfb['dnsbl_file']}.tsp", $domain_line, FILE_APPEND | LOCK_EX);
3160

    
3161
					// Add Domain to remove file for [ 1- 'redirect zone' Domains 2- Unbound memory domains ]
3162
					// This removes any of these domains and sub-domains
3163
					@file_put_contents("{$pfb['dnsbl_tld_remove']}", ".{$dfound} 60\n\"{$dfound} 60\n", FILE_APPEND | LOCK_EX);
3164

    
3165
					// Add Domain to remove file for 'transparent zone' domains
3166
					// This removes any of these sub-domains
3167
					@file_put_contents("{$pfb['dnsbl_tld_remove']}.tsp", ".{$dfound} 60\n", FILE_APPEND | LOCK_EX);
3168
				}
3169
			}
3170

    
3171
			// Create 'transparent zone' for Sub-Domain
3172
			else {
3173
				// DNSBL python blocking mode
3174
				if ($pfb['dnsbl_py_blacklist']) {
3175
					@fwrite($p_data, ",{$domain},{$d_info}");
3176
				}
3177
				else {
3178
					if (!empty($tld)) {
3179
						$dnsbl_file = "{$pfb['dnsbl_tmpdir']}/DNSBL_{$tld}.txt";
3180

    
3181
						// Create a temp file for each TLD. w/ 'transparent' header followed by each 'local-data' line
3182
						if (!file_exists($dnsbl_file)) {
3183
							$dnsbl_header = "local-zone: \"{$tld}\" \"transparent\"\n";
3184
							@file_put_contents($dnsbl_file, $dnsbl_header, LOCK_EX);
3185
						}
3186

    
3187
						$ipv6_dnsbl = '';
3188
						if ($pfb['dnsbl_v6'] == 'on') {
3189
							$ipv6_dnsbl = " local-data: \"{$domain} {$s_info6}\"";
3190
						}
3191
						$domain_line = "local-data: \"{$domain} {$s_info}\"{$ipv6_dnsbl}\n";
3192
						@file_put_contents($dnsbl_file, $domain_line, FILE_APPEND | LOCK_EX);
3193
					}
3194
					else {
3195
						$oline = htmlentities($line);
3196
						pfb_logger("\nDebug: Missing TLD: {$oline}", 1);
3197
					}
3198
				}
3199
			}
3200

    
3201
			// Increment Domain counter
3202
			$domain_cnt++;
3203
		}
3204
	}
3205

    
3206
	foreach (array($fhandle, $p_data, $p_zone, $p_tsp) as $handlex) {
3207
		if ($handlex) {
3208
			@fclose($handlex);
3209
		}
3210
	}
3211
	unset($tlds, $tld_blacklist, $tld_exclusion);
3212

    
3213
	// TLD 'redirect zones' found. Finalize TLD function
3214
	if ($pfb_found) {
3215
		$log = " completed [ NOW ]\n";
3216
		// Print TLD exceedance error message
3217
		if ($domain_cnt >= $pfb['domain_max_cnt']) {
3218
			$log .= "\n  ** TLD Domain count exceeded. [ {$pfb['domain_max_cnt']} ] All subsequent Domains listed as-is **\n\n";
3219
		}
3220
		$log .= "TLD finalize";
3221
		pfb_logger("{$log}", 1);
3222

    
3223
		// Execute Domain De-duplication
3224
		if ($pfb['dnsbl_py_blacklist']) {
3225
			exec("{$pfb['script']} domaintldpy >> {$pfb['log']} 2>&1");
3226
		}
3227
		else {
3228
			// Create a csv list of 'recently updated' DNSBL Feeds, as ordered by User
3229
			$dnsbl_feeds = '';
3230
			if (!empty($pfb['tld_update'])) {
3231
				foreach ($pfb['tld_update'] as $alias => $data) {
3232
					foreach ($data['feeds'] as $feed) {
3233
						$dnsbl_feeds .= "{$feed},";
3234
					}
3235
				}
3236
			}
3237

    
3238
			if (!empty(pfb_filter($dnsbl_feeds, PFB_FILTER_CSV, 'tld_analysis'))) {
3239
				exec("{$pfb['script']} domaintld x x x {$dnsbl_feeds} >> {$pfb['log']} 2>&1");
3240
			} else {
3241
				pfb_logger("\nFailed to create list of DNSBL Feeds", 1);
3242
			}
3243
		}
3244
		pfb_logger("\nTLD finalize... completed [ NOW ]\n", 1);
3245

    
3246
		// Update DNSBL Alias and Widget Stats
3247
		if (!empty($pfb['tld_update'])) {
3248
			foreach ($pfb['tld_update'] as $alias => $data) {
3249

    
3250
				// Create Alias summary file for each DNSBL Alias
3251
				$lists_dnsbl_current = array();
3252
				foreach ($data['feeds'] as $feed) {
3253
					$lists_dnsbl_current[] = "{$feed}";
3254
				}
3255
				dnsbl_alias_update('update', $alias, $pfb['dnsdir'], $lists_dnsbl_current, $data['count']);
3256
			}
3257
		}
3258
	}
3259
	else {
3260
		pfb_logger(" no changes\n", 1);
3261
	}
3262

    
3263
	// Save DNSBL Alias statistics
3264
	dnsbl_save_stats();
3265

    
3266
	if ($pfb['dnsbl_py_blacklist'] && file_exists("{$pfb['dnsbl_file']}.raw")) {
3267
		unlink_if_exists("{$pfb['dnsbl_file']}.raw");
3268
	}
3269
}
3270

    
3271
// Function to Start Unbound
3272
function pfb_stop_start_unbound($type) {
3273
	global $g, $pfb;
3274

    
3275
	$final = array();
3276
	if (file_exists("{$g['varrun_path']}/unbound.pid")) {
3277
		pfb_logger("\nStopping Unbound Resolver", 1);
3278
		sigkillbypid("{$g['varrun_path']}/unbound.pid", 'TERM');
3279
	}
3280

    
3281
	// If unbound is still running, wait up to 30 seconds for it to terminate.
3282
	for ($i=1; $i <= 30; $i++) {
3283
		if (is_process_running('unbound')) {
3284
			pfb_logger('.', 1);
3285
			sleep(1);
3286
		} else {
3287
			pfb_logger("\nUnbound stopped in {$i} sec.", 1);
3288
			break;
3289
		}
3290
	}
3291

    
3292
	// Add/Remove additional python mounts
3293
	if (file_exists('/var/unbound/pfb_unbound_include.inc')) {
3294
		$g['pfblockerng_include_verbose'] = TRUE;
3295
		pfb_logger("\nAdditional mounts{$type}:", 1);
3296
		require_once('/var/unbound/pfb_unbound_include.inc');
3297
		unset($g['pfblockerng_include_verbose']);
3298
	}
3299

    
3300
	// Remove Unbound python mounts
3301
	if ($pfb['dnsbl_python_unmount']) {
3302
		pfb_unbound_python_unmount();
3303
	}
3304

    
3305
	pfb_logger("\nStarting Unbound Resolver", 1);
3306
	exec("/usr/local/sbin/unbound -c /var/unbound/unbound.conf 2>&1", $final['result'], $final['retval']);
3307
	return $final;
3308
}
3309

    
3310

    
3311
// Reload Resolver
3312
function pfb_reload_unbound($mode, $cache=FALSE, $pfbpython=FALSE) {
3313
	global $g, $pfb;
3314

    
3315
	$final = array();
3316
	$type = '';
3317
	if ($mode == 'enabled' && $pfb['dnsbl_py_blacklist']) {
3318
		$type = ' (DNSBL python)';
3319
	}
3320

    
3321
	if (!$pfb['dnsbl_py_blacklist'] && file_exists("{$pfb['dnsbl_file']}.raw")) {
3322
		@rename("{$pfb['dnsbl_file']}.raw", "{$pfb['dnsbl_file']}.conf");
3323
	}
3324

    
3325
	$cache_dumpfile = tempnam('/var/tmp/', 'unbound_cache_');
3326
	if ($mode == 'enabled' && is_process_running('unbound') && !$pfb['dnsbl_python_unmount'] && !$pfbpython) {
3327

    
3328
		$log = "\nReloading Unbound Resolver{$type}";
3329
		pfb_logger($log, 1);
3330

    
3331
		if ($cache && $pfb['dnsbl_res_cache'] == 'on') {
3332
			exec("{$pfb['chroot_cmd']} dump_cache > " . escapeshellarg($cache_dumpfile) . " 2>&1");
3333
			pfb_logger('.', 1);
3334
		}
3335
	}
3336

    
3337
	$final = pfb_stop_start_unbound($type);
3338
	pfb_logger('.', 1);
3339

    
3340
	if ($final['retval'] != 0) {
3341

    
3342
		@copy("{$pfb['dnsbldir']}/unbound.conf", "{$pfb['dnsbldir']}/unbound.conf.error");
3343
		if ($mode == 'enabled') {
3344
			if (!$pfb['dnsbl_py_blacklist']) {
3345
				$log = "\nDNSBL {$mode} FAIL - restoring Unbound conf *** Fix error(s) and a Force Reload required! ***\n";
3346

    
3347
				// Try to restore previous DNSBL database
3348
				if (file_exists("{$pfb['dnsbl_file']}.bk")) {
3349
					@rename("{$pfb['dnsbl_file']}.bk", "{$pfb['dnsbl_file']}.conf");
3350
				}
3351

    
3352
				// Wipe DNSBL database
3353
				else {
3354
					$log .= ' Restore previous database Failed!';
3355
					unlink_if_exists("{$pfb['dnsbl_file']}.conf");
3356
					touch("{$pfb['dnsbl_file']}.conf");
3357

    
3358
					// Restore previous unbound.conf
3359
					if (file_exists("{$pfb['dnsbldir']}/unbound.bk")) {
3360
						@rename("{$pfb['dnsbldir']}/unbound.bk", "{$pfb['dnsbldir']}/unbound.conf");
3361
					}
3362
				}
3363
			}
3364
			else {
3365
				$log = "\nDNSBL {$mode} FAIL  *** Fix error(s) and a Force Reload required! ***\n";
3366
				if (file_exists("{$pfb['dnsbldir']}/unbound.bk")) {
3367
					@rename("{$pfb['dnsbldir']}/unbound.bk", "{$pfb['dnsbldir']}/unbound.conf");
3368
				}
3369
			}
3370
			pfb_logger("{$log}", 2);
3371
		}
3372
		else {
3373
			$log = "\nDNSBL {$mode} - Unbound conf update FAIL *** Fix error(s) and a Force Reload required! ***\n";
3374
			pfb_logger("{$log}", 2);
3375
		}
3376

    
3377
		$log = htmlspecialchars(implode("\n", $final['result']));
3378
		pfb_logger("\n\n====================\n\n{$log}\n\n====================\n\n", 2);
3379
		$final = pfb_stop_start_unbound($type);
3380
	}
3381

    
3382
	// Confirm that Resolver is running
3383
	if (is_process_running('unbound')) {
3384
		pfb_logger('.', 1);
3385

    
3386
		// $final['result'] will be appended with previous result above
3387
		exec("{$pfb['chroot_cmd']} status 2>&1", $final['result'], $final['retval']);
3388
		pfb_logger('.', 1);
3389
		if (preg_grep("/is running.../", $final['result'])) {
3390
			pfb_logger(" completed [ NOW ]", 1);
3391

    
3392
			// Restore Resolver cache
3393
			if ($cache && $pfb['dnsbl_res_cache'] == 'on' && file_exists($cache_dumpfile) && filesize($cache_dumpfile) > 0) {
3394
				exec("{$pfb['chroot_cmd']} load_cache < " . escapeshellarg($cache_dumpfile) . " 2>&1");
3395
				$log = "\nResolver cache restored [ NOW ]";
3396
				pfb_logger($log, 1);
3397
			}
3398
		}
3399
		else {
3400
			$log = htmlspecialchars(implode("\n", $final['result']));
3401
			pfb_logger(" Not completed. [ NOW ]\n{$log}\n", 1);
3402
		}
3403
	}
3404
	else {
3405
		$log = htmlspecialchars(implode("\n", $final['result']));
3406
		pfb_logger(" Not completed. [ NOW ]\n{$log}\n", 1);
3407
	}
3408
	unlink_if_exists($cache_dumpfile);
3409
}
3410

    
3411

    
3412
// Function to clear Unbound/DNSBL work files
3413
function pfb_unbound_clear_work_files() {
3414
	global $pfb;
3415

    
3416
	foreach (array( $pfb['dnsbl_cache'],
3417
			"{$pfb['dnsbldir']}/unbound.bk",
3418
			"{$pfb['dnsbldir']}/unbound.tmp",
3419
			"{$pfb['dnsbl_file']}.bk",
3420
			"{$pfb['dnsbl_file']}.tsp",
3421
			"{$pfb['dnsbl_file']}.sync",
3422
			"/tmp/dnsbl_remove*",
3423
			"/tmp/dnsbl_add*",
3424
			"/tmp/dnsbl_tld*",
3425
			"{$pfb['unbound_py_data']}.raw",
3426
			"{$pfb['unbound_py_zone']}.raw",
3427
			"/var/tmp/unbound_cache_*") as $remove) {
3428
		unlink_if_exists($remove);
3429
	}
3430
}
3431

    
3432

    
3433
// Load new DNSBL updates to Unbound Resolver
3434
function pfb_update_unbound($mode, $pfbupdate, $pfbpython) {
3435
	global $g, $pfb;
3436

    
3437
	if ($mode == 'enabled') {
3438
		$ext = '.bk';
3439
	} else {
3440
		$ext = '.*';	// Remove all DNSBL Unbound files
3441
	}
3442

    
3443
	// Execute TLD analysis, if configured
3444
	if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && !$pfb['save']) {
3445
		if ($pfb['dnsbl_tld']) {
3446
			tld_analysis();
3447
		} else {
3448
			unlink_if_exists("{$pfb['dnsbl_tld_txt']}");
3449
		}
3450
	}
3451

    
3452
	// Create file marker to disable DNSBL Queries daemon to avoid unbound-control collisions
3453
	touch("{$pfb['dnsbl_file']}.sync");
3454

    
3455
	// Marker file(s) to instruct Unbound to be reloaded
3456
	if ($pfb['reuse_dnsbl'] == 'on' ||
3457
	    file_exists("{$pfb['dnsbl_file']}.reload") ||
3458
	    file_exists("{$pfb['dnsbl_unlock']}")) {
3459

    
3460
		$pfbupdate = TRUE;
3461
		unlink_if_exists("{$pfb['dnsbl_file']}.reload");
3462
		unlink_if_exists("{$pfb['dnsbl_unlock']}");
3463
		unlink_if_exists("{$pfb['dnsbl_unlock']}.data");
3464
	}
3465

    
3466
	// Backup existing unbound.conf and rename new unbound.conf file
3467
	if (file_exists("{$pfb['dnsbldir']}/unbound.tmp")) {
3468
		@copy("{$pfb['dnsbldir']}/unbound.conf", "{$pfb['dnsbldir']}/unbound.bk");
3469
		@rename("{$pfb['dnsbldir']}/unbound.tmp", "{$pfb['dnsbldir']}/unbound.conf");
3470
	}
3471

    
3472
	// When pfBlockerNG is disabled and 'keep blocklists' is disabled.
3473
	if ($pfb['enable'] == '' && $pfb['keep'] == '' && !$pfb['install']) {
3474
		unlink_if_exists("{$pfb['dnsbl_file']}{$ext}");
3475
	}
3476

    
3477
	// Disable DNSBL
3478
	if (($pfb['enable'] != 'on' || $pfb['dnsbl'] != 'on') && !$pfb['install']) {
3479

    
3480
		pfb_reload_unbound('disabled', FALSE, $pfbpython);
3481
		if (is_service_running('pfb_dnsbl')) {
3482
			pfb_logger("\nStop Service DNSBL", 1);
3483
			stop_service('pfb_dnsbl');
3484
		}
3485
		pfb_unbound_clear_work_files();
3486

    
3487
		// Unmount Unbound python 'lib/bin' folders after Unbound has been reloaded with the python integration enabled
3488
		if ($pfb['dnsbl_python_unmount']) {
3489
			pfb_unbound_python_unmount();
3490
			unset($pfb['dnsbl_python_unmount']);
3491
		}
3492
		pfb_logger("\nDNSBL is disabled\n", 1);
3493
		return;
3494
	}
3495

    
3496
	// Load new DNSBL updates
3497
	if (is_service_running('unbound')) {
3498

    
3499
		// 'Live sync' new DNSBL updates utilizing unbound-control
3500
		if (!$pfb['dnsbl_py_blacklist'] && $pfb['dnsbl_sync'] && !$pfbpython &&
3501
		    file_exists("{$pfb['dnsbl_file']}.conf") && filesize("{$pfb['dnsbl_file']}.conf") > 0) {
3502

    
3503
			$sync_fail = FALSE;
3504

    
3505
			pfb_logger("\nResolver Live Sync analysis", 1);
3506
			exec("{$pfb['script']} dnsbl_livesync >> {$pfb['log']} 2>&1");
3507
			pfb_logger(" completed [ NOW ]", 1);
3508

    
3509
			$ucsync = array(array(	'dnsbl_remove_zone',	'local_zones_remove',	'Remove local-zone(s)' ),
3510
					array(	'dnsbl_remove_data',	'local_datas_remove',	'Remove local-data(s)' ),
3511
					array(	'dnsbl_add_zone',	'local_zones',		'Add local-zone(s)' ),
3512
					array(	'dnsbl_add_data',	'local_datas',		'Add local-data(s)' ));
3513

    
3514
			// Disable zone updates when TLD is disabled
3515
			if (!$pfb['dnsbl_tld']) {
3516
				unset($ucsync[0], $ucsync[2]);
3517
			}
3518

    
3519
			pfb_logger("\nResolver Live Sync finalizing:", 1);
3520
			foreach ($ucsync as $skey => $sync) {
3521
				$file = $pfb[$sync[0]];
3522

    
3523
				if (filesize("{$file}") > 0) {
3524
					$result = array();
3525
					exec("{$pfb['chroot_cmd']} {$sync[1]} < {$file}", $result, $retval);
3526
					$result	= implode("\n", $result);
3527
					$log	= "\n\t{$sync[2]}:\t\t{$result}";
3528
				}
3529
				else {
3530
					$log	= "\n\t{$sync[2]}:\t\tno changes";
3531
				}
3532
				pfb_logger("{$log}", 1);
3533

    
3534
				if (!$sync_fail && !empty($retval)) {
3535
					$sync_fail = TRUE;
3536
				}
3537
			}
3538

    
3539
			if ($sync_fail) {
3540
				pfb_logger("\nResolver Live Sync ... FAILED!", 1);
3541
				pfb_reload_unbound('reload', FALSE, $pfbpython);
3542
			}
3543
		}
3544

    
3545
		// Do a full Reload of Unbound
3546
		else {
3547
			pfb_reload_unbound($mode, TRUE, $pfbpython);
3548
		}
3549
	}
3550

    
3551
	// Start Unbound Service with new DNSBL Updates
3552
	else {
3553
		pfb_reload_unbound($mode, FALSE, $pfbpython);
3554
	}
3555

    
3556
	if ($pfbpython) {
3557
		$log = "\nRestarting DNSBL Service (DNSBL python)";
3558
		pfb_logger("{$log}", 1);
3559
		restart_service('pfb_dnsbl');
3560
	}
3561

    
3562
	$dnsbl_cnt = exec("/bin/cat {$pfb['dnsdir']}/*.txt | {$pfb['grep']} -c ^ 2>&1");
3563

    
3564
	// Unbound blocking mode enabled
3565
	if (!$pfb['dnsbl_py_blacklist']) {
3566
		$final_cnt = exec("{$pfb['grep']} -v '\"transparent\"\|\"static\"' {$pfb['dnsbl_file']}.conf | {$pfb['grep']} -c ^ 2>&1");
3567
		if ($final_cnt == $dnsbl_cnt) {
3568
			$log = "\nDNSBL update [ {$final_cnt} | PASSED  ]... completed [ NOW ]";
3569
		} else {
3570
			$log = "\n*** DNSBL update [ {$final_cnt} ] [ {$dnsbl_cnt} ] ... OUT OF SYNC ! *** [ NOW ]";
3571
		}
3572
		pfb_logger("{$log}", 1);
3573
	}
3574

    
3575
	// Python blocking mode enabled
3576
	else {
3577
		$tld_cnt = @file_get_contents($pfb['unbound_py_count']);
3578
		$dnsbl_cnt = $dnsbl_cnt - $tld_cnt;
3579
		$final_cnt = exec("/usr/bin/find {$pfb['unbound_py_data']} {$pfb['unbound_py_zone']} -type f 2>/dev/null | xargs cat | {$pfb['grep']} -c ^ 2>&1");
3580
		if ($final_cnt == $dnsbl_cnt) {
3581
			$log = "\nDNSBL update [ {$final_cnt} | PASSED  ]... completed [ NOW ]";
3582
		} else {
3583
			$log = "\n*** DNSBL update [ {$final_cnt} ] [ {$dnsbl_cnt} ] ... OUT OF SYNC ! *** [ NOW ]";
3584
		}
3585
		pfb_logger("{$log}", 1);
3586
	}
3587

    
3588
	// DEBUG Live Sync
3589
	if (!$pfb['dnsbl_py_blacklist'] && $pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && $pfb['dnsbl_sync'] && !$pfbupdate && !$pfbpython && !$sync_fail) {
3590
		pfb_logger("\n\nDNSBL DEBUG", 1);
3591
		$datacnt = $zonecnt = 0;
3592

    
3593
		exec("{$pfb['chroot_cmd']} list_local_data | {$pfb['grep']} '{$pfb['dnsbl_vip']}\|0\.0\.0\.0\$' | {$pfb['grep']} -v 'AAAA' | {$pfb['grep']} -c ^ 2>&1", $datacnt, $retval);
3594
		$datacnt = implode($datacnt);
3595
		pfb_logger('.', 1);
3596

    
3597
		if ($pfb['dnsbl_tld']) {
3598
			exec("{$pfb['chroot_cmd']} list_local_zones | {$pfb['grep']} \"redirect\" | {$pfb['grep']} -c ^ 2>&1", $zonecnt, $retval);
3599
			pfb_logger('.', 1);
3600
			$zonecnt = implode($zonecnt);
3601

    
3602
			$tldcnt = array('0');
3603
			if (file_exists('/var/db/pfblockerng/dnsbl/DNSBL_TLD.txt')) {
3604
				exec("{$pfb['grep']} -c ' A \| AAAA ' /var/db/pfblockerng/dnsbl/DNSBL_TLD.txt 2>&1", $tldcnt, $retval);
3605
			}
3606
			$tldcnt = implode($tldcnt);
3607
			$datacnt = $datacnt + $tldcnt;
3608
		}
3609
		pfb_logger("[ Data(s): {$datacnt}\tZone(s): {$zonecnt} | NOW ]", 1);
3610
	}
3611

    
3612
	// Clear work files
3613
	pfb_unbound_clear_work_files();
3614
	pfb_logger("\n------------------------------------------------------------------------", 1);
3615
}
3616

    
3617

    
3618
// Process TOP1M database
3619
function pfblockerng_top1m() {
3620
	global $pfb;
3621

    
3622
	if (empty($pfb['dnsbl_alexa_inc'])) {
3623
		pfb_logger("\n  TOP1M: No TLD Inclusions found.\n", 1);
3624
		return;
3625
	}
3626

    
3627
	// Array of TLDs to include in Whitelist
3628
	$pfb_include = explode(',', $pfb['dnsbl_alexa_inc']);
3629
	if (!empty($pfb_include)) {
3630
		$pfb_include = array_flip($pfb_include);
3631
	}
3632

    
3633
	$linecnt = $x = 0;
3634
	pfb_logger(" Building TOP1M Whitelist [", 1);
3635

    
3636
	if (($handle = @fopen("{$pfb['dbdir']}/top-1m.csv", 'r')) !== FALSE) {
3637
		$pfb_output = @fopen("{$pfb['dbdir']}/pfbalexawhitelist.txt", 'w');
3638
		while (($line = @fgets($handle)) !== FALSE) {
3639

    
3640
			if (strpos($line, '.') === FALSE || strpos($line, ',') === FALSE || empty($line)) {
3641
				continue;
3642
			}
3643

    
3644
			// Display progress indicator
3645
			if ($linecnt % 100000 == 0) {
3646
				pfb_logger('.', 1);
3647
			}
3648

    
3649
			// Collect Domain TLD
3650
			$csvline	= str_getcsv($line);
3651
			$tld		= substr($csvline[1], strrpos($csvline[1], '.') + 1);
3652

    
3653
			if (isset($pfb_include[$tld])) {
3654
				// Whitelist both 'www.example.com' and 'example.com'
3655
				if (substr($csvline[1], 0, 4) == 'www.') {
3656
					$csvline[1] = substr($csvline[1], 4);
3657
				}
3658
				$x++;
3659

    
3660
				// Create three whitelist options per TOP1M whitelisted Domain
3661
				if ($pfb['dnsbl_py_blacklist']) {
3662
					@fwrite($pfb_output, ".{$csvline[1]},,\n,{$csvline[1]},,\n,www.{$csvline[1]},,\n");
3663
				} else {
3664
					@fwrite($pfb_output, ".{$csvline[1]} 60\n\"{$csvline[1]} 60\n\"www.{$csvline[1]} 60\n");
3665
				}
3666
			}
3667

    
3668
			if ($x >= $pfb['dnsbl_alexa_cnt']) {
3669
				break;
3670
			}
3671
			$linecnt++;
3672
		}
3673
		pfb_logger("] [ Parsed {$linecnt} lines | Found {$x} of {$pfb['dnsbl_alexa_cnt']} ]...", 1);
3674
	}
3675
	else {
3676
		$log = "\nTOP1M conversion Failed. File: top-1m.csv, not found...";
3677
		pfb_logger("{$log}", 2);
3678
	}
3679

    
3680
	if ($handle) {
3681
		@fclose($handle);
3682
	}
3683
	if ($pfb_output) {
3684
		@fclose($pfb_output);
3685
	}
3686

    
3687
	// Remove Top1M update file marker
3688
	unlink_if_exists("{$pfb['dbdir']}/top-1m.update");
3689
}
3690

    
3691

    
3692
// Function to remove any leading zeros in octets and to exclude private/reserved addresses.
3693
function sanitize_ipaddr($ipaddr, $custom, $pfbcidr) {
3694
	global $pfb;
3695

    
3696
	list ($subnet, $mask) = explode('/', $ipaddr);
3697
	$iparr = explode('.', $subnet);
3698

    
3699
	foreach ($iparr as $key => $octet) {
3700
		// Remove any leading zeros in octets
3701
		if ($octet == 0) {
3702
			$ip[$key] = 0;
3703
		} else {
3704
			$ip[$key] = ltrim($octet, '0');
3705
		}
3706

    
3707
		if ($key == 3) {
3708
			// If mask is not defined and 4th octet is '0', set mask to '24'
3709
			if ($octet == 0 && empty($mask)) {
3710
				$mask = 24;
3711
			}
3712

    
3713
			// If mask is '24', force 4th octet to '0'
3714
			if ($mask == 24 && $octet != 0) {
3715
				$ip[$key] = 0;
3716
			}
3717
		}
3718
	}
3719

    
3720
	$mask = str_replace('32', '', $mask);	// Strip '/32' mask
3721
	$ip_final = implode('.', $ip);
3722

    
3723
	// Exclude private/reserved IPs (bypass exclusion for custom lists)
3724
	if ($pfb['supp'] == 'on' && !$custom) {
3725

    
3726
		// Remove 'loopback' and '0.0.0.0' IPs
3727
		if ($ip[0] == 127 || $ip[0] == 0 || empty($ip[0])) {
3728
			return;
3729
		}
3730

    
3731
		// Advanced IPv4 Tunable (Set CIDR Block size limit)
3732
		if ($pfbcidr != 'Disabled' && !empty($mask) && $mask < $pfbcidr) {
3733
			pfb_logger("\n  Suppression CIDR Limit: {$ip_final}/{$mask}", 1);
3734
			$mask = '32';
3735
		}
3736

    
3737
		if ($mask > 32) {
3738
			$mask = '';
3739
		}
3740

    
3741
		if (!filter_var($ip_final, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== FALSE) {
3742
			return;
3743
		}
3744
	}
3745

    
3746
	if (!empty($mask)) {
3747
		return "{$ip_final}/{$mask}";
3748
	}
3749
	return "{$ip_final}";
3750
}
3751

    
3752

    
3753
// Validate IPv4 IP addresses
3754
function validate_ipv4($ipaddr) {
3755
	if (strpos($ipaddr, '/') !== FALSE) {
3756
		return is_subnetv4($ipaddr);
3757
	}
3758
	return is_ipaddrv4($ipaddr);
3759
}
3760

    
3761

    
3762
// Validate IPv6 IP addresses
3763
function validate_ipv6($ipaddr) {
3764
	if (strpos($ipaddr, '/') !== FALSE) {
3765
		return is_subnetv6($ipaddr);
3766
	}
3767
	return is_ipaddrv6($ipaddr);
3768
}
3769

    
3770

    
3771
// Validate IP addresses
3772
function validate_ip($ipaddr) {
3773
	if (strpos($ipaddr, '/') !== FALSE) {
3774
		return is_subnet($ipaddr);
3775
	}
3776
	return is_ipaddr($ipaddr);
3777
}
3778

    
3779

    
3780
// Function to check for loopback addresses (IPv4 range: 127.0.0.0/8, excluding IPv6)
3781
function FILTER_FLAG_NO_LOOPBACK_RANGE($value) {
3782
	// http://www.php.net/manual/en/filter.filters.flags.php
3783
	return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? $value : (((ip2long($value) & 0xff000000) == 0x7f000000) ? FALSE : $value);
3784
}
3785

    
3786

    
3787
// Explode IP for evaluations
3788
function ip_explode($ip) {
3789

    
3790
	$ix1	= '';
3791
	$ix	= explode('.', $ip);
3792
	foreach ($ix as $key => $octet) {
3793
		if ($key != 3) {
3794
			$ix1 .= "{$octet}.";
3795
		}
3796
	}
3797
	array_unshift($ix, $ip);
3798
	$ix[] = "{$ix1}0/24";
3799
	$ix[] = "{$ix1}";
3800

    
3801
	return $ix;
3802
}
3803

    
3804

    
3805
// Determine the header which Alerted an IP address and return the header name
3806
function find_reported_header($ip, $pfbfolder, $geoip=FALSE) {
3807
	global $pfb;
3808

    
3809
	// Find exact IP match
3810
	$q_ip	= escapeshellarg(str_replace('.', '\.', "^{$ip}"));
3811
	$query	= exec("{$pfb['grep']} -s {$q_ip} {$pfbfolder} 2>&1");
3812
	if (!empty($query)) {
3813
		$rx = pfb_parse_query($query);
3814
		return $rx;
3815
	}
3816
	else {
3817
		$v4_type = FALSE;
3818
		if (substr_count($ip, ':') > 1) {
3819
			$query		= strstr($ip, ':', TRUE);	// IPv6 Prefix
3820
			$query_esc	= escapeshellarg("^{$query}:");
3821
		} else {
3822
			$query		= strstr($ip, '.', TRUE);	// IPv4 Octet #1
3823
			$query_esc	= escapeshellarg("^{$query}\.");
3824
			$v4_type	= TRUE;
3825
		}
3826

    
3827
		$result = array();
3828
		if (!$geoip) {
3829
			exec("{$pfb['grep']} -s {$query_esc} {$pfbfolder} 2>&1", $result);
3830
		} else {
3831
			$geoip_list = "Africa\|Antarctica\|Asia\|Europe\|North_America\|South_America\|Oceania\|Proxy_and_Satellite\|Top_Spammers";
3832
			exec("{$pfb['grep']} -s {$query_esc} {$pfb['ccdir']}/*.txt | {$pfb['grep']} -v '{$geoip_list}' 2>&1", $result);
3833
		}
3834

    
3835
		$cidrs = array();
3836
		if (!empty($result)) {
3837
			foreach ($result as $line) {
3838
				$rx = pfb_parse_query($line);
3839

    
3840
				// Collect all CIDRs for analysis if Alert is from a CIDR
3841
				if (strpos($rx[1], '/') !== FALSE) {
3842
					$cidrs[] = $rx;
3843
				}
3844
			}
3845
		}
3846

    
3847
		if (isset($result)) {
3848
			unset($result);
3849
		}
3850

    
3851
		// Determine which CIDR alerted the IP address
3852
		if (!empty($cidrs)) {
3853
			foreach ($cidrs as $line) {
3854
				$validate = FALSE;
3855
				if ($v4_type) {
3856
					list($addr, $mask) = explode('/', $line[1]);
3857
					$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
3858
					$validate = ((ip2long($ip) & $mask) == (ip2long($addr) & $mask));
3859
				}
3860
				else {
3861
					/* Normalize IPv6 prefix to its start address to avoid PHP errors
3862
					 * https://redmine.pfsense.org/issues/14256
3863
					 */
3864
					list($prefix, $length) = explode('/', $line[1]);
3865
					$prefix = gen_subnetv6($prefix, $length);
3866
					$subnet = "{$prefix}/{$length}";
3867

    
3868
					$validate = (Net_IPv6::isInNetmask($ip, $subnet));
3869
				}
3870

    
3871
				// Return header on CIDR match
3872
				if ($validate) {
3873
					unset($cidrs);
3874
					return $line;
3875
				}
3876
			}
3877
			unset($cidrs);
3878
		}
3879
	}
3880

    
3881
	if (isset($result)) {
3882
		unset($result);
3883
	}
3884
	return array('Unknown', 'Unknown');
3885
}
3886

    
3887

    
3888
// Function to download feeds
3889
function pfb_download($list_url, $file_dwn, $pflex=FALSE, $header, $format, $logtype, $vtype='', $timeout=300, $type='', $username='', $password='', $srcint=FALSE) {
3890
	global $pfb;
3891
	$http_status = '';
3892
	$elog = ">> {$pfb['log']} 2>&1";
3893

    
3894
	// Remove any leading/trailing whitespace
3895
	$list_url = trim($list_url);
3896

    
3897
	// Re-evaluate URL
3898
	if ($format == 'whois') {
3899
		if (empty(pfb_filter($list_url, PFB_FILTER_DOMAIN, 'pfb_download'))) {
3900
			pfb_logger("\n Failed", 2);
3901
			return FALSE;
3902
		}
3903
	}
3904
	elseif ($format == 'asn') {
3905
		if (empty(pfb_filter($list_url, PFB_FILTER_ALNUM, 'pfb_download'))) {
3906
			pfb_logger("\n Failed", 2);
3907
			return FALSE;
3908
		}
3909
	}
3910
	elseif (!pfb_filter($list_url, PFB_FILTER_URL, 'pfb_download_failure')) {
3911
		pfb_logger("\n Failed", 2);
3912
		return FALSE;
3913
	}
3914

    
3915
	// Cron update function for md5 comparison
3916
	if ($type == 'md5') {
3917
		pfb_logger("\t\t\t\t( md5 feed )\t\t", 1);
3918
	}
3919

    
3920
	$file_dwn_esc	= escapeshellarg("{$file_dwn}.raw");
3921
	$file_org_esc	= escapeshellarg("{$file_dwn}.orig");
3922
	$list_url_esc	= escapeshellarg("{$list_url}");
3923
	$header_esc	= escapeshellarg("{$header}");
3924

    
3925
	$file_download	= trim("{$file_dwn_esc}", "'");
3926
	$orig_download	= trim("{$file_org_esc}", "'");
3927
	$list_download	= trim("{$list_url_esc}", "'");
3928
	$head_download	= trim("{$header_esc}", "'");
3929

    
3930
	$md5_download	= trim(escapeshellarg("{$file_dwn}.md5.raw"), "'");
3931

    
3932
	// If the Cron update function 'md5 comparison' generated an md5 file, re-utilize instead of downloading twice
3933
	if (file_exists("{$md5_download}")) {
3934
		$list_download = "{$md5_download}";
3935
		pfb_logger(' ( md5 feed ) ', 1);
3936
	}
3937

    
3938
	// Download RSYNC format
3939
	if ($format == 'rsync') {
3940
		$result	= exec("/usr/local/bin/rsync --timeout=5 {$list_url_esc} {$file_dwn_esc}");
3941
		if ($result == 0) {
3942
			$http_status = '200';
3943
		} else {
3944
			$log = "\n  RSYNC Failed...\n";
3945
			pfb_logger("{$log}", "{$logtype}");
3946
			return FALSE;
3947
		}
3948
	}
3949
	elseif ($format == 'whois' || $format == 'asn') {
3950
		// Convert a Domain name/AS into its respective IP addresses
3951
		exec("{$pfb['script']} whoisconvert {$header_esc} {$vtype} {$list_url_esc} {$elog}");
3952
		return TRUE;
3953
	}
3954
	else {
3955
		// Determine if URL is a pfSense localfile
3956
		$localfile = FALSE;
3957
		if (pfb_filter("{$list_download}", PFB_FILTER_URL, 'pfb_download', '', TRUE)) {
3958
			$localfile = TRUE;
3959
		}
3960

    
3961
		// Download localfile format
3962
		if ($localfile) {
3963
			$file_data = @file_get_contents("{$list_download}");
3964
			if ($file_data === FALSE) {
3965
				$error = error_get_last();
3966
				$log = "\n[ {$header} ] {$error['message']}\n";
3967
				pfb_logger("{$log}", "{$logtype}");
3968
				return FALSE;
3969
			} else {
3970
				// Save original downloaded file
3971
				@file_put_contents("{$file_download}", $file_data, LOCK_EX);
3972
				$http_status = '200';
3973
			}
3974
		}
3975

    
3976
		// Download using cURL
3977
		else {
3978
			$remote_stamp = -1;
3979
			if (($fhandle = @fopen("{$file_download}", 'w')) !== FALSE) {
3980
				if (!($ch = curl_init("{$list_download}"))) {
3981
					$log = "\nFailed to create cURL resource... Exiting...\n";
3982
					pfb_logger("{$log}", "{$logtype}");
3983
					return FALSE;
3984
				}
3985

    
3986
				curl_setopt_array($ch, $pfb['curl_defaults']);	// Load curl default settings
3987
				curl_setopt($ch, CURLOPT_FILE, $fhandle);	// Add $fhandle setting to cURL
3988
				curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);	// Set cURL download timeout
3989
				curl_setopt($ch, CURLOPT_ENCODING, 'gzip');	// Request 'gzip' encoding from server if available
3990
				if ($srcint) {
3991
					curl_setopt($ch, CURLOPT_INTERFACE, $srcint);	// Use a specific interface when downloading lists
3992
					pfb_logger("\nList: {$header} will be downloaded via interface: {$srcint}\n", 1);
3993
				}
3994

    
3995
				if (!empty($username) && !empty($password)) {
3996
					curl_setopt($ch, CURLOPT_USERPWD, "{$username}:{$password}");
3997
					curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
3998
				}
3999

    
4000
				// Attempt 3 Downloads before failing.
4001
				for ($retries = 1; $retries <= 3; $retries++) {
4002
					if (curl_exec($ch)) {
4003
						// Collect remote timestamp.
4004
						$raw_filetime = curl_getinfo($ch, CURLINFO_FILETIME);
4005
						if ($raw_filetime == -1) {
4006
							$remote_stamp = -1;
4007
						} elseif (!empty(pfb_filter($raw_filetime, PFB_FILTER_NUM, 'pfb_download - remote timestamp'))) {
4008
							$remote_stamp = $raw_filetime;
4009
						}
4010
						break;	// Break on success
4011
					}
4012

    
4013
					$curl_error = curl_errno($ch);
4014
					if ($logtype != 3) {
4015
						pfb_logger(" cURL Error: {$curl_error} [ NOW ]\n", 1);
4016
					} else {
4017
						pfb_logger(" {$header}\t\tcURL Error: {$curl_error} [ NOW ]\n\n", 3);
4018
					}
4019

    
4020
					/* 'Flex' Downgrade cURL errors -	[ 35 - GET_SERVER_HELLO:sslv3		]
4021
										[ 51 - NO alternative certificate	]
4022
										[ 60 - Local Issuer Certificate Subject	]	*/
4023

    
4024
					// Allow downgrade of cURL settings 'Flex' after 1st failure, if user configured.
4025
					if ($retries == 1 && $pflex && in_array($curl_error, array( '35', '51', '60'))) {
4026
						curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
4027
						curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
4028
						curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'TLSv1.3, TLSv1.2, TLSv1.1, TLSv1, SSLv3');
4029
						$log = "\n  Downgrading SSL settings (Flex)";
4030
						pfb_logger("{$log}", "{$logtype}");
4031
					}
4032
					else {
4033
						if ($retries == 3) {
4034
							$log = curl_error($ch) . " |{$head_download}|{$list_download}| Retry [{$retries}] in 5 seconds...\n";
4035
						} else {
4036
							$log = curl_error($ch) . " Retry [{$retries}] in 5 seconds...\n";
4037
						}
4038
						pfb_logger("{$log}", "{$logtype}");
4039
						sleep(5);
4040
						pfb_logger('.', "{$logtype}");
4041
					}
4042
				}
4043

    
4044
				// Collect RFC7231 http status code
4045
				$http_status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
4046

    
4047
				if (isset($pfb['rfc7231'][$http_status])) {
4048
					if ($logtype < 3) {
4049
						pfb_logger(". {$pfb['rfc7231'][$http_status]}", 2);
4050
					} else {
4051
						pfb_logger(" {$file_dwn}\t\t{$pfb['rfc7231'][$http_status]}\n", $logtype);
4052
					}
4053
				} else {
4054
					pfb_logger(". Unknown Failure Code [{$http_status}]", $logtype);
4055
				}
4056
				if ($ch) {
4057
					curl_close($ch);
4058
				}
4059
			}
4060
			if ($fhandle) {
4061
				@fclose($fhandle);
4062
			}
4063
		}
4064
	}
4065

    
4066
	// Cron update function for md5 comparison
4067
	if ($type == 'md5') {
4068
		if ($http_status == '200') {
4069
			return TRUE;
4070
		}
4071
		return FALSE;
4072
	}
4073

    
4074
	// Remove any downloaded files with md5 extension
4075
	unlink_if_exists("{$md5_download}");
4076

    
4077
	// '304 not modified' - Utilize previously downloaded file if available
4078
	if ($http_status == '304' && file_exists("{$orig_download}")) {
4079
		return TRUE;
4080
	}
4081

    
4082
	if (file_exists($file_download) && ($http_status == '200' || $http_status == '221' || $http_status == '226')) {
4083

    
4084
		if (isset($retval)) {
4085
			unset($retval);
4086
		}
4087

    
4088
		// Validate File Mime-type
4089
		$file_type = pfb_filter(array("{$file_dwn_esc}", "{$file_download}", "{$list_download}"), PFB_FILTER_FILE_MIME, 'pfb_download');
4090
		if (!$file_type) {
4091
			return FALSE;
4092
		}
4093

    
4094
		// Create update file markers on new downloads available
4095
		switch($type) {
4096
			case 'geoip':
4097
				touch("{$pfb['dbdir']}/geoip.update");
4098
				break;
4099
			case 'top1m':
4100
				touch("{$pfb['dbdir']}/top-1m.update");
4101
				break;
4102
			case 'asn':
4103
				touch("{$pfb['dbdir']}/asn.update");
4104
				break;
4105
			default:
4106
				break;
4107
		}
4108

    
4109
		// Set downloaded file timestamp to remote timestamp
4110
		if (isset($remote_stamp) && $remote_stamp != -1) {
4111
			@touch("{$file_download}", $remote_stamp);
4112
		}
4113

    
4114
		// Decompress file if required
4115
		if ($file_type == 'application/x-gzip' || $file_type == 'application/gzip') {
4116
			if ($type == 'geoip') {
4117
				// Extras - MaxMind downloads
4118
				exec("/usr/bin/tar -xzf {$file_dwn_esc} --strip=1 -C {$pfb['geoipshare']} >/dev/null 2>&1");
4119
				unlink_if_exists($file_dwn_esc);
4120
				return TRUE;
4121
			}
4122
			elseif ($type == 'asn') {
4123
				exec("/usr/bin/gunzip -c {$file_dwn_esc} > {$header_esc}", $output, $retval);
4124

    
4125
				// Update ASN Lookup table definitions
4126
				exec("{$pfb['script']} asn_table >> {$logtype} 2>&1");
4127

    
4128
				unlink_if_exists($file_dwn_esc);
4129
				return TRUE;
4130
			}
4131
			elseif ($type == 'blacklist') {
4132
				// Extras - Blacklist downloads
4133
				@rename("{$file_download}", strstr("{$file_download}", '.raw', TRUE));
4134
				$file_esc = trim(escapeshellarg("{$file_dwn}"), "'");
4135
				$filename = basename("{$file_esc}", '.tar.gz');
4136

    
4137
				if (!empty(pfb_filter($filename, PFB_FILTER_WORD, 'pfb_download category'))) {
4138
					rmdir_recursive("{$pfb['dbdir']}/{$filename}/");
4139
					safe_mkdir("{$pfb['dbdir']}/{$filename}/");
4140

    
4141
					// Extract Blacklist categories from sub-folders into a single folder structure
4142
					$cmd = "--include='*domains' -s',.*/\\(.*\\)/\\(.*\\)/domains,{$filename}_\\1_\\2,' -s',.*/\\(.*\\)/domains,{$filename}_\\1,'";
4143
					$filename_esc = escapeshellarg("{$pfb['dbdir']}/{$filename}/");
4144
					exec("/usr/bin/tar -xf " . escapeshellarg("{$file_dwn}") . " {$cmd} -C {$filename_esc} >/dev/null 2>&1");
4145

    
4146
					// Rename any Category files with dashes
4147
					$verifydir = "{$pfb['dbdir']}/{$filename}";
4148
					if (is_dir("{$verifydir}")) {
4149
						$list = glob("{$verifydir}/{$filename}*");
4150
						if (is_array($list) && !empty($list)) {
4151
							foreach ($list as $verify) {
4152
								if (strpos($verify, '-') !== FALSE) {
4153
									rename($verify, str_replace('-', '_', $verify));
4154
								}
4155
							}
4156
						}
4157
					}
4158

    
4159
					// Create update file indicator for update process
4160
					touch("{$pfb['dbdir']}/{$filename}/{$filename}.update");
4161
				}
4162
				else {
4163
					pfb_logger("\n Invalid filename [{$filename}]", 1);
4164
					return FALSE;
4165
				}
4166
			}
4167
			else {
4168
				if (!pfb_filter(array("{$file_dwn_esc}", "{$file_download}", "{$list_download}"), PFB_FILTER_FILE_MIME_COMPRESSED, 'pfb_download')) {
4169
					return FALSE;
4170
				}
4171
				pfb_logger('.', 1);
4172
				exec("/usr/bin/gunzip -c {$file_dwn_esc} > {$file_org_esc}", $output, $retval);
4173
			}
4174
		}
4175
		elseif ($file_type == 'application/x-bzip2') {
4176
			if (!pfb_filter(array("{$file_dwn_esc}", "{$file_download}", "{$list_download}"), PFB_FILTER_FILE_MIME_COMPRESSED, 'pfb_download')) {
4177
				return FALSE;
4178
			}
4179
			pfb_logger('.', 1);
4180
			exec("/usr/bin/bzip2 -dkc {$file_dwn_esc} > {$file_org_esc}", $output, $retval);
4181
		}
4182
		elseif ($file_type == 'application/zip') {
4183

    
4184
			// Extras - MaxMind/TOP1M downloads
4185
			if ($type == 'geoip' || $type == 'top1m') {
4186
				// Determine if Zip contains multiple files
4187
				exec("/usr/bin/tar -tf {$file_dwn_esc} 2>&1", $archive_count, $retval);
4188
				if ($archive_count[0] == 'tar: Failed to set default locale') {
4189
					unset($archive_count[0]);
4190
				}
4191
				if (count($archive_count) > 1) {
4192
					exec("/usr/bin/tar -xf {$file_dwn_esc} --strip=1 -C {$header_esc} >/dev/null 2>&1");
4193
				} else {
4194
					exec("/usr/bin/tar -xOf {$file_dwn_esc} > {$header_esc}");
4195
				}
4196
				return TRUE;
4197
			}
4198

    
4199
			pfb_logger('.', 1);
4200

    
4201
			/* TODO: FIX - Bypass ZIP Compression file mime type validation due to incompatability with ZIP files
4202
			if (!pfb_filter(array("{$file_dwn_esc}", "{$file_download}", "{$list_download}"), PFB_FILTER_FILE_MIME_COMPRESSED, 'pfb_download')) {
4203
				return FALSE;
4204
			}
4205
			*/
4206
	
4207
			// Check if ZIP archive contains xlsx files
4208
			$xlsxtest = exec("/usr/bin/tar -tf {$file_dwn_esc}");
4209
			if (strpos($xlsxtest, '.xlsx') !== FALSE) {
4210
				unlink_if_exists("{$orig_download}");
4211
				exec("{$pfb['script']} xlsx {$header_esc} {$elog}");
4212
				if (file_exists("{$orig_download}")) {
4213
					$retval = 0;
4214
				}
4215
			} else {
4216
				pfb_logger('.', 1);
4217
				// Process ZIP file (SFS and hpHosts workaround)
4218
				exec("/usr/bin/tar -xOf {$file_dwn_esc} | /usr/bin/sed 's/,[[:space:]]/; /g' | /usr/bin/tr ',' '\n' > {$file_org_esc}", $output, $retval);
4219
			}
4220

    
4221
			// TODO: Validate file contents after extraction (to be removed once ZIP compression file mime type is fixed above)
4222
			if (!pfb_filter(array("{$file_org_esc}", "{$file_download}", "{$list_download}"), PFB_FILTER_FILE_MIME, 'pfb_download')) {
4223
				unlink_if_exists("{$file_org_esc}");
4224
				return FALSE;
4225
			}
4226
		}
4227
		elseif ($file_type == 'application/x-7z-compressed') {
4228
			if (!pfb_filter(array("{$file_dwn_esc}", "{$file_download}", "{$list_download}"), PFB_FILTER_FILE_MIME_COMPRESSED, 'pfb_download')) {
4229
				return FALSE;
4230
			}
4231
			pfb_logger('.', 1);
4232
			exec("/usr/local/bin/7z e -so {$file_dwn_esc} > {$file_org_esc}", $output, $retval);
4233
		}
4234
		else {
4235
			// Uncompressed file format.
4236
			if ($type == 'geoip' || $type == 'asn') {
4237
				// Extras - MaxMind/TOP1M/ASN downloads
4238
				@rename("{$file_download}", "{$head_download}");
4239
				return TRUE;
4240
			}
4241
			elseif ($type == 'blacklist') {
4242
				$retval = 0;
4243
			}
4244
			else {
4245
				// Rename file to 'orig' format
4246
				@rename("{$file_download}", "{$orig_download}");
4247
				$retval = 0;
4248
			}
4249
		}
4250

    
4251
		if ($retval == 0) {
4252
			// Set downloaded file timestamp to remote timestamp
4253
			if (isset($remote_stamp) && $remote_stamp != -1) {
4254
				@touch("{$orig_download}", $remote_stamp);
4255
			}
4256

    
4257
			// Process Emerging Threats IQRisk if required
4258
			if (strpos($list_url, 'iprepdata.txt') !== FALSE) {
4259
				exec("{$pfb['script']} et {$header_esc} x x x x x {$pfb['etblock']} {$pfb['etmatch']} {$elog}");
4260
			}
4261
			return TRUE;
4262
		}
4263
		else {
4264
			$log = "   Decompression Failed\n";
4265
			pfb_logger("{$log}", 2);
4266
			return FALSE;
4267
		}
4268
	}
4269
	else {
4270
		// Download failed
4271
		unlink_if_exists("{$file_download}");
4272
	}
4273
	return FALSE;
4274
}
4275

    
4276

    
4277
// Determine reason for download failure
4278
function pfb_download_failure($alias, $header, $pfbfolder, $list_url, $format, $vtype) {
4279
	global $pfb;
4280
	$pfbfound = FALSE;
4281

    
4282
	// Re-evaluate URL
4283
	if ($format == 'whois') {
4284
		if (empty(pfb_filter($list_url, PFB_FILTER_DOMAIN, 'pfb_download_failure'))) {
4285
			pfb_logger("\n Invalid WHOIS. Terminating Download! [ {$list_url} ]\n", 1);
4286
		}
4287
	}
4288
	elseif ($format == 'asn') {
4289
		if (empty(pfb_filter($list_url, PFB_FILTER_ALNUM, 'pfb_download_failure'))) {
4290
			pfb_logger("\n Invalid ASN. Terminating Download! [ {$list_url} ]\n", 1);
4291
		}
4292
	}
4293
	elseif (!pfb_filter($list_url, PFB_FILTER_URL, 'pfb_download_failure')) {
4294
		pfb_logger("\n Invalid URL. Terminating Download! [ {$list_url} ]\n", 1);
4295
	}
4296
	else {
4297
		// Determine if URL is a localfile
4298
		$localfile = FALSE;
4299
		if (pfb_filter($list_url, PFB_FILTER_URL, 'pfb_download_failure', '', TRUE)) {
4300
			$localfile = TRUE;
4301
		}
4302

    
4303
		// Log FAILED downloads and check if firewall or Snort/Suricata is blocking host
4304
		$log = "\n\n [ {$alias} - {$header} ] Download FAIL [ NOW ]\n";
4305
		pfb_logger("{$log}", 2);
4306

    
4307
		// Only perform these checks if they are not 'localfiles'
4308
		if ($localfile) {
4309
			$log = "   Local File Failure\n";
4310
			pfb_logger("{$log}", 2);
4311
		}
4312
		else {
4313
			// Determine if Firewall/IPS/DNSBL is blocking download.
4314
			$data = parse_url("{$list_url}");
4315

    
4316
			$validate_list = array();
4317
			if (is_ipaddr($data['host'])) {
4318
				$validate_list = array(array('type' => 'IP', 'data' => $data['host']));
4319
			} else {
4320
				$validate_list = resolve_host_addresses($data['host']);
4321
			}
4322

    
4323
			$validate_list_final = array();
4324
			if (!empty($validate_list) && is_array($validate_list)) {
4325
				foreach ($validate_list as $validate) {
4326
					if ($validate['type'] == 'CNAME') {
4327
						$cname_list = resolve_host_addresses($validate['data']);
4328
						if (!empty($cname_list) && is_array($cname_list)) {
4329
							foreach ($cname_list as $cname) {
4330
								if (!empty($cname['data'])) {
4331
									$validate_list_final[$cname['data']] = "Host:{$data['host']} | CNAME:{$cname['host']}";
4332
								}
4333
							}
4334
						}
4335
					}
4336
					elseif (!empty($validate['data'])) {
4337
						$validate_list_final[$validate['data']] = $data['host'];
4338
					}
4339
				}
4340
			}
4341
			else {
4342
				pfb_logger("\n Cannot Resolve Host:{$data['host']}", 1);
4343
			}
4344

    
4345
			if (!empty($validate_list_final)) {
4346
				foreach ($validate_list_final as $ip => $host) {
4347
					if (is_ipaddr($ip)) {
4348

    
4349
						// Query Firewall aliastables
4350
						$result = find_reported_header($ip, "{$pfbfolder}/*", FALSE);
4351
						if (!empty($result) && $result[0] != 'Unknown') {
4352
							$log = " [ {$ip} ] Firewall IP block found in: [ {$result[0]} | {$result[1]} ] for HOST:{$host}!\n";
4353
							pfb_logger("{$log}", 2);
4354
							$pfbfound = TRUE;
4355
						}
4356

    
4357
						// Determine if Host is listed in DNSBL
4358
						if ($ip == $pfb['dnsbl_vip'] || $ip == "::{$pfb['dnsbl_vip']}" || $ip == '0.0.0.0') {
4359
							$log = " [ {$host} ] Domain blocked via DNSBL!\n";
4360
							pfb_logger("{$log}", 2);
4361
							$pfbfound = TRUE;
4362
						}
4363

    
4364
						// Query Snort/Suricata snort2c IP block table
4365
						$ip_esc = escapeshellarg($ip);
4366
						$result = substr(exec("{$pfb['pfctl']} -t snort2c -T test {$ip_esc} 2>&1"), 0, 1);
4367
						if ($result > 0) {
4368
							$log = " [ {$ip} ] IDS IP block found for HOST:{$host}!\n";
4369
							pfb_logger("{$log}", 2);
4370
							$pfbfound = TRUE;
4371
						}
4372
					}
4373
					else {
4374
						pfb_logger("\nInvalid IP or NXDOMAIN found for HOST:{$host}", 2);
4375
						$pfbfound = TRUE;
4376
					}
4377
				}
4378
			}
4379

    
4380
			if (!$pfbfound) {
4381
				$log = "  DNSBL, Firewall, and IDS (Legacy mode only) are not blocking download.\n";
4382
				pfb_logger("{$log}", 2);
4383
			}
4384
		}
4385
	}
4386

    
4387
	// Call function to get all previous download fails
4388
	pfb_failures();
4389

    
4390
	// On download failure, create file marker for subsequent download attempts. ('0' no download failure threshold)
4391
	if ($pfb['skipfeed'] == 0 || $pfb['failed'][$header] <= $pfb['skipfeed']) {
4392
		touch("{$pfbfolder}/{$header}.fail");
4393
		return;
4394
	}
4395

    
4396
	unlink_if_exists("{$pfbfolder}/{$header}.fail");
4397
	return;
4398
}
4399

    
4400

    
4401
// Collect all previously failed daily download notices
4402
function pfb_failures() {
4403
	global $pfb;
4404
	$pfb['failed'] = array();
4405

    
4406
	if (file_exists("{$pfb['errlog']}")) {
4407
		$today_date = date('m/j/y', time());
4408
		exec("{$pfb['grep']} 'FAIL' {$pfb['errlog']} | {$pfb['grep']} {$today_date}", $results);
4409
		if (!empty($results)) {
4410
			foreach ($results as $result) {
4411
				$header = explode(' ', $result);
4412
				$pfb['failed'][$header[4]] += 1;
4413
			}
4414
		}
4415
	}
4416
	return;
4417
}
4418

    
4419

    
4420
// Convert unique Alias details (via ascii table number) and return a 10 digit tracker ID
4421
function pfb_tracker($alias, $int, $text) {
4422
	global $pfb;
4423

    
4424
	$pfbtracker	= 0;
4425
	$real_int	= get_real_interface($int);
4426
	$ipaddr		= get_interface_ip($int);
4427

    
4428
	if (is_ipaddrv4($ipaddr)) {
4429
		$ipaddr = ip2long32($ipaddr);
4430
		$subnet = find_interface_subnet($real_int);
4431
	}
4432
	else {
4433
		$ipaddr = get_interface_ipv6($real_int);
4434
		$subnet = find_interface_subnetv6($real_int);
4435
	}
4436

    
4437
	$search		= array( '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' );
4438
	$replace	= array( 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'zero' );
4439
	$line		= "{$alias}{$int}{$text}{$real_int}{$ipaddr}{$subnet}";
4440
	$line		= str_replace($search, $replace, $line);
4441

    
4442
	for ($i = 0; $i < strlen($line); $i++) {
4443
		$pfbtracker += @ord($line[$i]);
4444
	}
4445

    
4446
	$pfbtracker = '177' . str_pad($pfbtracker, 7, '0', STR_PAD_LEFT);
4447
	if (strlen($pfbtracker) > 10) {
4448
        	$pfbtracker = substr($pfbtracker, 0, 10);
4449
	}
4450
	
4451
	// If duplicate Tracker ID found, pre-define a Tracker ID (Starts at 1700000010)
4452
	if (in_array($pfbtracker, $pfb['trackerids'])) {
4453
		$pfbtracker = ($pfb['last_trackerid'] + 1);
4454
		
4455
		// Increment prefix (digits 1&2) and reset Last_tracker ID after 10 digits
4456
		if (strlen($pfbtracker) > 10) {
4457
			$tracker_prefix = substr($pfbtracker, 0, 2);
4458
			$pfbtracker	= ($tracker_prefix + 1) . '00000010';
4459
		}
4460
		$pfb['last_trackerid'] = $pfbtracker;
4461
	}
4462
	
4463
	$pfb['trackerids'][] = $pfbtracker;
4464
	return (int)$pfbtracker;
4465
}
4466

    
4467

    
4468
// Define firewall rule settings
4469
function pfb_firewall_rule($action, $pfb_alias, $vtype, $pfb_log, $agateway_in='default', $agateway_out='default',
4470
	    $aaddrnot_in='', $adest_in='', $aports_in='', $aproto_in='', $anot_in='',
4471
	    $aaddrnot_out='', $asrc_out='', $aports_out='', $aproto_out='', $anot_out='') {
4472

    
4473
	global $pfb;
4474
	$rule = array();
4475

    
4476
	switch ($action) {
4477
		case 'Deny_Both':
4478
		case 'Deny_Outbound':
4479
			$rule = $pfb['base_rule'];
4480
			$rule['type'] = "{$pfb['deny_action_outbound']}";
4481
			if ($vtype == '_v6') {
4482
				$rule['ipprotocol'] = 'inet6';
4483
			}
4484
			if ($pfb['float'] == 'on') {
4485
				$rule['direction'] = 'any';
4486
			}
4487
			$rule['descr'] = "{$pfb_alias}{$pfb['suffix']}";
4488
			if (!empty($asrc_out)) {
4489
				$rule['source'] = array('address' => "{$asrc_out}");
4490
			} else {
4491
				$rule['source'] = array('any' => '');
4492
			}
4493
			if (!empty($asrc_out) && $anot_out == 'on') {
4494
				$rule['source']['not'] = '';
4495
			}
4496
			if (!empty($aports_out)) {
4497
				$rule['destination'] = array('address' => "{$pfb_alias}", 'port' => "{$aports_out}");
4498
			} else {
4499
				$rule['destination'] = array('address' => "{$pfb_alias}");
4500
			}
4501
			if ($aaddrnot_out == 'on') {
4502
				$rule['destination']['not'] = '';
4503
			}
4504
			if (!empty($aproto_out)) {
4505
				$rule['protocol'] = "{$aproto_out}";
4506
			}
4507
			if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') {
4508
				$rule['log'] = '';
4509
			}
4510
			if (!empty($agateway_out) && $agateway_out != 'default') {
4511
				$rule['gateway'] = "{$agateway_out}";
4512
				if ($pfb['float'] == 'on') {
4513
					$rule['direction'] = 'out';
4514
				}
4515
			}
4516
			$rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto');
4517
			$pfb['deny_outbound'][] = $rule;
4518
			if ($action != 'Deny_Both') {
4519
				break;
4520
			}
4521
		case 'Deny_Inbound':
4522
			$rule = $pfb['base_rule'];
4523
			$rule['type'] = "{$pfb['deny_action_inbound']}";
4524
			if ($vtype == '_v6') {
4525
				$rule['ipprotocol'] = 'inet6';
4526
			}
4527
			if ($pfb['float'] == 'on') {
4528
				$rule['direction'] = 'any';
4529
			}
4530
			$rule['descr'] = "{$pfb_alias}{$pfb['suffix']}";
4531
			$rule['source'] = array('address' => "{$pfb_alias}");
4532
			if ($aaddrnot_in == 'on') {
4533
				$rule['source']['not'] = '';
4534
			}
4535
			if (!empty($adest_in) && !empty($aports_in)) {
4536
				$rule['destination'] = array('address' => "{$adest_in}", 'port' => "{$aports_in}");
4537
			} elseif (!empty($adest_in) && empty($aports_in)) {
4538
				$rule['destination'] = array('address' => "{$adest_in}");
4539
			} elseif (empty($adest_in) && !empty($aports_in)) {
4540
				$rule['destination'] = array('any' => '', 'port' => "{$aports_in}");
4541
			} else {
4542
				$rule['destination'] = array('any' => '');
4543
			}
4544
			if (!empty($adest_in) && $anot_in == 'on') {
4545
				$rule['destination']['not'] = '';
4546
			}
4547
			if (!empty($aproto_in)) {
4548
				$rule['protocol'] = "{$aproto_in}";
4549
			}
4550
			if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') {
4551
				$rule['log'] = '';
4552
			}
4553
			if (!empty($agateway_in) && $agateway_in != 'default') {
4554
				$rule['gateway'] = "{$agateway_in}";
4555
				if ($pfb['float'] == 'on') {
4556
					$rule['direction'] = 'in';
4557
				}
4558
			}
4559
			$rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto');
4560
			$pfb['deny_inbound'][] = $rule;
4561
			break;
4562
		case 'Permit_Both':
4563
		case 'Permit_Outbound':
4564
			$rule = $pfb['base_rule'];
4565
			$rule['type'] = 'pass';
4566
			if ($vtype == '_v6') {
4567
				$rule['ipprotocol'] = 'inet6';
4568
			}
4569
			if ($pfb['float'] == 'on') {
4570
				$rule['direction'] = 'any';
4571
			}
4572
			$rule['descr'] = "{$pfb_alias}{$pfb['suffix']}";
4573
			if (!empty($asrc_out)) {
4574
				$rule['source'] = array('address' => "{$asrc_out}");
4575
			} else {
4576
				$rule['source'] = array('any' => '');
4577
			}
4578
			if (!empty($asrc_out) && $anot_out == 'on') {
4579
				$rule['source']['not'] = '';
4580
			}
4581
			if (!empty($aports_out)) {
4582
				$rule['destination'] = array('address' => "{$pfb_alias}", 'port' => "{$aports_out}");
4583
			} else {
4584
				$rule['destination'] = array('address' => "{$pfb_alias}");
4585
			}
4586
			if ($aaddrnot_out == 'on') {
4587
				$rule['destination']['not'] = '';
4588
			}
4589
			if (!empty($aproto_out)) {
4590
				$rule['protocol'] = "{$aproto_out}";
4591
			}
4592
			if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') {
4593
				$rule['log'] = '';
4594
			}
4595
			if (!empty($agateway_out) && $agateway_out != 'default') {
4596
				$rule['gateway'] = "{$agateway_out}";
4597
				if ($pfb['float'] == 'on') {
4598
					$rule['direction'] = 'out';
4599
				}
4600
			}
4601
			$rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto');
4602
			$pfb['permit_outbound'][] = $rule;
4603
			if ($action != 'Permit_Both') {
4604
				break;
4605
			}
4606
		case 'Permit_Inbound':
4607
			$rule = $pfb['base_rule'];
4608
			$rule['type'] = 'pass';
4609
			if ($vtype == '_v6') {
4610
				$rule['ipprotocol'] = 'inet6';
4611
			}
4612
			if ($pfb['float'] == 'on') {
4613
				$rule['direction'] = 'any';
4614
			}
4615
			$rule['descr'] = "{$pfb_alias}{$pfb['suffix']}";
4616
			$rule['source'] = array('address' => "{$pfb_alias}");
4617
			if ($aaddrnot_in == 'on') {
4618
				$rule['source']['not'] = '';
4619
			}
4620
			if (!empty($adest_in) && !empty($aports_in)) {
4621
				$rule['destination'] = array('address' => "{$adest_in}", 'port' => "{$aports_in}");
4622
			} elseif (!empty($adest_in) && empty($aports_in)) {
4623
				$rule['destination'] = array('address' => "{$adest_in}");
4624
			} elseif (empty($adest_in) && !empty($aports_in)) {
4625
				$rule['destination'] = array('any' => '', 'port' => "{$aports_in}");
4626
			} else {
4627
				$rule['destination'] = array('any' => '');
4628
			}
4629
			if (!empty($adest_in) && $anot_in == 'on') {
4630
				$rule['destination']['not'] = '';
4631
			}
4632
			if (!empty($aproto_in)) {
4633
				$rule['protocol'] = "{$aproto_in}";
4634
			}
4635
			if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') {
4636
				$rule['log'] = '';
4637
			}
4638
			if (!empty($agateway_in) && $agateway_in != 'default') {
4639
				$rule['gateway'] = "{$agateway_in}";
4640
				if ($pfb['float'] == 'on') {
4641
					$rule['direction'] = 'in';
4642
				}
4643
			}
4644
			$rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto');
4645
			$pfb['permit_inbound'][] = $rule;
4646
			break;
4647
		case 'Match_Both':
4648
		case 'Match_Outbound':
4649
			$rule = $pfb['base_rule_float'];
4650
			$rule['type'] = 'match';
4651
			if ($vtype == '_v6') {
4652
				$rule['ipprotocol'] = 'inet6';
4653
			}
4654
			$rule['direction'] = 'any';
4655
			$rule['descr'] = "{$pfb_alias}{$pfb['suffix']}";
4656
			if (!empty($asrc_out)) {
4657
				$rule['source'] = array('address' => "{$asrc_out}");
4658
			} else {
4659
				$rule['source'] = array('any' => '');
4660
			}
4661
			if (!empty($asrc_out) && $anot_out == 'on') {
4662
				$rule['source']['not'] = '';
4663
			}
4664
			if (!empty($aports_out)) {
4665
				$rule['destination'] = array('address' => "{$pfb_alias}", 'port' => "{$aports_out}");
4666
			} else {
4667
				$rule['destination'] = array('address' => "{$pfb_alias}");
4668
			}
4669
			if ($aaddrnot_out == 'on') {
4670
				$rule['destination']['not'] = '';
4671
			}
4672
			if (!empty($aproto_out)) {
4673
				$rule['protocol'] = "{$aproto_out}";
4674
			}
4675
			if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') {
4676
				$rule['log'] = '';
4677
			}
4678
			if (!empty($agateway_out) && $agateway_out != 'default') {
4679
				$rule['gateway'] = "{$agateway_out}";
4680
				$rule['direction'] = 'out';
4681
			}
4682
			$rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto');
4683
			$pfb['match_outbound'][] = $rule;
4684
			if ($action != 'Match_Both') {
4685
				break;
4686
			}
4687
		case 'Match_Inbound':
4688
			$rule = $pfb['base_rule_float'];
4689
			$rule['type'] = 'match';
4690
			if ($vtype == '_v6') {
4691
				$rule['ipprotocol'] = 'inet6';
4692
			}
4693
			$rule['direction'] = 'any';
4694
			$rule['descr'] = "{$pfb_alias}{$pfb['suffix']}";
4695
			$rule['source'] = array('address' => "{$pfb_alias}");
4696
			if ($aaddrnot_in == 'on') {
4697
				$rule['source']['not'] = '';
4698
			}
4699
			if (!empty($adest_in) && !empty($aports_in)) {
4700
				$rule['destination'] = array('address' => "{$adest_in}", 'port' => "{$aports_in}");
4701
			} elseif (!empty($adest_in) && empty($aports_in)) {
4702
				$rule['destination'] = array('address' => "{$adest_in}");
4703
			} elseif (empty($adest_in) && !empty($aports_in)) {
4704
				$rule['destination'] = array('any' => '', 'port' => "{$aports_in}");
4705
			} else {
4706
				$rule['destination'] = array('any' => '');
4707
			}
4708
			if (!empty($adest_in) && $anot_in == 'on') {
4709
				$rule['destination']['not'] = '';
4710
			}
4711
			if (!empty($aproto_in)) {
4712
				$rule['protocol'] = "{$aproto_in}";
4713
			}
4714
			if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') {
4715
				$rule['log'] = '';
4716
			}
4717
			if (!empty($agateway_in) && $agateway_in != 'default') {
4718
				$rule['gateway'] = "{$agateway_in}";
4719
				$rule['direction'] = 'in';
4720
			}
4721
			$rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto');
4722
			$pfb['match_inbound'][] = $rule;
4723
			break;
4724
	}
4725
	return;
4726
}
4727

    
4728

    
4729
// Archive IP aliastables and DNSBL database. ( Ramdisk installations only )
4730
function pfb_aliastables($mode) {
4731
	global $g, $pfb;
4732

    
4733
	$msg = '';
4734
	$earlyshellcmd = '/usr/local/pkg/pfblockerng/pfblockerng.sh aliastables';
4735

    
4736
	// Reload config.xml to get any recent changes
4737
	config_read_file(false, true);
4738

    
4739
	$a_earlyshellcmd = config_get_path('system/earlyshellcmd', []);
4740
	$a_shellcmdsettings = config_get_path('installedpackages/shellcmdsettings/config', []);
4741

    
4742
	// Only execute function if Ramdisks are used.
4743
	if (config_path_enabled('system', 'use_mfs_tmpvar')) {
4744

    
4745
		// Archive aliastable folder
4746
		if ($mode == 'update') {
4747
			pfb_logger("\nArchiving Aliastable folder", 1);
4748

    
4749
			$files_to_backup = '';
4750
			$files = glob("{{$pfb['aliasdir']}/pfB_*.txt,{$pfb['dnsbl_file']}.conf,/var/unbound/pfb_unbound*,/var/unbound/pfb_py_*}", GLOB_BRACE);
4751
			if (!empty($files)) {
4752
				$files_to_backup = implode(' ', array_map('escapeshellarg', array_filter($files)));
4753
			}
4754

    
4755
			// Archive IP Aliastables/Unbound DNSBL Database as required.
4756
			if (!empty($files_to_backup)) {
4757
				exec("/usr/bin/tar -jcvf {$pfb['aliasarchive']} {$files_to_backup} >/dev/null 2>&1");
4758
				pfb_logger("\nArchiving selected pfBlockerNG files.\n", 1);
4759
			} else {
4760
				pfb_logger("\nNo Files to archive.\n", 1);
4761
			}
4762
		}
4763

    
4764
		// Check conf file for earlyshellcmd/shellcmd package settings
4765
		elseif ($mode == 'conf') {
4766

    
4767
			// Add earlyshellcmd settings
4768
			if (!empty($a_earlyshellcmd)) {
4769
				if (!preg_grep("/pfblockerng.sh aliastables/", $a_earlyshellcmd)) {
4770
					$a_earlyshellcmd[] = "{$earlyshellcmd}";
4771
					$msg = "\n** Adding earlyshellcmd settings **\n";
4772
				}
4773
			}
4774
			else {
4775
				$a_earlyshellcmd = "{$earlyshellcmd}";
4776
				$msg = "\n** Adding earlyshellcmd settings **\n";
4777
			}
4778

    
4779
			// Add shellcmd package settings
4780
			$found = FALSE;
4781
			if (!empty($a_shellcmdsettings)) {
4782
				foreach ($a_shellcmdsettings as $key => $shellcmd) {
4783
					if (strpos($shellcmd['cmd'], 'pfblockerng.sh aliastables') !== FALSE) {
4784
						$found = TRUE;
4785
						break;
4786
					}
4787
				}
4788
			}
4789

    
4790
			if (!$found) {
4791
				$add = array(	'cmd' 		=> $earlyshellcmd,
4792
						'cmdtype'	=> 'earlyshellcmd',
4793
						'description'	=> 'pfBlockerNG earlyshellcmd. DO NOT EDIT/DELETE!');
4794

    
4795
				$a_shellcmdsettings[] = $add;
4796
				$msg .= "\n** Adding shellcmd package settings **\n";
4797
			}
4798
		}
4799
	}
4800
	else {
4801
		// Remove aliastables archive if found
4802
		if (file_exists("{$pfb['aliasarchive']}")) {
4803
			unlink_if_exists("{$pfb['aliasarchive']}");
4804
		}
4805

    
4806
		// Remove earlyshellcmd settings
4807
		if (!empty($a_earlyshellcmd)) {
4808
			if (preg_grep("/pfblockerng.sh aliastables/", $a_earlyshellcmd)) {
4809
				$a_earlyshellcmd = preg_grep("/pfblockerng.sh aliastables/", $a_earlyshellcmd, PREG_GREP_INVERT);
4810
				$msg = "\n** Removing earlyshellcmd settings **\n";
4811
			}
4812
		}
4813

    
4814
		// Remove shellcmd package settings
4815
		if (!empty($a_shellcmdsettings)) {
4816
			foreach ($a_shellcmdsettings as $key => $shellcmd) {
4817
				if (strpos($shellcmd['cmd'], 'pfblockerng.sh aliastables') !== FALSE) {
4818
					unset($a_shellcmdsettings[$key]);
4819
					$msg .= "\n** Removing shellcmd package settings**\n";
4820
				}
4821
			}
4822
		}
4823
	}
4824

    
4825
	if (!empty($msg)) {
4826
		pfb_logger("{$msg}", 1);
4827
		config_set_path('system/earlyshellcmd', $a_earlyshellcmd);
4828
		config_set_path('installedpackages/shellcmdsettings/config', $a_shellcmdsettings);
4829
		write_config('pfBlockerNG: saving earlyshellcmd');
4830
	}
4831
}
4832

    
4833

    
4834
// Collect pfBlockerNG rule names and Tracker IDs
4835
function pfb_filterrules() {
4836
	global $pfb;
4837

    
4838
	$rule_list		= array();
4839
	$rule_list['id']	= array();
4840
	$rule_list['other']	= array();
4841
	$rule_list['int']	= array();
4842

    
4843
	exec("{$pfb['pfctl']} -vvsr 2>&1", $results);
4844
	if (!empty($results)) {
4845
		foreach ($results as $result) {
4846
			if (substr($result, 0, 1) == '@') {
4847

    
4848
				$type = strstr(ltrim(strstr($result, ' ', FALSE), ' '), ' ', TRUE);
4849
				if (in_array($type, array('block', 'pass', 'match'))) {
4850

    
4851
					// Since pfSense CE 2.6 and pfSense Plus 22.01, pf rules use an 'ridentifier' string
4852
					if (strrpos($result, 'ridentifier') !== FALSE) {
4853
						$id_begin_delim = 'ridentifier ';
4854
						$id_end_delim = ' ';
4855
					} elseif (strpos($result, '(') !== FALSE && strpos($result, ')') !== FALSE) {
4856
						$id_begin_delim = '(';
4857
						$id_end_delim = ')';
4858
					} else {
4859
						continue;
4860
					}
4861

    
4862
					// Get the rule ID
4863
					$id_begin_offset	= strpos($result, $id_begin_delim) + strlen($id_begin_delim);
4864
					$id_end_offset		= strpos($result, $id_end_delim, $id_begin_offset);
4865

    
4866
					if ($id_end_offset !== FALSE) {
4867
						$id_length = $id_end_offset - $id_begin_offset;
4868
					} else {
4869
						$id_length = strlen($result) - $id_begin_offset;
4870
					}
4871
					$id = substr($result, $id_begin_offset, $id_length);
4872
					if (!is_numeric($id)) {
4873
						continue;
4874
					}
4875

    
4876
					// Add the rule to the list
4877
					if (strpos($result, ' <pfB_') !== FALSE) {
4878
						$descr = ltrim(stristr($result, '<pfb_', FALSE), '<');
4879
						$descr = strstr($descr, ':', TRUE);
4880
						$type  = strstr(ltrim(strstr($result, ' ', FALSE), ' '), ' ', TRUE);
4881
						if ($type == 'match') {
4882
							$type = 'unkn(%u)';
4883
						}
4884
	
4885
						if (!is_array($rule_list[$id])) {
4886
							$rule_list[$id] = array();
4887
						}
4888
	
4889
						$rule_list['id'][]	= $id;
4890
						$rule_list[$id]['name']	= $descr;
4891
						$rule_list[$id]['type']	= $type;
4892
	
4893
						$int = strstr(ltrim(strstr($result, ' on ', FALSE), ' on '), ' ', TRUE);
4894
						if (!empty($int)) {
4895
							 $rule_list['int'][$int] = '';
4896
						}
4897
					}
4898
					else {
4899
						// All other non-pfBlockerNG Tracker IDs
4900
						$rule_list['other'][$id] = '';
4901
					}
4902
				}
4903
			}
4904
		}
4905
	}
4906
	return $rule_list;
4907
}
4908

    
4909

    
4910
// Function to remove existing firewall states for IPs that are have been recently added to IP block/reject aliastables
4911
function pfb_remove_states() {
4912
	global $pfb;
4913

    
4914
	$log = "\n===[  Kill States  ]==================================================\n";
4915
	pfb_logger("{$log}", 1);
4916

    
4917
	$pfb_tables = array();
4918
	// Collect all 'pfB_' and 'pfb_' rules that are 'Block/Reject' and do not have bypass states enabled
4919
	foreach (config_get_path('aliases/alias', []) as $alias) {
4920
		if ($alias['type'] == 'urltable' && strpos($alias['name'], 'pfB_') !== FALSE && strpos($alias['descr'], '[s]') === FALSE) {
4921
			foreach (config_get_path('filter/rule', []) as $rule) {
4922
				if ($alias['name'] === $rule['source']['address'] || $alias['name'] === $rule['destination']['address']) {
4923

    
4924
					if ($rule['type'] == 'block' ||
4925
						$rule['type'] == 'reject' ||
4926
						strpos($rule['descr'], '[ks]') !== FALSE) {
4927

    
4928
						if (isset($rule['source']['address']) && !isset($rule['source']['not'])) {
4929
							$pfb_tables[]	= $rule['source']['address'];
4930
						}
4931
						elseif (isset($rule['destination']['address']) && !isset($rule['destination']['not'])) {
4932
							$pfb_tables[]	= $rule['destination']['address'];
4933
						}
4934
					}
4935
				}
4936
			}
4937
		}
4938
	}
4939
	$pfb_tables = array_unique($pfb_tables);
4940

    
4941
	// List of IPs to suppress
4942
	$pfb_local = $pfb_localsub = array();
4943

    
4944
	// Collect IPv4 Suppression list IPs
4945
	$v4suppression = pfbng_text_area_decode($pfb['ipconfig']['v4suppression'], TRUE, FALSE);
4946
	foreach ($v4suppression as $line) {
4947
		if (strpos($line, '/32') != FALSE) {
4948
			$pfb_local[] = str_replace('/32', '', $line);
4949
		}
4950

    
4951
		// Convert '/24' CIDRs
4952
		$pfb_suppcidr = array();
4953
		if (strpos($line, '/24') !== FALSE && is_ipaddrv4($line)) {
4954
			$pfb_suppcidr	= subnetv4_expand($line);
4955
			$pfb_local	= array_merge($pfb_local, $pfb_suppcidr);
4956
		}
4957
	}
4958

    
4959
	if (!empty($pfb_local)) {
4960
		$pfb_local = array_flip($pfb_local);
4961
	}
4962

    
4963
	// Collect Interface list that have pfB Rules assigned
4964
	$data		= pfb_filterrules();
4965
	$pfb_int	= $data['int'] ?: array();
4966

    
4967
	// Collect local IPs
4968
	$data = pfb_collect_localip();
4969
	if (!empty($data[0])) {
4970
		$pfb_local = array_merge($pfb_local, $data[0]);
4971
	}
4972
	$pfb_localsub = $data[1] ?: array();
4973

    
4974
	// Collect DNS servers to suppress
4975
	$pfb_dnsservers = get_dns_servers();
4976
	if (!empty($pfb_dnsservers)) {
4977
		$pfb_local = array_merge($pfb_local, $pfb_dnsservers);
4978
	}
4979

    
4980
	// Remove any duplicate IPs
4981
	if (!empty($pfb_local)) {
4982
		$pfb_local = array_flip(array_unique($pfb_local));
4983
	}
4984

    
4985
	// Collect any 'Permit' Customlist IPs to suppress
4986
	$custom_supp = array();
4987
	foreach (array('pfblockernglistsv4', 'pfblockernglistsv6') as $ip_type) {
4988
		foreach (config_get_path("installedpackages{$ip_type}/config", []) as $list) {
4989
			if (!empty($list['custom']) && strpos($list['action'], 'Permit_') !== FALSE) {
4990
				$custom		= pfbng_text_area_decode($list['custom'], TRUE, FALSE);
4991
				$custom_supp	= array_merge($custom_supp, $custom);
4992
			}
4993
		}
4994
	}
4995

    
4996
	$custom_supp = array_unique(array_filter($custom_supp));
4997
	// Append '/32' CIDR as required
4998
	foreach ($custom_supp as &$custom) {
4999
		if (strpos($custom, '/') === FALSE) {
5000
			$custom = $custom . '/32';
5001
		}
5002
	}
5003

    
5004
	// Collect firewall states and save to temp file
5005
	exec("{$pfb['pfctl']} -s state > {$pfb['states_tmp']} 2>&1");
5006

    
5007
	$state_count = $all_states = 0;
5008
	$states		= array();
5009
	$states[4]	= array();
5010
	$states[6]	= array();
5011

    
5012
	if (($s_handle = @fopen("{$pfb['states_tmp']}", 'r')) !== FALSE) {
5013
		while (($sline = @fgets($s_handle)) !== FALSE) {
5014

    
5015
			$all_states++;
5016

    
5017
			// SAMPLE : em0 udp 93.15.36.22:6881 -> 192.168.0.3:681		MULTIPLE:MULTIPLE
5018
			// SAMPLE : pppoe0 udp 35.170.3.40:57197 (192.168.0.45:681) -> 22.41.123.206:1001	MULTIPLE:MULTIPLE
5019
			// SAMPLE : em0 tcp 2001:65c:1398:101:124[443] <- 2001:170:2f:3e:a4c4:7b23:fe5f:b36e[52725]	FIN_WAIT_2:FIN_WAIT_2
5020

    
5021
			if (!empty($sline)) {
5022

    
5023
				$detail	= array_filter(explode(' ', $sline));
5024

    
5025
				// Validate states for pfB Interfaces only
5026
				if (!isset($pfb_int[$detail[0]])) {
5027
					continue;
5028
				}
5029

    
5030
				$count	= count($detail);
5031
				if ($count == 6) {
5032
					$orig_s_ip = $detail[2];
5033
				}
5034
				elseif ($count == 7) {
5035
					$orig_s_ip = $detail[5];
5036
				}
5037
				else {
5038
					continue; // Unknown state line
5039
				}
5040
			}
5041
			else {
5042
				continue;
5043
			}
5044

    
5045
			$ip_version = 4;
5046

    
5047
			// Strip IPv6 port
5048
			if (strpos($orig_s_ip, '[') !== FALSE) {
5049
				list($s_ip, $s_port)  = explode('[', $orig_s_ip);
5050
				$ip_version = 6;
5051
			}
5052

    
5053
			// Strip IPv4 port
5054
			elseif (strpos($orig_s_ip, ':') !== FALSE && substr_count($orig_s_ip, ':') == 1) {
5055
				list($s_ip, $s_port)  = explode(':', $orig_s_ip);
5056
				$ip_version = 4;
5057
			}
5058

    
5059
			// No port listed
5060
			else {
5061
				$s_ip	= $orig_s_ip;
5062
				$s_port = '';
5063

    
5064
				if (is_ipaddrv6($s_ip)) {
5065
					$ip_version = 6;
5066
				}
5067
			}
5068

    
5069
			// Exclude local and reserved IPs (Validate unique IPs only once)
5070
			if (!isset($states[$ip_version][$s_ip])) {
5071

    
5072
				if ($ip_version == 4) {
5073
					if (isset($pfb_local[$s_ip]) ||
5074
					    pfb_local_ip($s_ip, $pfb_localsub) ||
5075
					    is_private_ip($s_ip) ||
5076
					    substr($s_ip, 0, 2) == '0.' ||
5077
					    substr($s_ip, 0, 4) == '127.' ||
5078
					    substr($s_ip, 0, 3) >= 224) {
5079
						continue;
5080
					}
5081
				}
5082
				else {
5083
					if (isset($pfb_local[$s_ip]) ||
5084
					    pfb_local_ip($s_ip, $pfb_localsub) ||
5085
					    !filter_var($s_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) ||
5086
					    !filter_var($s_ip, FILTER_CALLBACK, array('options' => 'FILTER_FLAG_NO_LOOPBACK_RANGE'))) {
5087
						continue;
5088
					}
5089
				}
5090

    
5091
				// Exclude any 'Permit' Customlist IPs
5092
				foreach ($custom_supp as $custom) {
5093
					if (ip_in_subnet($s_ip, $custom)) {
5094
						continue;
5095
					}
5096
				}
5097
			}
5098

    
5099
			$state = rtrim($sline, "\x00..\x1F");
5100
			$state_count++;
5101

    
5102
			// Collect IP for state removal verification
5103
			if (!is_array($states[$ip_version][$s_ip])) {
5104
				$states[$ip_version][$s_ip] = array();
5105
			}
5106

    
5107
			if (!in_array($state, $states[$ip_version][$s_ip])) {
5108
				$states[$ip_version][$s_ip][] = $state;
5109
			}
5110
		}
5111
	}
5112
	else {
5113
		pfb_logger("\n No Firewall States found", 1);
5114
	}
5115

    
5116
	if ($s_handle) {
5117
		@fclose($s_handle);
5118
	}
5119
	unlink_if_exists("{$pfb['states_tmp']}");
5120
	unset($pfb_local, $pfb_localsub, $custom_supp);
5121

    
5122
	$pfbfound = FALSE;
5123
	foreach ($states as $ip_version => $details) {
5124

    
5125
		if (!empty($details)) {
5126
			$log = "\nFirewall state(s) validation for [ " . count($details) . " ] IPv{$ip_version} address(es)...";
5127
			pfb_logger("{$log}", 1);
5128
			ksort($details, SORT_NATURAL);
5129
		}
5130

    
5131
		foreach ($details as $s_ip => $state) {
5132
			foreach ($pfb_tables as $s_table) {
5133

    
5134
				// Compare IP version and aliastable type
5135
				if ($ip_version == 4 && strpos($s_table, '_v4') !== FALSE ||
5136
				    $ip_version == 6 && strpos($s_table, '_v6') !== FALSE) {
5137

    
5138
					$s_table_esc	= escapeshellarg($s_table);
5139
					$s_ip_esc	= escapeshellarg($s_ip);
5140
					$result = substr(exec("{$pfb['pfctl']} -t {$s_table_esc} -T test {$s_ip_esc} 2>&1"), 0, 1);
5141
					if ($result > 0) {
5142

    
5143
						$pfbfound = TRUE;
5144
						$log = "\n\n\t[ {$s_table} ] Removed " . count($state) . " state(s) for [ {$s_ip} ]\n\n";
5145
						pfb_logger("{$log}", 1);
5146

    
5147
						foreach ($state as $line) {
5148
							pfb_logger("\t\t{$line}\n", 1);
5149
						}
5150

    
5151
						// Kill all state entries originating from $s_ip
5152
						exec("{$pfb['pfctl']} -k {$s_ip_esc} 2>&1");
5153

    
5154
						// Kill all state entries to the target $s_ip
5155
						exec("{$pfb['pfctl']} -k 0.0.0.0/0 -k {$s_ip_esc} 2>&1");
5156

    
5157
						break;
5158
					}
5159
				}
5160
			}
5161
		}
5162
	}
5163
	unset($states, $pfb_tables);
5164

    
5165
	if ($pfbfound) {
5166
		pfb_logger("\n======================================================================\n", 1);
5167
	} else {
5168
		pfb_logger("\nNo matching states found\n\n======================================================================\n", 1);
5169
	}
5170
}
5171

    
5172

    
5173
// For subnet addresses - Determine if alert host 'dest' is within a local IP range.
5174
function pfb_local_ip($subnet, $pfb_localsub) {
5175

    
5176
	if (!empty($pfb_localsub)) {
5177
		foreach ($pfb_localsub as $line) {
5178
			if (ip_in_subnet($subnet, $line)) {
5179
				return TRUE;
5180
			}
5181
		}
5182
	}
5183
	return FALSE;
5184
}
5185

    
5186

    
5187
// Collect local IP addresses
5188
function pfb_collect_localip() {
5189
	$pfb_local = $pfb_localsub = array();
5190

    
5191
	// Collect gateway IP addresses for inbound/outbound list matching
5192
	$int_gateway = get_interfaces_with_gateway();
5193
	if (isset($int_gateway)) {
5194
		foreach ($int_gateway as $gateway) {
5195
			$pfb_local[] = get_interface_ip($gateway) ?: 'Disabled';
5196
		}
5197
	}
5198

    
5199
	// Collect virtual IP aliases for inbound/outbound list matching
5200
	foreach (config_get_path('virtualip/vip', []) as $list) {
5201
		if (!empty($list['subnet']) && !empty($list['subnet_bits']) && is_subnet("{$list['subnet']}/{$list['subnet_bits']}")) {
5202
			if (is_ipaddrv4($list['subnet'])) {
5203
				if ($list['subnet_bits'] >= 24) {
5204
					$pfb_local	= array_merge(subnetv4_expand("{$list['subnet']}/{$list['subnet_bits']}"), $pfb_local);
5205
				} else {
5206
					$pfb_localsub[]	= "{$list['subnet']}/{$list['subnet_bits']}";
5207
				}
5208
			}
5209
			elseif (is_ipaddrv6($list['subnet'])) {
5210
				$pfb_localsub[] = gen_subnetv6("{$list['subnet']}", "{$list['subnet_bits']}");
5211
			}
5212
		}
5213
		elseif (is_ipaddr($list['subnet'])) {
5214
			$pfb_local[] = "{$list['subnet']}";
5215
		}
5216
	}
5217

    
5218
	// Collect NAT IP addresses for inbound/outbound list matching
5219
	foreach (config_get_path('nat/rule', []) as $natent) {
5220
		$pfb_local[] = $natent['target'];
5221
	}
5222

    
5223
	// Collect 1:1 NAT IP addresses for inbound/outbound list matching
5224
	foreach (config_get_path('nat/onetoone', []) as $onetoone) {
5225
		$pfb_local[] = $onetoone['source']['address'];
5226
	}
5227

    
5228
	// Convert any 'Firewall Aliases' to IP address format
5229
	$aliases = config_get_path('aliases/alias', []);
5230
	if (is_array($aliases)) {
5231
		for ($cnt = 0; $cnt <= count($pfb_local); $cnt++) {
5232
			foreach ($aliases as $i=> $alias) {
5233
				if (isset($alias['name']) && isset($pfb_local[$cnt])) {
5234
					if ($alias['name'] == $pfb_local[$cnt]) {
5235
						$pfb_local[$cnt] = $alias['address'];
5236
					}
5237
				}
5238
			}
5239
		}
5240
	}
5241

    
5242
	// Collect all interface addresses for inbound/outbound list matching
5243
	foreach (config_get_path('interfaces', []) as $int) {
5244
		if ($int['ipaddr'] != 'dhcp') {
5245
			if (!empty($int['ipaddr']) && !empty($int['subnet']) && is_subnet("{$int['ipaddr']}/{$int['subnet']}")) {
5246
				if (is_ipaddrv4($int['ipaddr'])) {
5247
					if ($int['subnet'] >= 24) {
5248
						$pfb_local	= array_merge(subnetv4_expand("{$int['ipaddr']}/{$int['subnet']}"), $pfb_local);
5249
					} else {
5250
						$pfb_localsub[]	= "{$int['ipaddr']}/{$int['subnet']}";
5251
					}
5252
				}
5253
				elseif (is_ipaddrv6($int['ipaddr'])) {
5254
					$pfb_localsub[] = gen_subnetv6("{$int['ipaddr']}", "{$int['subnet']}");
5255
				}
5256
			}
5257
			elseif (is_ipaddr($int['ipaddr'])) {
5258
				$pfb_local[] = "{$int['ipaddr']}";
5259
			}
5260
		}
5261
	}
5262

    
5263
	// Remove any duplicate IPs
5264
	if (!empty($pfb_local)) {
5265
		$pfb_local = array_flip(array_filter(array_unique($pfb_local)));
5266
	}
5267
	$pfb_localsub = array_unique($pfb_localsub);
5268

    
5269
	return array($pfb_local, $pfb_localsub);
5270
}
5271

    
5272

    
5273
// Collect local hostnames
5274
function pfb_collect_localhosts() {
5275
	global $g, $pfb;
5276

    
5277
	// Collect DHCP hostnames/IPs
5278
	$local_hosts = array();
5279

    
5280
	// Collect configured pfSense interfaces
5281
	$pf_int = get_configured_ip_addresses();
5282
	if (isset($pf_int)) {
5283
		$local_hosts = array_merge($local_hosts, array_flip(array_filter($pf_int)));
5284
	}
5285
	$pf_int = get_configured_ipv6_addresses();
5286
	if (isset($pf_int)) {
5287
		$local_hosts = array_merge($local_hosts, array_flip(array_filter($pf_int)));
5288
	}
5289

    
5290
	// Collect dynamic DHCP hostnames/IPs
5291
	$leasesfile = "{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases";
5292

    
5293
	$end = $hostname = '';
5294
	if (file_exists("{$leasesfile}")) {
5295
		if (($l_handle = @fopen("{$leasesfile}", 'r')) !== FALSE) {
5296
			while (($line = @fgets($l_handle)) !== FALSE) {
5297
				if (strpos($line, '{') !== FALSE) {
5298
					$end = FALSE;
5299
					$data = explode(' ', $line);
5300
					$ip = $data[1];
5301
				}
5302
				if (strpos($line, 'client-hostname') !== FALSE) {
5303
					$data = explode(' ', $line);
5304
					$hostname = trim(str_replace(array('"', ';'), '', $data[3]));
5305
				}
5306
				if (strpos($line, '}') !== FALSE) {
5307
					$end = TRUE;
5308
				}
5309
				if ($end) {
5310
					if (!empty($ip)) {
5311
						$ip = pfb_filter($ip, PFB_FILTER_IP, 'Collect dynamic DHCP hostnames');
5312
					}
5313

    
5314
					if (!empty($hostname)) {
5315
						$hostname = pfb_filter($hostname, PFB_FILTER_HOSTNAME, 'Collect dynamic DHCP hostnames');
5316
					}
5317

    
5318
					if (!empty($ip) && !empty($hostname)) {
5319
						$local_hosts[$ip] = $hostname;
5320
					}
5321
					$ip = $hostname = '';
5322
				}
5323
			}
5324
		}
5325
	}
5326

    
5327
	// Collect static DHCP hostnames/IPs
5328
	foreach (config_get_path('dhcpd', []) as $dhcp) {
5329
		if (isset($dhcp['staticmap']) && is_array($dhcp['staticmap'])) {
5330
			foreach ($dhcp['staticmap'] as $smap) {
5331
				$local_hosts[$smap['ipaddr']] = strtolower("{$smap['hostname']}");
5332
			}
5333
		}
5334
	}
5335

    
5336
	// Collect static DHCPv6 hostnames/IPs
5337
	foreach (config_get_path('dhcpdv6', []) as $dhcpv6) {
5338
		if (isset($dhcpv6['staticmap']) && is_array($dhcpv6['staticmap'])) {
5339
			foreach ($dhcpv6['staticmap'] as $smap) {
5340
				$local_hosts[$smap['ipaddrv6']] = strtolower("{$smap['hostname']}");
5341
			}
5342
		}
5343
	}
5344

    
5345
	// Collect Unbound Host overrides
5346
	$hosts = config_get_path('unbound/hosts', []);
5347
	foreach ($hosts as $host) {
5348
		$local_hosts[$host['ip']] = strtolower("{$host['descr']}");
5349
	}
5350

    
5351
	// Collect NAT IP addresses by Target:Port
5352
	foreach (config_get_path('nat/rule', []) as $natent) {
5353
		$local_hosts["{$natent['target']}:{$natent['local-port']}"] = strtolower("{$natent['descr']}");
5354
	}
5355

    
5356
	// Collect virtual IP aliases
5357
	foreach (config_get_path('virtualip/vip', []) as $list) {
5358
		if (!empty($list['subnet']) && !empty($list['subnet_bits'])) {
5359
	
5360
			// Use pfSense hostname for DNSBL vip
5361
			if ($list['subnet'] == $pfb['dnsbl_vip']) {
5362
				$list['descr'] = config_get_path('system/hostname', 'pfSense') . '.' . (config_get_path('system/domain', 'localdomain'));
5363
			}
5364
			$local_hosts[$list['subnet']] = strtolower("{$list['descr']}");
5365
		}
5366
	}
5367

    
5368
	// Add localhost hostname
5369
	if (!isset($local_hosts['127.0.0.1'])) {
5370
		$local_hosts['127.0.0.1'] = strtolower((config_get_path('system/hostname', 'pfSense')) . '.' . (config_get_path('system/domain', 'localdomain')));
5371
	}
5372

    
5373
	return $local_hosts;
5374
}
5375

    
5376

    
5377
// Firewall filter.log parser daemon
5378
function pfb_daemon_filterlog() {
5379
	global $pfb;
5380

    
5381
	// ASN Reporting - cached setting
5382
	if ($pfb['asn_reporting'] != 'disabled') {
5383
		switch ($pfb['asn_reporting']) {
5384
			case '1hour':
5385
				$asn_cache = '-1 hour';
5386
				break;
5387
			case '4hour':
5388
				$asn_cache = '-4 hours';
5389
				break;
5390
			case '12hour':
5391
				$asn_cache = '-12 hours';
5392
				break;
5393
			case '24hour':
5394
				$asn_cache = '-24 hours';
5395
				break;
5396
			case 'week':
5397
				$asn_cache = '-1 week';
5398
				break;
5399
			default:
5400
				$asn_cache = '-24 hours';
5401
		}
5402
	}
5403

    
5404
	// Application paths
5405
	if (file_exists('/usr/local/bin/mmdblookup') && file_exists("{$pfb['geoipshare']}/GeoLite2-Country.mmdb")) {
5406
		$pathgeoip = "/usr/local/bin/mmdblookup -f {$pfb['geoipshare']}/GeoLite2-Country.mmdb -i";
5407
	} else {
5408
		$pathgeoip = '';
5409
	}
5410

    
5411
	// Proofpoint ET IQRisk header name reference
5412
	$et_header	= config_get_path('installedpackages/pfblockerngreputation/config/0/et_header', '');
5413
	$et_enabled = TRUE;
5414
	if (empty($et_header)) {
5415
		$et_enabled = FALSE;
5416
	}
5417

    
5418
	$p_entry 	= '';
5419
	$line_cnt	= 0;
5420
	$rule_list	= $rule_list['other'] = array();
5421

    
5422
	if (file_exists('/usr/local/sbin/clog_pfb')) {
5423

    
5424
		// Parse full filter.log on first run, otherwise only parse new filter.log events
5425
		if (!file_exists($pfb['ip_blocklog']) && !file_exists($pfb['ip_permitlog']) && !file_exists($pfb['ip_matchlog'])) {
5426
			$filter_cnt = 0;
5427
		} else {
5428
			$filter_cnt = max( exec("{$pfb['grep']} -c ^ /var/log/filter.log 2>&1") -1, 0) ?: 0;
5429
		}
5430
		$skip_cnt = $filter_cnt;
5431
	}
5432
	else {
5433
		$skip_cnt = $filter_cnt = 0;
5434
	}
5435

    
5436
	/* filter.log reference: URL: https://docs.netgate.com/pfsense/en/latest/monitoring/logs/raw-filter-format.html
5437

    
5438
		$line -> $f
5439

    
5440
		[ BSD format ]	[ syslog format ]
5441
		[0][1][2]	[1]			= Date/timestamp
5442
		[5]		[7]			= Event Details
5443

    
5444
		$f[5] -> $d
5445

    
5446
		[0]	= Rule number
5447
		[1]	= Sub-rule number
5448
		[2]	= Anchor
5449
		[3]	= Tracker ID
5450
		[4]	= Real Interface
5451
		[5]	= Reason
5452
		[6]	= Action
5453
		[7]	= Direction
5454
		[8]	= IP version
5455
		[9]	= IP Specific data
5456

    
5457
		IPv4	IPv6
5458
		[10]	[]	=
5459
		[11]	[]	=
5460
		[12]	[]	=
5461
		[13]	[]	=
5462
		[14]	[]	=
5463
		[15]	[13]	= Protocol ID
5464
		[16]	[12]	= Protocol
5465
		[17]	[]	=
5466
		[18]	[15]	= SRC IP
5467
		[19]	[16]	= DST IP
5468
		[20]	[17]	= SRC Port
5469
		[21]	[18]	= DST Port
5470
		[22]	[]	=
5471
		[23]	[20]	= TCP Protocol Flags
5472

    
5473
	Final output reference:
5474
		[0]	= Date/Timestamp
5475
		[1]	= Rulenum
5476
		[2]	= Real Interface
5477
		[3]	= Friendly Interface name
5478
		[4]	= Action
5479
		[5]	= Version
5480
		[6]	= Protocol ID
5481
		[7]	= Protocol
5482
		[8]	= SRC IP
5483
		[9]	= DST IP
5484
		[10]	= SRC Port
5485
		[11]	= DST Port
5486
		[12]	= Direction
5487
		[13]	= GeoIP code
5488
		[14]	= IP Alias Name
5489
		[15]	= IP evaluated
5490
		[16]	= Feed Name
5491
		[17]	= gethostbyaddr resolved hostname
5492
		[18]	= Client Hostname
5493
		[19]	= Duplicate ID indicator		*/
5494

    
5495
	// Disable pfctl Tracker ID lookup on too many lookup failures
5496
	$max_retries = 0;
5497
	
5498
	if (($s_handle = @fopen('php://stdin', 'r')) !== FALSE) {
5499
		syslog(LOG_NOTICE, '[pfBlockerNG] filterlog daemon started');
5500
		while (!feof($s_handle)) {
5501
			$line = @fgets($s_handle);
5502

    
5503
			// Only parse new filter events
5504
			if ($filter_cnt > 0 && $skip_cnt > 0) {
5505
				$skip_cnt--;
5506
				continue;
5507
			}
5508

    
5509
			$log_type = 'BSD';
5510
			$f_pos = 5;
5511
			if (substr($line, 0, 1) == '<') {
5512
				$log_type = 'syslog';
5513
				$f_pos = 7;
5514
			}
5515

    
5516
			// Remove any '^M' characters
5517
			$line = htmlspecialchars(rtrim($line, "\x00..\x1F"));
5518

    
5519
			$f = explode(' ', $line);
5520

    
5521
			// Remove double space for single date entry nuance
5522
			if ($log_type == 'BSD' && empty($f[1])) {
5523
				array_splice($f, 1, 1);
5524
			}
5525
			$d = explode(',', $f[$f_pos]);
5526

    
5527
			// Attempt to find the Tracker ID, if not found wait 5secs for filter_reload
5528
			$other = FALSE;
5529
			if (!empty($d[3])) {
5530
				for ($retries = 1; $retries <= 5; $retries++) {
5531

    
5532
					// Skip known non-pfBlockerNG Tracker IDs
5533
					if (isset($rule_list['other'][$d[3]])) {
5534
						$other = TRUE;
5535
						break;
5536
					}
5537

    
5538
					// Break on pfBlockerNG rule Tracker ID and Rule type (block|permit|match)
5539
					// If the user switched a manual rule from one type to another, the Tracker ID will stay the same
5540
					// So this comparison will refresh the data on changes
5541
					if (isset($rule_list[$d[3]]) && $rule_list[$d[3]]['type'] == $d[6]) {
5542
						break;
5543
					}
5544

    
5545
					// Collect updated pfctl Rule data, Host and local IP data
5546
					else {
5547
						$rule_list	= pfb_filterrules();
5548
						$data		= pfb_collect_localip();
5549
						$pfb_local	= $data[0] ?: array();
5550
						$pfb_localsub	= $data[1] ?: array();
5551

    
5552
						$local_hosts	= pfb_collect_localhosts();
5553
					}
5554

    
5555
					if ($retries < 5 && $max_retries < 30) {
5556
						$max_retries++;
5557
						sleep(5);
5558
					} else {
5559
						$other = TRUE;
5560
					}
5561
				}
5562

    
5563
				if ($other) {
5564
					continue;
5565
				}
5566

    
5567
				// Duplicate entry comparison: "Tracker ID/Action/SRC IP/DST IP/DST Port"
5568
				$dup_entry = '+';
5569
				if ($d[8] == 4 && "{$d[3]}{$d[6]}{$d[18]}{$d[19]}{$d[21]}" == $p_entry) {
5570
					$dup_entry = '-';
5571
				} elseif ($d[8] == 6 && "{$d[3]}{$d[6]}{$d[15]}{$d[16]}{$d[18]}" == $p_entry) {
5572
					$dup_entry = '-';
5573
				}
5574

    
5575
				if ($dup_entry == '+') {
5576

    
5577
					$int		= convert_real_interface_to_friendly_descr($d[4]);
5578
					$pfb_alias	= pfb_filter($rule_list[$d[3]]['name'], PFB_FILTER_WORD, 'pfb_daemon_filterlog', 'Unknown');
5579

    
5580
					// Action setting variables
5581
					if ($d[6] == 'block') {
5582
						$folder = "{$pfb['denydir']}/* {$pfb['nativedir']}/*";
5583
						$iplog	= "{$pfb['ip_blocklog']}";
5584
						$l_type = 'Block';
5585
					}
5586
					elseif ($d[6] == 'pass') {
5587
						$folder = "{$pfb['permitdir']}/* {$pfb['nativedir']}/*";
5588
						$iplog	= "{$pfb['ip_permitlog']}";
5589
						$l_type = 'Permit';
5590
					}
5591
					elseif ($d[6] == 'unkn(%u)') {
5592
						$d[6]	= 'match';
5593
						$folder = "{$pfb['matchdir']}/* {$pfb['nativedir']}/*";
5594
						$iplog	= "{$pfb['ip_matchlog']}";
5595
						$l_type = 'Match';
5596
					}
5597

    
5598
					if ($d[8] == 4) {
5599
						$srcip		= $d[18] ?: 'Unknown';
5600
						$dstip		= $d[19] ?: 'Unknown';
5601
						$tcp_flags	= $d[16] == 'tcp' ? $d[23] : '';	// Keep protocol flags for TCP only
5602
					} else {
5603
						$srcip		= $d[15] ?: 'Unknown';
5604
						$dstip		= $d[16] ?: 'Unknown';
5605
						$tcp_flags	= $d[12] == 'tcp' ? $d[20] : '';
5606
					}
5607

    
5608
					// Determine if DST IP or SRC IP is the external host
5609
					if (isset($pfb_local[$dstip]) || pfb_local_ip($dstip, $pfb_localsub)) {
5610
						$dir	= 'in';
5611
						$host	= $srcip;
5612
						$client	= $dstip;
5613
						$port	= $d[21];
5614
					} else {
5615
						$dir	= 'out';
5616
						$host	= $dstip;
5617
						$client	= $srcip;
5618
						$port	= $d[20];
5619
					}
5620

    
5621
					if (!is_ipaddr($host)) {
5622
						continue;
5623
					}
5624
					if (!is_ipaddr($client)) {
5625
						continue;
5626
					}
5627
					if (!is_port($port)) {
5628
						$port = '';
5629
					}
5630

    
5631
					switch (TRUE) {
5632
						case isset($local_hosts["{$client}:{$port}"]) && !empty($local_hosts["{$client}:{$port}"]):
5633
							$hostname = $local_hosts["{$client}:{$port}"];
5634
							break;
5635
						case isset($local_hosts[$client]) && !empty($local_hosts[$client]):
5636
							$hostname = $local_hosts[$client];
5637
							break;
5638
						default:
5639
							$hostname = 'Unknown';
5640
					}
5641

    
5642
					$ip_cache = FALSE;
5643
					$db_handle = pfb_open_sqlite(7, 'Query ip cache');
5644
					if ($db_handle) {
5645
						$db_update = "SELECT * FROM ipcache WHERE host = :host;";
5646
						$stmt = $db_handle->prepare($db_update);
5647
						if ($stmt) {
5648
							$stmt->bindValue(':host', $host, SQLITE3_TEXT);
5649
							$result = $stmt->execute();
5650
							if ($result) {
5651
								$ip_cache = $result->fetchArray(SQLITE3_ASSOC);
5652
							}
5653
						}
5654
					}
5655
					pfb_close_sqlite($db_handle);
5656

    
5657
					if (!$ip_cache) {
5658

    
5659
						// Find the header which alerted this host
5660
						$geoip_folder = FALSE;
5661
						$geoip_validate = substr($pfb_alias, 0, -3);
5662
						if (isset($pfb['continent_list'][$geoip_validate])) {
5663
							$geoip_folder = TRUE;
5664
						}
5665
						$pfb_query = find_reported_header($host, $folder, $geoip_folder);
5666

    
5667
						// Report specific ET IQRisk details
5668
						if ($et_enabled && strpos($pfb_query[0], "{$et_header}") !== FALSE) {
5669
							$ET_orig = $pfb_query;
5670
							$pfb_query = find_reported_header($host, "{$pfb['etdir']}/*", FALSE);
5671

    
5672
							// ET IQRisk category is unknown.
5673
							if ($pfb_query[1] == 'Unknown') {
5674
								$pfb_query = $ET_orig;
5675
							}
5676
							else {
5677
								// Prepend ET Header name
5678
								$pfb_query[0] = "{$et_header}:{$pfb_query[0]}";
5679
							}
5680
						}
5681

    
5682
						// Determine GeoIP isocode of host
5683
						if (!empty($pathgeoip)) {
5684
							$geoip = exec("{$pathgeoip} " . escapeshellarg($host) . " country iso_code | grep -v '^\$' | cut -d '\"' -f2 2>&1");
5685
							$geoip = pfb_filter($geoip, PFB_FILTER_ALPHA, 'pfb_daemon_filterlog', 'Unk');
5686
						}
5687
						else {
5688
							$geoip = 'Unk';
5689
						}
5690

    
5691
						$resolved_host = gethostbyaddr($host) ?: 'Unknown';
5692
						if ($host == $resolved_host || $resolved_host == 'Unknown') {
5693
							$resolved_host = 'Unknown';
5694
						}
5695

    
5696
						// Save entry to IP cache
5697
						$db_update = "INSERT into ipcache ( host, q0, q1, geoip, resolved_host ) VALUES ( :host, :q0, :q1, :geoip, :resolved_host )";
5698
						$db_handle = pfb_open_sqlite(7, 'Add to IP cache');
5699
						if ($db_handle) {
5700
							$pfb_host	= pfb_filter($host, PFB_FILTER_HTML, 'pfb_daemon_filterlog');
5701
							$pfb_query[0]	= pfb_filter($pfb_query[0], PFB_FILTER_HTML, 'pfb_daemon_filterlog');
5702
							$pfb_query[1]	= pfb_filter($pfb_query[1], PFB_FILTER_HTML, 'pfb_daemon_filterlog');
5703
							$geoip		= pfb_filter($geoip, PFB_FILTER_HTML, 'pfb_daemon_filterlog');
5704
							$resolved_host	= pfb_filter($resolved_host, PFB_FILTER_HOSTNAME, 'pfb_daemon_filterlog', 'Unknown');
5705

    
5706
							$stmt = $db_handle->prepare($db_update);
5707
							if ($stmt) {
5708
								$stmt->bindValue(':host', $host, SQLITE3_TEXT);
5709
								$stmt->bindValue(':q0', $pfb_query[0], SQLITE3_TEXT);
5710
								$stmt->bindValue(':q1', $pfb_query[1], SQLITE3_TEXT);
5711
								$stmt->bindValue(':geoip', $geoip, SQLITE3_TEXT);
5712
								$stmt->bindValue(':resolved_host', $resolved_host, SQLITE3_TEXT);
5713
								$stmt->execute();
5714
							}
5715
						}
5716
						pfb_close_sqlite($db_handle);
5717
					}
5718

    
5719
					// Use cached entries
5720
					else {
5721
						$host		= htmlspecialchars($ip_cache['host'])		?: 'Unknown';
5722
						$pfb_query	= array();
5723
						$pfb_query[0]	= htmlspecialchars($ip_cache['q0'])		?: 'Unknown';
5724
						$pfb_query[1]	= htmlspecialchars($ip_cache['q1'])		?: 'Unknown';
5725
						$geoip		= htmlspecialchars($ip_cache['geoip'])		?: 'Unknown';
5726
						$resolved_host	= htmlspecialchars($ip_cache['resolved_host'])	?: 'Unknown';
5727
					}
5728

    
5729
					// Modify host for ASN query
5730
					if ($d[8] == 4) {
5731
						// For ASN, query for IP Network address (x.x.x.0)
5732
						$ix = ip_explode($host);
5733
						$ip = "{$ix['6']}0";
5734
					} else {
5735
						// For ASN, query for IP Network address with /48 subnet
5736
						$ip = gen_subnetv6($host, 48);
5737
					}
5738
					$ip = pfb_filter($ip, PFB_FILTER_IP, 'pfb_daemon_filterlog');
5739

    
5740
					// Determine ASN number of host
5741
					$asn = 'null';
5742
					if (($pfb['asn_reporting'] != 'disabled' || !empty($pfb['asn_token'])) && !empty($ip)) {
5743

    
5744
						$is_asn_cache = FALSE;
5745
						$db_handle = pfb_open_sqlite(5, 'Query ASN cache');
5746
						if ($db_handle) {
5747

    
5748
							// Clear cached entries older than defined cache setting
5749
							$db_update = "DELETE FROM asncache WHERE timestamp <= datetime('now', 'localtime', :asn_cache)";
5750
							$stmt = $db_handle->prepare($db_update);
5751
							if ($stmt) {
5752
								$stmt->bindValue(':asn_cache', $asn_cache, SQLITE3_TEXT);
5753
								$stmt->execute();
5754
							}
5755

    
5756
							// Query for cached Host IP
5757
							$db_update = "SELECT * FROM asncache WHERE host = :host;";
5758
							$stmt = $db_handle->prepare($db_update);
5759
							if ($stmt) {
5760
								$stmt->bindValue(':host', $ip, SQLITE3_TEXT);
5761
								$result = $stmt->execute();
5762
								if ($result) {
5763
									$is_asn_cache = $result->fetchArray(SQLITE3_ASSOC);
5764
								}
5765
							}
5766
						}
5767
						pfb_close_sqlite($db_handle);
5768

    
5769
						// If Host IP is not in ASN cache, collect ASN from ASN database
5770
						if (!$is_asn_cache) {
5771
							$asn = exec("{$pfb['script']} iptoasn " . escapeshellarg($ip) . " 2>&1");
5772

    
5773
							// Convert any IDN to ASCII
5774
							$asn_final = '';
5775
							if (!ctype_print($asn)) {
5776
								if (strpos($asn, '|') !== FALSE) {
5777
									$asn_ex = explode('|', $asn);
5778
									if (is_array($asn_ex) && !empty($asn_ex)) {
5779
										foreach ($asn_ex as $asn_element) {
5780
											if (strpos($asn_element, ':') !== FALSE) {
5781
												$asn_element_ex = explode(':', $asn_element);
5782
												if (is_array($asn_element_ex) && !empty($asn_element_ex)) {
5783
													if (!ctype_print($asn_element_ex[1])) {
5784
														$asn_element_ex[1] = mb_convert_encoding($asn_element_ex[1], 'UTF-8',
5785
															mb_detect_encoding($asn_element_ex[1], 'UTF-8, ASCII, ISO-8859-1'));
5786
														$asn_element_ex[1] = idn_to_ascii($asn_element_ex[1]);
5787
													}
5788
													$asn_final .= "{$asn_element_ex[0]}:{$asn_element_ex[1]}|";
5789
												}
5790
											}
5791
										}
5792
									}
5793
								}
5794
							}
5795
							else {
5796
								$asn_final = $asn;
5797
							}
5798
							$asn = pfb_filter($asn_final, PFB_FILTER_HTML, 'pfb_daemon_filterlog - asn');
5799

    
5800
							if (empty($asn) || strpos($asn, 'ASN:') === FALSE) {
5801
								$asn = 'Unknown';
5802
							}
5803

    
5804
							// Save entry to ASN cache
5805
							else {
5806
								$db_update = "INSERT into asncache ( asn, host, timestamp ) VALUES ( :asn, :host, datetime('now', 'localtime'))";
5807
								$db_handle = pfb_open_sqlite(5, 'Add to ASN cache');
5808
								if ($db_handle) {
5809
									$stmt	= $db_handle->prepare($db_update);
5810
									if ($stmt) {
5811
										$asn = "|{$asn}";
5812
										$stmt->bindValue(':asn', $asn, SQLITE3_TEXT);
5813
										$stmt->bindValue(':host', $ip, SQLITE3_TEXT);
5814
										$stmt->execute();
5815
									}
5816
								}
5817
								pfb_close_sqlite($db_handle);
5818
							}
5819
						}
5820

    
5821
						// Use cached ASN Entry
5822
						else {
5823
							$asn = htmlspecialchars($is_asn_cache['asn'])	?: 'Unknown';
5824
						}
5825
					}
5826

    
5827
					// Log: "Date timestamp/Tracker ID/Interface/Interface Name/Action/IP Version"
5828
					if ($log_type == 'BSD') {
5829
						$log = "{$f[0]} {$f[1]} {$f[2]},{$d[3]},{$d[4]},{$int},{$d[6]},{$d[8]},";
5830
					} else {
5831
						$ts = date('M j H:i:s', strtotime($f[1]));
5832
						$log = "{$ts},{$d[3]},{$d[4]},{$int},{$d[6]},{$d[8]},";
5833
					}
5834

    
5835
					// Details:	"Direction/GeoIP/Aliasname/IP evaluated/Feed Name/Resolved Hostname/Client Hostname/ASN/Duplicate status"
5836
					$details	= "{$dir},{$geoip},{$pfb_alias},{$pfb_query[1]},{$pfb_query[0]},{$resolved_host},{$hostname},{$asn}";
5837

    
5838
					// Reverse Match text
5839
					if ($d[6] == 'match') {
5840
						$d[6] = 'unkn(%u)';
5841
					}
5842

    
5843
					if ($d[8] == 4) {
5844
						// Previous:	"Tracker ID/Action/SRC IP/DST IP/DST Port"
5845
						$p_entry	= "{$d[3]}{$d[6]}{$d[18]}{$d[19]}{$d[21]}";
5846
						$d[16]		= str_replace('TCP', 'TCP-', strtoupper($d[16]), $d[16]) . $tcp_flags;
5847

    
5848
						// Log:		"Protocol ID/Protocol/SRC IP/DST IP/SRC Port/DST Port"
5849
						$log		.= "{$d[15]},{$d[16]},{$d[18]},{$d[19]},{$d[20]},{$d[21]}";
5850
					}
5851
					else {
5852
						$p_entry	= "{$d[3]}{$d[6]}{$d[15]}{$d[16]}{$d[18]}";
5853
						$d[12]		= str_replace('TCP', 'TCP-', strtoupper($d[12]), $d[12]) . $tcp_flags;
5854
						$log		.= "{$d[13]},{$d[12]},{$d[15]},{$d[16]},{$d[17]},{$d[18]}";
5855
					}
5856
				}
5857
				@file_put_contents("{$iplog}", "{$log},{$details},{$dup_entry}\n", FILE_APPEND | LOCK_EX);
5858

    
5859
				// Write to Unified Log
5860
				if ($dup_entry == '+') {
5861
					@file_put_contents("{$pfb['unilog']}", "{$l_type},{$log},{$details},{$dup_entry}\n", FILE_APPEND | LOCK_EX);
5862
				}
5863
			}
5864
		}
5865
	}
5866
	else {
5867
		log_error('[pfBlockerNG] filterlog - Failed to read STDIN');
5868
	}
5869
	if ($s_handle) {
5870
		@fclose($s_handle);
5871
	}
5872
}
5873

    
5874

    
5875
// Function to parse grep output and return Aliasname and IP fields
5876
function pfb_parse_query($line) {
5877
	$rx = explode('.txt:', $line);
5878
	$rx[0] = ltrim(strrchr($rx[0], '/'), '/');
5879
	return $rx;
5880
}
5881

    
5882

    
5883
// Function to output Alias/Feed name string
5884
function pfb_parse_line($line) {
5885
	$match = strstr($line, ':local', TRUE);
5886
	if (strpos($match, '.txt') !== FALSE) {
5887
		$match = strstr($match, '.txt', TRUE);
5888
	}
5889
	$match = substr($match, strrpos($match, '/') + 1);
5890
	return $match;
5891
}
5892

    
5893

    
5894
// Functon to find which DNSBL Feed/Groupname blocked this event
5895
function pfb_dnsbl_parse($mode='daemon', $domain, $src_ip, $req_agent) {
5896
	global $pfb;
5897

    
5898
	$dnsbl_cache = FALSE;
5899
	$db_handle = pfb_open_sqlite(4, 'Query cache');
5900
	if ($db_handle) {
5901
		$db_update = "SELECT * FROM dnsblcache WHERE domain = :domain;";
5902
		$stmt = $db_handle->prepare($db_update);
5903
		if ($stmt) {
5904
			$stmt->bindValue(':domain', $domain, SQLITE3_TEXT);
5905
			$result = $stmt->execute();
5906
			if ($result) {
5907
				$dnsbl_cache = $result->fetchArray(SQLITE3_ASSOC);
5908
			}
5909
		}
5910
	}
5911
	pfb_close_sqlite($db_handle);
5912

    
5913
	// If domain is not in DNSBL cache, query for blocked domain details
5914
	if (!$dnsbl_cache) {
5915
		$o_domain = $domain; // Store original domain for CNAME query
5916

    
5917
		while (TRUE) {
5918

    
5919
			$domainparse = str_replace('.', '\.', $domain);
5920
			if ($pfb['dnsbl_py_blacklist']) {
5921
				$dquery_esc	= escapeshellarg(",{$domainparse},,");
5922
				$pfb_feed	= exec("{$pfb['grep']} -shm1 {$dquery_esc} {$pfb['unbound_py_data']} 2>&1");
5923
			} else {
5924
				$dquery_esc	= escapeshellarg(" \"{$domainparse} 60");
5925
				$pfb_feed	= pfb_parse_line(exec("{$pfb['grep']} -sHm1 {$dquery_esc} {$pfb['dnsdir']}/*.txt 2>&1"));
5926
			}
5927
			$pfb_group = $pfb_mode = $pfb_final = 'Unknown';
5928

    
5929
			// Exact Domain match found
5930
			if (!empty($pfb_feed)) {
5931

    
5932
				if ($pfb['dnsbl_py_blacklist']) {
5933
					list($dummy, $pfb_final, $empty_placeholder, $log_type, $pfb_feed, $pfb_group) = explode(',', $pfb_feed);
5934
					$pfb_mode = 'DNSBL';
5935
				}
5936
				else {
5937
					$pfb_group = pfb_parse_line(exec("{$pfb['grep']} -sHm1 {$dquery_esc} {$pfb['dnsalias']}/* 2>&1"));
5938

    
5939
					// Determine DNSBL Type
5940
					$pfb_mode = 'DNSBL';
5941
					$ip = gethostbyname("dnsbl.test.{$domain}");
5942
					if ($ip == $pfb['dnsbl_vip'] || $ip == "::{$pfb['dnsbl_vip']}" || $ip == '0.0.0.0') {
5943
						$pfb_mode = 'TLD';
5944
					}
5945
					$pfb_final = $domain;
5946
				}
5947
			}
5948

    
5949
			else {
5950
				$dparts = explode('.', $domain);
5951
				if (!$pfb['dnsbl_py_blacklist']) {
5952
					unset($dparts[0]);
5953
				}
5954
				$dcnt = count($dparts);
5955

    
5956
				for ($i=0; $i <= $dcnt; $i++) {
5957

    
5958
					$domainparse		= str_replace('.', '\.', implode('.', $dparts));
5959
					$domainparse_esc	= escapeshellarg("^{$domainparse}$");
5960
					if (!$pfb['dnsbl_py_blacklist']) {
5961
						$dquery_esc = escapeshellarg(" \"{$domainparse} 60");
5962

    
5963
						// Determine if TLD exists in TLD Blacklist
5964
						if (file_exists("{$pfb['dnsbl_tld_txt']}")) {
5965
							exec("/usr/bin/grep -l {$domainparse_esc} {$pfb['dnsbl_tld_txt']} 2>&1", $match);
5966
							if (!empty($match[0])) {
5967
								$pfb_group = $pfb_feed = $pfb_mode = 'DNSBL_TLD';
5968
								$pfb_final = $domainparse;
5969
								break;
5970
							}
5971
						}
5972
					}
5973

    
5974
					if ($pfb['dnsbl_py_blacklist']) {
5975
						$dquery_esc	= escapeshellarg(",{$domainparse},,");
5976
						$pfb_feed	= exec("{$pfb['grep']} -shm1 {$dquery_esc} {$pfb['unbound_py_zone']} 2>&1");
5977

    
5978
						// Collect Alias Group name
5979
						if (!empty($pfb_feed)) {
5980
							list($dummy, $d_found, $empty_placeholder, $log_type, $pfb_feed, $pfb_group) = explode(',', $pfb_feed);
5981

    
5982
							$pfb_final = str_replace('\.', '.', $domainparse);
5983
							$pfb_mode = 'TLD';
5984
							if ($pfb_feed == 'DNSBL_TLD') {
5985
								$pfb_mode = 'DNSBL_TLD';
5986
							}
5987
							break;
5988
						}
5989
					}
5990
					else {
5991
						$pfb_feed = pfb_parse_line(exec("{$pfb['grep']} -sHm1 {$dquery_esc} {$pfb['dnsdir']}/*.txt 2>&1"));
5992
					
5993
						// Collect Alias Group name
5994
						if (!empty($pfb_feed)) {
5995
							$pfb_group	= pfb_parse_line(exec("{$pfb['grep']} -sHm1 {$dquery_esc} {$pfb['dnsalias']}/* 2>&1"));
5996
							$pfb_mode	= 'TLD';
5997
							$pfb_final	= str_replace('\.', '.', $domainparse);
5998
							break;
5999
						}
6000
					}
6001
					unset($dparts[$i]);
6002
				}
6003
			}
6004

    
6005
			// Query for CNAME(s)
6006
			if (empty($pfb_feed)) {
6007
				if (!isset($cnames)) {
6008
					$domain_esc = escapeshellarg($domain);
6009
					$extdns_esc = escapeshellarg("@{$pfb['extdns']}");
6010
					exec("/usr/bin/drill {$domain_esc} {$extdns_esc} | /usr/bin/awk '/CNAME/ {sub(\"\.\$\", \"\", \$5); print \$5;}'", $cnames);
6011
					if (is_array($cnames) && !empty($cnames)) {
6012
						foreach ($cnames as $key => $cname) {
6013
							// Remove invalid CNAMES
6014
							if (empty(pfb_filter($cname, PFB_FILTER_DOMAIN, 'pfb_dnsbl_parse'))) {
6015
								unset($cnames[$key]);
6016
								continue;
6017
							} 
6018
                                                }
6019
						$cname_cnt = count($cnames);
6020
					}
6021
				}
6022
				if (is_array($cnames) && !empty($cnames)) {
6023
					$domain = array_shift($cnames);
6024
				}
6025

    
6026
				if ($cname_cnt == 0) {
6027
					$domain = $o_domain; // No CNAME match found, revert back to original domain
6028
					break;
6029
				}
6030
				$cname_cnt--;
6031
			}
6032
			else {
6033
				break;
6034
			}
6035
		}
6036
		$pfb_feed = $pfb_feed ?: 'Unknown';
6037

    
6038
		if (isset($cnames)) {
6039
			unset($cnames);
6040
			if ($pfb_feed != 'Unknown') {
6041
				$pfb_mode = "{$pfb_mode}-CNAME";
6042
			}
6043
		}
6044

    
6045
		// Save entry to DNSBL cache
6046
		$db_update = "INSERT into dnsblcache ( type, domain, groupname, final, feed ) VALUES ( :type, :domain, :groupname, :final, :feed )";
6047
		$db_handle = pfb_open_sqlite(4, 'Add to DNSBL cache');
6048
		if ($db_handle) {
6049
			$domain		= pfb_filter($domain, PFB_FILTER_HTML, 'pfb_dnsbl_parse');
6050
			$pfb_group	= pfb_filter($pfb_group, PFB_FILTER_HTML, 'pfb_dnsbl_parse');
6051
			$pfb_final	= pfb_filter($pfb_final, PFB_FILTER_HTML, 'pfb_dnsbl_parse');
6052
			$pfb_feed	= pfb_filter($pfb_feed, PFB_FILTER_HTML, 'pfb_dnsbl_parse');
6053

    
6054
			$stmt = $db_handle->prepare($db_update);
6055
			if ($stmt) {
6056
				$stmt->bindValue(':type', $pfb_mode, SQLITE3_TEXT);
6057
				$stmt->bindValue(':domain', $domain, SQLITE3_TEXT);
6058
				$stmt->bindValue(':groupname', $pfb_group, SQLITE3_TEXT);
6059
				$stmt->bindValue(':final', $pfb_final, SQLITE3_TEXT);
6060
				$stmt->bindValue(':feed', $pfb_feed, SQLITE3_TEXT);
6061
				$stmt->execute();
6062
			}
6063
		}
6064
		pfb_close_sqlite($db_handle);
6065
	}
6066

    
6067
	// Use cached entries
6068
	else {
6069
		$pfb_mode	= htmlspecialchars($dnsbl_cache['type'])	?: 'Unknown';
6070
		$pfb_group	= htmlspecialchars($dnsbl_cache['groupname'])	?: 'Unknown';
6071
		$pfb_final	= htmlspecialchars($dnsbl_cache['final'])	?: 'Unknown';
6072
		$pfb_feed	= htmlspecialchars($dnsbl_cache['feed'])	?: 'Unknown';
6073
	}
6074

    
6075
	if ($mode == 'daemon') {
6076
		$details = "{$domain},{$src_ip},{$req_agent},{$pfb_mode},{$pfb_group},{$pfb_final},{$pfb_feed}";
6077
		return array ($pfb_group, $details);
6078
	} else {
6079
		return array ('pfb_mode' => $pfb_mode, 'pfb_group' => $pfb_group, 'pfb_final' => $pfb_final, 'pfb_feed' => $pfb_feed);
6080
	}
6081
}
6082

    
6083

    
6084
// DNSBL Lighttpd 'dnsbl_error.log' conditional log parser
6085
function pfb_daemon_dnsbl() {
6086
	global $pfb;
6087

    
6088
	if (($h_handle = @fopen('php://stdin', 'r')) !== FALSE) {
6089
		syslog(LOG_NOTICE, '[pfBlockerNG] DNSBL parser daemon started');
6090

    
6091
		$chk_ver	= FALSE;
6092
		$lighty_47	= FALSE;
6093
		$lighty_58	= FALSE;
6094
		$lighty_59	= FALSE;
6095
		$checkpos	= 3; 	// Pre Lighttpd v1.4.47
6096

    
6097
		while (!feof($h_handle)) {
6098
			$pfb_buffer = @fgets($h_handle);
6099

    
6100
			if (!$chk_ver) {
6101

    
6102
				// Lighttpd v1.4.58+ conditional error log with 'ssl.verifyclient.activate' to collect the domain name
6103
				if (!$lighty_47 && strpos($pfb_buffer, 'lighttpd/1.4.58') !== FALSE) {
6104
					$chk_ver	= TRUE;
6105
					$lighty_58	= TRUE;
6106
					continue;
6107
				}
6108

    
6109
				// Lighttpd v1.4.59+ syntax changes to IP line
6110
                                elseif (!$lighty_47 &&
6111
				    (strpos($pfb_buffer, 'lighttpd/1.4.59') !== FALSE ||
6112
				    strpos($pfb_buffer, 'lighttpd/1.4.6') !== FALSE ||
6113
				    strpos($pfb_buffer, 'lighttpd/1.4.7') !== FALSE ||
6114
				    strpos($pfb_buffer, 'lighttpd/1.5') !== FALSE)) {
6115
					$chk_ver	= TRUE;
6116
					$lighty_59	= TRUE;
6117
					continue;
6118
				}
6119

    
6120
				// Lighttpd v1.4.47 uses mod_openssl with a different conditional log formatting
6121
				elseif (strpos($pfb_buffer, 'global/HTTPscheme') !== FALSE) {
6122
					$lighty_47	= TRUE;
6123
					$checkpos	= 1;
6124
					continue;
6125
				}
6126
			}
6127

    
6128
			if ($lighty_58 || $lighty_59) {
6129

    
6130
				// Verify HTTP["remoteip"]
6131
				if (empty($src_ip)) {
6132
					if ($lighty_58) {
6133
						if (strpos($pfb_buffer, '["re') !== FALSE) {
6134
							$src_ip = strstr($pfb_buffer, ') compare', TRUE);
6135
							$src_ip = ltrim(strstr($src_ip, '] (', FALSE), '] (');
6136
							$src_ip = (filter_var($src_ip, FILTER_VALIDATE_IP) !== FALSE) ? $src_ip : '';
6137
						}
6138
					} else {
6139
						if (strpos($pfb_buffer, '["re') !== FALSE && strpos($pfb_buffer, ' compare to ') !== FALSE) {
6140
							$src_ip = strstr($pfb_buffer, ' compare to ', FALSE);
6141
							$src_ip = trim(ltrim($src_ip, ' compare to '));
6142
							$src_ip = (filter_var($src_ip, FILTER_VALIDATE_IP) !== FALSE) ? $src_ip : '';
6143
						}
6144
					}
6145
					continue;
6146
				}
6147

    
6148
				if (!empty($src_ip) && strpos($pfb_buffer, "SSL: ") === FALSE) {
6149
					continue;
6150
				}
6151
				if (empty($domain) && strpos($pfb_buffer, "SSL: can't verify client without ssl.") !== FALSE) {
6152
					$domain = str_replace(' server name ', '', strstr(trim($pfb_buffer), ' server name ', FALSE));
6153
				}
6154

    
6155
				if (!empty($domain) && !empty($src_ip)) {
6156

    
6157
					$domain = pfb_filter($domain, PFB_FILTER_DOMAIN, 'pfb_daemon_dnsbl');
6158
					$src_ip = pfb_filter($src_ip, PFB_FILTER_IP, 'pfb_daemon_dnsbl');
6159
					if (!empty($domain) && !empty($src_ip)) {
6160

    
6161
						// URL/Referer/URI/Agent String not available for HTTPS events
6162
						$req_agent	= 'Unknown';
6163
						$type		= 'DNSBL-HTTPS';
6164

    
6165
						pfb_log_event($type, $domain, $src_ip, $req_agent);
6166
						$domain = $src_ip = '';
6167
					}
6168
				}
6169
			}
6170
			else {
6171
				// Parse only HTTP["xxx"] log lines
6172
				if (strpos($pfb_buffer, 'HTTP[') === FALSE) {
6173
					continue;
6174
				}
6175

    
6176
				// Verify only HTTPS lines
6177
				if (strpos($pfb_buffer, '( https') !== FALSE) {
6178
					if ($lighty_47) {
6179
						continue;
6180
					}
6181
					$checkpos = 0;
6182
				}
6183

    
6184
				// Verify HTTP["remoteip"]
6185
				if ($checkpos == 1 && strpos($pfb_buffer, '["re') !== FALSE) {
6186
					$src_ip = strstr($pfb_buffer, ' ) compare', TRUE);
6187
					$src_ip = ltrim(strstr($src_ip, '] ( ', FALSE), '] ( ');
6188
					$src_ip = (filter_var($src_ip, FILTER_VALIDATE_IP) !== FALSE) ? $src_ip : '';
6189
				}
6190

    
6191
				// Verify HTTP["host"]
6192
				elseif ($checkpos == 2 && strpos($pfb_buffer, '["ho') !== FALSE) {
6193
					$lighty_47	= FALSE;
6194
					$domain		= strstr($pfb_buffer, ' ) compare', TRUE);
6195
					$domain		= ltrim(strstr($domain, '] ( ', FALSE), '] ( ');
6196

    
6197
					// URL/Referer/URI/Agent String not available for HTTPS events
6198
					$req_agent	= 'Unknown';
6199
					$type		= 'DNSBL-HTTPS';
6200

    
6201
					// Log event and Increment SQLite DNSBL Group counter
6202
					if (!empty($domain) && !empty($src_ip)) {
6203

    
6204
						$domain = pfb_filter($domain, PFB_FILTER_DOMAIN, 'pfb_daemon_dnsbl');
6205
						$src_ip = pfb_filter($src_ip, PFB_FILTER_IP, 'pfb_daemon_dnsbl');
6206
						if (!empty($domain) && !empty($src_ip)) {
6207

    
6208
							// URL/Referer/URI/Agent String not available for HTTPS events
6209
							$req_agent	= 'Unknown';
6210
							$type		= 'DNSBL-HTTPS';
6211

    
6212
						 	pfb_log_event($type, $domain, $src_ip, $req_agent);
6213
							$domain = $src_ip = '';
6214
						}
6215
					}
6216
				}
6217
				$checkpos++;
6218
			}
6219
		}
6220
	}
6221
	else {
6222
		log_error('[pfBlockerNG] DNSBL conditional log parser - Failed to open handle');
6223
	}
6224
	@fclose($h_handle);
6225
}
6226

    
6227

    
6228
// DNSBL Lighttpd 'index.php' event parser
6229
function pfb_daemon_dnsbl_index() {
6230
	global $pfb;
6231

    
6232
	// Replace any [',' or '|' ] in HTTP_REFERER, REQUEST_URI or HTTP_USER_AGENT fields
6233
	// Replace placeholder characters [ '!' and '*' ] to [ ',' and '|' ]
6234
	$p1 = array( ',', '!', '|', ' * ' );
6235
	$p2 = array( '', ',', '--', '|' );
6236

    
6237
	if (($i_handle = @fopen('php://stdin', 'r')) !== FALSE) {
6238
		while (!feof($i_handle)) {
6239
			$pfb_buffer = @fgets($i_handle);
6240
			$pfb_buffer = str_replace($p1, $p2, $pfb_buffer);
6241

    
6242
			if (substr($pfb_buffer, 0, 6) == 'INDEX,' && substr_count($pfb_buffer, ',') == 4) {
6243
				$csvline = str_getcsv($pfb_buffer, ',', '', '"');
6244

    
6245
				// Determine blocked domain type (Full, 1x1 or JS)
6246
				if (isset($csvline[1]) && !empty($csvline[1])) {
6247
					$request = strstr($csvline[1], '/', FALSE);
6248
					$request = strstr($request, ' ', TRUE);
6249

    
6250
					if (strlen($request) < 2) {
6251
						$type = 'DNSBL-Full';
6252
					}
6253
					else {
6254
						if (pathinfo($request, PATHINFO_EXTENSION) == 'js') {
6255
							$type = 'DNSBL-JS';
6256
						} else {
6257
							$type = 'DNSBL-1x1';
6258
						}
6259
					}
6260
				}
6261
				else {
6262
					$type = 'DNSBL-Unknown';
6263
				}
6264

    
6265
				$csvline[2] = pfb_filter($csvline[2], PFB_FILTER_DOMAIN, 'pfb_daemon_dnsbl_index');
6266
				$csvline[3] = pfb_filter($csvline[3], PFB_FILTER_IP, 'pfb_daemon_dnsbl_index');
6267
				$csvline[4] = pfb_filter($csvline[4], PFB_FILTER_HTML, 'pfb_daemon_dnsbl_index');
6268
			}
6269
			else {
6270
				continue;
6271
			}
6272

    
6273
			// Log event and increment SQLite DNSBL Group counter
6274
			if (!empty($csvline[2]) && !empty($csvline[3])) {
6275
				pfb_log_event($type, $csvline[2], $csvline[3], $csvline[4]);
6276
			}
6277
		}
6278
	}
6279
	else {
6280
		log_error('[pfBlockerNG] DNSBL index event parser - Failed to open handle');
6281
	}
6282
	@fclose($i_handle);
6283
}
6284

    
6285

    
6286
// Function to create/open SQLite3 database(s)
6287
function pfb_open_sqlite($table, $message) {
6288
	global $pfb;
6289

    
6290
	if ($table == 1) {
6291
		$database	= $pfb['dnsbl_info'];
6292
		$db_table	= 'dnsbl';
6293
		$db_create	= "CREATE TABLE IF NOT EXISTS dnsbl ( groupname TEXT, timestamp TEXT, entries INTEGER, counter INTEGER );";
6294
	} elseif ($table == 2) {
6295
		$database	= $pfb['dnsbl_resolver'];
6296
		$db_table	= 'lastevent';
6297
		$db_create	= "CREATE TABLE IF NOT EXISTS lastevent ( row INTEGER, groupname TEXT, entry TEXT, details TEXT );";
6298
	} elseif ($table == 3) {
6299
		$database	= $pfb['dnsbl_resolver'];
6300
		$db_table	= 'resolver';
6301
		$db_create	= "CREATE TABLE IF NOT EXISTS resolver ( row INTEGER, totalqueries INTEGER, queries INTEGER );";
6302
	} elseif ($table == 4) {
6303
		$database	= $pfb['dnsbl_cache'];
6304
		$db_table	= 'dnsblcache';
6305
		$db_create	= "CREATE TABLE IF NOT EXISTS dnsblcache ( type TEXT, domain TEXT, groupname TEXT, final TEXT, feed TEXT );";
6306
	} elseif ($table == 5) {
6307
		$database	= $pfb['asn_cache'];
6308
		$db_table	= 'asncache';
6309
		$db_create	= "CREATE TABLE IF NOT EXISTS asncache ( asn TEXT, host TEXT, timestamp TEXT );";
6310
	} elseif ($table == 6) {
6311
		$database	= $pfb['dnsbl_resolver'];
6312
		$db_table	= 'stats';
6313
		$db_create	= "CREATE TABLE IF NOT EXISTS lastclear ( row INTEGER, lastipclear TEXT, lastdnsblclear TEXT );";
6314
	} elseif ($table == 7) {
6315
		$database	= $pfb['ip_cache'];
6316
		$db_table	= 'ipcache';
6317
		$db_create      = "CREATE TABLE IF NOT EXISTS ipcache ( host TEXT, q0 TEXT, q1 TEXT, geoip TEXT, resolved_host TEXT );";
6318
	}
6319

    
6320
	try {
6321
		$db_handle = new SQLite3($database);
6322
		$db_handle->busyTimeout("{$pfb['sqlite_timeout']}");
6323
	}
6324
	catch (Exception $e) {
6325
		@file_put_contents($pfb['errlog'], "\nDNSBL_SQL: Failed to open DB - {$message}", FILE_APPEND | LOCK_EX);
6326

    
6327
		try {
6328
			$db_handle = new SQLite3($database);
6329
			$db_handle->busyTimeout("{$pfb['sqlite_timeout']}");
6330
		}
6331
		catch (Exception $e) {
6332
			@file_put_contents($pfb['errlog'], "\nDNSBL_SQL: Failed to open DB 2nd attempt - {$message}", FILE_APPEND | LOCK_EX);
6333
		}
6334
	}
6335

    
6336
	if ($db_handle) {
6337

    
6338
		// Validate database integrity
6339
		$validate = $pfb_validate = FALSE;
6340
		try {
6341
			$validate = $db_handle->query("PRAGMA integrity_check;");
6342
		}
6343
		catch (Exception $e) {
6344
			@file_put_contents($pfb['errlog'], "\nDNSBL_SQL: Failed to validate database - {$message}", FILE_APPEND | LOCK_EX);
6345
		}
6346

    
6347
		if ($validate) {
6348
			while ($res = $validate->fetchArray(SQLITE3_ASSOC)) {
6349
				if ($res['integrity_check'] == 'ok') {
6350
					$pfb_validate = TRUE;
6351
				}
6352
			}
6353
		}
6354

    
6355
		if (!$pfb_validate) {
6356
			log_error("[pfBlockerNG] DNSBL SQLite3 database [ {$db_table} ] corrupt. Table deletion/re-creation completed.");
6357
			@copy("{$database}", "{$database}.invalid");
6358
			$db_create = "DROP TABLE {$db_table}; {$db_create}";
6359
		}
6360

    
6361
		try {
6362
			$db_handle->exec("BEGIN TRANSACTION; PRAGMA journal_mode = delete;"
6363
					. "{$db_create}"
6364
					. "END TRANSACTION;");
6365
		}
6366
		catch (Exception $e) {
6367
			@file_put_contents($pfb['errlog'], "\nDNSBL_SQL: Database failure - {$message}", FILE_APPEND | LOCK_EX);
6368
			return;
6369
		}
6370

    
6371
		if ($table <= 4 && file_exists($database)) {
6372
			@chown($database, 'unbound');
6373
			@chgrp($database, 'unbound');
6374
		}
6375
		return $db_handle;
6376
	}
6377
	return FALSE;
6378
}
6379

    
6380

    
6381
// Function to close SQLite3 database
6382
function pfb_close_sqlite($db_handle) {
6383
	if (!empty($db_handle)) {
6384
		$db_handle->close();
6385
		unset($db_handle);
6386
	}
6387
}
6388

    
6389

    
6390
// Function to Log event, Increment SQLite 'dnsbl' database and save last event to SQLite 'lastevent' Database
6391
function pfb_log_event($type, $domain, $src_ip, $req_agent) {
6392
	global $pfb;
6393

    
6394
	$datereq	= date('M j H:i:s', time());
6395
	$req_agent	= str_replace("'", '', $req_agent);
6396

    
6397
	// Collect lastevent without saving new event
6398
	$result		= pfb_dnsbl_lastevent('', "{$domain}{$src_ip}", '');
6399
	$p_entry	= $result['entry'];
6400
	$details	= $result['details'];
6401

    
6402
	// Duplicate entry comparison: "Domain/SRC IP"
6403
	if ("{$domain}{$src_ip}" == $p_entry) {
6404
		$dup_entry	= '-';
6405
		$pfb_group	= $result['groupname'];
6406
	}
6407

    
6408
	// If not duplicate entry, determine TLD type, Group Name and Feed name
6409
	else {
6410
		$dup_entry	= '+';
6411
		$data		= pfb_dnsbl_parse('daemon', $domain, $src_ip, $req_agent);
6412
		$pfb_group	= $data[0];
6413
		$details	= $data[1];
6414
		$req_agent	= $data[2];
6415

    
6416
		// Save new lastevent
6417
		pfb_dnsbl_lastevent($pfb_group, "{$domain}{$src_ip}", $details);
6418
	}
6419

    
6420
	$log = "{$type},{$datereq},{$details},{$dup_entry}\n";
6421
	@file_put_contents($pfb['dnslog'], "{$log}", FILE_APPEND | LOCK_EX);
6422
	
6423
	// Write to Unified Log
6424
	@file_put_contents($pfb['unilog'], "{$log}", FILE_APPEND | LOCK_EX);
6425

    
6426
	// Increment DNSBL Widget counter
6427
	if (!empty($pfb_group)) {
6428

    
6429
		$db_handle = pfb_open_sqlite(1, 'Increment Counter');
6430
		if ($db_handle) {
6431

    
6432
			$pfb_group	= pfb_filter($pfb_group, PFB_FILTER_HTML, 'pfb_log_event');
6433
			$db_update	= "UPDATE dnsbl SET counter = counter + 1 WHERE groupname = :pfb_group";
6434

    
6435
			$stmt = $db_handle->prepare($db_update);
6436
			if ($stmt) {
6437
				$stmt->bindValue(':pfb_group', $pfb_group, SQLITE3_TEXT);
6438
				$stmt->execute();
6439
			}
6440
		}
6441
		pfb_close_sqlite($db_handle);
6442
	}
6443
}
6444

    
6445

    
6446
// Function to 1) Collect lastevent and 2) Save lastevent to SQLite 'lastevent' Database
6447
function pfb_dnsbl_lastevent($p_group, $p_entry, $p_details) {
6448
	global $pfb;
6449

    
6450
	$db_update	= '';
6451
	$final		= array();
6452

    
6453
	$db_handle = pfb_open_sqlite(2, 'LastEvent');
6454
	if ($db_handle) {
6455
		$result	= $db_handle->query("SELECT * FROM lastevent WHERE row = 0;");
6456
		if ($result) {
6457
			$final = $result->fetchArray(SQLITE3_ASSOC);
6458
		}
6459
	}
6460
	pfb_close_sqlite($db_handle);
6461

    
6462
	// Collect or update existing row
6463
	if (!empty($final)) {
6464

    
6465
		// Only collect lastevent entry
6466
		if (empty($p_group)) {
6467
			;
6468
		}
6469

    
6470
		// Update lastevent entry
6471
		else {
6472
			$db_update = "UPDATE lastevent SET groupname=:p_group, entry=:p_entry, details=:p_details WHERE row = 0";
6473
		}
6474
	}
6475

    
6476
	// Add new lastevent entry
6477
	else {
6478
		$db_update = "INSERT into lastevent (row, groupname, entry, details) VALUES (0, :p_group, :p_entry, :p_details )";
6479
	}
6480

    
6481
	if (!empty($db_update)) {
6482
		$db_handle = pfb_open_sqlite(2, 'LastEvent');
6483
		if ($db_handle) {
6484

    
6485
			$p_group	= pfb_filter($p_group, PFB_FILTER_HTML, 'pfb_dnsbl_lastevent');
6486
			$p_entry	= pfb_filter($p_entry, PFB_FILTER_HTML, 'pfb_dnsbl_lastevent');
6487
			$p_details	= pfb_filter($p_details, PFB_FILTER_HTML, 'pfb_dnsbl_lastevent');
6488

    
6489
			$stmt = $db_handle->prepare($db_update);
6490
			if ($stmt) {
6491
				$stmt->bindValue(':p_group', $p_group, SQLITE3_TEXT);
6492
				$stmt->bindValue(':p_entry', $p_entry, SQLITE3_TEXT);
6493
				$stmt->bindValue(':p_details', $p_details, SQLITE3_TEXT);
6494
				$stmt->execute();
6495
			}
6496
		}
6497
		pfb_close_sqlite($db_handle);
6498
	}
6499
	return $final;
6500
}
6501

    
6502

    
6503
// Function to collect and update the Unbound Resolver query/pid entries into SQLite3 database
6504
function pfb_daemon_queries() {
6505
	global $g, $pfb;
6506

    
6507
	$sleep_freq	= config_get_path('installedpackages/pfblockerngglobal/widget-dnsblquery', 5);
6508

    
6509
	$nice		= '/usr/bin/nice -n20';
6510
	$unbound_pid	= "{$g['varrun_path']}/unbound.pid";
6511

    
6512
	while (TRUE) {
6513
		sleep($sleep_freq);
6514

    
6515
		// If 'Live Sync' file marker exists skip DNSBL Queries daemon to avoid unbound-control collisions
6516
		if (platform_booting() || $g['pfblockerng_install'] || file_exists("{$pfb['dnsbl_file']}.sync")) {
6517
			continue;
6518
		}
6519

    
6520
		// Collect Unbound Resolver pid
6521
		$pid = exec("{$nice} /usr/bin/pgrep -anx 'unbound' 2>&1");
6522
		if (!empty($pid)) {
6523
			$output	= exec("{$pfb['chroot_cmd']} stats_noreset | {$nice} {$pfb['grep']} 'total.num.queries=' | cut -d '=' -f2 2>&1");
6524
			$query	= intval($output);
6525
			if (!is_numeric($query)) {
6526
				$query = '';
6527
			}
6528

    
6529
			// On an Unbound Reload, the query stat is reset, therefore reuse previous query value
6530
			if ($query < $p_query) {
6531
				$t_query	= $query;
6532
				$query		= $query + $p_query;
6533
				$pid		= '';
6534
			}
6535
		}
6536

    
6537
		// On initial daemon load
6538
		if (empty($p_pid)) {
6539
			$p_pid = $pid;
6540
			continue;
6541
		}
6542

    
6543
		$pfb_found		= FALSE;
6544
		$stats			= array();
6545
		$stats['totalqueries']	= 0;
6546
		$stats['queries']	= 0;
6547

    
6548
		$db_handle = pfb_open_sqlite(3, 'Resolver collect queries');
6549
		if ($db_handle) {
6550
			$result = $db_handle->query("SELECT * FROM resolver WHERE row = 0;");
6551
			if ($result) {
6552
				while ($res = $result->fetchArray(SQLITE3_ASSOC)) {
6553
					$stats = $res;
6554
					$pfb_found = TRUE;
6555
				}
6556
			}
6557

    
6558
			// Create new row
6559
			if (!$pfb_found) {
6560
				$db_update = "INSERT INTO resolver ( row, totalqueries, queries ) VALUES ( 0, 0, 0 );";
6561
				$db_handle->exec("BEGIN TRANSACTION;"
6562
						. "{$db_update}"
6563
						. "END TRANSACTION;");
6564
			}
6565
		}
6566
		pfb_close_sqlite($db_handle);
6567

    
6568
		// If Unbound Resolver pid has changed, clear SQLite database 'queries' entry, and update 'totalqueries/pid' entries
6569
		if ($pfb_found && $pid != $p_pid) {
6570
			$totalqueries = ($stats['totalqueries'] ?: 0) + ($query ?: 0);
6571
			pfBlockerNG_clearsqlite('update_totalqueries', $totalqueries);
6572
		}
6573
		else {
6574
			if ($query != '' && $query != $p_query && is_numeric($query)) {
6575

    
6576
				// Update existing row
6577
				$db_handle = pfb_open_sqlite(3, 'Widget update queries');
6578
				if ($db_handle) {
6579
					$db_update = "UPDATE resolver SET queries = :query WHERE row = 0";
6580
					$stmt = $db_handle->prepare($db_update);
6581
					if ($stmt) {
6582
						$stmt->bindValue(':query', $query, SQLITE3_INTEGER);
6583
						$stmt->execute();
6584
					}
6585
				}
6586
				pfb_close_sqlite($db_handle);
6587
			}
6588
		}
6589

    
6590
		$p_pid = $pid;
6591
		if (isset($t_query)) {
6592
			$p_query = $t_query;
6593
			unset($t_query);
6594
		} else {
6595
			$p_query = $query;
6596
		}
6597
	}
6598
}
6599

    
6600

    
6601
// Read logfile in realtime (livetail)
6602
// Reference: http://stackoverflow.com/questions/3218895/php-how-to-read-a-file-live-that-is-constantly-being-written-to
6603
function pfb_livetail($logfile, $mode) {
6604
	global $pfb;
6605

    
6606
	if (!file_exists("{$logfile}")) {
6607
		touch("{$logfile}");
6608
	}
6609

    
6610
	$len		= @filesize("{$logfile}");	// Start at EOF
6611
	$lastpos_old	= $pfb_output = '';
6612

    
6613
	if ($mode == 'view') {
6614
		// Start at EOF ( - 15000)
6615
		if ($len > 15000) {
6616
			$lastpos = ($len - 15000);
6617
		} else {
6618
			$lastpos = 0;
6619
		}
6620
	}
6621
	else {
6622
		$lastpos = $len;
6623
	}
6624

    
6625
	while (TRUE) {
6626
		usleep(300000); //0.3s
6627
		clearstatcache(false, "{$logfile}");
6628
		$len = @filesize("{$logfile}");
6629

    
6630
		if ($len < $lastpos) {
6631
			$lastpos = $len;	// File deleted or reset
6632
		}
6633
		else {
6634
			$f = @fopen("{$logfile}", 'rb+');
6635
			if ($f === false) {
6636
				break;
6637
			}
6638
			@fseek($f, $lastpos);
6639

    
6640
			while (!feof($f)) {
6641
				$pfb_buffer = @fread($f, 2048);
6642
				$pfb_output .= str_replace( array ("\r", "\")"), '', $pfb_buffer);
6643
				// Refresh on new lines only. This allows Scrolling.
6644
				if ($lastpos != $lastpos_old) {
6645
					pfbupdate_output($pfb_output);
6646
				}
6647
				$lastpos_old = $lastpos;
6648
				ob_flush();
6649
				flush();
6650
			}
6651

    
6652
			$lastpos = @ftell($f);
6653
			if ($f) {
6654
				@fclose($f);
6655
			}
6656

    
6657
			// Capture remaining output
6658
			if ($mode != 'view' && strpos($pfb_output, 'UPDATE PROCESS ENDED') !== FALSE) {
6659
				$f = @fopen($pfb['log'], 'rb');
6660
				@fseek($f, $lastpos);
6661
				$pfb_buffer = @fread($f, 2048);
6662
				$pfb_output .= str_replace( "\r", '', $pfb_buffer);
6663
				pfbupdate_output($pfb_output);
6664
				clearstatcache(false, $pfb['log']);
6665
				ob_flush();
6666
				flush();
6667
				if ($f) {
6668
					@fclose($f);
6669
				}
6670

    
6671
				// Call log mgmt function
6672
				pfb_log_mgmt();
6673
				break;
6674
			}
6675
		}
6676
	}
6677
}
6678

    
6679

    
6680
// Load/convert Feeds (w/alternative aliasname(s), if user-configured) and return as array
6681
function convert_feeds_json() {
6682
	global $pfb;
6683

    
6684
	$aconfig		= config_get_path('installedpackages/pfblockerngglobal', []);
6685
	$pfb['feeds_list']	= $merge_feeds = $feed_info = array();
6686

    
6687
	$feed_info_raw = json_decode(@file_get_contents("{$pfb['feeds']}"), TRUE);
6688
	if (json_last_error() !== JSON_ERROR_NONE || !is_array($feed_info_raw)) {
6689
		return array('blank' => '');
6690
	}
6691

    
6692
	$feed_count = array();
6693
	foreach ($feed_info_raw as $type => $info) {
6694

    
6695
		if (!is_array($info) || (isset($info[0]) && $info[0] == '*')) {
6696
			continue;
6697
		}
6698

    
6699
		$feed_count[$type] = 0;
6700
		foreach ($info as $aliasname => $data) {
6701
			$l_aliasname = strtolower($aliasname);
6702

    
6703
			$feed_count[$type] = $feed_count[$type] + count($data['feeds']);
6704
			foreach ($data['feeds'] as $key => $feed) {
6705

    
6706
				// Remove discontinued Feeds
6707
				if (isset($feed['status']) && $feed['status'] == 'discontinued') {
6708
					unset($data['feeds'][$key]);
6709
					$feed_count[$type]--;
6710
					continue;
6711
				}
6712

    
6713
				if (isset($feed['alternate'])) {
6714
					foreach ($feed['alternate'] as $alternate) {
6715
						$feed_count[$type]++;
6716
					}
6717
				}
6718
			}
6719

    
6720
			if (!array_key_exists($type, $pfb['feeds_list'])) {
6721
				$pfb['feeds_list'][$type] = array();
6722
			}
6723
			if (!array_key_exists($type, $feed_info)) {
6724
				$feed_info[$type] = array();
6725
			}
6726

    
6727
			// Use alternative Aliasname(s) and/or merge multiple aliasname Feeds together (if user configured)
6728
			if (!empty($aconfig['feed_' . $l_aliasname])) {
6729

    
6730
				$alt_feed = $aconfig['feed_' . $l_aliasname];
6731
				$pfb['feeds_list'][$type][$aliasname] = $alt_feed;	// Global list of all known Feed aliasnames
6732

    
6733
				if (!is_array($merge_feeds[$alt_feed])) {
6734
					$merge_feeds[$alt_feed] = array();
6735
				}
6736
				$merge_feeds[$alt_feed] = array_merge( $merge_feeds[$alt_feed], (array)$data['feeds'] );
6737

    
6738
				if (!isset($feed_info[$type][$alt_feed])) {
6739

    
6740
					// Modify 'info' and 'description' fields to reference user-defined aliasname
6741
					foreach (array('info', 'description') as $atype) {
6742
						$match = strpos($data[$atype], $aliasname);
6743
						if ($match !== FALSE) {
6744
							$data[$atype] = substr_replace($data[$atype], $aconfig['feed_' . $l_aliasname], $match, strlen($aliasname));
6745
						}
6746
					}
6747
					$feed_info[$type][$aconfig['feed_' . $l_aliasname]] = $data;
6748
				}
6749
				$feed_info[$type][$alt_feed]['feeds'] = $merge_feeds[$alt_feed];
6750
			}
6751
			else {
6752
				$pfb['feeds_list'][$type][$aliasname] = $aliasname;
6753
				$feed_info[$type][$aliasname] = $data;
6754
			}
6755
		}
6756
	}
6757
	$feed_info['count'] = $feed_count;
6758
	return $feed_info;
6759
}
6760

    
6761

    
6762
// Define Alerts Tab 'default GET request' (Top row)
6763
function pfb_alerts_default_page() {
6764
	global $pfb;
6765

    
6766
	if (isset($pfb['config_global']) &&
6767
	    isset($pfb['config_global']['pfbpageload'])) {
6768
		switch($pfb['config_global']['pfbpageload']) {
6769
			case 'dnsbl_stat':
6770
				return '?view=dnsbl_stat';
6771
			case 'dnsbl_reply_stat':
6772
				return '?view=dnsbl_reply_stat';
6773
			case 'ip_block_stat':
6774
				return '?view=ip_block_stat';
6775
			case 'ip_permit_stat':
6776
				return '?view=ip_permit_stat';
6777
			case 'ip_match_stat':
6778
				return '?view=ip_match_stat';
6779
			case 'reply':
6780
				return '?view=reply';
6781
			case 'unified':
6782
				return '?view=unified';
6783
		}
6784
	}
6785
	return '';
6786
}
6787

    
6788

    
6789
// Clear IP Alias Packet Counts (widget)
6790
function pfBlockerNG_clearip() {
6791
	global $pfb;
6792

    
6793
	exec("{$pfb['pfctl']} -z 2>&1");
6794

    
6795
	/* TODO: Clear only pfB counters
6796
	$pfb_tables = array();
6797
	exec("{$pfb['pfctl']} -sTables | {$pfb['grep']} 'pfB_' 2>&1", $pfb_tables);
6798
	if (!empty($pfb_tables)) {
6799
		foreach ($pfb_tables as $table) {
6800
			exec("{$pfb['pfctl']} -t {$table} -T zero");
6801
		}
6802
	}
6803
	*/
6804
}
6805

    
6806

    
6807
// Clear DNSBL SQLite database statistics/queries as required
6808
function pfBlockerNG_clearsqlite($mode, $totalqueries = 0) {
6809
	global $g, $pfb;
6810

    
6811
	// Format of todo array: database, error message, SQLite command
6812
	$todo = array();
6813

    
6814
	// Clear SQLite database 'queries' entry and update totalqueries (+queries), if Unbound Resolver PID changed (Reload)
6815
	if ($mode == 'update_totalqueries') {
6816
		$todo[] = array(3, 'Clear Resolver queries', 'UPDATE resolver SET totalqueries = :totalqueries, queries = 0 WHERE row = 0;');
6817
	}
6818
	elseif ($mode == 'clearip') {
6819
		$lastipclear = date('M j H:i:s', time()); 
6820
		$todo[] = array(6, 'Reset IP last clear timestamp', 'UPDATE lastclear SET lastipclear = :lastipclear WHERE row = 0;');
6821
	}
6822
	elseif ($mode == 'cleardnsbl') {
6823
		$lastdnsblclear = date('M j H:i:s', time()); 
6824
		$todo[] = array(1, 'Clear Widget counters', 'UPDATE dnsbl SET counter = 0;');
6825
		$todo[] = array(3, 'Clear Resolver queries', 'UPDATE resolver SET totalqueries = 0, queries = 0;');
6826
		$todo[] = array(6, 'Reset DNSBL last clear timestamp', 'UPDATE lastclear SET lastdnsblclear = :lastdnsblclear WHERE row = 0;');
6827
	}
6828

    
6829
	// Clear Unbound Resolver statistics
6830
	if ($mode != 'clearip' && is_process_running('unbound')) {
6831
		exec("{$pfb['chroot_cmd']} flush_stats 2>&1");
6832
	}
6833

    
6834
	if (!empty($todo)) {
6835
		foreach ($todo as $data) {
6836
			$db_handle = pfb_open_sqlite($data[0], $data[1]);
6837

    
6838
			if ($db_handle) {
6839
				if ($mode == 'update_totalqueries') {
6840
					if (is_numeric($totalqueries)) {
6841
						$stmt = $db_handle->prepare($data[2]);
6842
						if ($stmt) {
6843
							$stmt->bindValue(':totalqueries', $totalqueries, SQLITE3_INTEGER);
6844
							$stmt->execute();
6845
						}
6846
					}
6847
				}
6848
				elseif ($mode == 'clearip') {
6849
					$stmt = $db_handle->prepare($data[2]);
6850
					if ($stmt) {
6851
						$stmt->bindValue(':lastipclear', $lastipclear, SQLITE3_TEXT);
6852
						$stmt->execute();
6853
					}
6854
				}
6855
				elseif ($mode == 'cleardnsbl') {
6856
					$stmt = $db_handle->prepare($data[2]);
6857
					if ($stmt) {
6858
						$stmt->bindValue(':lastdnsblclear', $lastdnsblclear, SQLITE3_TEXT);
6859
						$stmt->execute();
6860
					}
6861
				}
6862
				else {
6863
					$db_handle->exec("BEGIN TRANSACTION;"
6864
							. "{$data[2]}"
6865
							. "END TRANSACTION;");
6866
				}
6867
			}
6868
			pfb_close_sqlite($db_handle);
6869
		}
6870
	}
6871
}
6872

    
6873

    
6874
// Function to read/lock/unlock IP/Domains from Aliastables/DNSBL (Called via Alerts Page)
6875
function pfb_unlock($mode, $type, $remove='', $r_type='', $filename_unlock) {
6876
	global $pfb;
6877

    
6878
	if ($type == 'ip') {
6879
		$filename = $pfb['ip_unlock'];
6880
	} elseif ($type == 'dnsbl') {
6881
		$filename = $pfb['dnsbl_unlock'];
6882
	} elseif ($type == 'dnsbl_data') {
6883
		$filename = "{$pfb['dnsbl_unlock']}.data";
6884
	} else {
6885
		return;
6886
	}
6887

    
6888
	if ($mode == 'read') {
6889
		$filename_unlock = array();
6890
		if (($handle = @fopen("{$filename}", 'r')) !== FALSE) {
6891
			while (($line = @fgetcsv($handle)) !== FALSE) {
6892
				if (!empty($line)) {
6893
					$filename_unlock[$line[0]] = $line[1];
6894
				}
6895
			}
6896
		}
6897
		if ($handle) {
6898
			@fclose($handle);
6899
		}
6900

    
6901
		if (empty($filename_unlock)) {
6902
			unlink_if_exists("{$filename}");
6903
		}
6904
		return $filename_unlock;
6905
	}
6906
	elseif ($mode == 'dnsbl_data') {
6907
		$filename_unlock = '';
6908
		$data = array();
6909
		if (($handle = @fopen("{$filename}", 'r')) !== FALSE) {
6910
			while (($line = @fgets($handle)) !== FALSE) {
6911
				if (strpos($line, $remove) !== FALSE) {
6912
					$data = explode(',', $line);
6913
				} else {
6914
					$filename_unlock .= "{$line}"; 
6915
				}
6916
			}
6917
		}
6918
		if ($handle) {
6919
			@fclose($handle);
6920
		}
6921

    
6922
		if (empty($filename_unlock)) {
6923
			unlink_if_exists("{$filename}");
6924
		} else {
6925
			@file_put_contents("{$filename}", "{$filename_unlock}", LOCK_EX);
6926
		}
6927
		return $data;
6928
	}
6929
	elseif ($mode == 'unlock' && isset($filename_unlock[$remove])) {
6930
		return;
6931
	}
6932
	elseif (empty($remove)) {
6933
		return;
6934
	}
6935

    
6936
	// Add/Remove IP/Domain in unlock file
6937
	if (($pfb_output = @fopen("{$filename}", 'w')) !== FALSE) {
6938
		foreach ($filename_unlock as $key => $line) {
6939

    
6940
			// 'Remove locked IP/Domains' or 'Add existing unlocked IP/Domain' in unlock file
6941
			if ($mode == 'unlock' || ($mode == 'lock' && $key != $remove)) {
6942
				@fwrite($pfb_output, "{$key},{$line}\n");
6943
			}
6944
		}
6945

    
6946
		// Add IP/Domain to unlock file
6947
		if ($mode == 'unlock') {
6948
			$filename_unlock[$remove] = $r_type;
6949
			@fwrite($pfb_output, "{$remove},{$r_type}\n");
6950
		}
6951
	}
6952
	if ($pfb_output) {
6953
		@fclose($pfb_output);
6954
	}
6955

    
6956
	if (empty($filename_unlock)) {
6957
		unlink_if_exists("{$filename}");
6958
	}
6959
}
6960

    
6961

    
6962
// Function to clear pfBlockerNG folder/files
6963
function pfb_clear_contents() {
6964
	global $pfb;
6965

    
6966
	unlink_if_exists("{$pfb['dbdir']}/masterfile");
6967
	unlink_if_exists("{$pfb['dbdir']}/mastercat");
6968
	unlink_if_exists("{$pfb['supptxt']}");
6969
	unlink_if_exists("{$pfb['dnsbl_supptxt']}");
6970
	unlink_if_exists("{$pfb['dnsbl_info']}");
6971
	unlink_if_exists("{$pfb['dnsbl_resolver']}");
6972
	unlink_if_exists("{$pfb['dnsbl_cache']}");
6973
	unlink_if_exists("/var/tmp/unbound_cache_*");
6974
	unlink_if_exists("{$pfb['asn_cache']}");
6975
	unlink_if_exists("{$pfb['ip_cache']}");
6976
	rmdir_recursive("{$pfb['origdir']}");
6977
	rmdir_recursive("{$pfb['matchdir']}");
6978
	rmdir_recursive("{$pfb['permitdir']}");
6979
	rmdir_recursive("{$pfb['denydir']}");
6980
	rmdir_recursive("{$pfb['nativedir']}");
6981
	rmdir_recursive("{$pfb['etdir']}");
6982
	rmdir_recursive("{$pfb['dnsdir']}");
6983
	rmdir_recursive("{$pfb['dnsorigdir']}");
6984
	rmdir_recursive("{$pfb['dnsalias']}");
6985
}
6986

    
6987

    
6988
// Main pfBlockerNG function
6989
function sync_package_pfblockerng($cron='') {
6990
	global $g, $pfb, $pfbarr;
6991
	pfb_global();
6992

    
6993
	$pfb['conf_mod']		= FALSE;	// Flag to check for mods to the config.xml file. ('$pfb_config' array to hold changes)
6994
	$pfb['filter_configure']	= FALSE;	// Flag to call filter_configure once
6995

    
6996
	// Detect boot process or package installation
6997
	if (platform_booting() || $g['pfblockerng_install']) {
6998
		// Create DNSBL NAT, VIP, Lighttpd service and certs if required on reboot.
6999
		if ($pfb['dnsbl'] == 'on') {
7000
			pfb_create_dnsbl('enabled');
7001
		}
7002
		$log = 'Sync terminated during boot process.';
7003
		pfb_logger("\n{$log}\nUPDATE PROCESS ENDED [ NOW ]\n", 1);
7004
		log_error("[pfBlockerNG] {$log}");
7005
		return;
7006
	}
7007

    
7008
	// Reloads existing lists without downloading new lists when defined 'on'
7009
	$pfb['reuse'] = $pfb['config']['pfb_reuse'];
7010
	$pfb['reuse_dnsbl'] = '';
7011

    
7012
	// Define update process (update or reload)
7013
	switch ($cron) {
7014
		case 'noupdates':
7015
			// Force update - Set 'save' variable when 'No updates' found.
7016
			$pfb['save'] = TRUE;
7017
			break;
7018
		case 'cron':
7019
			if ($pfb['reuse'] == 'on') {
7020
				$pfb['reuse_dnsbl'] = 'on';
7021
				unlink_if_exists("{$pfb['dbdir']}/masterfile");
7022
				unlink_if_exists("{$pfb['dbdir']}/mastercat");
7023
			}
7024
			break;
7025
		case 'updatednsbl':
7026
			$pfb['reuse'] = '';
7027
			$pfb['reuse_dnsbl'] = 'on';
7028
			$pfb['updatednsbl'] = TRUE;
7029
			break;
7030
		case 'updateip':
7031
			$pfb['reuse'] = 'on';
7032
			$pfb['reuse_dnsbl'] = '';
7033
			unlink_if_exists("{$pfb['dbdir']}/masterfile");
7034
			unlink_if_exists("{$pfb['dbdir']}/mastercat");
7035
			break;
7036
	}
7037

    
7038
	// Start of pfBlockerNG logging to 'pfblockerng.log'
7039
	if ($pfb['enable'] == 'on' && !$pfb['save']) {
7040
		$log = " UPDATE PROCESS START [ " . pfb_pkg_ver() . " ] [ NOW ]\n";
7041
		pfb_logger("{$log}", 1);
7042
	} else {
7043
		if ($cron != 'noupdates') {
7044
			$log = "\n**Saving configuration [ NOW ]**\n";
7045
			pfb_logger("{$log}", 1);
7046
		}
7047
	}
7048

    
7049
	// Call function for Ramdisk processes.
7050
	pfb_aliastables('conf');
7051

    
7052
	// If table limit not defined, set default to 2M
7053
	if (empty(config_get_path('system/maximumtableentries'))) {
7054
		config_set_path('system/maximumtableentries', '2000000');
7055
		write_config('pfBlockerNG: save max Firewall table entries limit', false);
7056
	}
7057
	$pfb['table_limit'] = config_get_path('system/maximumtableentries');
7058

    
7059
	// Collect local web gui configuration
7060
	$pfb['weblocal'] = config_get_path('system/webgui/protocol', 'http');
7061
	$pfb['port'] = config_get_path('system/webgui/port');
7062
	if (empty($pfb['port'])) {
7063
		if (config_get_path('system/webgui/protocol') == 'http') {
7064
			$pfb['port'] = '80';
7065
		} else {
7066
			$pfb['port'] = '443';
7067
		}
7068
	}
7069
	$pfb['weblocal'] .= "://127.0.0.1:{$pfb['port']}/pfblockerng/pfblockerng.php";
7070

    
7071
	// Define Inbound/Outbound action is not user selected.
7072
	$pfb['deny_action_inbound']  = $pfb['ipconfig']['inbound_deny_action']	?: 'block';
7073
	$pfb['deny_action_outbound'] = $pfb['ipconfig']['outbound_deny_action']	?: 'reject';
7074

    
7075
	$pfb['float']		= $pfb['ipconfig']['enable_float'];				// Enable/Disable floating autorules
7076
	$pfb['dup']		= $pfb['ipconfig']['enable_dup'];				// Enable remove of duplicate IPs utilizing grepcidr
7077
	$pfb['agg']		= $pfb['ipconfig']['enable_agg'];				// Enable aggregation of CIDRs
7078
	$pfb['order']		= $pfb['ipconfig']['pass_order'];				// Order of the autorules
7079
	$pfb['global_log']	= $pfb['ipconfig']['enable_log'];				// Enable Global IP logging
7080
	$pfb['suffix']		= $pfb['ipconfig']['autorule_suffix'];				// Suffix used for autorules
7081
	$pfb['kstates'] 	= $pfb['ipconfig']['killstates'];				// Firewall states removal
7082

    
7083
	$pfb['ip_ph']		= pfb_filter($pfb['ipconfig']['ip_placeholder'], PFB_FILTER_IPV4, 'Placeholder IP Address', '127.1.7.7');	// Placeholder IP Address
7084

    
7085
	// DNSBL settings
7086
	$pfb['dnsbl_ip']	= $pfb['dnsblconfig']['action']		?:  'Disabled';		// Enable/Disable IP blocking from DNSBL lists
7087
	$pfb['dnsbl_rule']	= $pfb['dnsblconfig']['pfb_dnsbl_rule'] ?: 'Disabled';		// Auto create a Floating Pass Rule for other Lan subnets
7088
	$pfb['dnsbl_alexa_cnt']	= $pfb['dnsblconfig']['alexa_count']	?: '1000';		// TOP1M whitelist domain setting
7089
	$pfb['dnsbl_alexa_inc']	= $pfb['dnsblconfig']['alexa_inclusion']?: '';			// TOP1M TLDs inclusions for whitelisting
7090
	$pfb['dnsbl_tld']	= $pfb['dnsblconfig']['pfb_tld'];				// Enable TLD Function
7091
	$pfb['dnsbl_control']	= $pfb['dnsblconfig']['pfb_control']	?: '';			// Python Control integration
7092

    
7093
	// Validate pfB Script variable
7094
	$pfb['dnsbl_alexa_inc'] = pfb_filter($pfb['dnsbl_alexa_inc'], PFB_FILTER_CSV, 'Validate pfB Script variable');
7095

    
7096
	// Reputation config variables
7097
	$pfb['config_rep'] = config_get_path('installedpackages/pfblockerngreputation/config/0', []);
7098

    
7099
	// Validate pfB Script variables
7100
	foreach (array(
7101
			'enable_rep'		=> 'rep',					// Enable/Disable 'Max' Reputation
7102
			'enable_pdup'		=> 'prep',					// Enable/Disable 'pRep' Reputation
7103
			'enable_dedup'		=> 'drep',					// Enable/Disable 'dRep' Reputation
7104
			'et_update'		=> 'etupdate',					// Perform a Force Update on ET categories
7105
			'ccwhite'		=> 'ccwhite',					// Action for whitelist Country category
7106
			'ccblack'		=> 'ccblack',					// Action for blacklist Country category
7107
			'etblock'		=> 'etblock',					// Emerging Threats IQRisk block categories
7108
			'etmatch'		=> 'etmatch',					// Emerging Threats IQRisk match categories
7109
			'p24_max_var'		=> 'max',					// 'Max' variable setting for Reputation
7110
			'p24_dmax_var'		=> 'dmax',					// 'dMax' variable setting for Reputation
7111
			'p24_pmax_var'		=> 'pmax',					// 'pMax' variable setting for Reputation
7112
			'ccexclude'		=> 'ccexclude'					// List of Countries to whitelist
7113
		) as $conf_value => $pfb_value) {
7114

    
7115
		$pfb_variable = $pfb['config_rep'][$conf_value] ?: 'x';
7116
		if (empty(pfb_filter($pfb_variable, PFB_FILTER_CSV, 'Validate pfB Script variables'))) {
7117
			$pfb[$pfb_value] = 'x';
7118
		} else {
7119
			$pfb[$pfb_value] = $pfb_variable;
7120
		}
7121
	}
7122

    
7123
	// Starting variable to skip Reputation functions, if no changes are required
7124
	$pfb['repcheck'] = FALSE;
7125
	// $pfb['save'] is used to determine if user pressed "save" button to avoid collision with CRON.
7126

    
7127
	// For 'script' calls using exec() (used to shorten length of line)
7128
	$elog = ">> {$pfb['log']} 2>&1";
7129

    
7130

    
7131
	#################################
7132
	#	Configure ARRAYS	#
7133
	#################################
7134

    
7135
	$new_aliases		= array();		// An array of aliases (full details)
7136
	$new_aliases_list	= array();		// An array of alias names
7137
	$pfb_alias_lists	= array();		// An array of aliases that have updated lists via CRON/force update. ('Reputation' disabled)
7138
	$pfb_alias_lists_all	= array();		// An array of all active aliases. ('Reputation' enabled)
7139

    
7140
	$ip_types		= array( 'pfblockernglistsv4' => '_v4', 'pfblockernglistsv6' => '_v6');
7141
	$cont_types		= array( 'countries4' => '_v4', 'countries6' => '_v6');
7142

    
7143
	#################################
7144
	#	Tracker IDs		#
7145
	#################################
7146

    
7147
	$pfb['trackerids']	= array();		// An array of pfBlockerNG Firewall rule Tracker IDs.
7148
	$pfb['last_trackerid']	= 1700000009;		// Pre-defined 'starting' Tracker ID (Only used if duplicates found)
7149

    
7150

    
7151
	#########################################
7152
	#	Configure Rule Suffix		#
7153
	#########################################
7154

    
7155
	// Discover if any rules are autorules (If no autorules found, $pfb['autorules'] is FALSE, skip rules re-order )
7156
	// To configure auto rule suffix. pfBlockerNG must be disabled to change suffix and to avoid duplicate rules
7157
	$pfb['autorules'] = FALSE;
7158
	$action = array('Deny_Both', 'Deny_Inbound', 'Deny_Outbound', 'Match_Both', 'Match_Inbound',
7159
			'Match_Outbound', 'Permit_Both', 'Permit_Inbound', 'Permit_Outbound');
7160

    
7161
	foreach ($pfb['continents'] as $continent => $pfb_alias) {
7162
		$cont_key = 'pfblockerng' . strtolower(str_replace(' ', '', $continent));
7163
		if (!empty(config_get_path("installedpackages/{$cont_key}/config"))) {
7164
			$continent_config = config_get_path("installedpackages/{$cont_key}/config/0");
7165
			if ($continent_config['action'] != 'Disabled' && in_array($continent_config['action'], $action)) {
7166
				$pfb['autorules'] = TRUE;
7167
				break;
7168
			}
7169
		}
7170
	}
7171

    
7172
	if (!$pfb['autorules']) {
7173
		foreach ($ip_types as $ip_type => $vtype) {
7174
			foreach(config_get_path("installedpackages/{$ip_type}/config", []) as $list) {
7175
				if ($list['action'] != 'Disabled' && in_array($list['action'], $action)) {
7176
					$pfb['autorules'] = TRUE;
7177
					break;
7178
				}
7179
			}
7180
		}
7181
	}
7182

    
7183
	// Check if DNSBL auto permit rule or DNSBL 'Auto Deny' rules for DNSBL IPs are defined
7184
	if (!empty($pfb['dnsblconfig']['dnsbl_allow_int']) || strpos($pfb['dnsblconfig']['action'], 'Deny_') !== FALSE) {
7185
		$pfb['autorules'] = TRUE;
7186
	}
7187

    
7188
	// Configure auto rule suffix.
7189
	$pfbfound = FALSE;
7190
	$pfb_suffix_match = '';
7191
	foreach (config_get_path('filter/rule', []) as $rule) {
7192

    
7193
		// Query for previous IPv4 pfBlockerNG 'alias type' aliasnames which are not in the new '_v4' suffix format
7194
		foreach (array('source', 'destination') as $rtype) {
7195
			if (substr($rule[$rtype]['address'], 0, 4) == 'pfB_' &&
7196
				substr($rule[$rtype]['address'], -3) != '_v4' &&
7197
				$rule['ipprotocol'] == 'inet') {
7198
				$pfb['autorules'] = TRUE;	// Set flag to re-configure Firewall rules and add missing '_v4' suffix
7199
			}
7200
		}
7201

    
7202
		// Collect any pre-existing suffix
7203
		if (!empty($pfb_suffix_match) && preg_match('/^pfB_\w+(\s.*)/', $rule['descr'], $pfb_suffix_real)) {
7204
			$pfb_suffix_match = $pfb_suffix_real[1];
7205
		}
7206
	}
7207

    
7208
	// Change suffix only if no pfB rules found and autorules are enabled.
7209
	if ($pfb['autorules'] && !$pfbfound) {
7210
		switch ($pfb['suffix']) {
7211
			case 'autorule':
7212
				$pfb['suffix'] = ' auto rule';
7213
				break;
7214
			case 'standard':
7215
				$pfb['suffix'] = '';
7216
				break;
7217
			case 'ar':
7218
				$pfb['suffix'] = ' AR';
7219
				break;
7220
		}
7221
	} else {
7222
		$pfb['suffix'] = '';
7223
		if ($pfb['autorules']) {
7224
			$pfb['suffix'] = $pfb_suffix_match;	// Use existing suffix match
7225
		}
7226
	}
7227

    
7228

    
7229
	#########################################################
7230
	#	Configure INBOUND/OUTBOUND INTERFACES		#
7231
	#########################################################
7232

    
7233
	// Collect pfSense interface order
7234
	$ifaces = pfb_build_if_list(TRUE, FALSE);
7235

    
7236
	foreach (array('inbound', 'outbound') as $type) {
7237
		$pfb["{$type}_interfaces"] = $pfb["{$type}_floating"] = array();
7238

    
7239
		if (!empty($pfb['ipconfig']["{$type}_interface"])) {
7240
			$interfaces = explode(',', $pfb['ipconfig']["{$type}_interface"]);
7241

    
7242
			// CSV string for 'pfB_' match rules
7243
			$pfb["{$type}_floating"]		= ltrim(implode(',', $interfaces), ',');
7244

    
7245
			// Assign base rule/interfaces
7246
			if ($pfb['float'] == 'on') {
7247
				$pfb['base_rule']		= $pfb['base_rule_float'];
7248
				$pfb["{$type}_interfaces"]	= explode(' ', $pfb["{$type}_floating"]);
7249
			} else {
7250
				$pfb['base_rule']		= $pfb['base_rule_reg'];
7251
				$pfb["{$type}_interfaces"]	= $interfaces;
7252
			}
7253
		}
7254
	}
7255

    
7256
	// Determine max Domain count available for DNSBL TLD analysis (Avoid Unbound memory exhaustion)
7257
	$pfs_memory = (round(get_single_sysctl('hw.physmem') / (1024*1024)) ?: 1000);
7258

    
7259
	$pfb['pfs_mem'] = [
7260
		'0' => '100000',
7261
		'1500' => '150000',
7262
		'2000' => '200000',
7263
		'2500' => '250000',
7264
		'3000' => '400000',
7265
		'4000' => '600000',
7266
		'5000' => '1000000',
7267
		'6000' => '1500000',
7268
		'7000' => '2000000',
7269
		'8000' => '2500000',
7270
		'12000' => '3000000',
7271
		'16000' => '4000000',
7272
		'32000' => '8000000'
7273
	];
7274

    
7275
	if ($pfb['dnsbl_py_blacklist']) {
7276
		array_walk($pfb['pfs_mem'], function (&$value) {
7277
			$value = $value * 3;
7278
		});
7279
	}
7280

    
7281
	foreach ($pfb['pfs_mem'] as $pfb_mem => $domain_max) {
7282
		if ($pfs_memory >= $pfb_mem) {
7283
			$pfb['domain_max_cnt'] = $domain_max;
7284
		}
7285
	}
7286

    
7287

    
7288
	#################################################
7289
	#	Clear Removed Lists from Masterfiles	#
7290
	#################################################
7291

    
7292
	$pfb['sync_master']	= TRUE;		// Process to keep IP Masterfiles in sync with valid Lists from config.conf file
7293
	$pfb['remove']		= FALSE;	// Flag to execute pfctl and rules ordering or reload of DNSBL domains
7294
	$pfb['summary']		= FALSE;	// Execute final summary as a list was removed
7295

    
7296
	// Don't execute this function when pfBlockerNG is disabled and 'keep blocklists' is enabled.
7297
	if ($pfb['enable'] == '' && $pfb['keep'] == 'on') {
7298
		$pfb['sync_master'] = FALSE;
7299
	}
7300

    
7301
	if ($pfb['sync_master']) {
7302

    
7303
		// Find all enabled Continents lists
7304
		foreach ($pfb['continents'] as $continent => $pfb_alias) {
7305
			$cont_key = 'pfblockerng' . strtolower(str_replace(' ', '', $continent));
7306
			if (!empty(config_get_path("installedpackages/{$cont_key}/config")) && $pfb['enable'] == 'on') {
7307
				$continent_config = config_get_path("installedpackages/{$cont_key}/config/0");
7308
				if ($continent_config['action'] != 'Disabled') {
7309
					foreach ($cont_types as $c_type => $vtype) {
7310
						if (!empty($continent_config[$c_type])) {
7311

    
7312
							// Force 'Alias Native' setting to any Alias with 'Advanced Inbound/Outbound -Invert src/dst' settings.
7313
							// This will bypass Deduplication and Reputation features.
7314
							if ($continent_config['autoaddrnot_in'] == 'on' ||
7315
							    $continent_config['autoaddrnot_out'] == 'on') {
7316
								$pfb['existing']['native'][]		= "{$pfb_alias}{$vtype}";
7317
							}
7318
							else {
7319
								if (strpos($continent_config['action'], 'Match') !== FALSE) {
7320
									$pfb['existing']['match'][]	= "{$pfb_alias}{$vtype}";
7321
								}
7322
								elseif (strpos($continent_config['action'], 'Permit') !== FALSE) {
7323
									$pfb['existing']['permit'][]	= "{$pfb_alias}{$vtype}";
7324
								}
7325
								elseif (strpos($continent_config['action'], 'Deny') !== FALSE) {
7326
									$pfb['existing']['deny'][]	= "{$pfb_alias}{$vtype}";
7327
								}
7328
								elseif ($continent_config['action'] == 'Alias_Native') {
7329
									$pfb['existing']['native'][]	= "{$pfb_alias}{$vtype}";
7330
								}
7331
							}
7332
						}
7333
					}
7334
				}
7335
			}
7336
		}
7337

    
7338
		// Find all enabled IPv4/IPv6 lists and DNSBL lists
7339
		// Find all enabled IPv4 'Custom List' header names and check if 'Emerging Threats Update' and 'Custom List Update' needs force updating
7340
		$list_types = array(	'pfblockernglistsv4'		=> '_v4',
7341
					'pfblockernglistsv6'		=> '_v6',
7342
					'pfblockerngdnsbl'		=> '_v4'
7343
					);
7344

    
7345
		$pfb_invalid = FALSE;
7346
		foreach ($list_types as $ltype => $vtype) {
7347
			$lists = array();
7348

    
7349
			$type_config = config_get_path("installedpackages/{$ltype}/config");
7350
			if (!empty($type_config) && $pfb['enable'] == 'on') {
7351
				foreach ($type_config as $list) {
7352

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

    
7358
					if (!empty($list['custom'])) {
7359
						$list['row'][] = array( 'header'	=> "{$list['aliasname']}_custom",
7360
									'custom'	=> $list['custom'],
7361
									'state'		=> 'Enabled',
7362
									'url'		=> 'custom'
7363
									);
7364
					}
7365
					$lists[] = $list;
7366
				}
7367
			}
7368

    
7369
			// ADD DNSBL IP
7370
			if ($ltype == 'pfblockernglistsv4' && $pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && $pfb['dnsbl_ip'] != 'Disabled') {
7371
				$list = array();
7372
				$list['action'] = "{$pfb['dnsbl_ip']}";
7373
				$list['row'][]	= array('format'	=> 'auto',
7374
							'state'		=> 'Enabled',
7375
							'url'		=> "{$pfb['dbdir']}/DNSBLIP{$vtype}.txt",
7376
							'header'	=> 'DNSBLIP');
7377
				$lists[] = $list;
7378
			}
7379

    
7380
			if (!empty($lists)) {
7381
				foreach ($lists as $key => $list) {
7382

    
7383
					// Remove any spaces or special characters in existing Aliasnames
7384
					if (preg_match("/\W/", $list['aliasname'])) {
7385
						$pfb_invalid = TRUE;
7386
						$list['aliasname'] = preg_replace("/\W/", '', $list['aliasname']);
7387
						config_set_path("installedpackages/{$ltype}/config/{$key}/aliasname", $list['aliasname']);
7388
					}
7389

    
7390
					if (isset($list['row']) && $list['action'] != 'Disabled') {
7391

    
7392
						// Force 'Alias Native' setting to any Alias with 'Advanced Inbound/Outbound -Invert src/dst' settings.
7393
						// This will bypass Deduplication and Reputation features.
7394
						if ($list['action'] != 'unbound' && ($list['autoaddrnot_in'] == 'on' ||
7395
						    $list['autoaddrnot_out'] == 'on')) {
7396
							$list['action'] = 'Alias_Native';
7397
						}
7398

    
7399
						foreach ($list['row'] as $hkey => $row) {
7400

    
7401
							// Remove any spaces or special characters in existing Header names
7402
							if (preg_match("/\W/", $row['header'])) {
7403
								$pfb_invalid	= TRUE;
7404
								$row['header']	= preg_replace("/\W/", '', $row['header']);
7405
								config_set_path("installedpackages/{$ltype}/config/{$key}/row/{$hkey}/header", $row['header']);
7406
							}
7407

    
7408
							if ($ltype == 'pfblockerngdnsbl') {
7409
								$header = "{$row['header']}";
7410
							} else {
7411
								$header = "{$row['header']}{$vtype}";
7412
							}
7413

    
7414
							// Collect enabled lists
7415
							if (!empty($row['url']) && $row['state'] != 'Disabled') {
7416
								if (strpos($list['action'], 'Match') !== FALSE) {
7417
									$pfb['existing']['match'][]	= "{$header}";
7418
								}
7419
								elseif (strpos($list['action'], 'Permit') !== FALSE) {
7420
									$pfb['existing']['permit'][]	= "{$header}";
7421
								}
7422
								elseif (strpos($list['action'], 'Deny') !== FALSE) {
7423
									$pfb['existing']['deny'][]	= "{$header}";
7424
								}
7425
								elseif ($list['action'] == 'Alias_Native') {
7426
									$pfb['existing']['native'][]	= "{$header}";
7427
								}
7428
								elseif ($list['action'] == 'unbound') {
7429
									$pfb['existing']['dnsbl'][]	= "{$header}";
7430
								}
7431
							}
7432
						}
7433
					}
7434
				}
7435
			}
7436
		}
7437

    
7438
		// Save any existing Alias/Header names that have spaces or special characters
7439
		if ($pfb_invalid) {
7440
			write_config('pfBlockerNG: Remove spaces/special characters in Alias/Header names');
7441
		}
7442

    
7443
		// If 'TLD' enabled and TLD Blacklists are defined, add to enabled DNSBL lists
7444
		if ($pfb['dnsbl_tld']) {
7445
			$tld_blacklist = pfbng_text_area_decode($pfb['dnsblconfig']['tldblacklist'], TRUE, FALSE, TRUE);
7446
			if (!empty($tld_blacklist)) {
7447
				$pfb['existing']['dnsbl'][] = 'DNSBL_TLD';
7448
			}
7449
		}
7450

    
7451
		// Add 'Reputation - ccwhite Action' if found
7452
		if ($pfb['ccwhite'] == 'match' && file_exists("{$pfb['matchdir']}/matchdedup_v4.txt")) {
7453
			$pfb['existing']['match'][] = 'matchdedup_v4';
7454
		}
7455

    
7456
		// Add enabled 'DNSBL Blacklist categories'
7457
		if (isset($pfb['blconfig']) &&
7458
		    $pfb['blconfig']['blacklist_enable'] != 'Disable' &&
7459
		    !empty($pfb['blconfig']['blacklist_selected'])) {
7460

    
7461
			$selected = array_flip(explode(',', $pfb['blconfig']['blacklist_selected'])) ?: array();
7462
			foreach ($pfb['blconfig']['item'] as $item) {
7463

    
7464
				if (isset($selected[$item['xml']]) && !empty($item['selected'])) {
7465
					$categories = explode(',', $item['selected']) ?: array();
7466
					foreach ($categories as $category) {
7467
						if (!empty($category)) {
7468
							$category = str_replace('-', '_', $category);
7469
							$pfb['existing']['dnsbl'][] = "{$item['title']}_{$category}";
7470
						}
7471
					}
7472
				}
7473
			}
7474
		}
7475

    
7476
		// Collect all .txt file names for each list type
7477
		$list_types = array(	'match' => $pfb['matchdir'], 'permit' => $pfb['permitdir'], 'deny' => $pfb['denydir'],
7478
					'native' => $pfb['nativedir'], 'dnsbl' => $pfb['dnsdir']);
7479

    
7480
		// Collect all previouly downloaded filename headers
7481
		foreach ($list_types as $pftype => $pfbfolder) {
7482

    
7483
			$pfb_files = glob("{$pfbfolder}/*.txt");
7484
			foreach ($pfb_files as $pfb_list) {
7485
				$pfb['actual'][$pftype][] = basename($pfb_list, '.txt');
7486
			}
7487

    
7488
			$results = array_diff($pfb['actual'][$pftype], $pfb['existing'][$pftype]);
7489

    
7490
			// Remove any DNSBL Orig files that are not referenced
7491
			if ($pftype == 'dnsbl') {
7492
				$orig_list = glob("{$pfb['dnsorigdir']}/*.orig");
7493
				if (!empty($orig_list) && is_array($orig_list)) {
7494
					$orig_list_final = array();
7495
					foreach ($orig_list as $pfb_orig) {
7496
						$orig_list_final[] = basename($pfb_orig, '.orig');
7497
					}
7498

    
7499
					$results_flip = array_flip($pfb['actual'][$pftype]);
7500
					if (!empty($results_flip) && is_array($results_flip)) {
7501
						foreach ($orig_list_final as $orig) {
7502
							if (!isset($results_flip[$orig])) {
7503
								unlink_if_exists("{$pfb['dnsorigdir']}/{$orig}.*");
7504
							}
7505
						}
7506
					}
7507
				}
7508
			}
7509

    
7510
			if (empty($results)) {
7511
				continue;	// No changes required
7512
			}
7513

    
7514
			$f_result = implode(',', $results);
7515
			if (empty(pfb_filter($f_result, PFB_FILTER_CSV, 'Failed to complete sync of Feeds'))) {
7516
				pfb_logger("\nFailed to complete sync of Feeds!", 1);
7517
				continue;
7518
			}
7519

    
7520
			$log = "\n[ Removing '{$pftype}' \tList(s) : {$f_result} ]";
7521
			pfb_logger("{$log}", 1);
7522

    
7523
			// Process to remove lists from IP Masterfile/DB folder if they are not referenced any longer
7524
			switch ($pftype) {
7525
				case 'deny':
7526
					// Script to Remove un-associated List(s)
7527
					exec("{$pfb['script']} remove x x x {$f_result} {$elog}");
7528
					$pfb['summary'] = $pfb['remove'] = TRUE;
7529
					break;
7530
				case 'match':
7531
				case 'permit':
7532
				case 'native':
7533
					foreach ($results as $pfb_result) {
7534
						if (!empty(pfb_filter($pfb_result, PFB_FILTER_WORD, "Remove un-associated List - {$pftype}"))) {
7535
							unlink_if_exists("{$pfbfolder}/{$pfb_result}.txt");
7536
							unlink_if_exists("{$pfb['origdir']}/{$pfb_result}.*");
7537
						} else {
7538
							pfb_logger("\nFailed to remove Native Feed [$pfb_result}]", 1);
7539
						}
7540
					}
7541
					$pfb['summary'] = $pfb['remove'] = TRUE;
7542
					break;
7543
				case 'dnsbl':
7544
					foreach ($results as $pfb_result) {
7545
						if (!empty(pfb_filter($pfb_result, PFB_FILTER_WORD, "Remove un-associated List - {$pftype}"))) {
7546
							unlink_if_exists("{$pfb['dnsorigdir']}/{$pfb_result}.*");
7547
						} else {
7548
							pfb_logger("\nFailed to remove DNSBL Feed [{$pfb_result}]", 1);
7549
						}
7550
					}
7551

    
7552
					rmdir_recursive("{$pfb['dnsdir']}");
7553
					safe_mkdir("{$pfb['dnsdir']}");
7554

    
7555
					pfb_logger("\n ** DNSBL Changes found, Reloading...\n", 1);
7556
					$pfb['reuse_dnsbl'] = 'on';
7557
					break;
7558
			}
7559

    
7560
			// Allow rebuilding of changed Alias to purge 'SKIP' Lists (when pfBlockerNG is enabled)
7561
			if ($pfb['enable'] == 'on' && $pftype != 'dnsbl') {
7562
				foreach ($ip_types as $ltype => $vtype) {
7563
					foreach ($results as $removed_header) {
7564
						foreach (config_get_path("installedpackages/{$ltype}/config", []) as $list) {
7565
							if (!empty($list['row'])) {
7566
								foreach ($list['row'] as $row) {
7567
									$removed = rtrim($removed_header, ',');
7568
									if ($row['header'] == $removed) {
7569
										$pfb['summary'] = $pfb['remove'] = TRUE;
7570

    
7571
										// Add Alias to update array
7572
										$pfb_alias_lists[]	= "pfB_{$list['aliasname']}{$vtype}";
7573
										$pfb_alias_lists_all[]	= "pfB_{$list['aliasname']}{$vtype}";
7574
									}
7575
								}
7576
							}
7577
						}
7578
					}
7579
				}
7580
			}
7581
		}
7582
	}
7583

    
7584
	#########################################################
7585
	#	Clear Match/Pass/ET/Original Files/Folders	#
7586
	#########################################################
7587

    
7588
	// When pfBlockerNG is Disabled and 'Keep Blocklists' is Disabled.
7589
	if ($pfb['enable'] == '' && $pfb['keep'] == '' && !$pfb['install']) {
7590
		$log = "\n  Removing DB Files/Folders \n";
7591
		pfb_logger("{$log}", 1);
7592
		pfb_clear_contents();
7593
	}
7594

    
7595
	#################################################
7596
	#	Create IP Suppression Txt File		#
7597
	#################################################
7598

    
7599
	if ($pfb['enable'] == 'on' && $pfb['supp'] == 'on') {
7600
		pfb_create_suppression_file();
7601
	}
7602

    
7603
	#########################################
7604
	#	DNSBL - Processes		#
7605
	#########################################
7606

    
7607
	if (!$pfb['save']) {
7608
		$log = "\n===[  DNSBL Process  ]================================================\n";
7609
		pfb_logger("{$log}", 1);
7610
	}
7611

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

    
7618
	$dnsbl_error = FALSE;
7619
	if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && !$pfb['save']) {
7620

    
7621
		// Terminate if DNSBL VIP is empty
7622
		if (empty($pfb['dnsbl_vip']) || empty($pfb['dnsbl_port']) || empty($pfb['dnsbl_port_ssl'])) {
7623
			$log = "\n\n===[  DNSBL Virtual IP and/or Ports are not defined. Exiting  ]======\n";
7624
			pfb_logger("{$log}", 1);
7625
			$dnsbl_error = TRUE;
7626
		}
7627
	}
7628

    
7629
	if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && !$pfb['save'] && !$dnsbl_error) {
7630
		if ((config_get_path('installedpackages/pfblockerngdnsbl/config') != null) ||
7631
		    (config_get_path('installedpackages/pfblockerngblacklist/blacklist_enable') == 'Enable')) {
7632

    
7633
			$dnsbl_missing = FALSE;
7634

    
7635
			// Collect existing DNSBL group statistics
7636
			// SQLite3 Database format [ group name, updated timestamp, total domain count, total blocked count ]
7637

    
7638
			$pfb['dnsbl_info_stats'] = array();
7639
			if (file_exists("{$pfb['dnsbl_info']}")) {
7640
				pfb_logger("\n Loading DNSBL Statistics...", 1);
7641

    
7642
				$db_handle = pfb_open_sqlite(1, 'Reading DNSBL database');
7643
				if ($db_handle) {
7644
					$result = $db_handle->query("SELECT * FROM dnsbl;");
7645
					if ($result) {
7646
						while ($res = $result->fetchArray(SQLITE3_ASSOC)) {
7647
							$pfb['dnsbl_info_stats'][] = $res;
7648
						}
7649
					}
7650
				}
7651
				else {
7652
					pfb_logger(" FAILED", 1);
7653
					unlink_if_exists("{$pfb['dnsbl_info']}");
7654
					$dnsbl_missing = TRUE;
7655
				}
7656

    
7657
				pfb_close_sqlite($db_handle);
7658
				pfb_logger(" completed", 1);
7659
			}
7660
			else {
7661
				$dnsbl_missing = TRUE;
7662
			}
7663

    
7664
			if ($pfb['dnsbl_py_blacklist']) {
7665

    
7666
				// When DNSBL python blocking mode is enabled, zone should not exist, and data should exist
7667
				if (!$pfb['dnsbl_tld']) {
7668
					if (file_exists($pfb['unbound_py_zone'])) {
7669
						$dnsbl_missing = TRUE;
7670
					}
7671
					if (!file_exists($pfb['unbound_py_data'])) {
7672
						$dnsbl_missing = TRUE;
7673
					}
7674
				}
7675

    
7676
				// When DNSBL python blocking mode is enabled, atleast one 'data or zone' file must exist
7677
				else {
7678
					$found = FALSE;
7679
					if (file_exists($pfb['unbound_py_data'])) {
7680
						$found = TRUE;
7681
					}
7682
					if (file_exists($pfb['unbound_py_zone'])) {
7683
						$found = TRUE;
7684
					} 
7685
					if (!$found) {
7686
						$dnsbl_missing = TRUE;
7687
					}
7688
				}
7689
			}
7690

    
7691
			// Check if DNSBL database is missing, when DNSBL python blocking mode is not enabled
7692
			if (!$pfb['dnsbl_py_blacklist'] && !file_exists("{$pfb['dnsbl_file']}.conf")) {
7693
				$dnsbl_missing = TRUE;
7694
			}
7695

    
7696
			if ($dnsbl_missing) {
7697
				$log = "\n Missing DNSBL stats and/or Unbound DNSBL files - Rebuilding\n";
7698
				pfb_logger("{$log}", 1);
7699
				$pfb['reuse_dnsbl'] = 'on';
7700
				touch("{$pfb['dnsbl_file']}.reload");
7701
			}
7702

    
7703
			// SafeSearch
7704
			pfb_logger("\n Loading DNSBL SafeSearch...", 1);
7705
			$safesearch_hosts = array();
7706
			$pfb['safesearch_tlds'] = array();
7707
			$safesearch_types = array(	array( 'safesearch_enable',	'Enable',	'safesearch'),
7708
							array( 'safesearch_youtube',	'Strict',	'youtube_restrict'),
7709
							array( 'safesearch_youtube',	'Moderate',	'youtube_restrictmoderate'),
7710
							array( 'safesearch_doh',	'Enable',	'doh')
7711
							);
7712

    
7713
			$safesearch_update = $pfb_found = FALSE;
7714
			$CNAME_LOG = '';
7715

    
7716
			// Remove deprecated firefox conf file
7717
			if (file_exists("{$pfb['dnsbldir']}/pfb_dnsbl.firefoxdoh.conf")) {
7718
				unlink_if_exists("{$pfb['dnsbldir']}/pfb_dnsbl.firefoxdoh.conf");
7719
				$safesearch_update = TRUE;
7720
			}
7721

    
7722
			foreach ($safesearch_types as $safe_type) {
7723
				if ($pfb[$safe_type[0]] == $safe_type[1]) {
7724
					$pfb_found = TRUE;
7725

    
7726
					// SafeSearch DoH validation (Add/remove comment '#' to DoH entries)
7727
					if ($safe_type[0] == 'safesearch_doh') {
7728
						$doh_file = file($pfb["dnsbl_{$safe_type[2]}"]);
7729
						if (!empty($doh_file)) {
7730
							$doh_file_final = '';
7731
							foreach ($doh_file as $host) {
7732
								if (strpos($host, '#') !== FALSE) {
7733
									$host = ltrim(str_replace('#', '', $host));
7734
								}
7735

    
7736
								$doh_found = FALSE;
7737
								if (!empty($pfb['safesearch_doh_list'])) {
7738
									foreach ($pfb['safesearch_doh_list'] as $doh) {
7739
										if (strpos($host, $doh) !== FALSE) {
7740
											$doh_found = TRUE;
7741
											break;
7742
										}
7743
									}
7744
								}
7745
								if (!$doh_found) {
7746
									$doh_file_final .= "# {$host}";
7747
								} else {
7748
									$doh_file_final .= "{$host}";
7749
								}
7750
							}
7751
							if (!empty($doh_file_final)) {
7752
								@file_put_contents($pfb["dnsbl_{$safe_type[2]}"], $doh_file_final, LOCK_EX);
7753
							}
7754
						}
7755
					}
7756

    
7757
					if ($pfb['dnsbl_py_blacklist']) {
7758

    
7759
						// Python Mode determine if user manually entered these SafeSearch CNAMES to avoid duplicate zone errors
7760
						if ($safe_type[0] == 'safesearch_enable') {
7761
							$DDG = $PIX = $ss_cname = FALSE;
7762
							if (file_exists('/var/unbound/unbound.conf')) {
7763
								$pfb_py_conf_ex = @file_get_contents('/var/unbound/unbound.conf');
7764
								if (strstr($pfb_py_conf_ex, 'duckduckgo.com')) {
7765
									$CNAME_LOG = "\n *** Found manual Resolver entry for duckduckgo.com. This manual entry should be removed in future!\n";
7766
									$DDG = TRUE;
7767
								}
7768
								if (strstr($pfb_py_conf_ex, 'pixabay.com')) {
7769
									$CNAME_LOG .= "\n *** Found manual Resolver entry for pixabay.com. This manual entry should be removed in future!\n";
7770
									$PIX = TRUE;
7771
								}
7772
							}
7773
							$ss_ex = @file_get_contents("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf");
7774
						}
7775
						unlink_if_exists("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf"); // Remove Unbound mode file
7776

    
7777
						$safesearch_file = file($pfb["dnsbl_{$safe_type[2]}"]);
7778
						if (!empty($safesearch_file)) {
7779
							foreach ($safesearch_file as $host) {
7780

    
7781
								if (substr($host, 0, 1) == '#') {
7782
									continue;
7783
								}
7784

    
7785
								$line = str_replace('"', '', strstr($host, '"', False));
7786
								$host_ip = trim(str_replace('A ', '', strstr($line, 'A ', FALSE)));
7787
								$domain = strstr($line, ' ', TRUE);
7788
								if (substr($domain, 0, 4) == 'www.') {
7789
									$domain = substr($domain, 4);
7790
								}
7791

    
7792
								if (strpos($line, ' A ') !== FALSE) {
7793
									$safesearch_hosts[$domain]['A'] = $host_ip;
7794
								} elseif (strpos($line, ' AAAA ') !== FALSE) {
7795
									$safesearch_hosts[$domain]['AAAA'] = $host_ip;
7796

    
7797
								# CNAME SafeSearch is not compatible in Python Mode, use Unbound mode for now
7798
								} elseif (strpos($line, ' CNAME ') !== FALSE) {
7799
									$ss_cname = TRUE;
7800
									if (!$DDG && strpos($line, 'duckduckgo.com') !== FALSE) {
7801
										@file_put_contents("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf", "{$host}", FILE_APPEND);
7802
									}
7803
									if (!$PIX && strpos($line, 'pixabay.com') !== FALSE) {
7804
										@file_put_contents("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf", "{$host}", FILE_APPEND);
7805
									}
7806
									# $cname = trim(str_replace('CNAME ', '', strstr($line, 'CNAME', FALSE)));
7807
									# $safesearch_hosts[$domain]['A'] = 'cname';
7808
									# $safesearch_hosts[$domain]['AAAA'] = $cname;
7809
								} elseif (strpos($line, 'always_nxdomain') !== FALSE) {
7810
									$safesearch_hosts[$domain]['nxdomain'] = '';
7811
								}
7812
							}
7813
						}
7814

    
7815
						if ($safe_type[2] == 'safesearch' && ($ss_cname && $ss_ex !== @file_get_contents("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf")) ||
7816
						    (($DDG || $PIX) && !$ss_cname)) {
7817
							$safesearch_update = TRUE;
7818
						}
7819
					}
7820
					else {
7821
						unlink_if_exists("/var/unbound/pfb_py_ss.*"); // Remove Python mode file
7822
						$ss_cname = FALSE;
7823

    
7824
						// Determine if user manually entered these SafeSearch CNAMES to avoid duplicate zone errors
7825
						if ($safe_type[0] == 'safesearch_enable') {
7826

    
7827
							// Step one - See if DDG and PIX are manually configured
7828
							$DDG = $PIX = FALSE;
7829
							if (file_exists('/var/unbound/unbound.conf')) {
7830
								$pfb_py_conf_ex = @file_get_contents('/var/unbound/unbound.conf');
7831
								if (strstr($pfb_py_conf_ex, 'duckduckgo.com')) {
7832
									$CNAME_LOG = "\n *** Found manual Resolver entry for duckduckgo.com. This manual entry should be removed in future!\n";
7833
									$DDG = TRUE;
7834
								}
7835
								if (strstr($pfb_py_conf_ex, 'pixabay.com')) {
7836
									$CNAME_LOG .= "\n *** Found manual Resolver entry for pixabay.com. This manual entry should be removed in future!\n";
7837
									$PIX = TRUE;
7838
								}
7839
							}
7840

    
7841
							// Step two - If they are configured rebuild SafeSearch file
7842
							$safesearch_file = file($pfb["dnsbl_{$safe_type[2]}"]);
7843
							$ss_lines = '';
7844
							if (!empty($safesearch_file)) {
7845
								foreach ($safesearch_file as $host) {
7846
									if ($DDG && strpos($host, 'duckduckgo.com') !== FALSE) {
7847
										$ss_lines .= "# {$host}";
7848
										$ss_cname = TRUE;
7849
									}
7850
									elseif ($PIX && strpos($host, 'pixabay.com') !== FALSE) {
7851
										$ss_lines .= "# {$host}";
7852
										$ss_cname = TRUE;
7853
									}
7854
									else {
7855
										$ss_lines .= "{$host}";
7856
									}
7857
								}
7858
							}
7859

    
7860
							// Step three - Compare previous SafeSearch file to new and update if changes are found 
7861
							if ($ss_cname) {
7862
								if (file_exists("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf")) {
7863
									$ss_lines_ex = @file_get_contents("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf");
7864

    
7865
									if ($ss_lines !== $ss_lines_ex) {
7866
										@file_put_contents("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf", $ss_lines, LOCK_EX);
7867
										$safesearch_update = TRUE;
7868
									}
7869
								}
7870
								else {
7871
									@file_put_contents("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf", $ss_lines, LOCK_EX);
7872
									$safesearch_update = TRUE;
7873
								}
7874
							}
7875
						}
7876

    
7877
						// Copy file if not exists or has been updated
7878
						if (!file_exists("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf") ||
7879
							@md5_file($pfb["dnsbl_{$safe_type[2]}"]) !==
7880
							@md5_file("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf")) {
7881

    
7882
							// Temp Workaround for Duplicate CNAME Zone issue:
7883
							if (!$ss_cname) {
7884
								@copy($pfb["dnsbl_{$safe_type[2]}"], "{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf");
7885
								$safesearch_update = TRUE;
7886
							}
7887
						}
7888

    
7889
						// Collect SafeSearch domains for wildcard whitelisting
7890
						exec("cut -d '\"' -f2 {$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf | cut -d ' ' -f1", $safesearch_hosts);
7891

    
7892
						// Collect all Domains/TLDs for 'TLD Blacklist' validation (Unbound mode only. Cannot have duplicate zones)
7893
						if (!$pfb['dnsbl_py_blacklist'] && !empty($safesearch_hosts)) {
7894
							foreach ($safesearch_hosts as $host) {
7895
								$safesearch_tlds[$host] = '';
7896
								for ($i= substr_count($host, '.'); $i > 0; $i--) {
7897
									$host = ltrim(strstr($host, '.', FALSE), '.');
7898
									$pfb['safesearch_tlds'][$host] = '';
7899
								}
7900
							}
7901
						}
7902
					}
7903
				}
7904
				elseif (file_exists("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf")) {
7905
					unlink("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf");
7906
					$safesearch_update = TRUE;
7907
				}
7908
			}
7909

    
7910
			// Python mode create a CSV list of SafeSearch hosts
7911
			if ($pfb['dnsbl_py_blacklist'] && !empty($safesearch_hosts)) {
7912
				foreach ($safesearch_hosts as $host => $data) {
7913
					if (isset($data['nxdomain'])) {
7914
						$line = "{$host},nxdomain,nxdomain\n";
7915
					} else {
7916
						$line = "{$host}," . ($data['A'] ?: '') . ',' . ($data['AAAA'] ?: '') . "\n"
7917
							. "www.{$host}," . ($data['A'] ?: '') . ',' . ($data['AAAA'] ?: '') . "\n";
7918
					}
7919
					@file_put_contents('/var/unbound/pfb_py_ss.raw', $line, FILE_APPEND);
7920
				}
7921

    
7922
				// Copy file if not exists or has been updated
7923
				if (!file_exists("{$pfb['unbound_py_ss']}") ||
7924
				    @md5_file("{$pfb['unbound_py_ss']}") !==
7925
				    @md5_file('/var/unbound/pfb_py_ss.raw')) {
7926
					@rename('/var/unbound/pfb_py_ss.raw', "{$pfb['unbound_py_ss']}");
7927
					$safesearch_update = TRUE;
7928
					touch("{$pfb['dnsbl_file']}.reload");
7929
				}
7930
				unlink_if_exists('/var/unbound/pfb_py_ss.raw');
7931
			}
7932
			else {
7933
				unlink_if_exists("/var/unbound/pfb_py_ss.*");
7934
			}
7935

    
7936
			// Wildcard whitelist SafeSearch domains (Unbound mode only)
7937
			$pfb_whitelist = '';
7938
			if (!$pfb['dnsbl_py_blacklist'] && !empty($safesearch_hosts)) {
7939
				$safesearch_hosts = array_unique($safesearch_hosts);
7940
				foreach ($safesearch_hosts as $line) {
7941
					if (!empty($line)) {
7942

    
7943
						// Remove 'www.' prefix
7944
						if (substr($line, 0, 4) == 'www.') {
7945
							$line = substr($line, 4);
7946
						}
7947

    
7948
						if ($pfb['dnsbl_py_blacklist']) {
7949
							$pfb_whitelist .= ".{$line},,\n,{$line},,\n";
7950
						} else {
7951
							$pfb_whitelist .= ".{$line} 60\n\"{$line} 60\n";
7952
						}
7953
					}
7954
				}
7955
			}
7956

    
7957
			$s_log = ' disabled';
7958
			if ($pfb_found) {
7959
				$s_log = ' enabled';
7960
			}
7961
			pfb_logger("{$s_log}{$CNAME_LOG}", 1);
7962

    
7963
			// Collect Whitelist, create string, and save to file (for grep -vF -f cmd)
7964
			pfb_logger("\n Loading DNSBL Whitelist...", 1);
7965
			$pfb_white = pfbng_text_area_decode($pfb['dnsblconfig']['suppression'], TRUE, FALSE, TRUE);
7966
			if (!empty($pfb_white)) {
7967
				foreach ($pfb_white as $line) {
7968
					if (!empty($line)) {
7969
						$wildcard = FALSE;
7970
						if (substr($line, 0, 1) == '.') {
7971
							$line = ltrim($line, '.');
7972
							$wildcard = TRUE;
7973
						}
7974

    
7975
						// Remove 'www.' prefix
7976
						if (substr($line, 0, 4) == 'www.') {
7977
							$line = substr($line, 4);
7978
						}
7979

    
7980
						if ($wildcard) {
7981
							if ($pfb['dnsbl_py_blacklist']) {
7982
								$pfb_whitelist .= ".{$line},,\n,{$line},,\n";
7983
							} else {
7984
								$pfb_whitelist .= ".{$line} 60\n\"{$line} 60\n";
7985
							}
7986
						} else {
7987
							if ($pfb['dnsbl_py_blacklist']) {
7988
								$pfb_whitelist .= ",{$line},,\n,www.{$line},,\n";
7989
							} else {
7990
								$pfb_whitelist .= "\"{$line} 60\n\"www.{$line} 60\n";
7991
							}
7992
						}
7993
					}
7994
				}
7995
			}
7996

    
7997
			//  Added due to SWC Feed
7998
			if ($pfb['dnsbl_py_blacklist']) {
7999
				$pfb_whitelist .= ",localhost.localdomain,,\n";
8000
			} else {
8001
				$pfb_whitelist .= "\"localhost.localdomain 60\n";
8002
			}
8003

    
8004
			@file_put_contents("{$pfb['dnsbl_supptxt']}", $pfb_whitelist, LOCK_EX);
8005
			pfb_logger(" completed", 1);
8006

    
8007
			if (!$pfb['dnsbl_py_blacklist'] && $safesearch_update) {
8008
				$log = "\n DNSBL - SafeSearch changes found - Rebuilding!\n";
8009
				pfb_logger("{$log}", 1);
8010
				$pfb['reuse_dnsbl'] = 'on';
8011
				touch("{$pfb['dnsbl_file']}.reload");
8012
			}
8013

    
8014
			// Call TOP1M whitelist process
8015
			if ($pfb['dnsbl_alexa'] == 'on') {
8016
				pfb_logger("\n Loading TOP1M Whitelist...", 1);
8017

    
8018
				// Check if TOP1M database exists
8019
				if (!file_exists("{$pfb['dbdir']}/top-1m.csv")) {
8020
					// Check if TOP1M download already in progress
8021
					exec('/bin/ps -wax', $result_cron);
8022
					if (!preg_grep("/pfblockerng[.]php\s+al/", $result_cron)) {
8023
						$log = "\n TOP1M Database downloading ( approx 21MB ) ... Please wait ...\n";
8024
						pfb_logger("{$log}", 1);
8025
						exec('/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php al');
8026
					}
8027
					else {
8028
						$log = "\n TOP1M download already in process...\n";
8029
						pfb_logger("{$log}", 1);
8030
					}
8031
				}
8032

    
8033
				// Process TOP1M database
8034
				if (!file_exists("{$pfb['dbdir']}/pfbalexawhitelist.txt") ||
8035
				    file_exists("{$pfb['dbdir']}/top-1m.update")) {
8036
					pfblockerng_top1m();
8037
					$pfb['reuse_dnsbl'] = 'on';
8038
					touch("{$pfb['dnsbl_file']}.reload");
8039
					pfb_logger("\n DNSBL - TOP1M changes found - Rebuilding!\n", 1);
8040
				}
8041
				pfb_logger(" completed", 1);
8042
			}
8043
			pfb_logger("\n", 1);
8044

    
8045
			// List of invalid Domains to skip parsed failed logging function
8046
			$dnsbl_skip = array_flip (array('broadcasthost',
8047
							'local',
8048
							'localhost',
8049
							'<pre>',
8050
							'Vault',
8051
							'Site',
8052
							'list',
8053
							'::1',
8054
							':',
8055
							'ip6-localhost',
8056
							'ip6-localnet',
8057
							'ip6-mcastprefix',
8058
							'ip6-allnodes',
8059
							'ip6-allrouters',
8060
							'ip6-allhosts'
8061
							));
8062

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

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

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

    
8074
				$bl_count = 0;
8075
				$bl_validate = FALSE;
8076

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

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

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

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

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

    
8095
						foreach ($categories as $category) {
8096

    
8097
							// Replace dash to underscore in Header name
8098
							$category = str_replace('-', '_', $category);
8099

    
8100
							if (!empty($category)) {
8101
								$list['row'][] = array(	'format'	=> 'auto',
8102
											'state'		=> 'Enabled',
8103
											'url'		=> "{$pfb['dbdir']}/{$type}/{$type}_{$category}",
8104
											'header'	=> "{$item['title']}_{$category}"
8105
											);
8106

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

    
8115
						if (isset($list['row'])) {
8116
							$lists[] = $list;
8117
						}
8118

    
8119
						// Check if Blacklist database has not been previously downloaded
8120
						if (!is_dir("{$pfb['dbdir']}/{$type}") ||
8121
						    is_dir("{$pfb['dbdir']}/{$type}") && (count(scandir("{$pfb['dbdir']}/{$type}")) <= 2)) {
8122
							if (!is_array($bl_validate)) {
8123
								$bl_validate = array();
8124
							}
8125
							$bl_validate[$type] = $item['size'];
8126
						}
8127
					}
8128
				}
8129

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

    
8133
					// Create commandline arguments for download script
8134
					$bl_string = $bl_sources = '';
8135
					foreach ($bl_validate as $type => $size) {
8136
						$bl_string	.= ",{$type}";
8137
						$bl_sources	.= " {$type} (~{$size}MB) |";
8138
					}
8139

    
8140
					if (!empty(pfb_filter($bl_string, PFB_FILTER_CSV, 'Blacklist database commandline arguments'))) {
8141
						$bl_string	= ltrim($bl_string, ',');
8142
						$bl_sources	= rtrim($bl_sources, ' |');
8143

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

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

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

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

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

    
8168
							// Remove Blacklist Category updates until database download is completed
8169
							while ($bl_count != 0) {
8170
								array_pop($lists);
8171
								$bl_count--;
8172
							}
8173
						}
8174
					}
8175
					else {
8176
						pfb_logger("\n Invalid Blacklist arguments [{$bl_string}]", 1);
8177
					}
8178
				}
8179
				else {
8180
					pfb_logger(" Blacklist database(s) ... exists.\n", 1);
8181
				}
8182
			}
8183

    
8184
			foreach (config_get_path('installedpackages/pfblockerngdnsbl/config', []) as $list) {
8185
				// If only the 'customlist' is defined. Remove the 'List row' data.
8186
				if (isset($list['row']) && empty($list['row'][0]['url'])) {
8187
					unset($list['row']);
8188
				}
8189

    
8190
				if (!empty($list['custom'])) {
8191
						$list['row'][] = array( 'header'	=> "{$list['aliasname']}_custom",
8192
												'custom'	=> $list['custom'],
8193
												'state'		=> 'Enabled',
8194
												'url'		=> 'custom'
8195
						);
8196
				}
8197

    
8198
				// Move DNSBL Group to primary position before the Blacklist settings
8199
				if ($list['order'] == 'primary') {
8200
					$list_primary = array();
8201
					$list_primary[] = $list;
8202
					$lists = array_merge($list_primary, $lists);
8203
				} else {
8204
					$lists[] = $list;
8205
				}
8206
			}
8207

    
8208
			foreach ($lists as $list) {
8209

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

    
8216
				if ($list['action'] != 'Disabled' && isset($list['row'])) {
8217
					$alias				= "DNSBL_{$list['aliasname']}";
8218
					if (empty(pfb_filter($alias, PFB_FILTER_WORD, 'DNSBL - Processes'))) {
8219
						pfb_logger("\n Invalid Aliasname:{$list['aliasname']}, *skipping*", 1);
8220
						continue;
8221
					}
8222
					$pfb['alias_dnsbl_all'][]	= "{$alias}";
8223

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

    
8227
							// Empty header field validation check
8228
							$header		= pfb_filter($row['header'], PFB_FILTER_WORD, 'DNSBL - Processes');
8229
							if (empty($header)) {
8230
								$log = "\n[ {$row['url']} ]{$logtab} Header Field cannot be empty. *Skipping* \n";
8231
								pfb_logger("{$log}", 2);
8232
								continue;
8233
							}
8234
							$header_esc	= escapeshellarg($header);
8235

    
8236
							$liteparser	= FALSE;	// Minimal DNSBL Parser
8237
							$rev_format	= FALSE;	// Host style format is reversed
8238
							$domain_data_ip	= array();	// Array of IPs found in feed
8239
							$domain_data	= '';		// List of Domains found in feed
8240

    
8241
							// If row is a custom_list, set flag.
8242
							if (isset($row['custom'])) {
8243
								$custom = TRUE;
8244
							} else {
8245
								$custom = FALSE;
8246
							}
8247

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

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

    
8258
							// If Null Blocking mode is selected, use '0.0.0.0|::0', otherwise utilize DNSBL WebServer/DNSBL VIP
8259
							if ($list['logging'] == 'disabled') {
8260
								$sinkhole_type4 = '0.0.0.0';
8261
								$sinkhole_type6 = '::0';
8262
								$logging_type	= '2';	// Null Blocking no logging
8263
							}
8264
							elseif ($list['logging'] == 'disabled_log') {
8265
								$logging_type	= '0';	// Null Blocking logging
8266
							} else {
8267
								$sinkhole_type4 = "{$pfb['dnsbl_vip']}";
8268
								$sinkhole_type6 = "::{$pfb['dnsbl_vip']}";
8269
								$logging_type	= '1';	// DNSBL VIP logging
8270
							}
8271

    
8272
							// Determine 'list' details (return array $pfbarr)
8273
							pfb_determine_list_detail($list['action'], $header, 'pfblockerngdnsblsettings', '0');
8274
							$pfbadv		= $pfbarr['adv'];
8275
							$pfbfolder	= $pfbarr['folder'];
8276
							$pfborig	= $pfbarr['orig'];
8277
							$pfbreuse	= $pfbarr['reuse'];
8278
							$logtab		= $pfbarr['logtab'];
8279

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

    
8285

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

    
8293
								// Collect existing list stats
8294
								$lists_dnsbl_all[]	= "{$row['header']}.txt";
8295
								$lists_dnsbl_current[]	= "{$row['header']}";
8296
								$file_esc		= escapeshellarg("{$pfbfolder}/{$header}.txt");
8297
								$list_cnt		= exec("{$pfb['grep']} -c ^ {$file_esc}");
8298
								$alias_cnt		= $alias_cnt + $list_cnt;
8299
							}
8300
							else {
8301
								if ($pfbreuse == 'on' && file_exists("{$pfborig}/{$header}.orig")) {
8302
									$log = "\n[ {$header} ]{$logtab} Reload [ NOW ]";
8303
								} else {
8304
									$log = "\n[ {$header} ]{$logtab} Downloading update [ NOW ]";
8305
								}
8306
								pfb_logger("{$log}", 1);
8307
								$file_dwn = "{$pfborig}/{$header}";
8308

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

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

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

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

    
8330
											// Utilize previously download file (If 'fail' marker exists)
8331
											if (file_exists("{$pfbfolder}/{$header}.fail") &&
8332
											    file_exists("{$file_dwn}.orig")) {
8333
												pfb_logger("\n  Restoring previously downloaded file\n ", 2);
8334
											} else {
8335
												continue;
8336
											}
8337
										}
8338
										else {
8339
											// Clear any previous download fail marker
8340
											unlink_if_exists("{$pfbfolder}/{$header}.fail");
8341
										}
8342
									}
8343
								}
8344
								else {
8345
									// Collect custom list data.
8346
									$custom_list = pfbng_text_area_decode($row['custom'], FALSE, TRUE, TRUE);
8347
									@file_put_contents("{$file_dwn}.orig", $custom_list, LOCK_EX);
8348
									unset($custom_list);
8349
									$liteparser = TRUE;
8350
								}
8351

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

    
8356
								$run_once = $csv_parser = FALSE;
8357
								$csv_type = '';
8358
								$ipcount = $ip_cnt = 0;
8359

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

    
8365
											// Collect original line
8366
											$oline = $line;
8367

    
8368
											// Validate EasyList/AdBlock/uBlock/ADGuard Feeds
8369
											if (!$validate_header) {
8370
												if (strpos($line, '[Adblock Plus ') !== FALSE ||
8371
												    strpos($line, '[Adblock Plus]') !== FALSE ||
8372
												    strpos($line, '[uBlock Origin') !== FALSE ||
8373
												    strpos($line, '! Title: AdGuard') !== FALSE) {
8374
													$easylist = $validate_header = TRUE;
8375
													continue;
8376
												}
8377
												elseif (substr($line, 0, 1) === '!') {
8378
													continue;
8379
												}
8380
												else {
8381
													$validate_header = TRUE;
8382
												}
8383
											}
8384

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

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

    
8393
											if ($easylist) {
8394
												if (substr($line, 0, 2) !== '||' ||
8395
												    substr($line, -1) !== '^' ||
8396
												    strpos($line, '$') !== FALSE ||
8397
												    strpos($line, '*') !== FALSE ||
8398
												    strpos($line, '/') !== FALSE) {
8399
													continue;
8400
												}
8401

    
8402
												$lite = TRUE;
8403
												$line = str_replace($e_replace, '', $line);
8404
											}
8405
											else {
8406
												// If 'tab' character found, replace with whitespace
8407
												if (strpos($line, "\x09") !== FALSE) {
8408
													$line = str_replace("\x09", ' ', $line);
8409
												}
8410

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

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

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

    
8428
													if ($line == '#family,type,url,status,first_seen,'
8429
															. 'first_active,last_active,last_update') {
8430
														$csv_type	= 'h3x';
8431
														$csv_parser	= TRUE;
8432
													}
8433
													continue;
8434
												}
8435

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

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

    
8446
												// Convert CSV line into array
8447
												if ($csv_parser) {
8448
													$csvline = str_getcsv($line, ',', '', '"');
8449
												}
8450
												elseif (!$run_once) {
8451
													if (substr_count($line, ',') >= 2) {
8452
														$csvline = str_getcsv($line, ',', '', '"');
8453
														$csv_parser = TRUE;
8454
													}
8455
													$run_once = TRUE;
8456
												}
8457
											}
8458

    
8459
											// Remove blank lines
8460
											if (empty($line)) {
8461
												continue;
8462
											}
8463

    
8464
											// CSV parser
8465
											if ($csv_parser) {
8466

    
8467
												$csv_found = FALSE;
8468
												$csv_count = count($csvline);
8469

    
8470
												switch ($csv_type) {
8471
													case 'pt':
8472
														if ($csv_count == 8) {
8473
															if (strpos($csvline[1], ' ') !== FALSE) {
8474
																$line = str_replace(' ', '', $csvline[1]);
8475
															} else {
8476
																$line = $csvline[1];
8477
															}
8478
															$csv_found = TRUE;
8479
														}
8480
														break;
8481
													case 'bbc':
8482
														if ($csv_count == 4) {
8483
															$line		= $csvline[0];
8484
															$csv_found	= TRUE;
8485
														}
8486
														break;
8487
													case 'h3x':
8488
														if ($csv_count == 8) {
8489
															$line		= $csvline[2];
8490
															if (strpos($line, 'btc://') !== FALSE) {
8491
																continue 2;
8492
															}
8493
															$csv_found	= TRUE;
8494
														}
8495
														break;
8496
													case 'otx':
8497
														if ($csv_count == 3) {
8498
															if (isset($alienvault_types[$csvline[0]])) {
8499
																$line		= $csvline[1];
8500
																$csv_found	= TRUE;
8501
															} else {
8502
																continue 2;
8503
															}
8504
														}
8505
														break;
8506
													case 'pon':
8507
														if ($csv_count == 9) {
8508
															$line		= $csvline[2];
8509
															$csv_found	= TRUE;
8510

    
8511
															// Collect additional IP csv entry
8512
															if (is_ipaddrv4($csvline[0]) &&
8513
															    $pfb['dnsbl_ip'] != 'Disabled') {
8514
																$parsed = sanitize_ipaddr($line, $custom, 'Disabled');
8515
																if (validate_ipv4($parsed)) {
8516
																	$domain_data_ip[] = $parsed;
8517
																	$pfb['updateip'] = TRUE;
8518
																	$ipcount++;
8519
																}
8520
															}
8521
														}
8522
														break;
8523
													case 'et':
8524
														if ($csv_count == 3) {
8525
															$line		= $csvline[0];
8526
															$csv_found	= TRUE;
8527
														}
8528
														break;
8529
													default:
8530

    
8531
														// Parse Phishtank Feed
8532
														if (strpos($line, 'phish_id,url,'
8533
																. 'phish_detail_url') !== FALSE) {
8534
															$csv_type = 'pt';
8535
															continue 2;
8536
														}
8537

    
8538
														// Parse Bambenek Consulting Feed
8539
														elseif (strpos($csvline[3], 'osint.'
8540
															. 'bambenekconsulting.com') !== FALSE) {
8541
															$csv_type	= 'bbc';
8542
															$line		= $csvline[0];
8543
															$csv_found	= TRUE;
8544
															$liteparser	= TRUE;
8545
														}
8546

    
8547
														// Parse Alienvault OTX pulse Feed
8548
														elseif ($line == 'Indicator type,Indicator,'
8549
																. 'Description') {
8550
															$csv_type	= 'otx';
8551
															$liteparser	= FALSE;
8552
															continue 2;
8553
														}
8554

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

    
8562
														// Parse Proofpoint/ET IQRisk IPRep Feed
8563
														elseif ($line == 'domain, category, score') {
8564
															$csv_type	= 'et';
8565
															$liteparser	= TRUE;
8566
															continue 2;
8567
														}
8568

    
8569
														// Reset variables for CSV determination
8570
														else {
8571
															$csv_parser = $run_once = FALSE;
8572
														}
8573
														break;
8574
												}
8575

    
8576
												// Record Failed CSV Parse event
8577
												if (!$csv_found || empty($csv_type)) {
8578
													pfb_parsed_fail($header, '', $oline, $pfb['dnsbl_parse_err']);
8579
													continue;
8580
												}
8581
											}
8582
											$line = trim($line);
8583

    
8584
											if (!$easylist) {
8585

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

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

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

    
8599
													$lite = FALSE;
8600
													if (strpos($line, '.') !== FALSE &&
8601
													    ctype_alnum(str_replace('.', '', $line))) {
8602
														$lite = TRUE;
8603
													}
8604
												}
8605
												else {
8606
													$lite = TRUE;
8607
												}
8608
											}
8609

    
8610
											if (!$lite) {
8611

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

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

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

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

    
8632
												// If special characters found, parse line for host
8633
												if (strpos($line, ';') !== FALSE) {
8634
													$host = parse_url($line);
8635
													if (isset($host['host'])) {
8636
														$line = $host['host'];
8637
													} else {
8638
														$line = strstr($line, ';', TRUE);
8639
													}
8640
												}
8641

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

    
8649
											// Collect any IPs found in domain feed
8650
											if (is_ipaddrv4($line)) {
8651
												if ($pfb['dnsbl_ip'] != 'Disabled') {
8652
													$parsed = sanitize_ipaddr($line, $custom, 'Disabled');
8653
													if (validate_ipv4($parsed)) {
8654
														$domain_data_ip[] = $parsed;
8655
														$pfb['updateip'] = TRUE;
8656
														$ipcount++;
8657
													}
8658
												}
8659
												continue;
8660
											}
8661

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

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

    
8669
												$log = "\n  IDN converted: [ {$line} ]\t";
8670
												$line = idn_to_ascii($line);
8671
												if (!empty($line)) {
8672
													pfb_logger("{$log} [ {$line} ]", 1);
8673
												}
8674
												else {
8675
													// Record failed parsed line
8676
													pfb_parsed_fail($header, '', $oline, $pfb['dnsbl_parse_err']);
8677
													continue;
8678
												}
8679
											}
8680

    
8681
											// Remove leading/trailing dots
8682
											$line = trim(trim($line), '.');
8683

    
8684
											// Domain Validation
8685
											if (empty(pfb_filter($line, PFB_FILTER_DOMAIN, 'DNSBL_Download'))) {
8686

    
8687
												// Reset lite parser
8688
												$liteparser = FALSE;
8689

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

    
8695
												// Log invalid Domains
8696
												if (!isset($dnsbl_skip[$line])) {
8697
													pfb_parsed_fail($header, $line, $oline, $pfb['dnsbl_parse_err']);
8698
												}
8699
												continue;
8700
											}
8701

    
8702
											// For DNSBL python, save domain and Logging type
8703
											if ($pfb['dnsbl_py_blacklist']) {
8704
												$domain_data = ',' . strtolower($line)
8705
													. ",,{$logging_type},{$header},{$alias}\n";
8706
											}
8707
											else {
8708
												$ipv6_dnsbl = "\n";
8709
												if ($pfb['dnsbl_v6'] == 'on' && !$pfb['dnsbl_tld']) {
8710
													$ipv6_dnsbl = " local-data: \"" . strtolower($line)
8711
															. " 60 IN AAAA {$sinkhole_type6}\"\n";
8712
												}
8713
												$domain_data = "local-data: \"" . strtolower($line)
8714
														. " 60 IN A {$sinkhole_type4}\"{$ipv6_dnsbl}";
8715
											}
8716
											@fwrite($dhandle, $domain_data);
8717
										}
8718
									}
8719
									if ($dhandle) {
8720
										@fclose($dhandle);
8721
									}
8722
								}
8723
								if ($fhandle) {
8724
									@fclose($fhandle);
8725
								}
8726
								if (isset($csvline)) {
8727
									unset($csvline);
8728
								}
8729

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

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

    
8751
									pfb_logger(".\n", 1);
8752

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

    
8761
									// DNSBL python requires a different deduplication process
8762
									$dup_mode = '';
8763
									if ($pfb['dnsbl_py_blacklist']) {
8764
										$dup_mode = 'python';
8765
									}
8766

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

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

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

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

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

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

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

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

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

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

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

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

    
8824
								// Remove update file indicator
8825
								unlink_if_exists("{$pfbfolder}/{$header}.update");
8826
							}
8827
						}
8828
					}
8829

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

    
8835
					// Collect Alias/Feeds for post TLD function
8836
					if ($pfb['dnsbl_tld']) {
8837
						if (!is_array($pfb['tld_update'][$alias])) {
8838
							$pfb['tld_update'][$alias] = array();
8839
						}
8840
						$pfb['tld_update'][$alias]['feeds']	= $lists_dnsbl_current;
8841
						$pfb['tld_update'][$alias]['count']	= $alias_cnt;
8842
					}
8843
				}
8844
				else {
8845
					dnsbl_alias_update('disabled', $alias, '', '', '');
8846
				}
8847
			}
8848

    
8849
		}
8850

    
8851
		// Remove any unused DNSBL aliases
8852
		$daliases = glob("{$pfb['dnsalias']}/*");
8853
		if (!empty($daliases)) {
8854
			foreach ($daliases as $dlist) {
8855
				if (!in_array(basename($dlist), $pfb['alias_dnsbl_all'])) {
8856
					unlink_if_exists ("{$dlist}");
8857
				}
8858
			}
8859
		}
8860

    
8861
		// Add DNSBL Python options to widget statistics
8862
		if ($pfb['dnsbl_mode'] == 'dnsbl_python') {
8863
			if ($pfb['dnsbl_idn'] == 'on') {
8864
				$idn_cnt = 1;
8865
				$pfb['alias_dnsbl_all'][] = 'DNSBL_IDN';
8866
				dnsbl_alias_update('update', 'DNSBL_IDN', '', '', $idn_cnt);
8867
			}
8868
			if ($pfb['dnsbl_pytld'] == 'on') {
8869
				$pytld_cnt = 0;
8870
				foreach (array('gtld', 'cctld', 'itld', 'bgtld') as $pytld) {
8871
					if (isset($pfb['dnsblconfig']['pfb_pytlds_' . $pytld]) && !empty($pfb['dnsblconfig']['pfb_pytlds_' . $pytld])) {
8872
						$p_cnt = count(explode(',', $pfb['dnsblconfig']['pfb_pytlds_' . $pytld]));
8873
						if (is_numeric($p_cnt) && $p_cnt > 0) {
8874
							$pytld_cnt += $p_cnt;
8875
						}
8876
					}
8877
				}
8878

    
8879
				$pfb['alias_dnsbl_all'][] = 'DNSBL_TLD_Allow';
8880
				dnsbl_alias_update('update', 'DNSBL_TLD_Allow', '', '', $pytld_cnt);
8881
			}
8882
			if ($pfb['dnsbl_regex'] == 'on') {
8883
				$regex_cnt = 0;
8884
				if (isset($pfb['dnsbl_regex_list'])) {
8885
					$regex_cnt = count(pfbng_text_area_decode($pfb['dnsbl_regex_list'], TRUE, FALSE, FALSE)) ?: 0;
8886
				}
8887
				$pfb['alias_dnsbl_all'][] = 'DNSBL_Regex';
8888
				dnsbl_alias_update('update', 'DNSBL_Regex', '', '', $regex_cnt);
8889
			}
8890
		}
8891

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

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

    
8901
		$dnsbl_ip = glob("{$pfb['dnsdir']}/*_v4.ip");
8902
		if (!empty($dnsbl_ip)) {
8903
			$pfb_ips = @fopen("{$pfb['dbdir']}/DNSBLIP_v4.txt", 'w');
8904
			foreach ($dnsbl_ip as $d_ip) {
8905
				if (($handle = @fopen("{$d_ip}", 'r')) !== FALSE) {
8906
					while (($line = @fgets($handle)) !== FALSE) {
8907
						@fwrite($pfb_ips, $line);
8908
					}
8909
				}
8910
				if ($handle) {
8911
					@fclose($handle);
8912
				}
8913
			}
8914
			if ($pfb_ips) {
8915
				@fclose($pfb_ips);
8916
			}
8917
		}
8918

    
8919
		// Add empty placeholder IP
8920
		else {
8921
			@file_put_contents("{$pfb['dbdir']}/DNSBLIP_v4.txt", "{$pfb['ip_ph']}\n", LOCK_EX);
8922
		}
8923
		unlink_if_exists($pfb['ip_cache']);
8924
		touch("{$pfb['denydir']}/DNSBLIP_v4.update");
8925
	}
8926

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

    
8933
	#########################################
8934
	#	UPDATE Unbound DNS Database	#
8935
	#########################################
8936

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

    
8941
			pfb_logger('Assembling DNSBL database...', 1);
8942
			unlink_if_exists("{$pfb['dnsbl_file']}.raw");
8943
			$pfb_output = @fopen("{$pfb['dnsbl_file']}.raw", 'w');
8944
			foreach ($lists_dnsbl_all as $current_list) {
8945
				if (($handle = @fopen("{$pfb['dnsdir']}/{$current_list}", 'r')) !== FALSE) {
8946
					while (($line = @fgets($handle)) !== FALSE) {
8947
						@fwrite($pfb_output, $line);
8948
					}
8949
				}
8950
				if ($handle) {
8951
					@fclose($handle);
8952
				}
8953
			}
8954
			if ($pfb_output) {
8955
				@fclose($pfb_output);
8956
			}
8957
			pfb_logger("... completed [ NOW ]", 1);
8958

    
8959
			// DNSBL Python blocking mode, if TLD is not enabled
8960
			if ($pfb['dnsbl_py_blacklist'] && !$pfb['dnsbl_tld']) {
8961
				unlink_if_exists($pfb['unbound_py_data']);
8962
				unlink_if_exists($pfb['unbound_py_zone']);
8963
				unlink_if_exists($pfb['unbound_py_count']);
8964
				rename("{$pfb['dnsbl_file']}.raw", $pfb['unbound_py_data']);
8965
			}
8966
		}
8967

    
8968
		else {
8969
			$log = "\nDNSBL not Updated!\n";
8970
			pfb_logger("{$log}", 1);
8971
		}
8972
	}
8973
	else {
8974
		if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on') {
8975

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

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

    
8988
				// Remove DNSBL Python files
8989
				else {
8990
					unlink_if_exists($pfb['unbound_py_data']);
8991
					unlink_if_exists($pfb['unbound_py_zone']);
8992
					unlink_if_exists($pfb['unbound_py_wh']);
8993
					unlink_if_exists($pfb['unbound_py_count']);
8994
				}
8995
			}
8996
		}
8997
		else {
8998
			foreach (array("{$pfb['dnsbl_file']}.conf", $pfb['unbound_py_data'], $pfb['unbound_py_zone']) as $pfb_file) {
8999
				if (file_exists($pfb_file)) {
9000
					$pfb['domain_clear'] = TRUE;
9001
					@unlink($pfb_file);
9002
				}
9003
			}
9004
		}
9005
	}
9006

    
9007
	#################################
9008
	#	UNBOUND INTEGRATION	#
9009
	#################################
9010

    
9011
	$pfbupdate = $pfbpython = FALSE;
9012
	if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && $pfb['unbound_state'] == 'on') {
9013
		$mode = 'enabled';
9014
	}
9015
	elseif (($pfb['enable'] == '' || $pfb['dnsbl'] == '') && !$pfb['install']) {
9016
		$mode = 'disabled';
9017
	}
9018

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

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

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

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

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

    
9036
		pfb_update_unbound($mode, $pfbupdate, $pfbpython);
9037
	}
9038

    
9039

    
9040
	#################################
9041
	#	Assign Countries	#
9042
	#################################
9043

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

    
9049
	// Download MaxMind Databases if not found
9050
	$maxmind_verify = FALSE;
9051
	if (!empty($pfb['maxmind_key']) && !empty($pfb['maxmind_account'])) {
9052

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

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

    
9074
	$maxmind_run_once = TRUE;
9075
	foreach ($pfb['continents'] as $continent => $pfb_alias) {
9076
		$cont_key = 'pfblockerng' . strtolower(str_replace(' ', '', $continent));
9077
		if (!empty(config_get_path("installedpackages/{$cont_key}/config"))) {
9078
			$continent_config = config_get_path("installedpackages/{$cont_key}/config/0");
9079
			$cc_name = 'pfblockerng' . strtolower(str_replace(' ', '', $continent));
9080
			if (isset($continent_config['action']) && $continent_config['action'] != 'Disabled' && $pfb['enable'] == 'on') {
9081

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

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

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

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

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

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

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

    
9111
								$urlvalue .= "{$iso},";
9112
								$isofile = "{$pfb['ccdir']}/{$iso}{$vtype}.txt";
9113
								if (($handle = @fopen("{$isofile}", 'r')) !== FALSE) {
9114
									while (($line = @fgets($handle)) !== FALSE) {
9115
										@fwrite($pfb_output, $line);
9116
									}
9117
								}
9118
								else {
9119
									pfb_logger("\nCould not open ISO [ {$iso}{$vtype} ]\n", 1);
9120
								}
9121
								if ($handle) {
9122
									@fclose($handle);
9123
								}
9124
							}
9125
						}
9126
						else {
9127
							pfb_logger("\n[ {$cc_alias} ] Could not create GeoIP file handle\n", 1);
9128
						}
9129
						if ($pfb_output) {
9130
							@fclose($pfb_output);
9131
						}
9132

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

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

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

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

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

    
9157
						// Compare existing (original file) and new Continent data
9158
						if ($continent == $continent_ex && !empty($pfctlck)
9159
						    && file_exists("{$pfbfolder}/{$cc_alias}.txt") && $pfb['reuse'] == ''
9160
						    && !file_exists("{$pfb['dbdir']}/geoip.update")) {
9161
							if (!$pfb['save']) {
9162
								$log = "\n[ {$cc_alias} ]{$logtab} exists. [ NOW ]";
9163
								pfb_logger("{$log}", 1);
9164
							}
9165
						} else {
9166
							// Do not proceed with changes on user 'save'
9167
							if (!$pfb['save']) {
9168
								$log = "\n[ {$cc_alias} ]{$logtab} Changes found... Updating\n";
9169
								pfb_logger("{$log}", 1);
9170

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

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

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

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

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

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

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

    
9204
								if ($file_chk <= 1) {
9205
									if ($vtype == '_v6') {
9206
                                                                                $p_ip = "::{$pfb['ip_ph']}";
9207
                                                                        } else {
9208
                                                                                $p_ip = $pfb['ip_ph'];
9209
                                                                        }
9210

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

    
9219
						if (file_exists("{$pfbfolder}/{$cc_alias}.txt")) {
9220
							// Create alias config
9221
							$new_aliases_list[] = "{$cc_alias}";
9222
							$new_aliases[] = array( 'name'		=> "{$cc_alias}",
9223
										'url'		=> "{$pfb['weblocal']}?pfb={$cc_alias}",
9224
										'updatefreq'	=> '32',
9225
										'address'	=> '',
9226
										'descr'		=> "pfBlockerNG {$pfbdescr} GeoIP Alias [ {$urlvalue} ]",
9227
										'type'		=> 'urltable',
9228
										'detail'	=> 'DO NOT EDIT THIS ALIAS'
9229
										);
9230

    
9231
							// Define firewall rule settings
9232
							pfb_firewall_rule($continent_config['action'], $cc_alias, $vtype, $continent_config['aliaslog'],
9233
							    $pfbarr['agateway_in'], $pfbarr['agateway_out'], $pfbarr['aaddrnot_in'], $pfbarr['aaddr_in'],
9234
							    $pfbarr['aports_in'], $pfbarr['aproto_in'], $pfbarr['anot_in'], $pfbarr['aaddrnot_out'],
9235
							    $pfbarr['aaddr_out'], $pfbarr['aports_out'], $pfbarr['aproto_out'], $pfbarr['anot_out']);
9236
						}
9237
						else {
9238
							// unlink Continent list
9239
							unlink_if_exists("{$pfb['aliasdir']}/{$cc_alias}.txt");
9240
						}
9241
					}
9242
				}
9243
			}
9244
		}
9245
	}
9246

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

    
9250
	#################################################
9251
	#	Download and Collect IPv4/IPv6 lists	#
9252
	#################################################
9253

    
9254
	// IPv4 REGEX Definitions
9255
	$pfb['range']	= '/((?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))-((?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))/';
9256
	$pfb['ipv4']	= '/(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)(?:\/(?:\d|[12]\d|3[0-2])\b)?/';
9257

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

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

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

    
9267
		// Collect lists and custom list configuration and format into one array ($lists).
9268
		foreach	($ip_types as $ip_type	=> $vtype) {
9269
			foreach (config_get_path("installedpackages/{$ip_type}/config", []) as $key => $list) {
9270
				if (!is_array($list)) {
9271
					$list = array();
9272
				}
9273
				if (!is_array($list['row'])) {
9274
					$list['row'] = array();
9275
				}
9276

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

    
9280
				// If only the 'customlist' is defined. Remove the 'List row' data.
9281
				if (empty(array_get_path($list, 'row/0/url'))) {
9282
					unset($list['row']);
9283
				}
9284

    
9285
				if (!empty($list['custom'])) {
9286
					array_init_path($list, 'row');
9287
					$list['row'][] = array(	'header'	=> "{$list['aliasname']}_custom",
9288
											'custom'	=> $list['custom'],
9289
											'state'		=> 'Enabled',
9290
											'url'		=> 'custom'
9291
					);
9292
				}
9293
				$lists[] = $list;
9294
			}
9295

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

    
9299
				$list = array(	'aliasname'	=> 'DNSBLIP',
9300
						'vtype'		=> "{$vtype}",
9301
						'key'		=> 0,
9302
						'dnsblip'	=> '',
9303
						'action'	=> "{$pfb['dnsbl_ip']}",
9304
						);
9305

    
9306
				$list['row'][] = array(	'format'	=> 'auto',
9307
							'state'		=> 'Enabled',
9308
							'url'		=> "{$pfb['dbdir']}/DNSBLIP{$vtype}.txt",
9309
							'header'	=> 'DNSBLIP');
9310
				$lists[] = $list;
9311
			}
9312
		}
9313

    
9314
		$maxmind_run_once = $asn_run_once = TRUE;
9315
		foreach	($lists	as $list) {
9316
			if ($runonce_v4 && $list['vtype'] == '_v4') {
9317
				$runonce_v4 = FALSE;
9318
				$log = "\n\n===[  IPv4 Process  ]=================================================\n";
9319
				pfb_logger("{$log}", 1);
9320
			} elseif ($runonce_v6 && $list['vtype'] == '_v6') {
9321
				$runonce_v6 = FALSE;
9322
				$log = "\n\n===[  IPv6 Process  ]=================================================\n";
9323
				pfb_logger("{$log}", 1);
9324
			}
9325

    
9326
			if ($list['action'] != 'Disabled' && isset($list['row'])) {
9327
				$alias = "pfB_{$list['aliasname']}{$list['vtype']}";	// Capture Alias name
9328
				if (empty(pfb_filter($alias, PFB_FILTER_WORD, 'Download and Collect IPv4/IPv6 lists'))) {
9329
					pfb_logger("\n Invalid Aliasname:{$list['aliasname']}{$list['vtype']}, *skipping*", 1);
9330
					continue;
9331
				}
9332

    
9333
				foreach	($list['row'] as $row) {
9334
					if (!empty($row['url']) && $row['state'] != 'Disabled') {
9335
						$header = "{$row['header']}{$list['vtype']}";	// Capture Header/Label name
9336
						if (empty(pfb_filter($header, PFB_FILTER_WORD, 'Download and Collect IPv4/IPv6 lists'))) {
9337
							pfb_logger("\n Invalid Aliasname:{$list['aliasname']}{$list['vtype']} | Header:{$row['header']}{$list['vtype']}, *skipping*", 1);
9338
							continue;
9339
						}
9340
						$header_esc = escapeshellarg($header);
9341

    
9342
						// If row is a custom_list, set	flag.
9343
						if (isset($row['custom'])) {
9344
							$custom	= TRUE;
9345
						} else {
9346
							$custom	= FALSE;
9347
						}
9348

    
9349
						// Maxmind License Key verification
9350
						if ($maxmind_run_once && $row['format'] == 'geoip') {
9351
							$mmsg = 'MaxMind now requires an Account ID and License Key! Review the IP tab: MaxMind settings for more information.';
9352
							if (empty($pfb['maxmind_key']) || empty($pfb['maxmind_account'])) {
9353
								pfb_logger("\n\nURGENT:\n    {$mmsg}.\n", 1);
9354
								file_notice('pfBlockerNG MaxMind', $mmsg, 'pfBlockerNG', '/pfblockerng/pfblockerng_ip.php', 2);
9355
							}
9356
							$maxmind_run_once = FALSE;
9357
						}
9358

    
9359
						// IPinfo ASN Token verification
9360
						if ($asn_run_once && $row['format'] == 'asn') {
9361
							$mmsg = 'To utilize the ASN functionality, you must register for a free IPinfo Account. Review IP Tab for more information.';
9362
							if (empty($pfb['asn_token'])) {
9363
								pfb_logger($mmsg, $logtype);
9364
								file_notice('pfBlockerNG ASN', $mmsg, 'pfBlockerNG', '/pfblockerng/pfblockerng_ip.php', 2);
9365
							}
9366
							$asn_run_once = FALSE;
9367
						}
9368

    
9369
						// IPv4 Advanced Tunables
9370
						$pfbcidr = 'Disabled';
9371
						if (isset($list['suppression_cidr']) && $list['suppression_cidr'] != 'Disabled' && is_numeric($list['suppression_cidr'])) {
9372
							$pfbcidr = "{$list['suppression_cidr']}";
9373
						}
9374
						
9375
						// cURL Source Interface (sets CURLOPT_INTERFACE)
9376
						$srcint = $list['srcint'] ?: FALSE;
9377

    
9378
						// IP v4/6 Advanced Tunable - (Pre/Post Script processing)
9379
						$pfb_script_pre = FALSE;
9380
						if (isset($list['script_pre']) && !empty($list['script_pre'])) {
9381
							$script_pre = basename($list['script_pre']);
9382
							if (file_exists("/usr/local/pkg/pfblockerng/{$script_pre}")) {
9383
								$pfb_script_pre = "/usr/local/pkg/pfblockerng/{$script_pre}";
9384
							}
9385
						}
9386

    
9387
						$pfb_script_post = FALSE;
9388
						if (isset($list['script_post']) && !empty($list['script_post'])) {
9389
							$script_post = basename($list['script_post']);
9390
							if (file_exists("/usr/local/pkg/pfblockerng/{$script_post}")) {
9391
								$pfb_script_post = "/usr/local/pkg/pfblockerng/{$script_post}";
9392
							}
9393
						}
9394

    
9395
						// Determine 'list' details (return array $pfbarr)
9396
						if (isset($list['dnsblip'])) {
9397
							$list_type = 'pfblockerngdnsblsettings';
9398
						} else {
9399
							$list_type = "{$ip_type}";
9400
						}
9401

    
9402
						pfb_determine_list_detail($list['action'], $header, $list_type, $list['key']);
9403
						$pfbadv		= $pfbarr['adv'];
9404
						$pfbfolder	= $pfbarr['folder'];
9405
						$pfborig	= $pfbarr['orig'];
9406
						$pfbreuse	= $pfbarr['reuse'];
9407
						$logtab		= $pfbarr['logtab'];
9408

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

    
9412
						// Set update flags on new downloads available for GeoIP and ASN
9413
						if ($row['format'] == 'geoip' && file_exists("{$pfb['dbdir']}/geoip.update")) {
9414
							touch("{$pfbfolder}/{$header}.update");
9415
						}
9416
						if ($row['format'] == 'asn' && file_exists("{$pfb['dbdir']}/asn.update")) {
9417
							touch("{$pfbfolder}/{$header}.update");
9418
						}
9419

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

    
9425
							if ($row['state'] == 'Hold') {
9426
								$log = "\n[ {$header} ]{$logtab} static hold. [ NOW ]";
9427
							} else {
9428
								$log = "\n[ {$header} ]{$logtab} exists. [ NOW ]";
9429
							}
9430
							pfb_logger("{$log}", 1);
9431
						}
9432
						else {
9433
							if ($pfbreuse == 'on' && file_exists("{$pfborig}/{$header}.orig")) {
9434
								$log = "\n[ {$header} ]{$logtab} Reload [ NOW ]";
9435
							} else {
9436
								$log = "\n[ {$header} ]{$logtab} Downloading update [ NOW ]";
9437
							}
9438
							pfb_logger("{$log}", 1);
9439
							$file_dwn = "{$pfborig}/{$header}";
9440

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

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

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

    
9456
								// Adjust 'geoip' format to GeoIP path location
9457
								if ($row['format'] == 'geoip') {
9458
									if (strpos($row['url'], ' ') !== FALSE) {
9459
										$row['url'] = strstr($row['url'], ' ', TRUE);
9460
									}
9461
									if (!empty(pfb_filter($row['url'], PFB_FILTER_WORD, 'Adjust geoip format to GeoIP path location'))) {
9462
										$row['url'] = "/usr/local/share/GeoIP/cc/{$row['url']}{$list['vtype']}.txt";
9463
									} else {
9464
										$row['url'] = '';
9465
									}
9466
								}
9467

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

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

    
9479
									// Process Emerging Threats IQRisk if required
9480
									if (strpos($row['url'], 'iprepdata.txt') !== FALSE) {
9481
										if (file_exists("{$file_dwn}.raw")) {
9482
											$file_dwn_esc = escapeshellarg("{$file_dwn}.raw");
9483
											$file_org_esc = escapeshellarg("{$file_dwn}.orig");
9484
											exec("/usr/bin/gunzip -c {$file_dwn_esc} > {$file_org_esc}");
9485
										}
9486
										exec("{$pfb['script']} et {$header_esc} x x x x x {$pfb['etblock']} {$pfb['etmatch']} {$elog}");
9487
									}
9488
								}
9489
								else {
9490
									// Download list
9491
									if (!pfb_download($row['url'], $file_dwn, $pflex, $header, $row['format'],
9492
										1, $list['vtype'], '', '', '', '', $srcint)) {
9493

    
9494
										// Determine reason for download failure
9495
										pfb_download_failure($alias, $header, $pfbfolder, $row['url'], $row['format'], $list['vtype']);
9496

    
9497
										// Utilize previously download file (If 'fail' marker exists)
9498
										if (file_exists("{$pfbfolder}/{$header}.fail") &&
9499
										    file_exists("{$file_dwn}.orig")) {
9500
											pfb_logger("\n  Restoring previously downloaded file contents...", 2);
9501
										}
9502
										else {
9503
											if ($pfbadv) {
9504
												// Script to Remove failed lists from masterfile
9505
												exec("{$pfb['script']} remove x x x {$header_esc} {$elog}");
9506
											}
9507
											continue;
9508
										}
9509
									}
9510
									else {
9511
										// Clear any previous download fail marker
9512
										unlink_if_exists("{$pfbfolder}/{$header}.fail");
9513
										pfb_logger('.', 1);
9514
									}
9515
								}
9516
								pfb_logger(' completed .', 1);
9517
							}
9518
							else {
9519
								if ($list['whois_convert'] == 'on') {
9520
									// Process Domain/AS based custom list
9521
									$custom_list = str_replace("\n", ',', pfbng_text_area_decode($list['custom'], FALSE, TRUE, TRUE));
9522
									if (!empty(pfb_filter($custom_list, PFB_FILTER_CSV_WHOIS, 'Process Domain/AS based custom list'))) {
9523
										exec("{$pfb['script']} whoisconvert {$header_esc} {$list['vtype']} {$custom_list} {$elog}");
9524
									} else {
9525
										pfb_logger("\nFailed to process customlist [AS/Whois convert | " . htmlspecialchars($custom_list) . " ]", 1);
9526
									}
9527
								}
9528
								else {
9529
									// Process IP based custom list
9530
									$custom_list = pfbng_text_area_decode($list['custom'], FALSE, TRUE, FALSE);
9531
									@file_put_contents("{$file_dwn}.orig", $custom_list, LOCK_EX);
9532
								}
9533
								pfb_logger(' . completed .', 1);
9534
							}
9535

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

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

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

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

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

    
9565
							// IPv4/6 Advanced Tunable - (Pre Script processing)
9566
							if ($pfb_script_pre && file_exists("{$pfb_script_pre}")) {
9567
								pfb_logger("\nExecuting pre-script: {$list['script_pre']}\n", 1);
9568
								$file_dwn_esc = escapeshellarg("{$file_dwn}.orig");
9569
								@copy("{$file_dwn}.orig", "{$file_dwn}.orig.pre"); // Save original file for restoration
9570
								exec("{$pfb_script_pre} {$file_dwn_esc} {$list['vtype']} {$elog}");
9571
							}
9572

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

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

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

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

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

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

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

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

    
9608
										// Range parser
9609
										if (strpos($line, '-') !== FALSE) {
9610
											$matches = explode('-', $line);
9611
											if (count($matches) == 2) {
9612
												$a_cidr = ip_range_to_subnet_array($matches[0],$matches[1]);
9613
												if (!empty($a_cidr)) {
9614
													foreach ($a_cidr as $cidr) {
9615
														$cidr = sanitize_ipaddr($cidr, $custom, $pfbcidr);
9616
														if (!empty($cidr)) {
9617
															if (validate_ipv4($cidr)) {
9618
																$ip_data .= $cidr . "\n";
9619
															}
9620
															else {
9621
																$parse_error = TRUE;
9622
															}
9623
														}
9624
													}
9625
													if (!$parse_error) {
9626
														continue;
9627
													}
9628
												}
9629
											}
9630
											else {
9631
												$parse_error = TRUE;
9632
											}
9633
										}
9634

    
9635
										if (!$parse_error) {
9636
											// Single address parser
9637
											$parsed = sanitize_ipaddr($line, $custom, $pfbcidr);
9638
											if (validate_ipv4($parsed)) {
9639
												$ip_data .= $parsed . "\n";
9640
												continue;
9641
											}
9642
											else {
9643
												$parse_error = TRUE;
9644
											}
9645
										}
9646
									}
9647

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

    
9650
										// Use regex as last alternative.
9651

    
9652
										if (strpos($oline, '-') !== FALSE && strpos($oline, '.') !== FALSE) {
9653
											// Network range 192.168.0.0-192.168.0.254
9654
											if (preg_match($pfb['range'], $oline, $matches)) {
9655
												$a_cidr = ip_range_to_subnet_array($matches[1], $matches[2]);
9656
												if (!empty($a_cidr)) {
9657
													foreach ($a_cidr as $cidr) {
9658
														$cidr = sanitize_ipaddr($cidr, $custom, $pfbcidr);
9659
														if (validate_ipv4($cidr)) {
9660
															$ip_data .= $cidr . "\n";
9661
														}
9662
														else {
9663
															$parse_fail++;
9664
														}
9665
													}
9666
												}
9667
												continue;
9668
											}
9669
										}
9670

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

    
9676
												// Workaround to skip cloudflare error pages
9677
												if (strpos($oline, 'cf-footer-item') === FALSE) {
9678
													$parsed = sanitize_ipaddr($match, $custom, $pfbcidr);
9679
													if (validate_ipv4($parsed)) {
9680
														$ip_data .= $parsed . "\n";
9681
													}
9682
												}
9683
											}
9684
											continue;
9685
										}
9686
									}
9687

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

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

    
9698
												if (validate_ipv6($line)) {
9699
													$ip_data .= $line . "\n";
9700
													continue;
9701
												}
9702
											}
9703
										}
9704

    
9705
										// Range parser
9706
										if (strpos($line, '-') !== FALSE && strpos($line, ':') !== FALSE) {
9707
											$matches = explode('-', $line);
9708
											if (count($matches) == 2) {
9709
												$a_cidr = ip_range_to_subnet_array($matches[0],$matches[1]);
9710
												if (!empty($a_cidr)) {
9711
													foreach ($a_cidr as $cidr) {
9712
														if (!empty($cidr)) {
9713
															if (validate_ipv6($cidr)) {
9714
																$ip_data .= $cidr . "\n";
9715
															}
9716
															else {
9717
																$parse_error = TRUE;
9718
															}
9719
														}
9720
													}
9721
												}
9722
												if (!$parse_error) {
9723
													continue;
9724
												}
9725
											}
9726
											else {
9727
												$parse_error = TRUE;
9728
											}
9729
										}
9730

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

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

    
9741
												// Workaround to skip cloudflare error pages
9742
												if (strpos($oline, 'cf-footer-item') === FALSE) {
9743
													if (validate_ipv6($match)) {
9744
														$ip_data .= $match . "\n";
9745
													}
9746
												}
9747
											}
9748
										}
9749
									}
9750
								}
9751

    
9752
								// Check for parse failures
9753
								if (!empty($line) && !preg_match('/[a-zA-Z,;|\"\'?]/', $line)) {
9754
									$parse_fail++;
9755
									$log = "[!] Parse Errors [ {$parse_fail} ]\n";
9756
									pfb_logger("{$log}", 2);
9757
								}
9758
							}
9759
							if ($fhandle) {
9760
								@fclose($fhandle);
9761
							}
9762
							pfb_logger("\n", 1);
9763

    
9764
							// IP v4/6 Advanced Tunable - (Post Script processing)
9765
							if ($pfb_script_post && file_exists("{$pfb_script_post}")) {
9766
								pfb_logger("\nExecuting post-script: {$list['script_pre']}\n", 1);
9767
								$file_org_esc = escapeshellarg("{$file_dwn}.orig");
9768
								@copy("{$file_dwn}.orig", "{$file_dwn}.orig.post"); // Save original file for restoration
9769
								exec("{$pfb_script_post} {$file_org_esc} {$list['vtype']} {$elog}");
9770
							}
9771

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

    
9780
								if ($file_chk == 0) {
9781
									if ($list['vtype'] == '_v6') {
9782
										$p_ip = "::{$pfb['ip_ph']}";
9783
									} else {
9784
										$p_ip = $pfb['ip_ph'];
9785
									}
9786

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

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

    
9797
								// Call 'shell script' functions (Deny Actions only)
9798
								if ($pfbadv && $list['vtype'] == '_v4') {
9799
									$args = '';
9800
									// Call Process255
9801
									if ($pfb['dup'] == 'on' || $pfb['agg'] == 'on') {
9802
										$args  = '_255';
9803
									}
9804
									// Call Aggregate process
9805
									if ($pfb['agg'] == 'on') {
9806
										$args .= '_agg';
9807
									}
9808
									// Call Reputation Max process
9809
									if ($pfb['rep'] == 'on') {
9810
										$args .= '_rep';
9811
									}
9812
									// Call Duplication process
9813
									if ($pfb['dup'] == 'on') {
9814
										$args .= '_dup';
9815
									}
9816
									if (!empty($args)) {
9817
										exec("{$pfb['script']} {$args} {$header_esc} {$pfb['max']} {$pfb['drep']} {$pfb['ccexclude']} {$pfb['ccwhite']} {$pfb['ccblack']} {$elog}");
9818
									}
9819
								}
9820

    
9821
								if (!$pfbadv && $list['vtype'] == '_v4') {
9822
									// Call Aggregate process
9823
									if ($pfb['agg'] == 'on') {
9824
										exec("{$pfb['script']} cidr_aggregate {$header_esc} {$pfbfolder} {$elog}");
9825
									}
9826
								}
9827

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

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

    
9835
									// Enable suppression process due to updates
9836
									if ($pfb['supp'] == 'on') {
9837
										$pfb['supp_update'] = TRUE;
9838
									}
9839
								}
9840
							} else {
9841
								if (!$custom) {
9842
									$log = "[ {$alias} {$header} ] No IPs found! Ensure only IP based Feeds are used! ]\n";
9843
								} else {
9844
									$log = "[ {$alias} {$header} ] Custom List: No IPs found! Ensure only IP based Feeds are used! ]\n";
9845
								}
9846
								pfb_logger("{$log}", 1);
9847
							}
9848
							unset($ip_data);
9849

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

    
9853
							// Restore Original Downloaded file after Post Script function
9854
							if (file_exists("{$file_dwn}.orig.post")) {
9855
								@rename("{$file_dwn}.orig.post", "{$file_dwn}.orig");
9856
							}
9857
							// Restore Original Downloaded file after Pre Script function 
9858
							if (file_exists("{$file_dwn}.orig.pre")) {
9859
								@rename("{$file_dwn}.orig.pre", "{$file_dwn}.orig");
9860
							}
9861
						}
9862
					}
9863
				}
9864
			}
9865
		}
9866
	}
9867

    
9868
	// Remove database update file markers
9869
	unlink_if_exists("{$pfb['dbdir']}/geoip.update");
9870
	unlink_if_exists("{$pfb['dbdir']}/asn.update");
9871

    
9872
	#################################
9873
	#	REPUTATION PROCESSES	#
9874
	#################################
9875

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

    
9886
	#################################################
9887
	#	CONFIGURE ALIASES AND FIREWALL RULES	#
9888
	#################################################
9889

    
9890
	foreach ($ip_types as $ip_type => $vtype) {
9891
		$lists = config_get_path("installedpackages/{$ip_type}/config");
9892

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

    
9896
			$list = array(	'aliasname'	=> 'DNSBLIP',
9897
					'vtype'		=> "{$vtype}",
9898
					'key'		=> 0,
9899
					'dnsblip'	=> '',
9900
					'action'	=> "{$pfb['dnsbl_ip']}",
9901
					'aliaslog'	=> "{$pfb['dnsblconfig']['aliaslog']}");
9902

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

    
9910
		if (!empty($lists) && $pfb['enable'] == 'on') {
9911
			$pfbrunonce = TRUE;
9912
			foreach ($lists as $key => $list) {
9913
				$alias = "pfB_{$list['aliasname']}{$vtype}";
9914
				if (empty(pfb_filter($alias, PFB_FILTER_WORD, 'Configure Aliases and Firewall Rules'))) {
9915
					pfb_logger("\n Invalid Aliasname:{$list['aliasname']}{$vtype}, *skipping*", 1);
9916
					continue;
9917
				}
9918
				$alias_esc = escapeshellarg($alias);
9919

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

    
9926
				if (isset($list['dnsblip'])) {
9927
					$list_type = 'pfblockerngdnsblsettings';
9928
				} else {
9929
					$list_type = "{$ip_type}";
9930
				}
9931

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

    
9938
				// Only Save aliases that have been updated.
9939
				// When 'Reputation' is used, all aliases need to be updated.
9940
				$final_alias = array();
9941
				if ($pfb['drep'] == 'on' || $pfb['prep'] == 'on') {
9942
					if (!empty($pfb_alias_lists_all)) {
9943
						$final_alias = array_unique($pfb_alias_lists_all);
9944
					}
9945
				}
9946
				else {
9947
					if (!empty($pfb_alias_lists)) {
9948
						$final_alias = array_unique($pfb_alias_lists);
9949
					}
9950
				}
9951

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

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

    
9961
								$header = "{$row['header']}{$vtype}";
9962
								if (empty(pfb_filter($header, PFB_FILTER_WORD, 'Configure Aliases and Firewall Rules'))) {
9963
									pfb_logger("\n Invalid Aliasname:{$list['aliasname']}{$vtype} | Header:{$row['header']}{$vtype}, *skipping*", 1);
9964
									continue;
9965
								}
9966
								$header_esc	= escapeshellarg($header);
9967
								$urlvalue	.= "{$header},";
9968

    
9969
								$pfctlck = exec("{$pfb['pfctl']} -vvsTables | {$pfb['grep']} -A1 {$alias_esc} | {$pfb['awk']} '/Addresses/ {s+=\$2}; END {print s}'");
9970

    
9971
								// Update alias if list file exists and its been updated or if the alias URL table is empty.
9972
								if (file_exists("{$pfbfolder}/{$header}.txt") && (in_array($alias, $final_alias) || empty($pfctlck))) {
9973
									// Script to run suppression process (print header only)
9974
									if ($pfbrunonce && $pfb['supp'] == 'on' && $vtype == '_v4' && $pfb['supp_update']) {
9975
										exec("{$pfb['script']} suppress suppressheader {$elog}");
9976
										$pfbrunonce = FALSE;
9977
									}
9978
									// Script to run suppression process (body)
9979
									if ($pfb['supp'] == 'on' && $vtype == '_v4' && $pfb['supp_update'] && $pfbadv) {
9980
										if ($pfb['dup'] == 'on') {
9981
											exec("{$pfb['script']} suppress {$header_esc} {$pfbfolder} on {$elog}");
9982
										} else {
9983
											exec("{$pfb['script']} suppress {$header_esc} {$pfbfolder} off {$elog}");
9984
										}
9985
									}
9986
									$alias_ips .= file_get_contents("{$pfbfolder}/{$header}.txt");
9987
									$pfbupdate = TRUE;
9988
								}
9989
							}
9990
						}
9991
					}
9992

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

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

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

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

    
10024
						// Create alias
10025
						$new_aliases_list[] = "{$alias}";
10026
						$new_aliases[] = array(	'name'		=> "{$alias}",
10027
									'url'		=> "{$pfb['weblocal']}?pfb={$alias}",
10028
									'updatefreq'	=> '32',
10029
									'address'	=> '',
10030
									'descr'		=> "{$adescr} [ {$urlvalue} ]",
10031
									'type'		=> 'urltable',
10032
									'detail'	=> 'DO NOT EDIT THIS ALIAS'
10033
									);
10034

    
10035
						// Define firewall rule settings
10036
						pfb_firewall_rule($list['action'], $alias, $vtype, $list['aliaslog'], $pfbarr['agateway_in'], $pfbarr['agateway_out'],
10037
						    $pfbarr['aaddrnot_in'], $pfbarr['aaddr_in'], $pfbarr['aports_in'], $pfbarr['aproto_in'], $pfbarr['anot_in'],
10038
						    $pfbarr['aaddrnot_out'], $pfbarr['aaddr_out'], $pfbarr['aports_out'], $pfbarr['aproto_out'], $pfbarr['anot_out']);
10039
					}
10040
				}
10041
				else {
10042
					// unlink previous pfblockerNG alias list
10043
					unlink_if_exists("{$pfb['aliasdir']}/{$alias}.txt");
10044
				}
10045
			}
10046
		}
10047
	}
10048
	// Clear variables
10049
	$alias_ips = '';
10050

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

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

    
10063
		if ($pfb['dnsbl_v6'] == 'on') {
10064
			$new_aliases_list[] = 'pfB_DNSBL_VIPs';
10065
			$new_aliases[] = array(	'name'		=> 'pfB_DNSBL_VIPs',
10066
						'address'	=> "{$pfb['dnsbl_vip']} ::{$pfb['dnsbl_vip']}",
10067
						'descr'		=> 'pfBlockerNG DNSBL VIPs',
10068
						'type'		=> 'host',
10069
						'detail'	=> 'DO NOT EDIT THIS HOST||DO NOT EDIT THIS HOST'
10070
						);
10071
		}
10072
	}
10073

    
10074
	#########################################
10075
	#	UPDATE pfSense ALIAS TABLES	#
10076
	#########################################
10077

    
10078
	// Reload config.xml to get any recent changes
10079
	config_read_file(false, true);
10080

    
10081
	$exist_aliases = config_get_path('aliases/alias', []);
10082
	foreach ($exist_aliases as $cbalias) {
10083

    
10084
		if (substr($cbalias['name'], 0, 4) == 'pfB_') {
10085
			// Remove unreferenced pfB aliastable files
10086
			if (!in_array($cbalias['name'], $new_aliases_list)) {
10087
				unlink_if_exists("{$pfb['aliasdir']}/{$cbalias['name']}.*");
10088
			}
10089
		}
10090
		else {
10091
			$new_aliases[] = $cbalias;
10092
		}
10093
	}
10094

    
10095
	// Update config.xml, if changes required
10096
	if ($exist_aliases !== $new_aliases) {
10097
		config_set_path('aliases/alias', $new_aliases);
10098
		write_config('pfBlockerNG: saving Aliases');
10099
	}
10100
	unset($new_aliases, $exist_aliases);
10101

    
10102
	#########################
10103
	#	Assign Rules	#
10104
	#########################
10105

    
10106
	// Only execute if autorules are defined or if an alias has been removed.
10107
	if ($pfb['autorules'] || $pfb['enable'] == '' || $pfb['remove']) {
10108
		$message = '';
10109
		if (!empty($pfb['deny_inbound']) || !empty($pfb['permit_inbound']) || !empty($pfb['match_inbound'])) {
10110
			if (empty($pfb['inbound_interfaces'])) {
10111
				$message = " Unable to apply rules. Inbound interface option not configured.";
10112
			}
10113
		}
10114
		if (!empty($pfb['deny_outbound']) || !empty($pfb['permit_outbound']) || !empty($pfb['match_outbound'])) {
10115
			if (empty($pfb['outbound_interfaces'])) {
10116
				$message .= "\n Unable to apply rules. Outbound interface option not configured.";
10117
			}
10118
		}
10119

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

    
10123
			// Reload config.xml to get any recent changes
10124
			config_read_file(false, true);
10125

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

    
10129
			// Collect all existing rules
10130
			$rules = config_get_path('filter/rule', []);
10131

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

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

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

    
10145
								// Add '_v4' suffix
10146
								$rule[$rtype]['address'] = "{$rule[$rtype]['address']}_v4";
10147
							}
10148
						}
10149

    
10150
						// Floating rules collection 'Floating Pass/Match', balance to 'other'
10151
						if ($pfb['float'] == 'on') {
10152
							if ($pfb['order'] == 'order_0' && $rule['floating'] == 'yes') {
10153
								$fother_rules[] = $rule;
10154
							}
10155
							else {
10156
								if ($rule['type'] == 'pass' && $rule['floating'] == 'yes') {
10157
									$fpermit_rules[] = $rule;
10158
								} elseif ($rule['type'] == 'match' && $rule['floating'] == 'yes') {
10159
									$fmatch_rules[] = $rule;
10160
								} elseif ($rule['floating'] == 'yes') {
10161
									$fother_rules[] = $rule;
10162
								} else {
10163
									$other_rules[] = $rule;
10164
								}
10165
							}
10166
						} else {
10167
							// Collect only 'selected inbound and outbound interfaces'. balance to 'other'
10168
							if (in_array($rule['interface'], $pfb['inbound_interfaces']) ||
10169
							    in_array($rule['interface'], $pfb['outbound_interfaces'])) {
10170
								// Floating rules 'off'. Collect 'floating other', pass, balance to 'other'
10171
								if ($rule['floating'] == 'yes') {
10172
									$fother_rules[] = $rule;
10173
								} elseif ($rule['type'] == 'pass' || isset($rule['associated-rule-id'])) {
10174
									if ($pfb['order'] == 'order_0') {
10175
										$other_rules[] = $rule;
10176
									} else {
10177
										$permit_rules[] = $rule;
10178
									}
10179
								} else {
10180
									$other_rules[] = $rule;
10181
								}
10182
							} else {
10183
								if ($rule['floating'] == 'yes') {
10184
									$fother_rules[] = $rule;
10185
								} else {
10186
									$other_rules[] = $rule;
10187
								}
10188
							}
10189
						}
10190
					}
10191

    
10192
					// Remove 'created' tag
10193
					if (isset($rule['created'])) {
10194
						unset($rule['created']);
10195
					}
10196
					$orig_rules_nocreated[] = $rule;
10197
				}
10198
			}
10199

    
10200
			#################################################################################
10201
			#			IP FIREWALL RULES ORDER					#
10202
			#  ORDER 0 |	pfB (p/m/b/r)	| All other	|				#
10203
			#  ORDER 1 |	pfSense (p/m)	| pfB (p/m)	| pfB (b/r) 	| pfSense (b/r)	#
10204
			#  ORDER 2 |	pfB (p/m)	| pfSense (p/m) | pfB (b/r)	| pfSense (b/r)	#
10205
			#  ORDER 3 |	pfB (p/m)	| pfB (b/r) 	| pfSense (p/m)	| pfSense (b/r)	#
10206
			#  ORDER 4 |	pfB (p/m)	| pfB (b/r)	| pfSense (b/r)	| pfSense (p/m)	#
10207
			#################################################################################
10208

    
10209

    
10210
			if ($pfb['float'] == '' && $pfb['order'] == 'order_1' && !empty($fother_rules)) {
10211
				foreach ($fother_rules as $cb_rules) {
10212
					$new_rules[] = $cb_rules;
10213
				}
10214
			}
10215
			if ($pfb['float'] == 'on' && $pfb['order'] == 'order_1') {
10216
				foreach (array($fpermit_rules, $fmatch_rules) as $rtype) {
10217
					if (!empty($rtype)) {
10218
						foreach ($rtype as $cb_rules) {
10219
							$new_rules[] = $cb_rules;
10220
						}
10221
					}
10222
				}
10223
			}
10224

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

    
10230
				$rule			= $pfb['base_rule_float'];
10231
				$rule['tracker']	= pfb_tracker('pfB_DNSBL_Ping', '', '');
10232
				$rule['type']		= 'pass';
10233
				$rule['direction']	= 'any';
10234
				$rule['interface']	= "{$pfb['dnsblconfig']['dnsbl_allow_int']}";
10235
				$rule['ipprotocol']	= ($pfb['dnsbl_v6'] == 'on' ? $rule['ipprotocol'] = 'inet46' : $rule['ipprotocol'] = 'inet');
10236
				$rule['descr']		= "pfB_DNSBL_Ping{$pfb['suffix']}";
10237
				$rule['protocol']	= 'icmp';
10238
				$rule['icmptype']	= 'echoreq';
10239
				$rule['source']		= array('any' => '');
10240
				$rule['destination']	= array('address' => ($pfb['dnsbl_v6'] == 'on' ? 'pfB_DNSBL_VIPs' : $pfb['dnsbl_vip']));
10241
				$rule['created']	= array('time' => (int)microtime(true), 'username' => 'Auto');
10242
				$new_rules[]		= $rule;
10243

    
10244
				$rule			= $pfb['base_rule_float'];
10245
				$rule['tracker']	= pfb_tracker('pfB_DNSBL_Permit', '', '');
10246
				$rule['type']		= 'pass';
10247
				$rule['direction']	= 'any';
10248
				$rule['interface']	= "{$pfb['dnsblconfig']['dnsbl_allow_int']}";
10249
				$rule['ipprotocol']	= ($pfb['dnsbl_v6'] == 'on' ? $rule['ipprotocol'] = 'inet46' : $rule['ipprotocol'] = 'inet');
10250
				$rule['descr']		= "pfB_DNSBL_Permit{$pfb['suffix']}";
10251
				$rule['protocol']	= 'tcp/udp';
10252
				$rule['source']		= array('any' => '');
10253
				$rule['destination']	= array('address' => ($pfb['dnsbl_v6'] == 'on' ? 'pfB_DNSBL_VIPs' : $pfb['dnsbl_vip']),
10254
								 'port' => 'pfB_DNSBL_Ports');
10255
				$rule['created']	= array('time' => (int)microtime(true), 'username' => 'Auto');
10256
				$new_rules[]		= $rule;
10257
			}
10258

    
10259
			// Define inbound interface rules
10260
			if (!empty($pfb['inbound_interfaces'])) {
10261
				$pfbrunonce = TRUE;
10262
				foreach ($pfb['inbound_interfaces'] as $inbound_interface) {
10263
					if ($pfb['order'] == 'order_1' && !empty($permit_rules)) {
10264
						foreach ($permit_rules as $cb_rules) {
10265
							if ($cb_rules['interface'] == $inbound_interface) {
10266
								$new_rules[] = $cb_rules;
10267
							}
10268
						}
10269
					}
10270
					if (!empty($pfb['permit_inbound'])) {
10271
						foreach ($pfb['permit_inbound'] as $cb_rules) {
10272
							$cb_rules['interface'] = $inbound_interface;
10273
							$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $inbound_interface, 'permit_in');
10274
							$new_rules[] = $cb_rules;
10275
						}
10276
					}
10277
					// Match inbound rules defined as floating only.
10278
					if ($pfbrunonce && !empty($pfb['match_inbound'])) {
10279
						foreach ($pfb['match_inbound'] as $cb_rules) {
10280
							$cb_rules['interface'] = $pfb['inbound_floating'];
10281
							$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $inbound_interface, 'match_in');
10282
							$new_rules[] = $cb_rules;
10283
							$pfbrunonce = FALSE;
10284
						}
10285
					}
10286
					if ($pfb['order'] == 'order_2') {
10287
						foreach (array($fpermit_rules, $fmatch_rules) as $rtype) {
10288
							if (!empty($rtype)) {
10289
								foreach ($rtype as $cb_rules) {
10290
									$new_rules[] = $cb_rules;
10291
								}
10292
							}
10293
						}
10294
						if (!empty($permit_rules)) {
10295
							foreach ($permit_rules as $cb_rules) {
10296
								if ($cb_rules['interface'] == $inbound_interface) {
10297
									$new_rules[] = $cb_rules;
10298
								}
10299
							}
10300
						}
10301
					}
10302
					if (!empty($pfb['deny_inbound'])) {
10303
						foreach ($pfb['deny_inbound'] as $cb_rules) {
10304
							$cb_rules['interface'] = $inbound_interface;
10305
							$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $inbound_interface, 'deny_in');
10306
							$new_rules[] = $cb_rules;
10307
						}
10308
					}
10309
				}
10310
			}
10311

    
10312
			// Define outbound interface rules
10313
			if (!empty($pfb['outbound_interfaces'])) {
10314
				$pfbrunonce = TRUE;
10315
				foreach ($pfb['outbound_interfaces'] as $outbound_interface) {
10316
					if ($pfb['order'] == 'order_1' && !empty($permit_rules)) {
10317
						foreach ($permit_rules as $cb_rules) {
10318
							if ($cb_rules['interface'] == $outbound_interface) {
10319
								$new_rules[] = $cb_rules;
10320
							}
10321
						}
10322
					}
10323
					if (!empty($pfb['permit_outbound'])) {
10324
						foreach ($pfb['permit_outbound'] as $cb_rules) {
10325
							$cb_rules['interface'] = $outbound_interface;
10326
							$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $outbound_interface, 'permit_out');
10327
							$new_rules[] = $cb_rules;
10328
						}
10329
					}
10330
					// Match outbound rules defined as floating only.
10331
					if ($pfbrunonce && !empty($pfb['match_outbound'])) {
10332
						foreach ($pfb['match_outbound'] as $cb_rules) {
10333
							$cb_rules['interface'] = $pfb['outbound_floating'];
10334
							$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $outbound_interface, 'match_out');
10335
							$new_rules[] = $cb_rules;
10336
							$pfbrunonce = FALSE;
10337
						}
10338
					}
10339
					if ($pfb['order'] == 'order_2' && !empty($permit_rules)) {
10340
						foreach ($permit_rules as $cb_rules) {
10341
							if ($cb_rules['interface'] == $outbound_interface) {
10342
								$new_rules[] = $cb_rules;
10343
							}
10344
						}
10345
					}
10346
					if (!empty($pfb['deny_outbound'])) {
10347
						foreach ($pfb['deny_outbound'] as $cb_rules) {
10348
							$cb_rules['interface'] = $outbound_interface;
10349
							$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $outbound_interface, 'deny_out');
10350
							$new_rules[] = $cb_rules;
10351
						}
10352
					}
10353
				}
10354
			}
10355

    
10356
			if ($pfb['float'] == 'on' && in_array($pfb['order'], array('order_0', 'order_3', 'order_4'))) {
10357
				if ($pfb['order'] != 'order_3') {
10358
					$rule_order = array($fother_rules, $fpermit_rules, $fmatch_rules);
10359
				} else {
10360
					$rule_order = array($fpermit_rules, $fmatch_rules, $fother_rules);
10361
				}
10362
				foreach ($rule_order as $rtype) {
10363
					if (!empty($rtype)) {
10364
						foreach ($rtype as $cb_rules) {
10365
							$new_rules[] = $cb_rules;
10366
						}
10367
					}
10368
				}
10369
			}
10370
			if ($pfb['float'] == 'on' && in_array($pfb['order'], array('order_1', 'order_2')) && !empty($fother_rules)) {
10371
				foreach ($fother_rules as $cb_rules) {
10372
					$new_rules[] = $cb_rules;
10373
				}
10374
			}
10375
			if ($pfb['float'] == '' && $pfb['order'] != 'order_1' && !empty($fother_rules)) {
10376
				foreach ($fother_rules as $cb_rules) {
10377
					$new_rules[] = $cb_rules;
10378
				}
10379
			}
10380
			if ($pfb['order'] == 'order_4' && !empty($other_rules)) {
10381
				foreach ($other_rules as $cb_rules) {
10382
					$new_rules[] = $cb_rules;
10383
				}
10384
			}
10385
			if ($pfb['order'] == 'order_4' && !empty($permit_rules)) {
10386
				foreach ($permit_rules as $cb_rules) {
10387
					$new_rules[] = $cb_rules;
10388
				}
10389
			}
10390
			if ($pfb['order'] == 'order_3' && !empty($permit_rules)) {
10391
				foreach ($permit_rules as $cb_rules) {
10392
					$new_rules[] = $cb_rules;
10393
				}
10394
			}
10395
			if ($pfb['order'] != 'order_4' && !empty($other_rules)) {
10396
				foreach ($other_rules as $cb_rules) {
10397
					$new_rules[] = $cb_rules;
10398
				}
10399
			}
10400

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

    
10405
			// Remove 'created' tag (New vs old rules array comparison)
10406
			foreach ($new_rules as $rule) {
10407
				if (isset($rule['created'])) {
10408
					unset($rule['created']);
10409
				}
10410
				$new_rules_nocreated[] = $rule;
10411
			}
10412

    
10413
			// Update config.xml, if changes required
10414
			if ($orig_rules_nocreated != $new_rules_nocreated) {
10415
				config_set_path('filter/rule', $new_rules);
10416
				write_config('pfBlockerNG: saving Firewall rules');
10417
			}
10418
		}
10419
		else {
10420
			$log = "\n\n{$message}\n";
10421
			pfb_logger("{$log}", 1);
10422
		}
10423
	}
10424

    
10425
	#################################
10426
	#	pfSense Integration	#
10427
	#################################
10428

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

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

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

    
10441
		// Remove all pfB aliastables
10442
		exec("{$pfb['pfctl']} -s Tables | {$pfb['grep']} '^pfB_'", $pfb_tables);
10443
		if (isset($pfb_tables)) {
10444
			foreach ($pfb_tables as $pfb_table) {
10445
				$pfb_table_esc = escapeshellarg($pfb_table);
10446
				exec("{$pfb['pfctl']} -t {$pfb_table_esc} -T kill 2>&1", $result);
10447
			}
10448
		}
10449

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

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

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

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

    
10471
			// Only Save Aliases that have been updated.
10472
			// When 'Reputation' is used, all aliases need to be updated when any alias has been updated.
10473
			$final_alias = array();
10474
			if ($pfb['repcheck'] && ($pfb['drep'] == 'on' || $pfb['prep'] == 'on')) {
10475
				if (!empty($pfb_alias_lists_all)) {
10476
					$final_alias = array_unique($pfb_alias_lists_all);
10477
				}
10478
			} else {
10479
				if (!empty($pfb_alias_lists)) {
10480
					$final_alias = array_unique($pfb_alias_lists);
10481
				}
10482
			}
10483

    
10484
			if (!empty($final_alias)) {
10485
				foreach ($final_alias as $final) {
10486
					$log = "\n Updating: {$final}\n";
10487
					pfb_logger("{$log}", 1);
10488
					$result = '';
10489
					if (file_exists("{$pfb['aliasdir']}/{$final}.txt")) {
10490
						$final_esc	= escapeshellarg($final);
10491
						$final_path_esc	= escapeshellarg("{$pfb['aliasdir']}/{$final}.txt");
10492
						exec("{$pfb['pfctl']} -t {$final_esc} -T replace -f {$final_path_esc} 2>&1", $result);
10493
						$log = implode($result);
10494
					} else {
10495
						$log = "Aliastable file not found\n";
10496
					}
10497
					pfb_logger("{$log}", 1);
10498
				}
10499
				pfb_logger("\n", 1);
10500

    
10501
				// Call function for Ramdisk processes.
10502
				pfb_aliastables('update');
10503
			} else {
10504
				$log = "No Changes to Aliases, Skipping pfctl Update\n";
10505
				pfb_logger("{$log}", 1);
10506
			}
10507
		}
10508
	}
10509
	unset($rules, $new_rules, $orig_rules_nocreated, $new_rules_nocreated);
10510

    
10511

    
10512
	#################################
10513
	#	SAVE CONFIGURATION	#
10514
	#################################
10515

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

    
10522
	// Only save config.xml, if changes are found.
10523
	if ($pfb['conf_mod'] && isset($pfb_config)) {
10524
		pfb_logger("\nSaving config changes", 1);
10525

    
10526
		// Reload config.xml to get any recent changes and merge/save changes.
10527
		config_read_file(false, true);
10528
		config_set_path('', array_replace_recursive(config_get_path(''), $pfb_config));
10529
		write_config('pfBlockerNG: save settings');
10530

    
10531
		pfb_logger("... completed", 1);
10532
	}
10533

    
10534

    
10535
	// Query NAT Rules for previous IPv4 pfBlockerNG aliasnames which are not in the new '_v4' suffix format
10536
	$pfb_found	= FALSE;
10537
	config_read_file(false, true);
10538

    
10539
	foreach (config_get_path('nat/rule', []) as $key => $ex_nat) {
10540
		foreach (array('source', 'destination') as $rtype) {
10541
			if (isset($ex_nat[$rtype]['address']) && substr($ex_nat[$rtype]['address'], 0, 4) == 'pfB_') {
10542
				$pfb_suffix = substr($ex_nat[$rtype]['address'], -3);
10543

    
10544
				if ($pfb_suffix == '_v6') {
10545
					continue;
10546
				}
10547

    
10548
				// Add '_v4' suffix if missing
10549
				elseif ($pfb_suffix != '_v4') {
10550
					config_set_path("nat/rule/{$key}/{$rtype}/address", "{$ex_nat[$rtype]['address']}_v4");
10551
					$pfb_found = TRUE;
10552
				}
10553
			}
10554
		}
10555
	}
10556

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

    
10561
	#################################
10562
	#  Call filter_configure once	#
10563
	#################################
10564

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

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

    
10571
		// Remove IP Cache file
10572
		unlink_if_exists($pfb['ip_cache']);
10573

    
10574
		require_once('filter.inc');
10575
		filter_configure();
10576

    
10577
		// Stop/Restart pfb_filter service for new rule changes
10578
		if ($pfb['enable'] == '') {
10579
			if (is_service_running('pfb_filter')) {
10580
				$log = 'Stopping firewall filter daemon';
10581
				stop_service('pfb_filter');
10582
			}
10583
		}
10584
		else {
10585
			$log = 'Restarting firewall filter daemon';
10586
			restart_service('pfb_filter');
10587
		}
10588
	}
10589
	else {
10590
		// Stop/Start Firewall filter daemon
10591
		if ($pfb['enable'] == '' && is_service_running('pfb_filter')) {
10592
			$log = 'Stopping firewall filter daemon';
10593
			stop_service('pfb_filter');
10594
		}
10595
		elseif ($pfb['enable'] == 'on' && !is_service_running('pfb_filter')) {
10596
			$log = 'Starting firewall filter daemon';
10597
			start_service('pfb_filter');
10598
		}
10599
	}
10600

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

    
10606

    
10607
	#################################
10608
	#	KILL STATES		#
10609
	#################################
10610

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

    
10615

    
10616
	#########################################
10617
	#	XMLRPC - sync process		#
10618
	#########################################
10619

    
10620
	if (!platform_booting() && !$g['pfblockerng_install']) {
10621
		pfblockerng_sync_on_changes();
10622
	}
10623

    
10624
	#########################################
10625
	#	Define/Apply CRON Jobs		#
10626
	#########################################
10627

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

    
10634
		// Define CRON hour (CRON interval & start hour)
10635
		if ($pfb['interval'] == 1) {
10636
			$pfb_hour = '*';
10637
		} elseif ($pfb['interval'] == 24) {
10638
			$pfb_hour = $pfb['24hour'];
10639
		} else {
10640
			$pfb_hour = implode(',', pfb_cron_base_hour($pfb['interval']));
10641
		}
10642

    
10643
		if ($pfb_hour == 0 || !empty(pfb_filter($pfb_hour, PFB_FILTER_CSV_CRON, 'Define pfBlockerNG CRON job'))) {
10644
			$pfb_mday	= '*';
10645
			$pfb_month	= '*';
10646
			$pfb_wday	= '*';
10647
			$pfb_who	= 'root';
10648

    
10649
			// Determine if CRON job requires updating
10650
			if (!pfblockerng_cron_exists($pfb_cmd, $pfb['min'], $pfb_hour, $pfb_mday, $pfb_wday)) {
10651
				install_cron_job('pfblockerng.php cron', false);
10652
				install_cron_job($pfb_cmd, true, $pfb['min'], $pfb_hour, $pfb_mday, $pfb_month, $pfb_wday, $pfb_who);
10653
			}
10654
		}
10655
		else {
10656
			pfb_logger("\n Failed to create pfBlockerNG cron task [{$pfb_hour}]", 1);
10657
		}
10658
	}
10659
	else {
10660
		// Clear any existing pfBlockerNG CRON jobs
10661
		install_cron_job('pfblockerng.php cron', false);
10662
	}
10663

    
10664
	// Define pfBlockerNG MaxMind/TOP1M/ASN Cron job
10665
	if ($pfb['enable'] == 'on') {
10666
		$pfb_gcmd = "/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php dcc >> {$pfb['extraslog']} 2>&1";
10667
		// CRON hour is randomized between 0-23 Hour to minimize effect on Servers
10668
		$pfb_gmin	= '0';
10669
		$pfb_ghour	= rand(0,23);
10670
		$pfb_gmday	= '*';
10671
		$pfb_gmonth	= '*';
10672
		$pfb_gwday	= '*';
10673
		$pfb_gwho	= 'root';
10674

    
10675
		// Determine if CRON job requires updating
10676
		if (!pfblockerng_cron_exists($pfb_gcmd, $pfb_gmin, 'random', $pfb_gmday, $pfb_gwday)) {
10677
			install_cron_job('pfblockerng.php dcc', false);
10678
			install_cron_job($pfb_gcmd, true, $pfb_gmin, $pfb_ghour, $pfb_gmday, $pfb_gmonth, $pfb_gwday, $pfb_gwho);
10679
		}
10680
	}
10681
	else {
10682
		// Clear any existing pfBlockerNG CRON jobs
10683
		install_cron_job('pfblockerng.php dcc', false);
10684
	}
10685

    
10686

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

    
10694
		$bl_string = '';
10695
		$selected = array_flip(explode(',', $pfb['blconfig']['blacklist_selected'])) ?: array();
10696
		foreach ($pfb['blconfig']['item'] as $item) {
10697
			if (isset($selected[$item['xml']]) && !empty($item['selected'])) {
10698
				$bl_string .= ",{$item['xml']}";
10699
			}
10700
		}
10701
		$bl_string = pfb_filter(ltrim($bl_string, ','), PFB_FILTER_CSV, 'Define pfBlockerNG Blacklist CRON job');
10702

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

    
10706
			$pfb_bmin	= '0';
10707
			$pfb_bhour	= rand(0,23);
10708
			$pfb_bmday	= '*';
10709
			$pfb_bmonth	= '*';
10710
			$pfb_bwday	= ($pfb['blconfig']['blacklist_freq'] == 'Weekly' ? '7' : '*');
10711
			$pfb_bwho	= 'root';
10712

    
10713
			// Determine if CRON job requires updating
10714
			if (!pfblockerng_cron_exists($pfb_bcmd, $pfb_bmin, 'random', $pfb_bmday, $pfb_bwday)) {
10715
				install_cron_job('pfblockerng.php bl', false);
10716
				install_cron_job($pfb_bcmd, true, $pfb_bmin, $pfb_bhour, $pfb_bmday, $pfb_bmonth, $pfb_bwday, $pfb_bwho);
10717
			}
10718
		}
10719
		else {
10720
			pfb_logger("\n Failed to create pfBlockerNG Blacklist cron task [{$bl_string}]", 1);
10721
		}
10722
	}
10723
	else {
10724
		// Clear any existing pfBlockerNG Blacklist CRON jobs
10725
		install_cron_job('pfblockerng.php bl', false);
10726
	}
10727

    
10728
	// Define pfBlockerNG clear [ dnsbl and/or IP ] counter CRON job
10729
	foreach (array( 'clearip', 'cleardnsbl') as $type) {
10730
		$pfb_cmd = "/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php {$type} >/dev/null 2>&1";
10731
		if (config_get_path("installedpackages/pfblockerngglobal/widget-{$type}") !== null) {
10732
			if (config_get_path("installedpackages/pfblockerngglobal/widget-{$type}") != 'never') {
10733
				$pfb_day = '*';
10734
				if (config_get_path("installedpackages/pfblockerngglobal/widget-{$type}") == 'weekly') {
10735
					$pfb_day = '7';
10736
				}
10737

    
10738
				// Remove unreferenced 'daily' or 'weekly' cron job
10739
				$pfb_other = ($pfb_day == '*') ? '7' : '*';
10740
				if (pfblockerng_cron_exists($pfb_cmd, '0', '0', '*', $pfb_other)) {
10741
					install_cron_job("pfblockerng.php {$type}", false);
10742
				}
10743

    
10744
				if (!pfblockerng_cron_exists($pfb_cmd, '0', '0', '*', $pfb_day)) {
10745
					install_cron_job($pfb_cmd, true, '0', '0', '*', '*', $pfb_day, 'root');
10746
				}
10747
			}
10748
			else {
10749
				if (pfblockerng_cron_exists($pfb_cmd, '0', '0', '*', '*')) {
10750
					install_cron_job("pfblockerng.php {$type}", false);
10751
			}
10752
				if (pfblockerng_cron_exists($pfb_cmd, '0', '0', '*', '7')) {
10753
					install_cron_job("pfblockerng.php {$type}", false);
10754
				}
10755
			}
10756
		}
10757
		else {
10758
			if (pfblockerng_cron_exists($pfb_cmd, '0', '0', '*', '*')) {
10759
				install_cron_job("pfblockerng.php {$type}", false);
10760
			}
10761
			if (pfblockerng_cron_exists($pfb_cmd, '0', '0', '*', '7')) {
10762
				install_cron_job("pfblockerng.php {$type}", false);
10763
			}
10764
		}
10765
	}
10766

    
10767
	#################################
10768
	#	FINAL REPORTING		#
10769
	#################################
10770

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

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

    
10785

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

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

    
10794
	update_status("Removing pfBlockerNG...");
10795
	sync_package_pfblockerng();
10796

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

    
10804
		// Remove all pfB aliastables
10805
		exec("{$pfb['pfctl']} -s Tables | {$pfb['grep']} '^pfB_'", $pfb_tables);
10806
		if (isset($pfb_tables)) {
10807
			foreach ($pfb_tables as $pfb_table) {
10808
				$pfb_table_esc = escapeshellarg($pfb_table);
10809
				exec("{$pfb['pfctl']} -t {$pfb_table_esc} -T kill 2>&1", $result);
10810
			}
10811
		}
10812

    
10813
		// Remove aliastables archive and earlyshellcmd if found.
10814
		@unlink_if_exists("{$pfb['aliasarchive']}");
10815
		if (config_get_path('system/earlyshellcmd') !== null) {
10816
			$a_earlyshellcmd = config_get_path('system/earlyshellcmd', '');
10817
			if (preg_grep("/pfblockerng.sh aliastables/", $a_earlyshellcmd)) {
10818
				config_set_path('system/earlyshellcmd',
10819
								preg_grep("/pfblockerng.sh aliastables/", $a_earlyshellcmd, PREG_GREP_INVERT));
10820
			}
10821
		}
10822
		foreach (config_get_path('installedpackages/shellcmdsettings/config', []) as $key => $shellcmd) {
10823
			if (strpos($shellcmd['cmd'], 'pfblockerng.sh aliastables') !== FALSE) {
10824
				config_del_path("installedpackages/shellcmdsettings/config/{$key}");
10825
			}
10826
		}
10827

    
10828
		// Remove settings from config.xml
10829
		pfb_remove_config_settings();
10830

    
10831
		unlink_if_exists("{$pfb['dnsbl_conf']}");
10832
		unlink_if_exists("{$pfb['dnsbl_cert']}");
10833
		unlink_if_exists("{$pfb['aliasarchive']}");
10834
		unlink_if_exists("{$pfb['dnsbl_info']}");
10835
		unlink_if_exists("{$pfb['dnsbl_resolver']}");
10836

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

    
10841
		// Remove widget (code from Snort deinstall)
10842
		$pfb['widgets'] = config_get_path('widgets/sequence', '');
10843
		if (!empty($pfb['widgets'])) {
10844
			$widgetlist = explode(',', $pfb['widgets']);
10845
			foreach ($widgetlist as $key => $widget) {
10846
				if (strpos($widget, 'pfblockerng') !== FALSE) {
10847
					unset($widgetlist[$key]);
10848
				}
10849
			}
10850
			config_set_path('widgets/sequence', implode(',', $widgetlist));
10851
			write_config('pfBlockerNG: Remove widget', false);
10852
		}
10853
	}
10854
	else {
10855
		update_status(" All customizations/data will be retained...");
10856
	}
10857

    
10858
	unlink_if_exists('/usr/local/sbin/lighttpd_pfb');
10859
	unlink_if_exists('/usr/local/bin/php_pfb');
10860
	unlink_if_exists('/usr/local/sbin/clog_pfb');
10861
	unlink_if_exists('/usr/bin/tail_pfb');
10862
	unlink_if_exists('/usr/local/etc/rc.d/pfb_filter.sh');
10863
	unlink_if_exists('/usr/local/etc/rc.d/pfb_dnsbl.sh');
10864

    
10865
	unlink_if_exists("{$pfb['dnsbl_cache']}");
10866
	unlink_if_exists("/var/tmp/unbound_cache_*");
10867
	unlink_if_exists("{$pfb['ip_cache']}");
10868

    
10869
	// Remove incorrect xml setting
10870
	config_del_path('installedpackages/pfblockerngantartica');
10871

    
10872
	update_status(" done.\n");
10873
}
10874

    
10875

    
10876
// Remove settings from config.xml
10877
function pfb_remove_config_settings() {
10878
	global $pfb;
10879

    
10880
	foreach (array(	'pfblockerng',
10881
			'pfblockerngglobal',
10882
			'pfblockerngsync',
10883
			'pfblockerngreputation',
10884
			'pfblockerngipsettings',
10885
			'pfblockernglistsv4',
10886
			'pfblockernglistsv6',
10887
			'pfblockerngdnsbl',
10888
			'pfblockerngdnsblsettings',
10889
			'pfblockerngsafesearch',
10890
			'pfblockerngblacklist',
10891
			'pfblockerngafrica',
10892
			'pfblockerngantarctica',
10893
			'pfblockerngasia',
10894
			'pfblockerngeurope',
10895
			'pfblockerngnorthamerica',
10896
			'pfblockerngoceania',
10897
			'pfblockerngsouthamerica',
10898
			'pfblockerngtopspammers',
10899
			'pfblockerngproxyandsatellite' ) as $type) {
10900

    
10901
		config_del_path("installedpackages/{$type}");
10902
	}
10903
}
10904

    
10905

    
10906
/* Uses XMLRPC to synchronize the changes to a remote node */
10907
function pfblockerng_sync_on_changes() {
10908
	// Create array of sync settings and exit if sync is disabled.
10909
	$pfb_sync = config_get_path('installedpackages/pfblockerngsync/config/0', []);
10910
	if (!empty($pfb_sync)) {
10911
		if ($pfb_sync['varsynconchanges'] == 'disabled' || empty($pfb_sync['varsynconchanges'])) {
10912
			return;
10913
		}
10914
		$synctimeout = $pfb_sync['varsynctimeout'] ?: 150;
10915
	} else {
10916
		return;
10917
	}
10918

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

    
10922
	if (config_get_path('installedpackages/pfblockerngsync/config') != null) {
10923
		switch ($pfb_sync['varsynconchanges']) {
10924
			case 'manual':
10925
				if (isset($pfb_sync['row'])) {
10926
					$rs = $pfb_sync['row'];
10927
				} else {
10928
					log_error('[pfBlockerNG] Manual XMLRPC sync is enabled but there are no replication targets configured.');
10929
					return;
10930
				}
10931
				break;
10932
			case 'auto':
10933
				$hasync = config_get_path('hasync');
10934
				if ($hasync != null) {
10935
					$system_carp			= $hasync;
10936
					$rs[0]['varsyncipaddress']	= $system_carp['synchronizetoip'];
10937
					$rs[0]['varsyncusername']	= $system_carp['username'];
10938
					$rs[0]['varsyncpassword']	= $system_carp['password'];
10939
					$rs[0]['varsyncdestinenable']	= FALSE;
10940

    
10941
					// XMLRPC sync is currently only supported over connections using the same protocol and port as this system
10942
					if (config_get_path('system/webgui/protocol') == 'http') {
10943
						$rs[0]['varsyncprotocol']	= 'http';
10944
						$rs[0]['varsyncport']		= config_get_path('system/webgui/port', '80');
10945
					} else {
10946
						$rs[0]['varsyncprotocol']	= 'https';
10947
						$rs[0]['varsyncport']		= config_get_path('system/webgui/port', '443');
10948
					}
10949

    
10950
					if (empty($system_carp['synchronizetoip'])) {
10951
						log_error('[pfBlockerNG] Auto XMLRPC sync is enabled but there is no sync IP address configured.');
10952
						return;
10953
					} else {
10954
						$rs[0]['varsyncdestinenable']	= TRUE;
10955
					}
10956
				} else {
10957
					log_error('[pfBlockerNG] Auto XMLRPC sync is enabled but there are no replication targets configured.');
10958
					return;
10959
				}
10960
				break;
10961
			default:
10962
				return;
10963
				break;
10964
		}
10965
		if (isset($rs)) {
10966
			foreach ($rs as $sh) {
10967
				// Only sync enabled replication targets
10968
				if ($sh['varsyncdestinenable']) {
10969
					$sync_to_ip	= $sh['varsyncipaddress'];
10970
					$port		= $sh['varsyncport'];
10971
					$password	= $sh['varsyncpassword'];
10972
					$protocol	= $sh['varsyncprotocol'];
10973
					$username	= $sh['varsyncusername'] ?: 'admin';
10974

    
10975
					$validate = TRUE;
10976
					$error = '| ';
10977

    
10978
					if (empty($password)) {
10979
						$error .= 'Password parameter missing. | ';
10980
						$validate = FALSE;
10981
					}
10982
					if (!is_ipaddr($sync_to_ip) && !is_hostname($sync_to_ip) && !is_domain($sync_to_ip)) {
10983
						$error .= 'Mis-configured Target IP/Host address. | ';
10984
						$validate = FALSE;
10985
					}
10986
					if (!is_port($port)) {
10987
						$error .= 'Mis-configured Target Port setting. |';
10988
						$validate = FALSE;
10989
					}
10990

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

    
10995
						if ($success) {
10996
							pfb_logger(" done.\n", 1);
10997
							syslog(LOG_NOTICE, "[pfBlockerNG] XMLRPC sync to [ {$sync_to_ip}:{port} ] completed successfully.");
10998
						} else {
10999
							pfb_logger(" Failed!\n", 1);
11000
						}
11001
					} else {
11002
						pfb_logger(" terminated due to the following error(s): {$error}", 1);
11003
						log_error("[pfBlockerNG] XMLRPC sync to [ {$sync_to_ip}:{port} ] terminated due to the following error(s): {$error}");
11004
					}
11005
				}
11006
			}
11007
		}
11008
	}
11009
	pfb_logger("\n======================================================================\n", 1);
11010
}
11011

    
11012

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

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

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

    
11026
	// If User Disabled, remove 'General/IP/DNSBL Tab Customizations' from Sync
11027
	if (config_get_path('installedpackages/pfblockerngsync/config/0/syncinterfaces') != 'on') {
11028
		if (config_get_path('installedpackages/pfblockerng') != null) {
11029
			$xml['pfblockerng']		= config_get_path('installedpackages/pfblockerng');
11030
		}
11031
		if (config_get_path('installedpackages/pfblockerngipsettings') != null) {
11032
			$xml['pfblockerngipsettings']	= config_get_path('installedpackages/pfblockerngipsettings');
11033
		}
11034
		if (config_get_path('installedpackages/pfblockerngdnsblsettings') != null) {
11035
			$xml['pfblockerngdnsblsettings']= config_get_path('installedpackages/pfblockerngdnsblsettings');
11036

    
11037
			// Increase CARP Advskew value, see https://redmine.pfsense.org/issues/11964
11038
			if (isset($xml['pfblockerngdnsblsettings']['config'][0]['pfb_dnsvip_skew'])) {
11039
				$advskew = intval($xml['pfblockerngdnsblsettings']['config'][0]['pfb_dnsvip_skew']);
11040
				$advskew += 100;
11041
				if ($advskew > 254) {
11042
					$advskew = 254;
11043
				}
11044
				$xml['pfblockerngdnsblsettings']['config'][0]['pfb_dnsvip_skew'] = $advskew;
11045
			}
11046
		}
11047
	}
11048

    
11049
	if (config_get_path('installedpackages/pfblockernglistsv4') !== null)
11050
		$xml['pfblockernglistsv4']		= config_get_path('installedpackages/pfblockernglistsv4');
11051
	if (config_get_path('installedpackages/pfblockernglistsv6') !== null)
11052
		$xml['pfblockernglistsv6']		= config_get_path('installedpackages/pfblockernglistsv6');
11053
	if (config_get_path('installedpackages/pfblockerngreputation') !== null)
11054
		$xml['pfblockerngreputation']		= config_get_path('installedpackages/pfblockerngreputation');
11055
	if (config_get_path('installedpackages/pfblockerngtopspammers') !== null)
11056
		$xml['pfblockerngtopspammers']		= config_get_path('installedpackages/pfblockerngtopspammers');
11057
	if (config_get_path('installedpackages/pfblockerngafrica') !== null)
11058
		$xml['pfblockerngafrica']		= config_get_path('installedpackages/pfblockerngafrica');
11059
	if (config_get_path('installedpackages/pfblockerngantarctica') !== null)
11060
		$xml['pfblockerngantarctica']		= config_get_path('installedpackages/pfblockerngantarctica');
11061
	if (config_get_path('installedpackages/pfblockerngasia') !== null)
11062
		$xml['pfblockerngasia']			= config_get_path('installedpackages/pfblockerngasia');
11063
	if (config_get_path('installedpackages/pfblockerngeurope') !== null)
11064
		$xml['pfblockerngeurope']		= config_get_path('installedpackages/pfblockerngeurope');
11065
	if (config_get_path('installedpackages/pfblockerngnorthamerica') !== null)
11066
		$xml['pfblockerngnorthamerica']		= config_get_path('installedpackages/pfblockerngnorthamerica');
11067
	if (config_get_path('installedpackages/pfblockerngoceania') !== null)
11068
		$xml['pfblockerngoceania']		= config_get_path('installedpackages/pfblockerngoceania');
11069
	if (config_get_path('installedpackages/pfblockerngsouthamerica') !== null)
11070
		$xml['pfblockerngsouthamerica']		= config_get_path('installedpackages/pfblockerngsouthamerica');
11071
	if (config_get_path('installedpackages/pfblockerngproxyandsatellite') !== null)
11072
		$xml['pfblockerngproxyandsatellite']	= config_get_path('installedpackages/pfblockerngproxyandsatellite');
11073
	if (config_get_path('installedpackages/pfblockerngdnsbl') !== null)
11074
		$xml['pfblockerngdnsbl']		= config_get_path('installedpackages/pfblockerngdnsbl');
11075
	if (config_get_path('installedpackages/pfblockerngblacklist') !== null)
11076
		$xml['pfblockerngblacklist']		= config_get_path('installedpackages/pfblockerngblacklist');
11077
	if (config_get_path('installedpackages/pfblockerngglobal') !== null)
11078
		$xml['pfblockerngglobal']		= config_get_path('installedpackages/pfblockerngglobal');
11079
	if (config_get_path('installedpackages/pfblockerngsafesearch') !== null)
11080
		$xml['pfblockerngsafesearch']		= config_get_path('installedpackages/pfblockerngsafesearch');
11081

    
11082

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

    
11086
		require_once('xmlrpc.inc');
11087
		require_once('xmlrpc_client.inc');
11088

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

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

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

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

    
11105
		if (!$resp) {
11106
			log_error("[pfBlockerNG] XMLRPC communications error occurred while attempting sync with {$url}:{$port}.");
11107
			file_notice('pfBlockerNG Sync settings', $error, 'pfBlockerNG', '/pfblockerng/pfblockerng_sync.php', 2);
11108
			$success = FALSE;
11109
		} elseif ($resp->faultCode()) {
11110
			$cli->setDebug(1);
11111
			$resp = $cli->send($msg, $synctimeout);
11112
			log_error("[pfBlockerNG] XMLRPC errors syncing with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString());
11113
			file_notice('pfBlockerNG Sync settings', $error, 'pfBlockerNG', '/pfblockerng/pfblockerng_sync.php', 2);
11114
			$success = FALSE;
11115
		}
11116
		return $success;
11117
	}
11118
	else {
11119
		require_once('xmlrpc_client.inc');
11120

    
11121
		// xmlrpc cannot encode NULL objects/arrays
11122
		foreach ($xml as $xmlkey => $xmlvalue) {
11123
			if (gettype($xmlvalue) == 'NULL') {
11124
				$xml[$xmlkey] = array();
11125
			}
11126
		}
11127

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

    
11133
		if (!isset($resp)) {
11134
			return FALSE;
11135
		} else {
11136
			return TRUE;
11137
		}
11138
	}
11139
}
(3-3/5)