|
<?php
|
|
/*
|
|
* pfblockerng.inc
|
|
*
|
|
* part of pfSense (https://www.pfsense.org)
|
|
* Copyright (c) 2015-2025 Rubicon Communications, LLC (Netgate)
|
|
* Copyright (c) 2015-2024 BBcan177@gmail.com
|
|
* All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
require_once('config.lib.inc');
|
|
require_once('util.inc');
|
|
require_once('functions.inc');
|
|
require_once('pkg-utils.inc');
|
|
require_once('pfsense-utils.inc');
|
|
require_once('globals.inc');
|
|
require_once('services.inc');
|
|
require_once('service-utils.inc');
|
|
if (file_exists('/usr/local/pkg/pfblockerng/pfblockerng_extra.inc')) {
|
|
require_once('/usr/local/pkg/pfblockerng/pfblockerng_extra.inc'); // 'include functions' not yet merged into pfSense
|
|
}
|
|
|
|
global $g, $pfb;
|
|
if (!is_array($pfb)) {
|
|
$pfb = array();
|
|
}
|
|
|
|
// Folders
|
|
$pfb['dbdir'] = "{$g['vardb_path']}/pfblockerng";
|
|
$pfb['aliasdir'] = "{$g['vardb_path']}/aliastables";
|
|
$pfb['logdir'] = "{$g['varlog_path']}/pfblockerng";
|
|
$pfb['etdir'] = "{$pfb['dbdir']}/ET";
|
|
$pfb['nativedir'] = "{$pfb['dbdir']}/native";
|
|
$pfb['denydir'] = "{$pfb['dbdir']}/deny";
|
|
$pfb['matchdir'] = "{$pfb['dbdir']}/match";
|
|
$pfb['permitdir'] = "{$pfb['dbdir']}/permit";
|
|
$pfb['origdir'] = "{$pfb['dbdir']}/original";
|
|
$pfb['dnsdir'] = "{$pfb['dbdir']}/dnsbl";
|
|
$pfb['dnsorigdir'] = "{$pfb['dbdir']}/dnsblorig";
|
|
$pfb['dnsalias'] = "{$pfb['dbdir']}/dnsblalias";
|
|
$pfb['geoipshare'] = '/usr/local/share/GeoIP';
|
|
$pfb['ccdir'] = '/usr/local/share/GeoIP/cc';
|
|
$pfb['ccdir_tmp'] = '/tmp/geoip_cc';
|
|
$pfb['dnsbl_tmp'] = '/tmp/dnsbl_tmp';
|
|
$pfb['dnsbl_tmpdir'] = '/tmp/DNSBL_TMP';
|
|
|
|
// Application Paths
|
|
$pfb['grep'] = '/usr/bin/grep';
|
|
$pfb['ggrep'] = '/usr/local/bin/ggrep'; // textproc/gnugrep
|
|
$pfb['awk'] = '/usr/bin/awk';
|
|
$pfb['cut'] = '/usr/bin/cut';
|
|
$pfb['sed'] = '/usr/bin/sed';
|
|
$pfb['cat'] = '/bin/cat';
|
|
$pfb['ls'] = '/bin/ls';
|
|
$pfb['pfctl'] = '/sbin/pfctl';
|
|
|
|
// Folder Array
|
|
$pfb['folder_array'] = array( "{$pfb['dbdir']}", "{$pfb['logdir']}", "{$pfb['ccdir']}", "{$pfb['origdir']}", "{$pfb['nativedir']}",
|
|
"{$pfb['denydir']}", "{$pfb['matchdir']}","{$pfb['permitdir']}", "{$pfb['aliasdir']}",
|
|
"{$pfb['dnsdir']}", "{$pfb['dnsorigdir']}", "{$pfb['dnsalias']}");
|
|
|
|
// Files
|
|
$pfb['log'] = "{$pfb['logdir']}/pfblockerng.log";
|
|
$pfb['dnslog'] = "{$pfb['logdir']}/dnsbl.log";
|
|
$pfb['dnsreplylog'] = "{$pfb['logdir']}/dns_reply.log";
|
|
$pfb['ip_blocklog'] = "{$pfb['logdir']}/ip_block.log";
|
|
$pfb['ip_permitlog'] = "{$pfb['logdir']}/ip_permit.log";
|
|
$pfb['ip_matchlog'] = "{$pfb['logdir']}/ip_match.log";
|
|
$pfb['unilog'] = "{$pfb['logdir']}/unified.log";
|
|
$pfb['errlog'] = "{$pfb['logdir']}/error.log";
|
|
$pfb['pyerrlog'] = "{$pfb['logdir']}/py_error.log";
|
|
$pfb['extraslog'] = "{$pfb['logdir']}/extras.log";
|
|
$pfb['dnsbl_parse_err'] = "{$pfb['logdir']}/dnsbl_parsed_error.log";
|
|
|
|
$pfb['master'] = "{$pfb['dbdir']}/masterfile";
|
|
$pfb['supptxt'] = "{$pfb['dbdir']}/pfbsuppression.txt";
|
|
$pfb['dnsbl_supptxt'] = "{$pfb['dbdir']}/pfbdnsblsuppression.txt";
|
|
$pfb['geoip_isos'] = "{$pfb['dbdir']}/geoip.txt";
|
|
$pfb['dnsbl_info'] = '/var/unbound/pfb_py_dnsbl.sqlite';
|
|
$pfb['dnsbl_resolver'] = '/var/unbound/pfb_py_resolver.sqlite';
|
|
$pfb['dnsbl_cache'] = '/var/unbound/pfb_py_cache.sqlite';
|
|
$pfb['asn_cache'] = "{$pfb['dbdir']}/asn_cache.sqlite";
|
|
$pfb['ip_cache'] = "{$pfb['dbdir']}/ip_cache.sqlite";
|
|
|
|
$pfb['script'] = '/usr/local/pkg/pfblockerng/pfblockerng.sh';
|
|
$pfb['feeds'] = '/usr/local/www/pfblockerng/pfblockerng_feeds.json';
|
|
$pfb['aliasarchive'] = '/usr/local/etc/aliastables.tar.bz2';
|
|
|
|
$pfb['dnsbl_tld_txt'] = "{$pfb['dnsdir']}/DNSBL_TLD.txt";
|
|
$pfb['dnsbl_tld_data'] = '/usr/local/pkg/pfblockerng/dnsbl_tld';
|
|
$pfb['dnsbl_conf'] = '/var/unbound/pfb_dnsbl_lighty.conf';
|
|
$pfb['dnsbl_cert'] = '/var/unbound/dnsbl_cert.pem';
|
|
$pfb['unbound_py_conf'] = '/var/unbound/pfb_unbound.ini';
|
|
$pfb['unbound_py_wh'] = '/var/unbound/pfb_py_whitelist.txt';
|
|
$pfb['unbound_py_data'] = '/var/unbound/pfb_py_data.txt';
|
|
$pfb['unbound_py_zone'] = '/var/unbound/pfb_py_zone.txt';
|
|
$pfb['unbound_py_ss'] = '/var/unbound/pfb_py_ss.txt';
|
|
$pfb['unbound_py_count']= '/var/unbound/pfb_py_count';
|
|
|
|
$pfb['dnsbl_safesearch'] = '/usr/local/pkg/pfblockerng/pfb_dnsbl.safesearch.conf';
|
|
$pfb['dnsbl_youtube_restrict'] = '/usr/local/pkg/pfblockerng/pfb_dnsbl.youtube_restrict.conf';
|
|
$pfb['dnsbl_youtube_restrictmoderate'] = '/usr/local/pkg/pfblockerng/pfb_dnsbl.youtube_restrictmoderate.conf';
|
|
$pfb['dnsbl_doh'] = '/usr/local/pkg/pfblockerng/pfb_dnsbl.doh.conf';
|
|
|
|
// tmp files
|
|
$pfb['geoip_tmp'] = '/tmp/pfb_continent';
|
|
$pfb['ip_unlock'] = '/tmp/ip_unlock';
|
|
|
|
$pfb['dnsbl_tld_remove'] = '/tmp/dnsbl_tld_remove';
|
|
$pfb['dnsbl_add'] = '/tmp/dnsbl_add';
|
|
$pfb['dnsbl_add_zone'] = '/tmp/dnsbl_add_zone';
|
|
$pfb['dnsbl_add_data'] = '/tmp/dnsbl_add_data';
|
|
$pfb['dnsbl_remove'] = '/tmp/dnsbl_remove';
|
|
$pfb['dnsbl_remove_zone'] = '/tmp/dnsbl_remove_zone';
|
|
$pfb['dnsbl_remove_data'] = '/tmp/dnsbl_remove_data';
|
|
$pfb['dnsbl_unlock'] = '/tmp/dnsbl_unlock';
|
|
$pfb['states_tmp'] = '/tmp/pfb_states';
|
|
|
|
// Unbound files and folders
|
|
$pfb['dnsbl_file'] = '/var/unbound/pfb_dnsbl'; // Filename Extension not referenced
|
|
$pfb['dnsbldir'] = '/var/unbound';
|
|
|
|
// Array definitions
|
|
$pfb['continents'] = array ( 'Top Spammers' => 'pfB_Top',
|
|
'Africa' => 'pfB_Africa',
|
|
'Antarctica' => 'pfB_Antarctica',
|
|
'Asia' => 'pfB_Asia',
|
|
'Europe' => 'pfB_Europe',
|
|
'North America' => 'pfB_NAmerica',
|
|
'Oceania' => 'pfB_Oceania',
|
|
'South America' => 'pfB_SAmerica',
|
|
'Proxy and Satellite' => 'pfB_PS'
|
|
);
|
|
|
|
$pfb['continent_list'] = array_flip(array('pfB_Africa', 'pfB_Antarctica', 'pfB_Asia', 'pfB_Europe', 'pfB_NAmerica', 'pfB_Oceania', 'pfB_SAmerica', 'pfB_Top'));
|
|
|
|
// Base rule array
|
|
$pfb['base_rule_reg'] = array('ipprotocol' => 'inet');
|
|
|
|
// Floating rules, base rule array
|
|
$pfb['base_rule_float'] = array('quick' => 'yes', 'floating' => 'yes', 'ipprotocol' => 'inet');
|
|
|
|
// Define Arrays for managing the IP mastefile
|
|
foreach (array('existing', 'actual') as $pftype) {
|
|
$pfb[$pftype]['match'] = array();
|
|
$pfb[$pftype]['permit'] = array();
|
|
$pfb[$pftype]['deny'] = array();
|
|
$pfb[$pftype]['native'] = array();
|
|
$pfb[$pftype]['dnsbl'] = array();
|
|
}
|
|
|
|
// Default cURL options
|
|
$pfb['curl_defaults'] = array( CURLOPT_USERAGENT => 'pfSense/pfBlockerNG cURL download agent-' . system_get_uniqueid(),
|
|
CURLOPT_SSL_CIPHER_LIST => 'TLSv1.3, TLSv1.2',
|
|
CURLOPT_FOLLOWLOCATION => true,
|
|
CURLOPT_SSL_VERIFYPEER => true,
|
|
CURLOPT_SSL_VERIFYHOST => true,
|
|
CURLOPT_FRESH_CONNECT => true,
|
|
CURLOPT_FILETIME => true,
|
|
CURLOPT_TCP_NODELAY => true,
|
|
CURLOPT_CONNECTTIMEOUT => 15,
|
|
CURLOPT_AUTOREFERER => true,
|
|
CURLOPT_MAXREDIRS => 10,
|
|
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_NONE,
|
|
CURLOPT_FORBID_REUSE => true,
|
|
CURLOPT_SSL_ENABLE_ALPN => true,
|
|
CURLOPT_SSL_ENABLE_NPN => true,
|
|
);
|
|
|
|
// RFC7231 HTTP response codes
|
|
$pfb['rfc7231'] = array(
|
|
1 => 'Unsupported Protocol', 2 => 'Early Initilization failed', 3 => 'Malformed URL',
|
|
4 => 'Requested feature not found', 5 => 'Could not Resolve Proxy', 6 => 'Could not Resolve Host',
|
|
7 => 'Failed to connect', 8 => 'Failed to parse data', 9 => 'Denied access',
|
|
10 => 'FTP failed', 11 => 'FTP password failure', 12 => 'FTP Accept timeout',
|
|
13 => 'FTP PASV failure', 14 => 'FTP 227 Format failure', 15 => 'FTP Host lookup failure',
|
|
16 => 'HTTP2 framing layer failure', 17 => 'FTP Could not set transfer mode', 18 => 'File Transfer failure',
|
|
19 => 'FTP weird reply', 20 => 'Obsolete error', 21 => 'FTP quote command error',
|
|
22 => 'HTTP Returned Error code', 23 => 'Write Error', 24 => 'Obsolete error',
|
|
25 => 'FTP failted starting upload', 26 => 'Read Error', 27 => 'Memory allocation request error',
|
|
28 => 'Operation timed out', 29 => 'Obsolete error', 30 => 'FTP Port command failure',
|
|
31 => 'FTP REST failure', 32 => 'Obsolete error', 33 => 'Range Request failure',
|
|
34 => 'HTTP Post error', 35 => 'SSL Connect error', 37 => 'Could not read file',
|
|
38 => 'LDAP Cannot bind', 39 => 'LDAP Search failed', 40 => 'Obsolete error',
|
|
41 => 'Function not found', 42 => 'Aborted by callback', 43 => 'Bad parameter',
|
|
44 => 'Obsolete error', 45 => 'Interface failure', 46 => 'Obsolete error',
|
|
47 => 'Too many redirects', 48 => 'Unknown option', 49 => 'Setopt Syntax error',
|
|
50 => 'Obsolete error', 51 => 'Obsolete error', 52 => 'Curl got nothing',
|
|
53 => 'SSL Engine not found', 54 => 'SSL Engine failure', 55 => 'Failed sending data',
|
|
56 => 'Failure receving data', 57 => 'Obsolete error', 58 => 'Local client certificate error',
|
|
59 => 'Cipher error', 60 => 'SSL certificate error', 61 => 'Transfer encoding error',
|
|
62 => 'Obsolete error', 63 => 'Maximum file size exceeded', 64 => 'FTP SSL level failure',
|
|
65 => 'Rewinding operation failed', 66 => 'SSL Engine failure', 67 => 'Login Denied',
|
|
68 => 'TFTP server not found', 69 => 'TFTP permission error', 70 => 'Out of disk space on server',
|
|
71 => 'Illegal TFTP operation', 72 => 'Unknown TFTP transfer ID', 73 => 'File already exists',
|
|
74 => 'TFTP no such user', 75 => 'Obsolete error', 76 => 'Obsolete error',
|
|
77 => 'SSL CA certificate error', 78 => 'Remote file not found', 79 => 'SSH connection failure',
|
|
80 => 'SSH connection failure', 81 => 'Socket is not ready', 82 => 'CRL Bad File error',
|
|
83 => 'SSL Issurer failure', 84 => 'FTP PRET failure', 85 => 'RTSP CSeq number error',
|
|
86 => 'RTSP Session mismatch', 87 => 'FTP Bad file list', 88 => 'Chunk callback error',
|
|
89 => 'No connection available', 90 => 'Pin Key match failure', 91 => 'Invalid Certificate status',
|
|
92 => 'HTTP/2 framing error', 93 => 'Recursive API call error', 94 => 'Authentication function error',
|
|
95 => 'HTTP/3 layer error',
|
|
96 => 'QUIC connect error', 97 => 'Proxy handshake error', 98 => 'SSL Client certificate required',
|
|
99 => 'Unrecoveranle Poll error',
|
|
|
|
100 => '100 Continue', 101 => '101 Switching Protocols', 102 => '102 Processing',
|
|
|
|
200 => '200 OK', 201 => '201 Created', 202 => '202 Accepted',
|
|
203 => '203 Non-Authoritative Info', 204 => '204 No Content', 205 => '205 Reset Content',
|
|
206 => '206 Partial Content', 207 => '207 Multi-Status', 208 => '208 Already Reported',
|
|
226 => '226 IM Used',
|
|
|
|
300 => '300 Multiple Choices', 301 => '301 Moved Permanently', 302 => '302 Found',
|
|
303 => '303 See Other', 304 => '304 Not Modified', 305 => '305 Use Proxy',
|
|
306 => '306 Switch Proxy', 307 => '307 Temporary Redirect', 308 => '308 Permanent Redirect',
|
|
|
|
400 => '400 Bad Request', 401 => '401 Unauthorized', 402 => '402 Payment Required',
|
|
403 => '403 Forbidden', 404 => '404 Not Found', 405 => '405 Method Not Allowed',
|
|
406 => '406 Not Acceptable', 407 => '407 Proxy Authentication Required', 408 => '408 Request Timeout',
|
|
409 => '409 Conflict', 410 => '410 Gone', 411 => '411 Length Required',
|
|
412 => '412 Precondition Failed', 413 => '413 Request Entity Too Large', 414 => '414 Request-URI Too Long',
|
|
415 => '415 Unsupported Media Type', 416 => '416 Requested Range Not Satisfiable', 417 => '417 Expectation Failed',
|
|
418 => '418 Im a teapot', 419 => '419 Authentication Timeout', 420 => '420 Method Failure',
|
|
421 => '421 Misdirected Request', 422 => '422 Unprocessable Entity', 423 => '423 Locked',
|
|
424 => '424 Failed Dependency', 426 => '426 Upgrade Required', 428 => '428 Precondition Required',
|
|
429 => '429 Too Many Requests', 431 => '431 Request Header Fields Large', 440 => '440 Login Timeout',
|
|
444 => '444 No Response', 449 => '449 Retry With', 450 => '450 Blocked Windows Parental Controls',
|
|
451 => '451 Unavailable Legal Reasons', 494 => '494 Request Header too Large', 495 => '495 Cert Error',
|
|
496 => '496 No Cert', 497 => '497 HTTP to HTTPS', 498 => '498 Token expired/invalid',
|
|
499 => '499 Client Closed Request',
|
|
|
|
500 => '500 Internal Server Error', 501 => '501 Not Implemented', 502 => '502 Bad Gateway',
|
|
503 => '503 Service Unavailable', 504 => '504 Gateway Timeout', 505 => '505 HTTP Version Not Supported',
|
|
506 => '506 Variant Also Negotiates', 507 => '507 Insufficient Storage', 508 => '508 Loop Detected',
|
|
509 => '509 Bandwidth Limit Exceeded', 510 => '510 Not Extended', 511 => '511 Network Authentication Required',
|
|
521 => '521 Web Server is down', 598 => '598 Network read timeout error', 599 => '599 Network connect timeout error',
|
|
|
|
520 => 'CF 520 Unknown Error', 521 => 'CF 521 Web Server is Down', 522 => 'CF 522 Connection Timed Out',
|
|
523 => 'CF 523 Origin is Unreachable', 524 => 'CF 524 A Timeout Occured', 525 => 'CF 525 SSL Handshake Failed',
|
|
526 => 'CF 526 Invalid SSL Certificate',527 => 'CF 527 Railgun Error'
|
|
);
|
|
|
|
// File download Mime-Types
|
|
$pfb['mime_types'] = array_flip(array( 'inode/x-empty',
|
|
'text/x-file',
|
|
'text/plain',
|
|
'text/html',
|
|
'text/xml',
|
|
'text/csv',
|
|
'application/csv',
|
|
'application/json',
|
|
'application/x-ndjson',
|
|
'application/x-tar',
|
|
'application/gzip',
|
|
'application/x-gzip',
|
|
'application/x-bzip2',
|
|
'application/zip'));
|
|
|
|
// pfb_filter constants
|
|
define('PFB_FILTER_HTML', 1);
|
|
define('PFB_FILTER_URL', 2);
|
|
define('PFB_FILTER_WORD', 3);
|
|
define('PFB_FILTER_WORD_DOT', 4);
|
|
define('PFB_FILTER_TLD', 5);
|
|
define('PFB_FILTER_DOMAIN', 6);
|
|
define('PFB_FILTER_HOSTNAME', 7);
|
|
define('PFB_FILTER_IPV4', 8);
|
|
define('PFB_FILTER_IP', 9);
|
|
define('PFB_FILTER_ALPHA', 10);
|
|
define('PFB_FILTER_ALNUM', 11);
|
|
define('PFB_FILTER_NUM', 12);
|
|
define('PFB_FILTER_CSV', 13);
|
|
define('PFB_FILTER_CSV_WHOIS', 14);
|
|
define('PFB_FILTER_CSV_CRON', 15);
|
|
define('PFB_FILTER_FILE_MIME_COMPARE', 16);
|
|
define('PFB_FILTER_FILE_MIME', 17);
|
|
define('PFB_FILTER_FILE_MIME_COMPRESSED', 18);
|
|
define('PFB_FILTER_ATYPE', 19);
|
|
define('PFB_FILTER_HEX_COLOR', 20);
|
|
define('PFB_FILTER_ON_OFF', 21);
|
|
|
|
// Function to filter/sanitize user input
|
|
function pfb_filter($input, $type, $reference='Unknown', $default='', $escape=FALSE) {
|
|
global $pfb;
|
|
|
|
$header = "\n PFB_FILTER - {$type} | {$reference} [ NOW ]";
|
|
|
|
$return_type = $default;
|
|
if (in_array($type, array(PFB_FILTER_URL, PFB_FILTER_FILE_MIME_COMPARE, PFB_FILTER_FILE_MIME, PFB_FILTER_FILE_MIME_COMPRESSED))) {
|
|
$return_type = FALSE;
|
|
}
|
|
|
|
if (!in_array($type, array(PFB_FILTER_ON_OFF, PFB_FILTER_NUM))) {
|
|
if (empty($input) || is_null($input)) {
|
|
return $return_type;
|
|
}
|
|
}
|
|
|
|
// Check for control characters
|
|
if (is_array($input)) {
|
|
foreach ($input as $vline) {
|
|
if (preg_match("/[\p{C}]+/", $vline)) {
|
|
pfb_logger("{$header} Control characters found [ " . htmlspecialchars($vline) . " ]", 6);
|
|
return $return_type;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (preg_match("/[\p{C}]+/", $input)) {
|
|
pfb_logger("{$header} Control characters found [ " . htmlspecialchars($input) . " ]", 6);
|
|
return $return_type;
|
|
}
|
|
}
|
|
|
|
$result = FALSE;
|
|
switch ($type) {
|
|
case PFB_FILTER_HTML:
|
|
$result = htmlspecialchars(trim($input));
|
|
break;
|
|
case PFB_FILTER_URL:
|
|
// Validate URL input
|
|
$is_RSYNC = FALSE;
|
|
if (strpos($input, '::') !== FALSE && !$escape) {
|
|
$rsync = explode('::', $input);
|
|
if (count($rsync) == 2 && !empty($rsync[0]) && filter_var($rsync[0], FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
|
|
$is_RSYNC = TRUE;
|
|
}
|
|
}
|
|
|
|
if ($is_RSYNC || filter_var($input, FILTER_VALIDATE_URL)) {
|
|
if ($is_RSYNC) {
|
|
$data = array('host' => $rsync[0], 'path' => $rsync[1]);
|
|
} else {
|
|
$data = parse_url($input);
|
|
if (!in_array($data['scheme'], array('http', 'https', 'rsync', 'ftp'))) {
|
|
pfb_logger("{$header} Invalid URL Scheme [ " . htmlspecialchars($input) . " ]", 6);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
$validate_list = '';
|
|
if (!$data || !is_array($data)) {
|
|
pfb_logger("{$header} Invalid URL (missing data) [ " . htmlspecialchars($input) . " ]", 6);
|
|
return FALSE;
|
|
} elseif (!isset($data['host']) || empty($data['host'])) {
|
|
pfb_logger("{$header} Invalid URL (missing hostname) [ " . htmlspecialchars($input) . " ]", 6);
|
|
return FALSE;
|
|
} elseif (is_ipaddr($data['host'])) {
|
|
$validate_list = array(array('type' => 'IP', 'data' => $data['host']));
|
|
} else {
|
|
$validate_list = resolve_host_addresses("{$data['host']}.");
|
|
}
|
|
|
|
$pfsense_configured = FALSE;
|
|
if (!empty($validate_list) && is_array($validate_list)) {
|
|
foreach ($validate_list as $validate) {
|
|
if ($validate['type'] == 'CNAME' && !empty($validate['data'])) {
|
|
|
|
if (is_ipaddr($validate['data'])) {
|
|
$cname_list = resolve_host_addresses($validate['data']);
|
|
} else {
|
|
$cname_list = resolve_host_addresses("{$validate['data']}.");
|
|
}
|
|
|
|
if (!empty($cname_list) && is_array($cname_list)) {
|
|
foreach ($cname_list as $cname) {
|
|
if (!empty($cname['data']) && is_ipaddr_configured($cname['data'])) {
|
|
$pfsense_configured = TRUE;
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!empty($validate['data']) && is_ipaddr_configured($validate['data'])) {
|
|
$pfsense_configured = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Cannot resolve host
|
|
pfb_logger("{$header} Invalid URL (cannot resolve) [ " . htmlspecialchars($input) . " ]", 6);
|
|
return FALSE;
|
|
}
|
|
|
|
$path = pathinfo($data['path'], PATHINFO_DIRNAME);
|
|
|
|
// Validate only pfSense URLS (localfile check and Alerts Tab refresh)
|
|
if ($escape) {
|
|
if ($pfsense_configured ||
|
|
($data['host'] == '127.0.0.1') ||
|
|
($data['host'] == '::1')) {
|
|
|
|
// Allow '/usr/local/www'
|
|
if ($path == '/') {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if ($reference == 'alerts refresh') {
|
|
pfb_logger("{$header} Invalid URL (alerts tab) [ " . htmlspecialchars($input) . " ]", 6);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// pfSense URL
|
|
if (($pfsense_configured) ||
|
|
($data['host'] == '127.0.0.1') ||
|
|
($data['host'] == '::1')) {
|
|
|
|
// Allow '/usr/local/www'
|
|
if ($path == '/') {
|
|
return TRUE;
|
|
}
|
|
pfb_logger("{$header} Invalid URL (not allowed) [ " . htmlspecialchars($input) . " ]", 6);
|
|
return FALSE;
|
|
}
|
|
|
|
// all other remote URLs no path validation
|
|
return TRUE;
|
|
}
|
|
|
|
// Local file path validation
|
|
else {
|
|
$path = pathinfo($input, PATHINFO_DIRNAME) . '/';
|
|
$allowed_path = array_flip(array( '/var/db/pfblockerng/',
|
|
'/var/db/pfblockerng/deny/',
|
|
'/var/db/pfblockerng/permit/',
|
|
'/var/db/pfblockerng/match/',
|
|
'/var/db/pfblockerng/native/',
|
|
'/var/db/pfblockerng/original/',
|
|
'/var/db/pfblockerng/dnsbl/',
|
|
'/var/db/pfblockerng/dnsblorig/',
|
|
'/var/db/pfblockerng/ET/',
|
|
'/var/db/pfblockerng/ut1/',
|
|
'/var/db/pfblockerng/shallalist/',
|
|
'/usr/local/www/',
|
|
'/usr/local/share/GeoIP/',
|
|
'/usr/local/share/GeoIP/cc/' ));
|
|
if (isset($allowed_path[$path])) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
pfb_logger("\n[PFB_FILTER - {$type}] Invalid URL (not allowed2) [ " . htmlspecialchars($input) . " ]", 6);
|
|
return FALSE;
|
|
break;
|
|
case PFB_FILTER_WORD:
|
|
// Validate for 'Any word character (letter, number, underscore)'
|
|
if (!preg_match("/\W/", $input)) {
|
|
$result = htmlspecialchars($input);
|
|
}
|
|
break;
|
|
case PFB_FILTER_WORD_DOT:
|
|
// Validate for '(letter, number, underscore, dash) dot (letter, number, underscore, dash)'
|
|
if (preg_match("/^[a-zA-Z0-9_\-]+\.{1}[a-zA-Z0-9_\-]+$/", $input)) {
|
|
$result = htmlspecialchars($input);
|
|
}
|
|
break;
|
|
case PFB_FILTER_TLD:
|
|
// Validate TLD
|
|
if (preg_match("/^[a-zA-Z0-9_\.\-]+$/", $input)) {
|
|
$result = htmlspecialchars($input);
|
|
}
|
|
break;
|
|
case PFB_FILTER_DOMAIN:
|
|
// Validate domain
|
|
if ((strpos($input, '.') !== FALSE) && // Exclude no dots
|
|
(strpos($input, '..') === FALSE) && // Exclude double dot
|
|
(strlen($input) < 255) && // Validate length of domain (Max 255 chars)
|
|
(pfb_validate_domain_labels($input) !== FALSE) && // Validate length of labels (Max of 63 chars)
|
|
(preg_match("/^[a-zA-Z0-9_\.\-]+$/", $input))) { // Exclude any other characters
|
|
$result = TRUE;
|
|
}
|
|
if ($result) {
|
|
$result = htmlspecialchars($input);
|
|
}
|
|
break;
|
|
case PFB_FILTER_HOSTNAME:
|
|
// Validate hostname
|
|
if (is_hostname($input)) {
|
|
$result = htmlspecialchars($input);
|
|
}
|
|
break;
|
|
case PFB_FILTER_IPV4:
|
|
// Validate IPv4
|
|
if (is_ipaddrv4($input)) {
|
|
$result = htmlspecialchars($input);
|
|
}
|
|
break;
|
|
case PFB_FILTER_IP:
|
|
// Validate any IP address v4/v6
|
|
if (is_ipaddr($input)) {
|
|
$result = htmlspecialchars($input);
|
|
}
|
|
break;
|
|
case PFB_FILTER_ALPHA:
|
|
// Validate input is alphabetic only
|
|
if (ctype_alpha($input)) {
|
|
$result = htmlspecialchars($input);
|
|
}
|
|
break;
|
|
case PFB_FILTER_ALNUM:
|
|
// Validate input is alphanumeric only
|
|
if (ctype_alnum($input)) {
|
|
$result = htmlspecialchars($input);
|
|
}
|
|
break;
|
|
case PFB_FILTER_NUM:
|
|
// Validate input is number only
|
|
if (preg_match("/^[0-9]+$/", $input)) {
|
|
$result = htmlspecialchars($input);
|
|
}
|
|
break;
|
|
case PFB_FILTER_CSV:
|
|
// Validate CSV string
|
|
if (preg_match("/^[a-zA-Z0-9,_-]+$/", $input)) {
|
|
$result = htmlspecialchars($input);
|
|
}
|
|
break;
|
|
case PFB_FILTER_CSV_WHOIS:
|
|
// Validate Whoisconvert string
|
|
if (preg_match("/^[a-zA-Z0-9,\._\-]+$/", $input)) {
|
|
$result = htmlspecialchars($input);
|
|
}
|
|
break;
|
|
case PFB_FILTER_CSV_CRON:
|
|
// Validate CSV string (cron hour setting)
|
|
if ($input == '*' || preg_match("/^[0-9,]+$/", $input)) {
|
|
$result = htmlspecialchars($input);
|
|
}
|
|
break;
|
|
case PFB_FILTER_FILE_MIME_COMPARE:
|
|
// Validate mime-type
|
|
// $input [0] path/file, [1] mime-type to be validated against
|
|
if (isset($retval)) {
|
|
unset($retval);
|
|
}
|
|
if (isset($output)) {
|
|
unset($output);
|
|
}
|
|
if (!file_exists($input[0])) {
|
|
pfb_logger("{$header} Invalid Mime-type (file missing): [" . htmlspecialchars($input[0]) . "|" . htmlspecialchars($input[1]) . "]", 2);
|
|
return FALSE;
|
|
}
|
|
exec("/usr/bin/file -b --mime-type " . escapeshellarg($input[0]) . " 2>&1", $output, $retval);
|
|
if ($retval != 0 || empty($output[0]) || $output[0] !== $input[1]) {
|
|
pfb_logger("{$header} Invalid Mime-type: [" . htmlspecialchars($input[0]) . "|" . htmlspecialchars($input[1]) . "]", 2);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
break;
|
|
case PFB_FILTER_FILE_MIME:
|
|
// Validate File Mime-types
|
|
// $input [0] path/file escaped, [1] path/file [2] URL
|
|
|
|
if (isset($retval)) {
|
|
unset($retval);
|
|
}
|
|
if (isset($output)) {
|
|
unset($output);
|
|
}
|
|
if (!file_exists($input[1])) {
|
|
pfb_logger("{$header} Downloaded file not found: [" . htmlspecialchars($input[0]) . "|" . htmlspecialchars($input[1]) . "]", 2);
|
|
return FALSE;
|
|
}
|
|
exec("/usr/bin/file -b --mime-type {$input[0]} 2>&1", $output, $retval);
|
|
if ($retval != 0 || empty($output[0]) || !isset($pfb['mime_types'][$output[0]])) {
|
|
|
|
// Exceptions
|
|
$hostname = parse_url($input[2], PHP_URL_HOST);
|
|
if ($output[0] == 'text/x-asm' &&
|
|
($hostname == 'easylist-downloads.adblockplus.org' || $hostname == 'easylist.to' )) {
|
|
$output[0] = 'text/plain';
|
|
}
|
|
elseif ($output[0] == 'application/octet-stream' && $hostname == 'ipinfo.io') {
|
|
$output[0] = 'text/plain';
|
|
}
|
|
else {
|
|
pfb_logger("\n[PFB_FILTER - {$type}] Failed or invalid Mime Type: [" . htmlspecialchars($output[0]) . "|" . htmlspecialchars($retval) . "]", 2);
|
|
unlink_if_exists($input[1]);
|
|
return FALSE;
|
|
}
|
|
}
|
|
$result = htmlspecialchars($output[0]);
|
|
break;
|
|
case PFB_FILTER_FILE_MIME_COMPRESSED:
|
|
// Validate File Mime-types in compressed files
|
|
// $input [0] path/file escaped, [1] path/file [2] URL
|
|
if (isset($retval)) {
|
|
unset($retval);
|
|
}
|
|
if (isset($output)) {
|
|
unset($output);
|
|
}
|
|
if (!file_exists($input[1])) {
|
|
pfb_logger("{$header} Downloaded file not found: [" . htmlspecialchars($input[0]) . "|" . htmlspecialchars($input[1]) . "]", 2);
|
|
return FALSE;
|
|
}
|
|
exec("/usr/bin/file -bZ --mime-type {$input[0]} 2>&1", $output, $retval);
|
|
if ($retval != 0 || empty($output[0]) || !isset($pfb['mime_types'][$output[0]])) {
|
|
pfb_logger("{$header} Failed or invalid Mime Type Compressed: [" . htmlspecialchars($output[0]) . "|" . htmlspecialchars($retval) . "]", 2);
|
|
unlink_if_exists($input[1]);
|
|
return FALSE;
|
|
}
|
|
$result = htmlspecialchars($output[0]);
|
|
break;
|
|
case PFB_FILTER_ATYPE:
|
|
// Validate atype entry in category_edit.php
|
|
if (preg_match("/^[a-zA-Z0-9\.|_]+$/", $input)) {
|
|
$result = htmlspecialchars($input);
|
|
}
|
|
break;
|
|
case PFB_FILTER_HEX_COLOR:
|
|
// Alerts Tab - Hex code validation
|
|
if ($input == 'none' || preg_match("/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/", $input)) {
|
|
$result = htmlspecialchars($input);
|
|
}
|
|
break;
|
|
case PFB_FILTER_ON_OFF:
|
|
// Validate on or off
|
|
if ($input == 'on' || $input == '') {
|
|
$result = htmlspecialchars($input);
|
|
} else {
|
|
pfb_logger("{$header} Invalid on/off [ " . htmlspecialchars($input) . " ]", 6);
|
|
}
|
|
break;
|
|
default:
|
|
pfb_logger("{$header} Type invalid [ " . htmlspecialchars($input) . " ]", 6);
|
|
break;
|
|
}
|
|
|
|
if ($result && $escape) {
|
|
$result = escapeshellarg($result);
|
|
}
|
|
|
|
// Log validation errors
|
|
if (!empty($input) && empty($result) &&
|
|
!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))) {
|
|
|
|
// Exceptions
|
|
if (in_array($reference, array( 'DNSBL_Download'))) {
|
|
//
|
|
} else {
|
|
pfb_logger("{$header} Failed validation [ " . htmlspecialchars($input) . " ]", 6);
|
|
}
|
|
}
|
|
|
|
return $result == FALSE ? $default : $result;
|
|
}
|
|
|
|
|
|
// Validate length of labels (Max of 63 chars)
|
|
function pfb_validate_domain_labels($input) {
|
|
|
|
$labels = explode('.', $input);
|
|
if (!empty($labels) && is_array($labels)) {
|
|
foreach ($labels as $label) {
|
|
if (strlen($label) > 63) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// [ $pfb ] pfBlockerNG global array. This needs to be called to get the updated settings.
|
|
function pfb_global() {
|
|
global $g, $pfb;
|
|
|
|
// Create folders if not exist.
|
|
foreach ($pfb['folder_array'] as $folder) {
|
|
safe_mkdir("{$folder}", 0755);
|
|
}
|
|
|
|
// Reload config.xml to get any recent changes
|
|
config_read_file(false, true);
|
|
|
|
// General variables
|
|
$pfb['config'] = config_get_path('installedpackages/pfblockerng/config/0', []);
|
|
$pfb['ipconfig'] = config_get_path('installedpackages/pfblockerngipsettings/config/0', []);
|
|
$pfb['dnsblconfig'] = config_get_path('installedpackages/pfblockerngdnsblsettings/config/0', []);
|
|
$pfb['blconfig'] = config_get_path('installedpackages/pfblockerngblacklist', []);
|
|
$pfb['config_global'] = config_get_path('installedpackages/pfblockerngglobal', []);
|
|
|
|
$pfb['enable'] = $pfb['config']['enable_cb']; // Enable/Disable of pfBlockerNG
|
|
$pfb['keep'] = $pfb['config']['pfb_keep']; // Keep blocklists on pfBlockerNG Disable
|
|
$pfb['interval'] = $pfb['config']['pfb_interval'] ?: '1'; // Hour cycle for scheduler
|
|
|
|
// Validate Cron settings
|
|
if (!is_numeric($pfb['interval'])) {
|
|
$pfb['interval'] = '1';
|
|
}
|
|
foreach (array( 'pfb_min' => 'min', // User defined CRON start minute
|
|
'pfb_hour' => 'hour', // Start hour of the scheduler
|
|
'pfb_dailystart' => '24hour' // Start hour of the 'Once a day' schedule
|
|
) as $conf_value => $pfb_value) {
|
|
|
|
$pfb_variable = $pfb['config'][$conf_value] ?: '0';
|
|
if (!is_numeric($pfb_variable)) {
|
|
$pfb[$pfb_value] = '0';
|
|
} else {
|
|
$pfb[$pfb_value] = $pfb_variable;
|
|
}
|
|
}
|
|
|
|
$pfb['supp'] = $pfb['ipconfig']['suppression']; // Enable Suppression
|
|
$pfb['cc'] = $pfb['ipconfig']['database_cc']; // Disable Country database CRON updates
|
|
$pfb['maxmind_locale'] = $pfb['ipconfig']['maxmind_locale'] ?: 'en'; // MaxMind Localized Language setting
|
|
$pfb['asn_reporting'] = $pfb['ipconfig']['asn_reporting'] ?: 'disabled'; // ASN Reporting
|
|
$pfb['asn_token'] = $pfb['ipconfig']['asn_token'] ?: ''; // ASN Token (IPinfo)
|
|
|
|
$pfb['maxmind_account'] = pfb_filter($pfb['ipconfig']['maxmind_account'], PFB_FILTER_WORD, 'pfb_global') ?: ''; // Maxmind Account ID
|
|
$pfb['maxmind_key'] = pfb_filter($pfb['ipconfig']['maxmind_key'], PFB_FILTER_WORD, 'pfb_global') ?: ''; // Maxmind License Key
|
|
|
|
$pfb['dnsbl'] = $pfb['dnsblconfig']['pfb_dnsbl']; // Enabled state of DNSBL
|
|
$pfb['dnsbl_vip_type'] = $pfb['dnsblconfig']['pfb_dnsvip_type'] ?: 'ipalias'; // Virtual IP type
|
|
|
|
$pfb['dnsbl_vip_vhid'] = isset($pfb['dnsblconfig']['pfb_dnsvip_vhid']) ? $pfb['dnsblconfig']['pfb_dnsvip_vhid'] : ''; // Virtual IP Carp VHID
|
|
$pfb['dnsbl_vip_base'] = isset($pfb['dnsblconfig']['pfb_dnsvip_base']) ? $pfb['dnsblconfig']['pfb_dnsvip_base'] : ''; // Virtual IP Carp advbase
|
|
$pfb['dnsbl_vip_skew'] = isset($pfb['dnsblconfig']['pfb_dnsvip_skew']) ? $pfb['dnsblconfig']['pfb_dnsvip_skew'] : ''; // Virtual IP Carp skew
|
|
$pfb['dnsbl_vip_pass'] = isset($pfb['dnsblconfig']['pfb_dnsvip_pass']) ? $pfb['dnsblconfig']['pfb_dnsvip_pass'] : ''; // Virtual IP Carp password (if required)
|
|
|
|
$pfb['dnsbl_iface'] = $pfb['dnsblconfig']['dnsbl_interface']?: 'lo0'; // VIP Local Interface setting
|
|
$pfb['dnsbl_vip'] = $pfb['dnsblconfig']['pfb_dnsvip'] ?: ''; // Virtual IP local address
|
|
$pfb['dnsbl_v6'] = $pfb['dnsblconfig']['pfb_dnsblv6'] ?: ''; // Enable/Disable DNSBL IPv6
|
|
$pfb['dnsbl_port'] = $pfb['dnsblconfig']['pfb_dnsport']; // Lighttpd web server http port setting
|
|
$pfb['dnsbl_port_ssl'] = $pfb['dnsblconfig']['pfb_dnsport_ssl']; // Lighttpd web server https port setting
|
|
$pfb['dnsbl_alexa'] = $pfb['dnsblconfig']['alexa_enable']; // TOP1M whitelist
|
|
$pfb['dnsbl_alexatype'] = $pfb['dnsblconfig']['alexa_type'] ?: 'tranco'; // TOP1M type (Tranco, Alexa or Cisco)
|
|
$pfb['dnsbl_res_cache'] = $pfb['dnsblconfig']['pfb_cache']; // DNSBL Option to backup/restore Resolver cache
|
|
$pfb['dnsbl_sync'] = $pfb['dnsblconfig']['pfb_dnsbl_sync']; // Live Updates to Resolver without a Reload
|
|
$pfb['dnsbl_global_log']= $pfb['dnsblconfig']['global_log'] ?: ''; // Global Logging/Blocking mode
|
|
|
|
$pfb['dnsbl_mode'] = $pfb['dnsblconfig']['dnsbl_mode']; // DNSBL Mode (Unbound/python mode)
|
|
$pfb['dnsbl_py_reply'] = $pfb['dnsblconfig']['pfb_py_reply']; // DNSBL Resolver python DNS Reply logging
|
|
$pfb['dnsbl_py_block'] = $pfb['dnsblconfig']['pfb_py_block']; // DNSBL Resolver python blocking mode
|
|
$pfb['dnsbl_hsts'] = $pfb['dnsblconfig']['pfb_hsts']; // DNSBL Resolver python block HSTS via Null Block mode
|
|
$pfb['dnsbl_idn'] = $pfb['dnsblconfig']['pfb_idn']; // DNSBL Resolver python block IDN domains
|
|
$pfb['dnsbl_regex'] = $pfb['dnsblconfig']['pfb_regex']; // DNSBL Resolver python regex
|
|
$pfb['dnsbl_regex_list']= $pfb['dnsblconfig']['pfb_regex_list']; // DNSBL Resolver python regex list
|
|
$pfb['dnsbl_cname'] = $pfb['dnsblconfig']['pfb_cname']; // DNSBL Resolver python CNAME Validation
|
|
$pfb['dnsbl_pytld'] = $pfb['dnsblconfig']['pfb_pytld']; // DNSBL Resolver python TLD Allow option
|
|
$pfb['dnsbl_py_nolog'] = $pfb['dnsblconfig']['pfb_py_nolog']; // DNSBL Resolver python - Log events via DNSBL Webserver vs python
|
|
$pfb['dnsbl_noaaaa'] = $pfb['dnsblconfig']['pfb_noaaaa']; // DNSBL Resolver python no AAAA
|
|
$pfb['dnsbl_noaaaa_list']=$pfb['dnsblconfig']['pfb_noaaaa_list']; // DNSBL Resolver python no AAAA list
|
|
|
|
$pfb['dnsbl_gp'] = $pfb['dnsblconfig']['pfb_gp']; // DNSBL Resolver python - DNSBL Bypass
|
|
$pfb['dnsbl_gp_bypass_list'] = $pfb['dnsblconfig']['pfb_gp_bypass_list']; // DNSBL Resolver python - List of Local IPs to bypass DNSBL
|
|
|
|
// DNSBL Resolver mode (Unbound/Python)
|
|
$pfb['dnsbl_py_blacklist'] = FALSE;
|
|
if ($pfb['dnsbl_mode'] == 'dnsbl_python' && $pfb['dnsbl_py_block'] == 'on') {
|
|
$pfb['dnsbl_py_blacklist'] = TRUE;
|
|
}
|
|
|
|
// SafeSearch
|
|
$pfb['safesearch_enable'] = config_get_path('installedpackages/pfblockerngsafesearch/safesearch_enable', 'Disable');
|
|
$pfb['safesearch_youtube'] = config_get_path('installedpackages/pfblockerngsafesearch/safesearch_youtube', 'Disable');
|
|
$pfb['safesearch_doh'] = config_get_path('installedpackages/pfblockerngsafesearch/safesearch_doh', 'Disable');
|
|
$pfb['safesearch_doh_list'] = explode(',', config_get_path('installedpackages/pfblockerngsafesearch/safesearch_doh_list', ''));
|
|
|
|
// DNSBL SafeSearch
|
|
$pfb['dnsbl_safe_search'] = FALSE;
|
|
if ($pfb['safesearch_enable'] !== 'Disable' ||
|
|
$pfb['safesearch_youtube'] !== 'Disable' ||
|
|
$pfb['safesearch_doh'] !== 'Disable') {
|
|
$pfb['dnsbl_safe_search'] = TRUE;
|
|
}
|
|
|
|
// External DNS Server for TLD drill and CNAME Queries
|
|
$pfb['extdns'] = pfb_filter($pfb['config_global']['pfbextdns'], PFB_FILTER_IPV4, 'pfb_global', '8.8.8.8');
|
|
|
|
// Unbound chroot cmd
|
|
$pfb['chroot_cmd'] = "/usr/sbin/chroot -u unbound -g unbound / /usr/local/sbin/unbound-control -c {$g['unbound_chroot_path']}/unbound.conf";
|
|
|
|
// Define SQLite3 parameters
|
|
$pfb['sqlite_timeout'] = 100000;
|
|
|
|
// Max daily download failure threshold (default to '0' unlimited failures)
|
|
$pfb['skipfeed'] = $pfb['config']['skipfeed'] != '' ? $pfb['config']['skipfeed'] : 0;
|
|
|
|
if (config_path_enabled('unbound')) {
|
|
$pfb['unbound_state'] = 'on';
|
|
} else {
|
|
$pfb['unbound_state'] = '';
|
|
}
|
|
|
|
// cURL - system proxy server setttings, if configured
|
|
if (!empty(config_get_path('system/proxyurl'))) {
|
|
$pfb['curl_defaults'][CURLOPT_PROXY] = config_get_path('system/proxyurl');
|
|
if (!empty(config_get_path('system/proxyport'))) {
|
|
$pfb['curl_defaults'][CURLOPT_PROXYPORT] = config_get_path('system/proxyport');
|
|
}
|
|
if (!empty(config_get_path('system/proxyuser')) && !empty(config_get_path('system/proxypass'))) {
|
|
$pfb['curl_defaults'][CURLOPT_PROXYAUTH] = 'CURLAUTH_ANY | CURLAUTH_ANYSAFE';
|
|
$pfb['curl_defaults'][CURLOPT_PROXYUSERPWD] = config_get_path('system/proxyuser') . ':' . config_get_path('system/proxypass');
|
|
}
|
|
}
|
|
else {
|
|
$pfb['curl_defaults'][CURLOPT_TCP_FASTOPEN] = true;
|
|
}
|
|
|
|
// Set pfBlockerNG to disabled on 're-install'
|
|
if (isset($pfb['install']) && $pfb['install']) {
|
|
$pfb['enable'] = $pfb['dnsbl'] = '';
|
|
$pfb['install'] = FALSE;
|
|
}
|
|
}
|
|
pfb_global();
|
|
|
|
// Function to get pfBlockerNG package version
|
|
function pfb_pkg_ver() {
|
|
$pkg_ver = 'v??';
|
|
foreach (config_get_path('installedpackages/package', []) as $pkg_info_data) {
|
|
if (strpos($pkg_info_data['name'], 'pfBlockerNG') !== FALSE) {
|
|
$pkg_ver = 'v' . $pkg_info_data['version'];
|
|
break;
|
|
}
|
|
}
|
|
return $pkg_ver;
|
|
}
|
|
|
|
|
|
// Firewall Filter Service
|
|
function pfb_filter_service() {
|
|
|
|
$rc = array();
|
|
$rc['file'] = 'pfb_filter.sh';
|
|
$rc['start'] = <<<EOF
|
|
|
|
# Check if pfBlockerNG is enabled
|
|
pfbcheck="\$(/usr/local/sbin/read_xml_tag.sh string installedpackages/pfblockerng/config/enable_cb)"
|
|
if [ "\${pfbcheck}" != 'on' ]; then
|
|
exit
|
|
fi
|
|
|
|
# Ensure all processes are stopped
|
|
rc_stop
|
|
|
|
# clog is not required for pfSense 2.5 and above
|
|
filter_type='tail_pfb'
|
|
if [ -e "/usr/local/sbin/clog_pfb" ]; then
|
|
filter_type='clog_pfb'
|
|
fi
|
|
|
|
# Compare php/php_pfb versions
|
|
php_path='/usr/local/bin'
|
|
|
|
phpver="\$(/usr/bin/stat \${php_path}/php | cut -d '/' -f1)"
|
|
if [ -e "\${php_path}/php_pfb" ]; then
|
|
phppfbver="\$(/usr/bin/stat \${php_path}/php_pfb | cut -d '/' -f1)"
|
|
else
|
|
phppfbver=''
|
|
fi
|
|
|
|
# Create new hard link for php_pfb on version mismatch
|
|
if [ "\${phpver}" != "\${phppfbver}" ]; then
|
|
if [ -e "\${php_path}/php_pfb" ]; then
|
|
tmpfile="\$(/usr/bin/mktemp /tmp/pfb_php.XXXXXX)"
|
|
/bin/mv "\${php_path}/php_pfb" "\${tmpfile}"
|
|
fi
|
|
|
|
/bin/ln "\${php_path}/php" "\${php_path}/php_pfb"
|
|
fi
|
|
|
|
# Start pfBlockerNG Firewall filter Daemon
|
|
if [ -e '/var/log/filter.log' ]; then
|
|
/usr/bin/logger -p daemon.info -t "\${filter_type}" "[pfBlockerNG] Firewall Filter Service started"
|
|
|
|
if [ "\${filter_type}" == 'clog_pfb' ]; then
|
|
/usr/local/sbin/clog_pfb -f /var/log/filter.log | /usr/local/bin/php_pfb -f /usr/local/pkg/pfblockerng/pfblockerng.inc filterlog &
|
|
else
|
|
/usr/bin/tail_pfb -n0 -F /var/log/filter.log | /usr/local/bin/php_pfb -f /usr/local/pkg/pfblockerng/pfblockerng.inc filterlog &
|
|
fi
|
|
fi
|
|
|
|
EOF;
|
|
$rc['stop'] = <<<EOF
|
|
|
|
# clog is not required for pfSense 2.5 and above
|
|
filter_type='tail_pfb'
|
|
if [ -e "/usr/local/sbin/clog_pfb" ]; then
|
|
filter_type='clog_pfb'
|
|
fi
|
|
|
|
# Terminate pfBlockerNG Firewall filter Daemon (clog) and filter Daemon, if found
|
|
/usr/bin/logger -p daemon.info -t "\${filter_type}" "[pfBlockerNG] Firewall Filter Service stopped"
|
|
/usr/bin/logger -p daemon.info -t php_pfb "[pfBlockerNG] filterlog daemon stopped"
|
|
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}')"
|
|
if [ ! -z "\${pidnum}" ]; then
|
|
for i in \${pidnum}; do
|
|
/bin/kill -9 "\${i}"
|
|
done
|
|
fi
|
|
|
|
EOF;
|
|
write_rcfile($rc);
|
|
}
|
|
|
|
|
|
// DNSBL Service
|
|
function pfb_dnsbl_service() {
|
|
|
|
$rc = array();
|
|
$rc['file'] = 'pfb_dnsbl.sh';
|
|
$rc['start'] = <<<EOF
|
|
|
|
# Check if DNSBL is enabled
|
|
dnsblcheck="\$(/usr/local/sbin/read_xml_tag.sh string installedpackages/pfblockerngdnsblsettings/config/pfb_dnsbl)"
|
|
if [ "\${dnsblcheck}" != 'on' ]; then
|
|
exit
|
|
fi
|
|
|
|
# Ensure all processes are stopped
|
|
rc_stop
|
|
|
|
# Start DNSBL Lighttpd webserver (Unbound/Python mode) and DNSBL HTTPS Daemon (Unbound mode only)
|
|
if [ -e '/var/unbound/pfb_dnsbl_lighty.conf' ]; then
|
|
/usr/bin/logger -p daemon.info -t lighttpd_pfb "[pfBlockerNG] DNSBL Webserver started"
|
|
/usr/local/sbin/lighttpd_pfb -f /var/unbound/pfb_dnsbl_lighty.conf
|
|
fi
|
|
|
|
# Check if Unbound mode is enabled
|
|
unbound_mode="\$(/usr/local/sbin/read_xml_tag.sh string installedpackages/pfblockerngdnsblsettings/config/dnsbl_mode)"
|
|
|
|
# Start DNSBL Resolver queries Daemon
|
|
if [ "\${unbound_mode}" == 'dnsbl_unbound' ]; then
|
|
/usr/local/bin/php -f /usr/local/pkg/pfblockerng/pfblockerng.inc queries &
|
|
elif [ ! -e '/var/unbound/pfb_unbound.py' ]; then
|
|
/usr/bin/logger -p daemon.err -t php_pfb "[pfBlockerNG] DNSBL missing python script - forced reload required"
|
|
fi
|
|
|
|
EOF;
|
|
$rc['stop'] = <<<EOF
|
|
|
|
# Terminate DNSBL Lighttpd webserver, if found
|
|
/usr/bin/logger -p daemon.info -t lighttpd_pfb "[pfBlockerNG] DNSBL Webserver stopped"
|
|
pidnum="\$(/bin/pgrep lighttpd_pfb)"
|
|
if [ ! -z "\${pidnum}" ]; then
|
|
/usr/bin/killall lighttpd_pfb
|
|
fi
|
|
|
|
# Terminate DNSBL queries Daemon, if found
|
|
pidnum="\$(/bin/ps -wax | /usr/bin/grep '[p]fblockerng.inc queries' | /usr/bin/awk '{print \$1}')"
|
|
if [ ! -z "\${pidnum}" ]; then
|
|
for i in \${pidnum}; do
|
|
/bin/kill -9 "\${i}"
|
|
done
|
|
fi
|
|
|
|
EOF;
|
|
write_rcfile($rc);
|
|
}
|
|
|
|
|
|
// Create Firewall filter service
|
|
if (!file_exists('/usr/local/etc/rc.d/pfb_filter.sh')) {
|
|
pfb_filter_service();
|
|
}
|
|
|
|
// Create DNSBL service
|
|
if (!file_exists('/usr/local/etc/rc.d/pfb_dnsbl.sh')) {
|
|
pfb_dnsbl_service();
|
|
}
|
|
|
|
// clog is not required for pfSense 2.5 and above
|
|
if (substr(trim(file_get_contents('/etc/version')), 0, 3) > '2.5' && file_exists('/usr/local/sbin/clog_pfb')) {
|
|
unlink_if_exists('/usr/local/sbin/clog_pfb');
|
|
unlink_if_exists('/usr/bin/tail_pfb');
|
|
link('/usr/bin/tail', '/usr/bin/tail_pfb');
|
|
restart_service('pfb_filter');
|
|
}
|
|
|
|
// Commandline arguments for daemons
|
|
if (isset($argv[1])) {
|
|
|
|
// DNSBL Lighttpd HTTPS daemon (Collects HTTPS events from the Lighttpd dnsbl_error.log)
|
|
if ($argv[1] == 'dnsbl') {
|
|
ignore_user_abort(TRUE);
|
|
set_time_limit(0);
|
|
pfb_daemon_dnsbl();
|
|
exit;
|
|
}
|
|
|
|
// DNSBL Lighttpd HTTP daemon (Collects HTTP events from the index.php script)
|
|
elseif ($argv[1] == 'index') {
|
|
ignore_user_abort(TRUE);
|
|
set_time_limit(0);
|
|
pfb_daemon_dnsbl_index();
|
|
exit;
|
|
}
|
|
|
|
// DNSBL daemon to monitor Resolver queries and manage SQLite3 database
|
|
elseif ($argv[1] == 'queries') {
|
|
ignore_user_abort(TRUE);
|
|
set_time_limit(0);
|
|
pfb_daemon_queries();
|
|
exit;
|
|
}
|
|
|
|
// IP filter daemon to convert filter.log to ip_block|ip_permit|ip_match log format
|
|
elseif ($argv[1] == 'filterlog') {
|
|
ignore_user_abort(TRUE);
|
|
set_time_limit(0);
|
|
if (!file_exists('/var/log/filter.log')) {
|
|
log_error('[pfBlockerNG] pfSense Firewall log missing');
|
|
exit;
|
|
}
|
|
pfb_daemon_filterlog();
|
|
exit;
|
|
}
|
|
}
|
|
|
|
|
|
// Function to convert string to lowercase (Not for comment line section)
|
|
function pfb_strtolower($line) {
|
|
if (strpos($line, '#') === FALSE) {
|
|
return trim(strtolower($line));
|
|
}
|
|
return trim($line);
|
|
}
|
|
|
|
|
|
// Function to decode alias custom entry box.
|
|
// Default (False, True): Return as string with comments
|
|
function pfbng_text_area_decode($text, $mode=FALSE, $type=TRUE, $idn=FALSE) {
|
|
|
|
if ($mode) {
|
|
$custom = array();
|
|
}
|
|
|
|
$customlist = explode("\r\n", base64_decode($text));
|
|
if (!empty($customlist)) {
|
|
foreach ($customlist as $line) {
|
|
if (substr(trim($line), 0, 1) != '#' && !empty($line)) {
|
|
if ($idn && !ctype_print($line)) {
|
|
$line_old = $line;
|
|
// Convert encodings to UTF-8
|
|
$line = mb_convert_encoding($line, 'UTF-8',
|
|
mb_detect_encoding($line, 'UTF-8, ASCII, ISO-8859-1'));
|
|
if (strpos($line, '#') !== FALSE) {
|
|
$tmpline = preg_split('/(?=#)/', $line);
|
|
if (substr($tmpline[0], 0, 1) == '.') {
|
|
// idn_to_ascii() returns empty string if it starts with '.'
|
|
$tmpline[0] = idn_to_ascii(ltrim($tmpline[0], '.'));
|
|
if (!empty($tmpline[0])) {
|
|
$tmpline[0] = '.' . $tmpline[0];
|
|
}
|
|
} else {
|
|
$tmpline[0] = idn_to_ascii($tmpline[0]);
|
|
}
|
|
if (empty($tmpline[0])) {
|
|
$log = "\nError converting IDN line '{$line_old}'\n";
|
|
pfb_logger($log, 2);
|
|
continue;
|
|
}
|
|
$line = implode(' ', $tmpline);
|
|
} else {
|
|
if (substr($line, 0, 1) == '.') {
|
|
$line = idn_to_ascii(ltrim($line, '.'));
|
|
if (!empty($line)) {
|
|
$line = '.' . $line;
|
|
}
|
|
} else {
|
|
$line = idn_to_ascii($line);
|
|
}
|
|
if (empty($line)) {
|
|
$log = "\nError converting IDN line '{$line_old}'\n";
|
|
pfb_logger($log, 2);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
// '#' commentline found
|
|
if (strpos($line, '#') !== FALSE) {
|
|
if ($mode) {
|
|
if ($type) {
|
|
// Split line into two elements (array)
|
|
$custom[] = array_map('pfb_strtolower', preg_split('/(?=#)/', $line));
|
|
} else {
|
|
// Remove commentline
|
|
$custom[] = trim(strtolower(strstr($line, '#', TRUE)));
|
|
}
|
|
} else {
|
|
// Remove commentline
|
|
$custom .= trim(strtolower(strstr($line, '#', TRUE))) . "\n";
|
|
}
|
|
}
|
|
|
|
// No '#' commentline found
|
|
else {
|
|
$line = trim(strtolower($line));
|
|
|
|
if ($mode) {
|
|
if ($type) {
|
|
$custom[][0] = $line;
|
|
} else {
|
|
$custom[] = $line;
|
|
}
|
|
} else {
|
|
$custom .= "{$line}\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $custom;
|
|
}
|
|
}
|
|
|
|
|
|
// Manage log files line limit
|
|
function pfb_log_mgmt() {
|
|
global $g, $pfb;
|
|
pfb_global();
|
|
|
|
$chroot_folder = '/var/unbound';
|
|
|
|
foreach (array( 'log', 'errlog', 'extraslog', 'ip_blocklog', 'ip_permitlog', 'ip_matchlog',
|
|
'dnslog', 'dnsbl_parse_err', 'dnsreplylog', 'unilog') as $logtype) {
|
|
|
|
// Max lines in Log file
|
|
$logmax = pfb_filter($pfb['config']['log_max_' . $logtype], PFB_FILTER_NUM, 'pfb_log_mgmt', 20000);
|
|
|
|
if ($logmax != 'nolimit' && file_exists($pfb[$logtype])) {
|
|
if ($logtype == 'dnslog' || $logtype == 'dnsreplylog' || $logtype == 'unilog') {
|
|
|
|
// Set DNSBL python logfile permissions using chroot folder
|
|
if (is_dir("{$chroot_folder}/var/log/pfblockerng")) {
|
|
$final_log_file = "{$chroot_folder}{$pfb[$logtype]}";
|
|
$temp = tempnam("{$chroot_folder}/var/log/pfblockerng", 'pfb_log_');
|
|
} else {
|
|
$final_log_file = $pfb[$logtype];
|
|
$temp = tempnam("{$g['tmp_path']}/", 'pfb_log_');
|
|
}
|
|
|
|
if (file_exists($final_log_file)) {
|
|
exec("/usr/bin/tail -n " . escapeshellarg($logmax) . " " . escapeshellarg($final_log_file) . " > " . escapeshellarg($temp));
|
|
@chown($temp, 'unbound');
|
|
@chgrp($temp, 'unbound');
|
|
exec("/bin/mv -f " . escapeshellarg($temp) . " " . escapeshellarg($final_log_file));
|
|
}
|
|
}
|
|
else {
|
|
$temp = tempnam("{$g['tmp_path']}/", 'pfb_log_');
|
|
exec("/usr/bin/tail -n " . escapeshellarg($logmax) . " {$pfb[$logtype]} > " . escapeshellarg($temp));
|
|
exec("/bin/mv -f " . escapeshellarg($temp) . " {$pfb[$logtype]}");
|
|
}
|
|
unlink_if_exists($temp);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Record log messsages to pfBlockerNG log file and/or error log file.
|
|
function pfb_logger($log, $logtype) {
|
|
global $g, $pfb;
|
|
|
|
$now = date('m/j/y H:i:s', time());
|
|
|
|
// Only log timestamp if new
|
|
if (strpos($log, 'NOW') !== FALSE) {
|
|
$elog = str_replace('NOW', $now, "{$log}"); // Always report timestamp to errorlog
|
|
if ($now == $pfb['pnow']) {
|
|
$log = str_replace(' [ NOW ]', '', "{$log}");
|
|
} else {
|
|
$log = str_replace('NOW', $now, "{$log}");
|
|
}
|
|
$pfb['pnow'] = "{$now}";
|
|
}
|
|
else {
|
|
$elog = "{$log} [ {$now} ]";
|
|
}
|
|
|
|
switch ($logtype) {
|
|
|
|
// Print to pfBlockerNG log
|
|
case 1:
|
|
@file_put_contents("{$pfb['log']}", "{$log}", FILE_APPEND);
|
|
break;
|
|
|
|
// Print to pfBlockerNG log and Error log
|
|
case 2:
|
|
@file_put_contents("{$pfb['log']}", "{$log}", FILE_APPEND);
|
|
@file_put_contents("{$pfb['errlog']}", "{$elog}", FILE_APPEND);
|
|
break;
|
|
|
|
// Print to Extras log
|
|
case 3:
|
|
@file_put_contents("{$pfb['extraslog']}", "{$log}", FILE_APPEND);
|
|
break;
|
|
|
|
// Print to screen and Extras log
|
|
case 4:
|
|
if (!$g['pfblockerng_install'] && !$pfb['extras_update']) {
|
|
print "{$log}";
|
|
}
|
|
@file_put_contents("{$pfb['extraslog']}", "{$log}", FILE_APPEND);
|
|
break;
|
|
|
|
// Print to debugger
|
|
case 5:
|
|
@file_put_contents("/tmp/pfb_debug", "{$now} | {$elog}", FILE_APPEND);
|
|
break;
|
|
|
|
// Print to Error log
|
|
case 6:
|
|
@file_put_contents("{$pfb['errlog']}", "{$elog}", FILE_APPEND);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// Record failed IP/DNSBL Feed parse errors
|
|
function pfb_parsed_fail($header, $line='', $oline, $logfile) {
|
|
|
|
$line = $line ?: 'null';
|
|
$now = date('m/j/y H:i:s', time());
|
|
|
|
$log = "{$now},{$header},{$line},{$oline}";
|
|
@file_put_contents("{$logfile}", "{$log}", FILE_APPEND);
|
|
}
|
|
|
|
|
|
// Determine 'list' details
|
|
function pfb_determine_list_detail($list='', $header='', $confconfig='', $key='') {
|
|
global $pfb, $pfbarr;
|
|
$pfbarr = array();
|
|
|
|
switch($list) {
|
|
case 'Deny_Both':
|
|
case 'Deny_Inbound':
|
|
case 'Deny_Outbound':
|
|
case 'Alias_Deny':
|
|
$pfbarr = array('adv' => TRUE, 'folder' => "{$pfb['denydir']}", 'orig' => "{$pfb['origdir']}", 'reuse' => "{$pfb['reuse']}");
|
|
break;
|
|
case 'unbound':
|
|
$pfbarr = array('adv' => FALSE, 'folder' => "{$pfb['dnsdir']}", 'orig' => "{$pfb['dnsorigdir']}", 'reuse' => "{$pfb['reuse_dnsbl']}");
|
|
break;
|
|
case 'Permit_Both':
|
|
case 'Permit_Inbound':
|
|
case 'Permit_Outbound':
|
|
case 'Alias_Permit':
|
|
$pfbarr = array('adv' => FALSE, 'folder' => "{$pfb['permitdir']}", 'orig' => "{$pfb['origdir']}", 'reuse' => "{$pfb['reuse']}");
|
|
break;
|
|
case 'Match_Both':
|
|
case 'Match_Inbound':
|
|
case 'Match_Outbound':
|
|
case 'Alias_Match':
|
|
$pfbarr = array('adv' => FALSE, 'folder' => "{$pfb['matchdir']}", 'orig' => "{$pfb['origdir']}", 'reuse' => "{$pfb['reuse']}");
|
|
break;
|
|
case 'Alias_Native':
|
|
$pfbarr = array('adv' => FALSE, 'folder' => "{$pfb['nativedir']}", 'orig' => "{$pfb['origdir']}", 'reuse' => "{$pfb['reuse']}");
|
|
break;
|
|
}
|
|
|
|
// Collect proper alias table description (alias only vs autorules)
|
|
if (strpos($list, 'Alias') !== FALSE) {
|
|
$pfbarr['descr'] = '';
|
|
} else {
|
|
$pfbarr['descr'] = ' Auto ';
|
|
}
|
|
|
|
// Determine length of header to format log output
|
|
$tabtype = strlen($header);
|
|
if ($tabtype > 27) {
|
|
$pfbarr['logtab'] = '';
|
|
} elseif ($tabtype > 19) {
|
|
$pfbarr['logtab'] = "\t";
|
|
} elseif ($tabtype > 11) {
|
|
$pfbarr['logtab'] = "\t\t";
|
|
} elseif ($tabtype < 4) {
|
|
$pfbarr['logtab'] = "\t\t\t\t";
|
|
} else {
|
|
$pfbarr['logtab'] = "\t\t\t";
|
|
}
|
|
|
|
// Configure autoports/protocol and auto destination if required.
|
|
if (!empty($confconfig) && is_array(config_get_path("installedpackages/{$confconfig}/config/{$key}"))) {
|
|
|
|
$conf_config = config_get_path("installedpackages/{$confconfig}/config/{$key}");
|
|
$autotype = array( 'autoports' => 'aliasports', 'autoaddr' => 'aliasaddr');
|
|
|
|
foreach (array('_out', '_in') as $dir) {
|
|
|
|
$pfbarr['aproto' . $dir] = $conf_config['autoproto' . $dir];
|
|
$pfbarr['anot' . $dir] = $conf_config['autonot' . $dir];
|
|
$pfbarr['aaddrnot' . $dir] = $conf_config['autoaddrnot' . $dir];
|
|
$pfbarr['agateway' . $dir] = $conf_config['agateway' . $dir];
|
|
|
|
foreach ($autotype as $akey => $atype) {
|
|
if ($conf_config[$akey . $dir] == 'on') {
|
|
foreach (config_get_path('aliases/alias', []) as $palias) {
|
|
if ($palias['name'] == $conf_config[$atype . $dir]) {
|
|
if (!empty($palias['address'])) {
|
|
$dalias = "{$atype}{$dir}";
|
|
switch($akey) {
|
|
case 'autoports':
|
|
$ctype = "aports{$dir}";
|
|
$pfbarr[$ctype] = $conf_config[$dalias];
|
|
break;
|
|
case 'autoaddr':
|
|
$ctype = "aaddr{$dir}";
|
|
$pfbarr[$ctype] = $conf_config[$dalias];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Force 'Alias Native' setting to any Alias with 'Advanced Inbound/Outbound -Invert src/dst' settings.
|
|
// This will bypass Deduplication and Reputation features.
|
|
if ($pfbarr['aaddrnot_in'] == 'on' || $pfbarr['aaddrnot_out'] == 'on') {
|
|
$pfbarr['adv'] = FALSE;
|
|
$pfbarr['folder'] = "{$pfb['nativedir']}";
|
|
}
|
|
|
|
return $pfbarr;
|
|
}
|
|
|
|
|
|
// Determine if cron task requires updating
|
|
function pfblockerng_cron_exists($pfb_cmd, $pfb_min, $pfb_hour, $pfb_mday, $pfb_wday) {
|
|
foreach (config_get_path('cron/item', []) as $item) {
|
|
if (strpos($item['command'], $pfb_cmd) !== FALSE) {
|
|
if ($item['command'] != $pfb_cmd) {
|
|
return FALSE;
|
|
}
|
|
if ($item['minute'] != $pfb_min) {
|
|
return FALSE;
|
|
}
|
|
if ($item['mday'] != $pfb_mday) {
|
|
return FALSE;
|
|
}
|
|
if ($item['wday'] != $pfb_wday) {
|
|
return FALSE;
|
|
}
|
|
if ($pfb_hour == 'random' && $item['hour'] != '') {
|
|
// MaxMind/Blacklist hour is randomized. Skip comparison.
|
|
return TRUE;
|
|
}
|
|
if ($item['hour'] != $pfb_hour) {
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// Calculate the cron task base hour setting
|
|
function pfb_cron_base_hour($freq) {
|
|
global $pfb;
|
|
|
|
switch($freq) {
|
|
case 'Disabled':
|
|
case 1:
|
|
case '01hour':
|
|
$j = 23; $k = 1;
|
|
break;
|
|
case 2:
|
|
case '02hours':
|
|
$j = 11; $k = 2;
|
|
break;
|
|
case 3:
|
|
case '03hours':
|
|
$j = 7; $k = 3;
|
|
break;
|
|
case 4:
|
|
case '04hours':
|
|
$j = 5; $k = 4;
|
|
break;
|
|
case 6:
|
|
case '06hours':
|
|
$j = 3; $k = 6;
|
|
break;
|
|
case 8:
|
|
case '08hours':
|
|
$j = 2; $k = 8;
|
|
break;
|
|
case 12:
|
|
case '12hours':
|
|
$j = 1; $k = 12;
|
|
break;
|
|
case 24:
|
|
return array($pfb['24hour']);
|
|
break;
|
|
default:
|
|
$pfb['interval'] = 1;
|
|
return [];
|
|
}
|
|
|
|
$shour = intval(substr($pfb['hour'], 0, 2));
|
|
$sch = strval($shour);
|
|
|
|
for ($i=0; $i < $j; $i++) {
|
|
$shour += $k;
|
|
if ($shour >= 24) {
|
|
$shour -= 24;
|
|
}
|
|
$sch .= ',' . strval($shour);
|
|
}
|
|
|
|
$sch = explode(',', $sch);
|
|
sort($sch);
|
|
return $sch;
|
|
}
|
|
|
|
|
|
// Collect 'gateway(s)' and 'gateway group(s)' for Adv. In/Outbound customizations
|
|
function pfb_get_gateways() {
|
|
$gateway = array();
|
|
$gateway['default'] = 'default';
|
|
|
|
foreach (config_get_path('gateways/gateway_item', []) as $item) {
|
|
$gateway[$item['name']] = $item['name'];
|
|
}
|
|
foreach (config_get_path('gateways/gateway_group', []) as $item) {
|
|
$gateway[$item['name']] = $item['name'];
|
|
}
|
|
|
|
return $gateway;
|
|
}
|
|
|
|
|
|
// Collect all Interfaces for General Tab and DNSBL Firewall Permit Rule
|
|
function pfb_build_if_list($show_wan=FALSE, $show_groups=FALSE) {
|
|
$pfb_interfaces = array();
|
|
|
|
foreach (get_configured_interface_with_descr() as $ifent => $ifdesc) {
|
|
if ($show_wan || $ifent != 'wan') {
|
|
$pfb_interfaces[$ifent] = $ifdesc;
|
|
}
|
|
}
|
|
|
|
if ($show_groups) {
|
|
foreach (config_get_path('ifgroups/ifgroupentry', []) as $ifgen) {
|
|
$pfb_interfaces[$ifgen['ifname']] = $ifgen['ifname'];
|
|
}
|
|
}
|
|
|
|
if (ipsec_enabled()) {
|
|
$pfb_interfaces['enc0'] = 'IPsec';
|
|
}
|
|
|
|
if (config_get_path('openvpn/openvpn-server') || config_get_path('openvpn/openvpn-client')) {
|
|
$pfb_interfaces['openvpn'] = 'OpenVPN';
|
|
}
|
|
|
|
if (config_get_path('l2tp/mode') == 'server') {
|
|
$pfb_interfaces['l2tp'] = 'L2TP VPN';
|
|
}
|
|
|
|
if (function_exists('is_wg_enabled') && is_wg_enabled()) {
|
|
$pfb_interfaces['wireguard'] = 'WireGuard';
|
|
}
|
|
|
|
return $pfb_interfaces;
|
|
}
|
|
|
|
|
|
// Create suppression file from suppression list
|
|
function pfb_create_suppression_file() {
|
|
global $pfb;
|
|
|
|
$v4suppression = pfbng_text_area_decode($pfb['ipconfig']['v4suppression'], FALSE, TRUE);
|
|
if (!empty($v4suppression)) {
|
|
@file_put_contents("{$pfb['supptxt']}", $v4suppression, LOCK_EX);
|
|
} else {
|
|
unlink_if_exists("{$pfb['supptxt']}");
|
|
}
|
|
}
|
|
|
|
|
|
// Function to update DNSBL aliases and widget stats
|
|
function dnsbl_alias_update($mode, $alias, $pfbfolder, $lists_dnsbl_current, $alias_cnt) {
|
|
global $pfb;
|
|
|
|
if ($mode == 'update') {
|
|
|
|
// Create master alias file
|
|
if ($lists_dnsbl_current != '') {
|
|
$pfb_output = @fopen("{$pfb['dnsalias']}/{$alias}", 'w');
|
|
foreach ($lists_dnsbl_current as $clist) {
|
|
if (($handle = @fopen("{$pfbfolder}/{$clist}.txt", 'r')) !== FALSE) {
|
|
while (($line = @fgets($handle)) !== FALSE) {
|
|
@fwrite($pfb_output, $line);
|
|
}
|
|
}
|
|
@fclose($handle);
|
|
}
|
|
@fclose($pfb_output);
|
|
}
|
|
|
|
// Update DNSBL alias statistics
|
|
$dns_now = date('M j H:i:s', time());
|
|
$pfbfound = FALSE;
|
|
|
|
if (!empty($pfb['dnsbl_info_stats'])) {
|
|
foreach ($pfb['dnsbl_info_stats'] as $key => $line) {
|
|
|
|
// Update existing alias stats
|
|
if ($line['groupname'] == "{$alias}") {
|
|
$pfbfound = TRUE;
|
|
$pfb['dnsbl_info_stats'][$key]['timestamp'] = "{$dns_now}";
|
|
$pfb['dnsbl_info_stats'][$key]['entries'] = "{$alias_cnt}";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$pfbfound) {
|
|
$pfb['dnsbl_info_stats'][] = array ( 'groupname' => $alias, 'timestamp' => $dns_now, 'entries' => $alias_cnt, 'counter' => 0);
|
|
}
|
|
}
|
|
elseif ($mode == 'disabled') {
|
|
// Record disabled alias statistics
|
|
$pfbfound = FALSE;
|
|
if (!empty($pfb['dnsbl_info_stats'])) {
|
|
foreach ($pfb['dnsbl_info_stats'] as $line) {
|
|
if ($line['groupname'] == "{$alias}") {
|
|
$pfbfound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$pfbfound) {
|
|
$dns_now = date('M j H:i:s', time());
|
|
$pfb['dnsbl_info_stats'][] = array ('groupname' => $alias, 'timestamp' => $dns_now, 'entries' => 'disabled', 'counter' => 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Function to save DNSBL Group statistics
|
|
function dnsbl_save_stats() {
|
|
global $pfb;
|
|
|
|
// Save group statistics to SQLite3 database (Remove any feeds that are not referenced)
|
|
$db_update = $db_delete = '';
|
|
pfb_logger("\nSaving DNSBL statistics...", 1);
|
|
|
|
// Collect existing SQL group names
|
|
$sql_groupnames = array();
|
|
$db_handle = pfb_open_sqlite(1, 'Collect Group');
|
|
if ($db_handle) {
|
|
$result = $db_handle->query("SELECT * FROM dnsbl;");
|
|
if ($result) {
|
|
while ($res = $result->fetchArray(SQLITE3_ASSOC)) {
|
|
$sql_groupnames[$res['groupname']] = '';
|
|
}
|
|
}
|
|
}
|
|
pfb_close_sqlite($db_handle);
|
|
|
|
// Compare SQL database Group names to latest Group names
|
|
if (!empty($pfb['dnsbl_info_stats'])) {
|
|
|
|
$db_handle = pfb_open_sqlite(1, 'Save DNSBL stats');
|
|
if ($db_handle) {
|
|
|
|
foreach ($pfb['dnsbl_info_stats'] as $group) {
|
|
|
|
// Keep row
|
|
$pfb_delete = FALSE;
|
|
if (in_array($group['groupname'], $pfb['alias_dnsbl_all'])) {
|
|
|
|
// Update existing row
|
|
if (isset($sql_groupnames[$group['groupname']])) {
|
|
$db_update = "UPDATE dnsbl SET timestamp = :timestamp, entries = :entries"
|
|
. " WHERE groupname = :groupname;\n";
|
|
}
|
|
|
|
// Add new row
|
|
else {
|
|
$db_update = "INSERT INTO dnsbl (groupname, timestamp, entries, counter)"
|
|
. " VALUES (:groupname, :timestamp, :entries, 0);\n";
|
|
}
|
|
}
|
|
|
|
// Remove row
|
|
else {
|
|
$db_update = "DELETE FROM dnsbl WHERE groupname = :groupname;\n";
|
|
$pfb_delete = TRUE;
|
|
}
|
|
|
|
if (is_numeric($group['entries'])) {
|
|
$group['groupname'] = pfb_filter($group['groupname'], PFB_FILTER_HTML, 'dnsbl_save_stats');
|
|
|
|
$stmt = $db_handle->prepare($db_update);
|
|
if ($stmt) {
|
|
$stmt->bindValue(':groupname', $group['groupname'], SQLITE3_TEXT);
|
|
if (!$pfb_delete) {
|
|
$group['timestamp'] = pfb_filter($group['timestamp'], PFB_FILTER_HTML, 'dnsbl_save_stats');
|
|
|
|
$stmt->bindValue(':timestamp', $group['timestamp'], SQLITE3_TEXT);
|
|
$stmt->bindValue(':entries', $group['entries'], SQLITE3_TEXT);
|
|
}
|
|
$stmt->execute();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pfb_close_sqlite($db_handle);
|
|
}
|
|
else {
|
|
$db_delete = 'DROP TABLE dnsbl;';
|
|
$db_handle = pfb_open_sqlite(1, 'Delete table');
|
|
if ($db_handle) {
|
|
$db_handle->exec("BEGIN TRANSACTION;"
|
|
. "{$db_delete}"
|
|
. "END TRANSACTION;");
|
|
}
|
|
pfb_close_sqlite($db_handle);
|
|
}
|
|
pfb_logger(" completed [ NOW ]", 1);
|
|
}
|
|
|
|
|
|
// Function to create DNSBL Lighttpd configuration file
|
|
function pfb_create_lighttpd() {
|
|
global $pfb;
|
|
|
|
$lighttpd_bind = $pfb['dnsbl_iface'] != 'lo0' ? "127.0.0.1" : "{$pfb['dnsbl_vip']}";
|
|
$lighttpd_port = $pfb['dnsbl_iface'] != 'lo0' ? "{$pfb['dnsbl_port']}" : '80';
|
|
|
|
$pfb_conf = '';
|
|
$pfb_conf = <<<EOF
|
|
#
|
|
#pfBlockerNG DNSBL Lighttpd configuration file
|
|
#
|
|
server.tag = "pfBlockerNG DNSBL"
|
|
server.bind = "{$lighttpd_bind}"
|
|
server.port = "{$lighttpd_port}"
|
|
server.event-handler = "freebsd-kqueue"
|
|
server.network-backend = "freebsd-sendfile"
|
|
server.dir-listing = "disable"
|
|
server.document-root = "/usr/local/www/pfblockerng/www/"
|
|
server.max-request-size = "1"
|
|
server.pid-file = "/var/run/dnsbl.pid"
|
|
|
|
EOF;
|
|
|
|
if (!$pfb['dnsbl_py_blacklist'] || $pfb['dnsbl_py_nolog'] == 'on') {
|
|
$pfb_conf .= <<<EOF
|
|
server.errorlog = "|/usr/local/bin/php -f /usr/local/pkg/pfblockerng/pfblockerng.inc dnsbl"
|
|
|
|
EOF;
|
|
}
|
|
|
|
if (file_exists('/usr/local/lib/lighttpd/mod_openssl.so')) {
|
|
if (!$pfb['dnsbl_py_blacklist'] || $pfb['dnsbl_py_nolog'] == 'on') {
|
|
$pfb_conf .= 'server.modules = ( "mod_access", "mod_auth", "mod_accesslog", "mod_fastcgi", "mod_rewrite", "mod_openssl" )';
|
|
} else {
|
|
$pfb_conf .= 'server.modules = ( "mod_auth", "mod_fastcgi", "mod_rewrite", "mod_openssl" )';
|
|
}
|
|
} else {
|
|
if (!$pfb['dnsbl_py_blacklist'] || $pfb['dnsbl_py_nolog'] == 'on') {
|
|
$pfb_conf .= 'server.modules = ( "mod_access", "mod_auth", "mod_accesslog", "mod_fastcgi", "mod_rewrite" )';
|
|
} else {
|
|
$pfb_conf .= 'server.modules = ( "mod_auth", "mod_fastcgi", "mod_rewrite" )';
|
|
}
|
|
}
|
|
|
|
$pfb_conf .= <<<EOF
|
|
|
|
index-file.names = ( "index.php" )
|
|
mimetype.assign = ( ".html" => "text/html", ".gif" => "image/gif" )
|
|
url.access-deny = ( "~", ".inc" )
|
|
fastcgi.server = ( ".php" => ( "localhost" => ( "socket" => "/var/run/php-fpm.socket", "broken-scriptfilename" => "enable" ) ) )
|
|
|
|
EOF;
|
|
if (!$pfb['dnsbl_py_blacklist'] || $pfb['dnsbl_py_nolog'] == 'on') {
|
|
|
|
$pfb_conf .= <<<EOF
|
|
debug.log-condition-handling = "enable"
|
|
accesslog.use-syslog = "disable"
|
|
accesslog.format = "INDEX!%r!%V!%h!%{Referer}i * %r * %{User-Agent}i"
|
|
accesslog.filename = "|/usr/local/bin/php -f /usr/local/pkg/pfblockerng/pfblockerng.inc index"
|
|
|
|
EOF;
|
|
}
|
|
// Lighttpd v1.4.58+ conditional error log with 'ssl.verifyclient.activate' to collect the domain name
|
|
$lighty_ver = exec('/usr/local/sbin/lighttpd -v 2>&1');
|
|
if ((!$pfb['dnsbl_py_blacklist'] || $pfb['dnsbl_py_nolog'] == 'on') &&
|
|
(strpos($lighty_ver, 'lighttpd/1.4.58') !== FALSE ||
|
|
strpos($lighty_ver, 'lighttpd/1.4.59') !== FALSE ||
|
|
strpos($lighty_ver, 'lighttpd/1.4.60') !== FALSE ||
|
|
strpos($lighty_ver, 'lighttpd/1.4.61') !== FALSE ||
|
|
strpos($lighty_ver, 'lighttpd/1.4.67') !== FALSE ||
|
|
strpos($lighty_ver, 'lighttpd/1.5') !== FALSE)) {
|
|
|
|
$pfb_conf .= <<<EOF
|
|
ssl.verifyclient.activate = "enable"
|
|
|
|
EOF;
|
|
}
|
|
|
|
$pfb_conf .= <<<EOF
|
|
|
|
\$HTTP["scheme"] == "http" {
|
|
url.rewrite-once = ( ".*" => "/index.php" )
|
|
}
|
|
|
|
\$HTTP["remoteip"] =~ ".*" {
|
|
|
|
EOF;
|
|
|
|
if ($pfb['dnsbl_iface'] != 'lo0') {
|
|
$pfb_conf .= <<<EOF
|
|
|
|
\$SERVER["socket"] == "127.0.0.1:{$pfb['dnsbl_port_ssl']}" {
|
|
ssl.engine = "enable"
|
|
ssl.pemfile = "/var/unbound/dnsbl_cert.pem"
|
|
ssl.dh-file = "/etc/dh-parameters.4096"
|
|
ssl.ec-curve = "secp384r1"
|
|
ssl.honor-cipher-order = "enable"
|
|
ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.2",
|
|
"Options" => "-ServerPreference",
|
|
"CipherString" => "EECDH+AESGCM:AES256+EECDH:CHACHA20:!SHA1:!SHA256:!SHA384")
|
|
}
|
|
|
|
\$SERVER["socket"] == "{$pfb['dnsbl_vip']}:80" {
|
|
#
|
|
}
|
|
|
|
EOF;
|
|
}
|
|
$pfb_conf .= <<<EOF
|
|
|
|
\$SERVER["socket"] == "{$pfb['dnsbl_vip']}:443" {
|
|
ssl.engine = "enable"
|
|
ssl.pemfile = "/var/unbound/dnsbl_cert.pem"
|
|
ssl.dh-file = "/etc/dh-parameters.4096"
|
|
ssl.ec-curve = "secp384r1"
|
|
ssl.honor-cipher-order = "enable"
|
|
ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.2",
|
|
"Options" => "-ServerPreference",
|
|
"CipherString" => "EECDH+AESGCM:AES256+EECDH:CHACHA20:!SHA1:!SHA256:!SHA384")
|
|
}
|
|
|
|
EOF;
|
|
if ($pfb['dnsbl_iface'] != 'lo0') {
|
|
$pfb_conf .= <<<EOF
|
|
|
|
\$SERVER["socket"] == "[::1]:{$pfb['dnsbl_port']}" {
|
|
#
|
|
}
|
|
|
|
\$SERVER["socket"] == "[::1]:{$pfb['dnsbl_port_ssl']}" {
|
|
ssl.engine = "enable"
|
|
ssl.pemfile = "/var/unbound/dnsbl_cert.pem"
|
|
ssl.dh-file = "/etc/dh-parameters.4096"
|
|
ssl.ec-curve = "secp384r1"
|
|
ssl.honor-cipher-order = "enable"
|
|
ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.2",
|
|
"Options" => "-ServerPreference",
|
|
"CipherString" => "EECDH+AESGCM:AES256+EECDH:CHACHA20:!SHA1:!SHA256:!SHA384")
|
|
}
|
|
|
|
EOF;
|
|
}
|
|
if ($pfb['dnsbl_v6'] == 'on') {
|
|
$pfb_conf .= <<<EOF
|
|
|
|
\$SERVER["socket"] == "[::{$pfb['dnsbl_vip']}]:80" {
|
|
#
|
|
}
|
|
|
|
\$SERVER["socket"] == "[::{$pfb['dnsbl_vip']}]:443" {
|
|
ssl.engine = "enable"
|
|
ssl.pemfile = "/var/unbound/dnsbl_cert.pem"
|
|
ssl.dh-file = "/etc/dh-parameters.4096"
|
|
ssl.ec-curve = "secp384r1"
|
|
ssl.honor-cipher-order = "enable"
|
|
ssl.openssl.ssl-conf-cmd = ("MinProtocol" => "TLSv1.2",
|
|
"Options" => "-ServerPreference",
|
|
"CipherString" => "EECDH+AESGCM:AES256+EECDH:CHACHA20:!SHA1:!SHA256:!SHA384")
|
|
}
|
|
|
|
EOF;
|
|
}
|
|
$pfb_conf .= <<<EOF
|
|
|
|
\$HTTP["host"] =~ ".*" {
|
|
url.rewrite-once = ( ".*" => "/index.php" )
|
|
}
|
|
}
|
|
|
|
EOF;
|
|
return $pfb_conf;
|
|
}
|
|
|
|
|
|
// Function to create DNSBL SSL certificate
|
|
function pfb_create_dnsbl_cert() {
|
|
global $pfb, $cert_strict_values;
|
|
|
|
$cert = array();
|
|
$cert['refid'] = uniqid();
|
|
$cert['descr'] = sprintf(gettext("pfBlockerNG DNSBL (%s)"), $cert['refid']);
|
|
$cert_hostname = config_get_path('system/hostname') . "-pfBNG-DNSBL-{$cert['refid']}";
|
|
|
|
$dn = array(
|
|
'organizationName' => "pfBlockerNG DNSBL Self-Signed Certificate",
|
|
'commonName' => $cert_hostname,
|
|
'subjectAltName' => "DNS:{$cert_hostname}");
|
|
|
|
$old_err_level = error_reporting(0); /* otherwise openssl_ functions throw warnings directly to a page screwing menu tab */
|
|
if (!cert_create($cert, null, 2048, $cert_strict_values['max_server_cert_lifetime'] ?: 398, $dn, 'self-signed', 'sha256')) {
|
|
while ($ssl_err = openssl_error_string()) {
|
|
log_error(sprintf(gettext("Error creating pfBlockerNG DNSBL Certificate: openssl library returns: %s"), $ssl_err));
|
|
}
|
|
error_reporting($old_err_level);
|
|
return null;
|
|
}
|
|
error_reporting($old_err_level);
|
|
$privatekey = base64_decode($cert['prv']);
|
|
$publickey = base64_decode($cert['crt']);
|
|
|
|
@file_put_contents("{$pfb['dnsbl_cert']}", "{$privatekey}{$publickey}", LOCK_EX);
|
|
}
|
|
|
|
|
|
// Create DNSBL VIP and NAT rules, lighttpd conf and services
|
|
function pfb_create_dnsbl($mode) {
|
|
global $pfb;
|
|
pfb_global();
|
|
|
|
// Reload config.xml to get any recent changes
|
|
config_read_file(false, true);
|
|
|
|
$new_nat = $new_vip = $pfb_ex_nat = $pfb_ex_vip = $dnsbl_ex_nat = $dnsbl_ex_vip = array();
|
|
$pfb['dnsbl_vip_changed'] = $pfbupdate = FALSE;
|
|
$iface = escapeshellarg(get_real_interface($pfb['dnsbl_iface']));
|
|
|
|
if ((!empty($pfb['dnsbl_port']) && !empty($pfb['dnsbl_port_ssl']) && !empty($pfb['dnsbl_vip']) && $mode == 'enabled') || $mode == 'disabled') {
|
|
|
|
// DNSBL NAT rules generation
|
|
$pfbfound = FALSE;
|
|
// Collect existing pfSense NAT rules
|
|
foreach (config_get_path('nat/rule', []) as $ex_nat) {
|
|
if (strpos($ex_nat['descr'], 'pfB DNSBL') !== FALSE) {
|
|
// Collect DNSBL NAT rules
|
|
$dnsbl_ex_nat[] = $ex_nat;
|
|
$pfbfound = TRUE;
|
|
} else {
|
|
// Collect all 'other' NAT rules
|
|
$pfb_ex_nat[] = $ex_nat;
|
|
}
|
|
}
|
|
|
|
if ($mode == 'enabled') {
|
|
|
|
// Generate new DNSBL NAT per DNSBL listening ports except for 'localhost' interface setting.
|
|
$dnsbl_new_nat = array();
|
|
if ($pfb['dnsbl_iface'] != 'lo0') {
|
|
$selected_ports = array("{$pfb['dnsbl_port']}" => '80', "{$pfb['dnsbl_port_ssl']}" => '443');
|
|
foreach ($selected_ports as $port => $lport) {
|
|
|
|
$dnsbl_new_nat[] = array ( 'source' => array('any' => ''),
|
|
'destination' => array('address' => "{$pfb['dnsbl_vip']}", 'port' => "{$lport}"),
|
|
'protocol' => 'tcp',
|
|
'target' => '127.0.0.1',
|
|
'local-port' => "{$port}",
|
|
'interface' => "{$pfb['dnsbl_iface']}",
|
|
'descr' => 'pfB DNSBL - DO NOT EDIT',
|
|
'associated-rule-id' => 'pass',
|
|
'natreflection' => 'purenat'
|
|
);
|
|
|
|
// Add DNSBL IPv6 NAT Rules
|
|
if ($pfb['dnsbl_v6'] == 'on') {
|
|
$dnsbl_new_nat[] = array ( 'source' => array('any' => ''),
|
|
'destination' => array('address' => "::{$pfb['dnsbl_vip']}",
|
|
'port' => "{$lport}"),
|
|
'protocol' => 'tcp',
|
|
'target' => '::1',
|
|
'local-port' => "{$port}",
|
|
'interface' => "{$pfb['dnsbl_iface']}",
|
|
'ipprotocol' => 'inet6',
|
|
'descr' => 'pfB DNSBL - DO NOT EDIT',
|
|
'associated-rule-id' => 'pass',
|
|
'natreflection' => 'purenat'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compare existing to new and if they are not identical update
|
|
if ($dnsbl_ex_nat !== $dnsbl_new_nat) {
|
|
$pfbupdate = TRUE;
|
|
$new_nat = array_merge($pfb_ex_nat, $dnsbl_new_nat);
|
|
} else {
|
|
$new_nat = array_merge($pfb_ex_nat, $dnsbl_ex_nat);
|
|
}
|
|
} else {
|
|
$new_nat = array_merge($pfb_ex_nat, $new_nat);
|
|
// Update when DNSBL NAT found but is now disabled.
|
|
if ($pfbfound) {
|
|
$pfbupdate = TRUE;
|
|
}
|
|
}
|
|
|
|
// DNSBL VIP generation
|
|
$dnsbl_new_vip = array();
|
|
$dnsbl_new_vip[0] = array();
|
|
$dnsbl_new_vip[0]['interface'] = "{$pfb['dnsbl_iface']}";
|
|
$dnsbl_new_vip[0]['descr'] = 'pfB DNSBL - DO NOT EDIT';
|
|
$dnsbl_new_vip[0]['type'] = 'single';
|
|
$dnsbl_new_vip[0]['subnet_bits'] = '32';
|
|
$dnsbl_new_vip[0]['subnet'] = "{$pfb['dnsbl_vip']}";
|
|
|
|
if ($pfb['dnsbl_vip_type'] == 'carp') {
|
|
$dnsbl_new_vip[0]['mode'] = 'carp';
|
|
$dnsbl_new_vip[0]['vhid'] = "{$pfb['dnsbl_vip_vhid']}";
|
|
$dnsbl_new_vip[0]['advskew'] = "{$pfb['dnsbl_vip_skew']}";
|
|
$dnsbl_new_vip[0]['advbase'] = "{$pfb['dnsbl_vip_base']}";
|
|
$dnsbl_new_vip[0]['password'] = "{$pfb['dnsbl_vip_pass']}";
|
|
} else {
|
|
$dnsbl_new_vip[0]['mode'] = 'ipalias';
|
|
}
|
|
|
|
// Add DNSBL IPv6 VIP
|
|
if ($pfb['dnsbl_v6'] == 'on') {
|
|
$dnsbl_new_vip[1] = array();
|
|
$dnsbl_new_vip[1]['interface'] = "{$pfb['dnsbl_iface']}";
|
|
$dnsbl_new_vip[1]['descr'] = 'pfB DNSBL - DO NOT EDIT';
|
|
$dnsbl_new_vip[1]['type'] = 'single';
|
|
$dnsbl_new_vip[1]['subnet_bits'] = '128';
|
|
$dnsbl_new_vip[1]['subnet'] = "::{$pfb['dnsbl_vip']}";
|
|
|
|
if ($pfb['dnsbl_vip_type'] == 'carp') {
|
|
$dnsbl_new_vip[1]['mode'] = 'carp';
|
|
$dnsbl_new_vip[1]['vhid'] = "{$pfb['dnsbl_vip_vhid']}";
|
|
$dnsbl_new_vip[1]['advskew'] = "{$pfb['dnsbl_vip_skew']}";
|
|
$dnsbl_new_vip[1]['advbase'] = "{$pfb['dnsbl_vip_base']}";
|
|
$dnsbl_new_vip[1]['password'] = "{$pfb['dnsbl_vip_pass']}";
|
|
} else {
|
|
$dnsbl_new_vip[1]['mode'] = 'ipalias';
|
|
}
|
|
}
|
|
|
|
$vip_count = 0;
|
|
$pfbfound = FALSE;
|
|
// Collect existing pfSense VIPs
|
|
foreach (config_get_path('virtualip/vip', []) as $ex_vip) {
|
|
if (strpos($ex_vip['descr'], 'pfB DNSBL') !== FALSE) {
|
|
// Collect DNSBL VIP
|
|
$dnsbl_ex_vip[] = $ex_vip;
|
|
$pfbfound = TRUE;
|
|
$vip_count++;
|
|
} else {
|
|
// Collect all 'other' VIPs
|
|
$pfb_ex_vip[] = $ex_vip;
|
|
}
|
|
}
|
|
|
|
if (isset($dnsbl_ex_vip[0]) && !isset($dnsbl_ex_vip[0]['uniqid'])) {
|
|
$dnsbl_new_vip[0]['uniqid'] = uniqid();
|
|
}
|
|
if (isset($dnsbl_ex_vip[1]) && !isset($dnsbl_ex_vip[1]['uniqid'])) {
|
|
$dnsbl_new_vip[1]['uniqid'] = uniqid();
|
|
}
|
|
|
|
if ($mode == 'enabled') {
|
|
// Compare existing to new and if they are not identical update
|
|
if ($dnsbl_ex_vip !== $dnsbl_new_vip) {
|
|
$pfb['dnsbl_vip_changed'] = TRUE;
|
|
$pfbupdate = TRUE;
|
|
$new_vip = array_merge($pfb_ex_vip, $dnsbl_new_vip);
|
|
} else {
|
|
$new_vip = array_merge($pfb_ex_vip, $dnsbl_ex_vip);
|
|
}
|
|
} else {
|
|
$new_vip = array_merge($pfb_ex_vip, $new_vip);
|
|
// Update when DNSBL NAT found but is now disabled.
|
|
if ($pfbfound) {
|
|
$pfbupdate = TRUE;
|
|
}
|
|
}
|
|
|
|
// Compare previous Lighttpd conf
|
|
$pfb_lighty_conf = pfb_create_lighttpd();
|
|
$pfb_lighty_conf_ex = @file_get_contents($pfb['dnsbl_conf']);
|
|
if ($pfb_lighty_conf !== $pfb_lighty_conf_ex) {
|
|
$pfbupdate = TRUE;
|
|
@file_put_contents($pfb['dnsbl_conf'], $pfb_lighty_conf, LOCK_EX);
|
|
$log = "\nSaving new DNSBL web server configuration to port [ {$pfb['dnsbl_port']} and {$pfb['dnsbl_port_ssl']} ]";
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
|
|
// Validate DNSBL VIP address(es)
|
|
$result = array();
|
|
foreach (array("inet {$pfb['dnsbl_vip']}", "inet6 ::{$pfb['dnsbl_vip']}") as $g_vip) {
|
|
$g_vip = escapeshellarg($g_vip);
|
|
exec("/sbin/ifconfig {$iface} | {$pfb['grep']} {$g_vip} 2>&1", $result, $retval);
|
|
}
|
|
if (count($result) != $vip_count) {
|
|
$pfbupdate = TRUE;
|
|
}
|
|
|
|
// Update config.xml, if changes required
|
|
if ($pfbupdate) {
|
|
config_set_path('nat/rule', $new_nat);
|
|
config_set_path('virtualip/vip', $new_vip);
|
|
write_config('pfBlockerNG: saving DNSBL changes');
|
|
}
|
|
|
|
if ($mode == 'enabled' && $pfbupdate) {
|
|
|
|
// Execute ifconfig to enable VIP address
|
|
if (!empty($iface) && !empty($pfb['dnsbl_vip'])) {
|
|
|
|
if (is_service_running('pfb_dnsbl')) {
|
|
pfb_logger("\nStop Service DNSBL", 1);
|
|
stop_service('pfb_dnsbl');
|
|
}
|
|
|
|
foreach (array("{$pfb['dnsbl_vip']}", "::{$pfb['dnsbl_vip']}") as $vip) {
|
|
|
|
$mask = '32';
|
|
$inet = 'inet';
|
|
if (strpos($vip, '::') !== FALSE) {
|
|
$mask = '128';
|
|
$inet = 'inet6';
|
|
}
|
|
|
|
$g_vip = escapeshellarg("{$inet} {$vip}");
|
|
$vip = escapeshellarg($vip);
|
|
|
|
// Clear any existing VIP
|
|
$result = exec("/sbin/ifconfig {$iface} | {$pfb['grep']} {$g_vip} 2>&1");
|
|
if (!empty($result)) {
|
|
exec("/sbin/ifconfig {$iface} {$inet} {$vip} -alias 2>&1");
|
|
}
|
|
|
|
if ($inet == 'inet6' && $pfb['dnsbl_v6'] != 'on') {
|
|
break;
|
|
}
|
|
|
|
// Configure VIP type (ipalias or carp)
|
|
if ($pfb['dnsbl_vip_type'] == 'carp') {
|
|
$vhid = escapeshellarg("vhid {$pfb['dnsbl_vip_vhid']}");
|
|
$advbase = escapeshellarg("advbase {$pfb['dnsbl_vip_base']}");
|
|
$advskew = escapeshellarg("advskew {$pfb['dnsbl_vip_skew']}");
|
|
if (config_path_enabled('', 'virtualip_carp_maintenancemode')) {
|
|
$advskew = 'advskew 254';
|
|
}
|
|
|
|
$password = '';
|
|
if (!empty($pfb['dnsbl_vip_pass'])) {
|
|
$password = 'pass ' . escapeshellarg(addslashes(str_replace(' ', '', $pfb['dnsbl_vip_pass'])));
|
|
}
|
|
|
|
exec("/sbin/ifconfig {$iface} {$inet} {$vhid} {$advskew} {$advbase} {$password} 2>&1");
|
|
exec("/sbin/ifconfig {$iface} {$inet} '{$vip}/{$mask}' alias {$vhid} 2>&1");
|
|
}
|
|
else {
|
|
exec("/sbin/ifconfig {$iface} {$inet} '{$vip}/{$mask}' alias 2>&1");
|
|
}
|
|
|
|
// Validate 'tentative' interface state
|
|
for ($i=10; $i > 0; $i--) {
|
|
$result = exec("/sbin/ifconfig {$iface} | {$pfb['grep']} {$g_vip} | {$pfb['grep']} 'tentative' 2>&1");
|
|
if (!empty($result)) {
|
|
pfb_logger('.', 1);
|
|
usleep(500000);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$log = "\nVIP address(es) configured";
|
|
pfb_logger("{$log}", 1);
|
|
$pfb['filter_configure'] = TRUE;
|
|
} else {
|
|
$log = "DNSBL ifconfig error : Interface:{$iface}, VIP:{$pfb['dnsbl_iface']}\n";
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save settings, restart services as required
|
|
if ($mode == 'enabled') {
|
|
|
|
// Create DNSBL SSL certificate
|
|
if (!file_exists("{$pfb['dnsbl_cert']}")) {
|
|
pfb_create_dnsbl_cert();
|
|
|
|
$log = "\nNew DNSBL certificate created";
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
|
|
if ($pfbupdate || !is_service_running('pfb_dnsbl')) {
|
|
|
|
// Remove any existing and create link for DNSBL lighttpd executable
|
|
unlink_if_exists('/usr/local/sbin/lighttpd_pfb');
|
|
link('/usr/local/sbin/lighttpd', '/usr/local/sbin/lighttpd_pfb');
|
|
|
|
$log = "\nRestarting DNSBL Service";
|
|
pfb_logger("{$log}", 1);
|
|
restart_service('pfb_dnsbl');
|
|
}
|
|
}
|
|
else {
|
|
if (is_service_running('pfb_dnsbl')) {
|
|
pfb_logger("Stop Service DNSBL\n", 1);
|
|
stop_service('pfb_dnsbl');
|
|
}
|
|
|
|
// Remove DNSBL VIP address
|
|
if (!empty($iface) && !empty($pfb['dnsbl_vip'])) {
|
|
foreach (array("{$pfb['dnsbl_vip']}" => 'inet', "::{$pfb['dnsbl_vip']}" => 'inet6') as $vip => $inet) {
|
|
|
|
$g_vip = escapeshellarg("{$inet} {$vip}");
|
|
$vip = escapeshellarg($vip);
|
|
|
|
$result = exec("/sbin/ifconfig {$iface} | {$pfb['grep']} {$g_vip} 2>&1");
|
|
if (!empty($result)) {
|
|
exec("/sbin/ifconfig {$iface} {$inet} {$vip} -alias 2>&1");
|
|
$pfb['filter_configure'] = TRUE;
|
|
}
|
|
|
|
// Validate 'tentative' interface state
|
|
for ($i=10; $i > 0; $i--) {
|
|
$result = exec("/sbin/ifconfig {$iface} | {$pfb['grep']} {$g_vip} | {$pfb['grep']} 'tentative' 2>&1");
|
|
if (!empty($result)) {
|
|
pfb_logger('.', 1);
|
|
usleep(500000);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Define DNSBL Unbound include settings (config.xml)
|
|
function pfb_unbound_dnsbl($mode) {
|
|
global $g, $pfb;
|
|
pfb_global();
|
|
|
|
// Reload config.xml to get any recent changes
|
|
config_read_file(false, true);
|
|
|
|
$pfbupdate = FALSE;
|
|
$unbound_include = "server:include: {$pfb['dnsbl_file']}.*conf";
|
|
|
|
// Collect Unbound custom option pfSense conf line
|
|
$pfb['unboundconfig'] = config_get_path('unbound/custom_options');
|
|
|
|
if (!empty($pfb['unboundconfig'])) {
|
|
$unbound_custom = base64_decode($pfb['unboundconfig']);
|
|
} else {
|
|
$unbound_custom = '';
|
|
}
|
|
|
|
// Determine if DNSBL include line exists
|
|
if (!empty($unbound_custom)) {
|
|
// Append DNSBL Unbound pfSense conf integration
|
|
if (!strstr($unbound_custom, 'pfb_dnsbl.*conf')) {
|
|
if ($mode == 'enabled') {
|
|
if (!$pfb['dnsbl_py_blacklist']) {
|
|
$pfbupdate = TRUE;
|
|
$unbound_custom .= "\n{$unbound_include}";
|
|
$log = "\nAdding DNSBL Unbound mode (Resolver adv. setting)";
|
|
}
|
|
|
|
// To be removed when SafeSearch CNAME python mode has been fixed
|
|
elseif ($pfb['safesearch_enable'] !== 'Disable') {
|
|
$pfbupdate = TRUE;
|
|
$unbound_custom .= "\n{$unbound_include}";
|
|
$log = "\nAdding DNSBL SafeSearch CNAME mode (Resolver adv. setting)";
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Remove DNSBL Unbound pfSense conf integration when disabled
|
|
// or when DNSBL python mode is enabled but not when SafeSearch is enabled
|
|
if ($mode == 'disabled' || $pfb['dnsbl_py_blacklist']) {
|
|
$custom = explode ("\n", $unbound_custom);
|
|
foreach ($custom as $key => $line) {
|
|
if (strpos($line, 'pfb_dnsbl.*conf') !== FALSE) {
|
|
$pfbupdate = TRUE;
|
|
if (!$pfb['dnsbl_py_blacklist']) {
|
|
$log = "\nRemoving DNSBL SafeSearch mode (Resolver adv. setting)";
|
|
unset($custom[$key]);
|
|
}
|
|
|
|
// To be removed when SafeSearch CNAME python mode has been fixed
|
|
elseif ($pfb['safesearch_enable'] !== 'Disable') {
|
|
//
|
|
}
|
|
|
|
else {
|
|
$log = "\nRemoving DNSBL Unbound mode and/or DNSBL SafeSearch CNAME mode (Resolver adv. setting)";
|
|
unset($custom[$key]);
|
|
}
|
|
}
|
|
}
|
|
$unbound_custom = implode("\n", $custom);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Add DNSBL Unbound pfSense conf integration
|
|
if ($mode == 'enabled') {
|
|
if (!$pfb['dnsbl_py_blacklist']) {
|
|
$pfbupdate = TRUE;
|
|
$unbound_custom = "{$unbound_include}";
|
|
$log = "\nAdding DNSBL Unbound mode (Resolver adv. setting)";
|
|
}
|
|
|
|
// To be removed when SafeSearch CNAME python mode has been fixed
|
|
elseif ($pfb['safesearch_enable'] !== 'Disable') {
|
|
$pfbupdate = TRUE;
|
|
$unbound_custom = "{$unbound_include}";
|
|
$log = "\nAdding DNSBL SafeSearch CNAME mode (Resolver adv. setting)";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the previous include line, see Bug #6603
|
|
$custom = explode ("\n", $unbound_custom);
|
|
foreach ($custom as $key => $line) {
|
|
if (strpos($line, 'pfb_dnsbl.conf') !== FALSE) {
|
|
$pfbupdate = TRUE;
|
|
$log .= "\nDNSBL - Removing previous DNSBL Unbound custom option\n";
|
|
unset($custom[$key]);
|
|
}
|
|
}
|
|
$unbound_custom = implode("\n", $custom);
|
|
|
|
// Update config.xml, if changes required
|
|
if ($pfbupdate) {
|
|
pfb_logger("{$log}", 1);
|
|
$unbound_custom = base64_encode(str_replace("\r\n", "\n", $unbound_custom));
|
|
$pfb['unboundconfig'] = "{$unbound_custom}";
|
|
config_set_path('unbound/custom_options', $pfb['unboundconfig']);
|
|
write_config('pfBlockerNG: saving Unbound custom options');
|
|
}
|
|
|
|
// Modify unbound.conf file as required
|
|
if (file_exists("{$pfb['dnsbldir']}/unbound.conf")) {
|
|
$conf = file("{$pfb['dnsbldir']}/unbound.conf");
|
|
if (empty($conf)) {
|
|
pfb_logger("\nDNS Resolver configuration file missing or empty, Exiting!", 1);
|
|
}
|
|
|
|
$unbound = FALSE; // Unbound mode
|
|
$unbound_py = FALSE; // Unbound python mode
|
|
|
|
$u_update = FALSE;
|
|
$u_msg = '';
|
|
foreach ($conf as $key => $line) {
|
|
|
|
if (empty($line)) {
|
|
continue;
|
|
}
|
|
|
|
elseif (strpos($line, 'pfb_dnsbl.*conf') !== FALSE) {
|
|
if ($mode == 'enabled') {
|
|
if (!$pfb['dnsbl_py_blacklist']) {
|
|
$unbound = TRUE;
|
|
}
|
|
|
|
// To be removed when SafeSearch CNAME python mode has been fixed
|
|
elseif ($pfb['safesearch_enable'] !== 'Disable') {
|
|
$unbound = TRUE;
|
|
}
|
|
|
|
else {
|
|
$u_update = TRUE;
|
|
$u_msg .= " Removed DNSBL SafeSearch mode\n";
|
|
unset($conf[$key]);
|
|
}
|
|
} else {
|
|
$u_update = TRUE;
|
|
$u_msg .= " Removed DNSBL Unbound mode\n";
|
|
unset($conf[$key]);
|
|
}
|
|
}
|
|
|
|
elseif (strpos($line, 'module-config:') !== FALSE) {
|
|
if ($mode == 'enabled' && $pfb['dnsbl_mode'] == 'dnsbl_python') {
|
|
if (strpos($line, 'module-config: "python') !== FALSE) {
|
|
$unbound_py = TRUE;
|
|
} else {
|
|
$u_update = TRUE;
|
|
$u_msg .= " Added DNSBL Unbound Python mode\n";
|
|
$conf[$key] = str_replace('module-config: "', 'module-config: "python ', $line);
|
|
}
|
|
}
|
|
else {
|
|
// Only remove python module if script is 'pfb_unbound'
|
|
if (strpos($line, 'module-config: "python') !== FALSE && in_array("python-script: pfb_unbound.py\n", $conf)) {
|
|
$u_update = TRUE;
|
|
$u_msg .= " Removed DNSBL Unbound Python mode\n";
|
|
$conf[$key] = str_replace('module-config: "python ', 'module-config: "', $line);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove any DNSBL VIPs added to unbound.conf on 'disable'
|
|
elseif ((strpos($line, 'interface:') !== FALSE) && ($mode == 'disabled') &&
|
|
!empty($pfb['dnsbl_vip']) && (strpos($line, $pfb['dnsbl_vip']) !== FALSE)) {
|
|
$u_update = TRUE;
|
|
$u_msg .= " Removed DNSBL VIP from Unbound Interface settings\n";
|
|
unset($conf[$key]);
|
|
}
|
|
|
|
elseif (strpos($line, 'python-script: pfb_unbound.py') !== FALSE) {
|
|
if ($mode == 'enabled' && $pfb['dnsbl_mode'] == 'dnsbl_python') {
|
|
$unbound_py = TRUE;
|
|
} else {
|
|
$u_update = TRUE;
|
|
$u_msg .= " Removed DNSBL Unbound Python mode script\n";
|
|
unset($conf[$key-1]); // remove 'python:' line above
|
|
unset($conf[$key]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add Unbound include line
|
|
if (!$unbound && $mode == 'enabled') {
|
|
if (!$pfb['dnsbl_py_blacklist']) {
|
|
$u_update = TRUE;
|
|
$u_msg .= " Added DNSBL Unbound mode\n";
|
|
$conf[] = "\nserver:include: {$pfb['dnsbl_file']}.*conf\n";
|
|
}
|
|
|
|
// To be removed when SafeSearch CNAME python mode has been fixed
|
|
elseif ($pfb['safesearch_enable'] !== 'Disable') {
|
|
$u_update = TRUE;
|
|
$u_msg .= " Added DNSBL SafeSearch CNAME mode\n";
|
|
$conf[] = "\nserver:include: {$pfb['dnsbl_file']}.*conf\n";
|
|
}
|
|
}
|
|
|
|
// Add python script line
|
|
if (!$unbound_py && $mode == 'enabled' && $pfb['dnsbl_mode'] == 'dnsbl_python') {
|
|
$u_update = TRUE;
|
|
$u_msg .= " Added DNSBL Unbound Python mode script\n";
|
|
$conf[] = "\npython:\npython-script: pfb_unbound.py\n";
|
|
}
|
|
|
|
if ($mode == 'enabled' && $pfb['dnsbl_mode'] == 'dnsbl_python') {
|
|
if (!file_exists("{$g['unbound_chroot_path']}/pfb_unbound.py")) {
|
|
@copy("/usr/local/pkg/pfblockerng/pfb_unbound.py", "{$g['unbound_chroot_path']}/pfb_unbound.py");
|
|
}
|
|
if (!file_exists("{$g['unbound_chroot_path']}/pfb_unbound_include.inc")) {
|
|
@copy("/usr/local/pkg/pfblockerng/pfb_unbound_include.inc", "{$g['unbound_chroot_path']}/pfb_unbound_include.inc");
|
|
}
|
|
if (!file_exists("{$g['unbound_chroot_path']}/pfb_py_hsts.txt")) {
|
|
@copy("/usr/local/pkg/pfblockerng/pfb_py_hsts.txt", "{$g['unbound_chroot_path']}/pfb_py_hsts.txt");
|
|
}
|
|
} else {
|
|
unlink_if_exists("{$g['unbound_chroot_path']}/pfb_unbound.py");
|
|
unlink_if_exists("{$g['unbound_chroot_path']}/pfb_unbound_include.inc");
|
|
unlink_if_exists("{$g['unbound_chroot_path']}/pfb_py_hsts.txt");
|
|
}
|
|
|
|
// Save changes to unbound.conf
|
|
if ($u_update) {
|
|
pfb_logger("\nDNS Resolver ( {$mode} ) unbound.conf modifications:\n{$u_msg}", 1);
|
|
@file_put_contents("{$pfb['dnsbldir']}/unbound.tmp", $conf, LOCK_EX);
|
|
@chown("{$pfb['dnsbldir']}/unbound.tmp", 'unbound');
|
|
@chgrp("{$pfb['dnsbldir']}/unbound.tmp", 'unbound');
|
|
return TRUE;
|
|
}
|
|
}
|
|
else {
|
|
pfb_logger("\n\n*** [ Unbound.conf file missing. Exiting! ] ***\n\n", 1);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// Create DNSBL Whitelist
|
|
function pfb_unbound_python_whitelist($mode='') {
|
|
global $pfb;
|
|
pfb_global();
|
|
|
|
$dnsbl_whitelist = '';
|
|
$dnsbl_white = pfbng_text_area_decode($pfb['dnsblconfig']['suppression'], TRUE, FALSE, TRUE);
|
|
if (!empty($dnsbl_white)) {
|
|
foreach ($dnsbl_white as $key => $line) {
|
|
if (!empty($line)) {
|
|
if (substr($line, 0, 4) == 'www.') {
|
|
$line = substr($line, 4);
|
|
}
|
|
|
|
// Minimize the python whitelist queries to the smallest tld segment count
|
|
if (!isset($tld_segments)) {
|
|
$tld_segments = (substr_count($line, '.') +1);
|
|
}
|
|
$tld_segments = @min((array((substr_count($line, '.') +1), $tld_segments) ?: 1));
|
|
|
|
if (substr($line, 0, 1) == '.') {
|
|
$line = ltrim($line, '.');
|
|
$dnsbl_whitelist .= "{$line},1\n";
|
|
} else {
|
|
$dnsbl_whitelist .= "{$line},0\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($mode == 'alerts') {
|
|
@file_put_contents($pfb['unbound_py_wh'], $dnsbl_whitelist, LOCK_EX);
|
|
} else {
|
|
return $dnsbl_whitelist;
|
|
}
|
|
}
|
|
|
|
|
|
// Unbound python configuration file
|
|
function pfb_unbound_python($mode) {
|
|
global $pfb;
|
|
pfb_global();
|
|
|
|
// Reload config.xml to get any recent changes
|
|
config_read_file(false, true);
|
|
|
|
$pfbpython = FALSE;
|
|
|
|
// Ensure log file permissions are set as 'unbound:unbound'
|
|
foreach (array('dnsbl.log', 'dns_reply.log', 'unified.log') as $logfile) {
|
|
|
|
if (!file_exists("{$pfb['logdir']}/{$logfile}")) {
|
|
touch("{$pfb['logdir']}/{$logfile}");
|
|
}
|
|
|
|
@chown("{$pfb['logdir']}/{$logfile}", 'unbound');
|
|
@chgrp("{$pfb['logdir']}/{$logfile}", 'unbound');
|
|
}
|
|
|
|
// Add python settings to DNS Resolver configuration
|
|
$python_enable = 'off';
|
|
if ($mode == 'enabled' && $pfb['dnsbl_mode'] == 'dnsbl_python') {
|
|
$python_enable = 'on';
|
|
|
|
if (!config_path_enabled('unbound', 'python') ||
|
|
config_get_path('unbound/python_script') != 'pfb_unbound') {
|
|
|
|
config_set_path('unbound/python', '');
|
|
config_set_path('unbound/python_order', 'pre_validator');
|
|
config_set_path('unbound/python_script', 'pfb_unbound');
|
|
|
|
$pfbpython = TRUE;
|
|
$log = 'Added DNSBL Unbound python integration settings';
|
|
pfb_logger("\n{$log}", 1);
|
|
write_config("pfBlockerNG: {$log}");
|
|
}
|
|
|
|
// If DNSBL python blocking mode enabled
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
|
|
// Create DNSBL Whitelist
|
|
$dnsbl_whitelist = pfb_unbound_python_whitelist();
|
|
|
|
// Compare previous DNSBL Whitelist to new Whitelist
|
|
$pfb_py_whitelist_ex = @file_get_contents($pfb['unbound_py_wh']);
|
|
if ($dnsbl_whitelist !== $pfb_py_whitelist_ex) {
|
|
$pfbpython = TRUE;
|
|
@file_put_contents($pfb['unbound_py_wh'], $dnsbl_whitelist, LOCK_EX);
|
|
}
|
|
}
|
|
|
|
// Remove previous whitelist and reload
|
|
elseif (file_exists($pfb['unbound_py_wh'])) {
|
|
unlink_if_exists($pfb['unbound_py_wh']);
|
|
$pfbpython = TRUE;
|
|
}
|
|
|
|
if (!isset($tld_segments)) {
|
|
$tld_segments = '1';
|
|
}
|
|
|
|
$python_ipv6 = 'off';
|
|
if ($pfb['dnsbl_v6'] == 'on') {
|
|
$python_ipv6 = 'on';
|
|
}
|
|
|
|
$python_reply = 'off';
|
|
if ($pfb['dnsbl_py_reply'] == 'on') {
|
|
$python_reply = 'on';
|
|
}
|
|
|
|
$python_blocking = 'off';
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
$python_blocking = 'on';
|
|
}
|
|
|
|
$python_hsts = 'off';
|
|
if ($pfb['dnsbl_hsts'] == 'on') {
|
|
$python_hsts = 'on';
|
|
}
|
|
|
|
$python_idn = 'off';
|
|
if ($pfb['dnsbl_idn'] == 'on') {
|
|
$python_idn = 'on';
|
|
}
|
|
|
|
$python_cname = 'off';
|
|
if ($pfb['dnsbl_cname'] == 'on') {
|
|
$python_cname = 'on';
|
|
}
|
|
|
|
$python_control = 'off';
|
|
if ($pfb['dnsbl_control'] == 'on') {
|
|
$python_control = 'on';
|
|
}
|
|
|
|
$python_noaaaa = 'off';
|
|
if ($pfb['dnsbl_noaaaa'] == 'on') {
|
|
$python_noaaaa = 'on';
|
|
}
|
|
|
|
$python_tld = 'off';
|
|
if ($pfb['dnsbl_pytld'] == 'on') {
|
|
$python_tlds = '';
|
|
if (!empty($pfb['dnsblconfig']['pfb_pytlds_gtld'])) {
|
|
$python_tld = 'on';
|
|
$python_tlds = $pfb['dnsblconfig']['pfb_pytlds_gtld'];
|
|
}
|
|
if (!empty($pfb['dnsblconfig']['pfb_pytlds_cctld'])) {
|
|
$python_tld = 'on';
|
|
$python_tlds .= ',' . $pfb['dnsblconfig']['pfb_pytlds_cctld'];
|
|
}
|
|
if (!empty($pfb['dnsblconfig']['pfb_pytlds_itld'])) {
|
|
$python_tld = 'on';
|
|
$python_tlds .= ',' . $pfb['dnsblconfig']['pfb_pytlds_itld'];
|
|
}
|
|
if (!empty($pfb['dnsblconfig']['pfb_pytlds_bgtld'])) {
|
|
$python_tld = 'on';
|
|
$python_tlds .= ',' . $pfb['dnsblconfig']['pfb_pytlds_bgtld'];
|
|
}
|
|
$python_tlds = ltrim($python_tlds, ',');
|
|
}
|
|
|
|
$python_nolog = 'off';
|
|
if ($pfb['dnsbl_py_nolog'] == 'on') {
|
|
$python_nolog = 'on';
|
|
}
|
|
|
|
$now = date('m/j/y H:i:s', time());
|
|
|
|
$pfb_py_conf = <<<EOF
|
|
; pfBlockerNG DNSBL Unbound python configuration file
|
|
; pfb_unbound.ini [ File created: {$now} ]
|
|
[MAIN]
|
|
dnsbl_ipv4 = {$pfb['dnsbl_vip']}
|
|
python_enable = {$python_enable}
|
|
python_ipv6 = {$python_ipv6}
|
|
python_reply = {$python_reply}
|
|
python_blocking = {$python_blocking}
|
|
python_hsts = {$python_hsts}
|
|
python_idn = {$python_idn}
|
|
python_tld_seg = {$tld_segments}
|
|
python_tld = {$python_tld}
|
|
python_tlds = {$python_tlds}
|
|
python_nolog = {$python_nolog}
|
|
python_cname = {$python_cname}
|
|
python_control = {$python_control}
|
|
|
|
EOF;
|
|
if ($pfb['dnsbl_regex'] == 'on' && isset($pfb['dnsbl_regex_list']) && !empty($pfb['dnsbl_regex_list'])) {
|
|
$regex = '';
|
|
$regex_list = pfbng_text_area_decode($pfb['dnsbl_regex_list'], TRUE, TRUE, FALSE);
|
|
if (!empty($regex_list)) {
|
|
$counter = 1;
|
|
$key_index = array();
|
|
foreach ($regex_list as $key => $list) {
|
|
if (!isset($list[1])) {
|
|
$list[1] = "Regex_{$counter}";
|
|
} else {
|
|
$list[1] = trim(ltrim($list[1], '#'));
|
|
$list[1] = preg_replace("/\W/", '', str_replace(' ', '_', $list[1]));
|
|
}
|
|
|
|
// Check if key exists
|
|
if (!isset($key_index[$list[1]])) {
|
|
$regex .= "{$list[1]} = {$list[0]}\n";
|
|
$key_index[$list[1]] = '';
|
|
} else {
|
|
$regex .= "{$list[1]}_{$counter} = {$list[0]}\n";
|
|
$key_index["{$list[1]}_{$counter}"] = '';
|
|
}
|
|
$counter++;
|
|
}
|
|
$pfb_py_conf .= <<<EOF
|
|
|
|
[REGEX]
|
|
{$regex}
|
|
EOF;
|
|
}
|
|
}
|
|
|
|
if ($pfb['dnsbl_noaaaa'] == 'on' && isset($pfb['dnsbl_noaaaa_list']) && !empty($pfb['dnsbl_noaaaa_list'])) {
|
|
$noaaaa = '';
|
|
$noaaaa_list = pfbng_text_area_decode($pfb['dnsbl_noaaaa_list'], TRUE, TRUE, TRUE);
|
|
if (!empty($noaaaa_list)) {
|
|
foreach ($noaaaa_list as $key => $list) {
|
|
if (substr($list[0], 0, 1) == '.') {
|
|
$list[0] = ltrim($list[0], '.');
|
|
$noaaaa .= "{$key} = {$list[0]},1\n";
|
|
} else {
|
|
$noaaaa .= "{$key} = {$list[0]},0\n";
|
|
}
|
|
}
|
|
$pfb_py_conf .= <<<EOF
|
|
|
|
[noAAAA]
|
|
{$noaaaa}
|
|
EOF;
|
|
}
|
|
}
|
|
|
|
if ($pfb['dnsbl_gp'] == 'on' && isset($pfb['dnsbl_gp_bypass_list']) && !empty($pfb['dnsbl_gp_bypass_list'])) {
|
|
$gp_bypass = '';
|
|
$gp_bypass_list = pfbng_text_area_decode($pfb['dnsbl_gp_bypass_list'], TRUE, TRUE, FALSE);
|
|
if (!empty($gp_bypass_list)) {
|
|
foreach ($gp_bypass_list as $key => $list) {
|
|
$gp_bypass .= "{$key} = {$list[0]}\n";
|
|
}
|
|
$pfb_py_conf .= <<<EOF
|
|
|
|
[GP_Bypass_List]
|
|
{$gp_bypass}
|
|
EOF;
|
|
}
|
|
}
|
|
|
|
// Compare previous ini file to new ini (bypass file timestamp string)
|
|
$pfb_py_conf_ex = @file_get_contents($pfb['unbound_py_conf']);
|
|
$pfb_py_conf_ex2 = preg_replace("/File created:.* ]/", "File created: {$now} ]", $pfb_py_conf_ex);
|
|
|
|
if ($pfb_py_conf !== $pfb_py_conf_ex2) {
|
|
$pfbpython = TRUE;
|
|
@file_put_contents($pfb['unbound_py_conf'], $pfb_py_conf, LOCK_EX);
|
|
}
|
|
}
|
|
|
|
else {
|
|
$mode = 'disabled';
|
|
|
|
// Remove python settings from DNS Resolver configuration
|
|
if (config_path_enabled('unbound', 'python') && config_get_path('unbound/python_script') == 'pfb_unbound') {
|
|
config_del_path('unbound/python');
|
|
config_set_path('unbound/python_order', '');
|
|
config_set_path('unbound/python_script', '');
|
|
|
|
$log = 'Removing DNSBL Unbound python integration settings';
|
|
pfb_logger("\n{$log}", 1);
|
|
write_config("pfBlockerNG: {$log}");
|
|
}
|
|
}
|
|
|
|
// Mount lib and bin folders
|
|
$base_py = '/usr/local';
|
|
|
|
$log = '';
|
|
foreach (array('/bin', '/lib') as $dir) {
|
|
$validate = exec("/sbin/mount | {$pfb['grep']} " . escapeshellarg("{$pfb['dnsbldir']}{$base_py}{$dir}") . " 2>&1");
|
|
|
|
if ($mode == 'enabled' && empty($validate)) {
|
|
if (!is_dir("{$pfb['dnsbldir']}{$base_py}{$dir}")) {
|
|
$log .= "\n Creating: {$pfb['dnsbldir']}{$base_py}{$dir}";
|
|
safe_mkdir("{$pfb['dnsbldir']}{$base_py}{$dir}");
|
|
}
|
|
$pfbpython = TRUE;
|
|
$log .= "\n Mounting: /usr/local{$dir}";
|
|
exec("/sbin/mount_nullfs -o ro " . escapeshellarg("/usr/local{$dir}") . ' '
|
|
. escapeshellarg("{$pfb['dnsbldir']}{$base_py}{$dir}") . " 2>&1", $output, $retval);
|
|
if ($retval != 0) {
|
|
$log .= "\n Failed to mount [ /usr/local{$dir} ] to [ {$pfb['dnsbldir']}{$base_py}{$dir} ]!";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($log)) {
|
|
pfb_logger("\nAdding DNSBL Unbound python mounts:{$log}\n", 1);
|
|
}
|
|
|
|
// Set flag to unmount python folders after the next reboot is completed to avoid crashing Unbound
|
|
if ($mode == 'disabled') {
|
|
$pfb['dnsbl_python_unmount'] = TRUE;
|
|
}
|
|
return $pfbpython;
|
|
}
|
|
|
|
|
|
// Unbound python unmount
|
|
function pfb_unbound_python_unmount() {
|
|
global $pfb;
|
|
|
|
// Unmount lib and bin folders
|
|
$base_py = '/usr/local';
|
|
|
|
$log = '';
|
|
foreach (array('/bin', '/lib') as $dir) {
|
|
$validate = exec("/sbin/mount | {$pfb['grep']} " . escapeshellarg("{$pfb['dnsbldir']}{$base_py}{$dir}") . " 2>&1");
|
|
if (!empty($validate)) {
|
|
$log .= "\n Unmounting: /usr/local{$dir}";
|
|
exec("/sbin/umount -t nullfs " . escapeshellarg("{$pfb['dnsbldir']}{$base_py}{$dir}") . " 2>&1", $output, $retval);
|
|
if ($retval != 0) {
|
|
$log .= "\n Failed to unmount [ {$pfb['dnsbldir']}{$base_py}{$dir} ]!";
|
|
}
|
|
}
|
|
|
|
foreach (array( "/usr/local{$dir}", '/usr/local', '/usr') as $folder) {
|
|
if (is_dir("{$pfb['dnsbldir']}{$folder}")) {
|
|
$log .= "\n Removing: {$pfb['dnsbldir']}{$folder}";
|
|
@rmdir("{$pfb['dnsbldir']}{$folder}");
|
|
}
|
|
|
|
// Delete remaining subfolders on next loop
|
|
if ($dir == '/bin') {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($log)) {
|
|
pfb_logger("\nRemoving DNSBL Unbound python mounts:{$log}\n", 1);
|
|
}
|
|
}
|
|
|
|
|
|
// Search for TLD match
|
|
function tld_search($tld, $dparts, $j, $k) {
|
|
global $tlds;
|
|
|
|
$tld_query = implode('.', array_slice($dparts, -$j, $j, TRUE));
|
|
if (isset($tlds[$tld][$tld_query])) {
|
|
return implode('.', array_slice($dparts, -$k, $k, TRUE));
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// Function to determine if each Domain is a Sub-Domain ('transparent' zone) or a whole Domain ('redirect' zone)
|
|
function tld_analysis() {
|
|
global $pfb, $tlds;
|
|
|
|
if (!file_exists("{$pfb['dnsbl_file']}.raw")) {
|
|
pfb_logger("\n\nTLD Analysis not required.", 1);
|
|
return;
|
|
}
|
|
|
|
pfb_logger("\nTLD:\n", 1);
|
|
|
|
$domain_cnt = 0;
|
|
$pfb_found = FALSE; // Flag to determine if TLD 'redirect' zones found
|
|
|
|
rmdir_recursive("{$pfb['dnsbl_tmpdir']}");
|
|
if (!$pfb['dnsbl_py_blacklist']) {
|
|
safe_mkdir("{$pfb['dnsbl_tmpdir']}");
|
|
}
|
|
unlink_if_exists("{$pfb['dnsbl_file']}.tsp");
|
|
unlink_if_exists("{$pfb['dnsbl_tld_txt']}.*");
|
|
unlink_if_exists("{$pfb['dnsbl_tld_remove']}.tsp");
|
|
unlink_if_exists("{$pfb['dnsbl_tld_remove']}");
|
|
unlink_if_exists("{$pfb['dnsbl_tmp']}.sup");
|
|
unlink_if_exists("{$pfb['dnsbl_tmp']}.adup");
|
|
|
|
// Master TLD Domain list
|
|
if (($t_handle = @fopen("{$pfb['dnsbl_tld_data']}", 'r')) !== FALSE) {
|
|
while (($line = @fgets($t_handle)) !== FALSE) {
|
|
$line = rtrim($line, "\x00..\x1F");
|
|
$tld = substr($line, strrpos($line, '.') + 1);
|
|
|
|
if (!empty($tld)) {
|
|
if (!is_array($tlds[$tld])) {
|
|
$tlds[$tld] = array();
|
|
}
|
|
$tlds[$tld][$line] = '';
|
|
}
|
|
}
|
|
@fclose($t_handle);
|
|
} else {
|
|
pfb_logger("\n ** TLD Master data file missing. Terminating TLD **\n", 1);
|
|
return;
|
|
}
|
|
|
|
// DNSBL python - create file handles for data, zone, and remove files
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
$p_data = @fopen("{$pfb['unbound_py_data']}.raw", 'w');
|
|
$p_zone = @fopen("{$pfb['unbound_py_zone']}.raw", 'w');
|
|
$p_tsp = @fopen("{$pfb['dnsbl_tld_remove']}", 'w');
|
|
|
|
if ((get_resource_type($p_data) != 'stream') ||
|
|
(get_resource_type($p_zone) != 'stream') ||
|
|
(get_resource_type($p_tsp) != 'stream')) {
|
|
|
|
pfb_logger("\nFailed to create DNSBL python data|zone|remove file handles! Exiting\n", 1);
|
|
foreach (array($p_data, $p_zone, $p_tsp) as $handlex) {
|
|
if ($handlex) {
|
|
@fclose($handlex);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Collect TLD Blacklist(s). If configured the whole TLD will be blocked
|
|
$tld_blacklist = pfbng_text_area_decode($pfb['dnsblconfig']['tldblacklist'], TRUE, FALSE, TRUE);
|
|
if (!empty($tld_blacklist)) {
|
|
$tld_blacklist = array_flip($tld_blacklist);
|
|
}
|
|
|
|
// Collect TLD Whitelist(s). If configured, create a 'static local-zone' Resolver entry (Not required for python mode blocking)
|
|
$whitelist = array();
|
|
if (!$pfb['dnsbl_py_blacklist']) {
|
|
$whitelist = pfbng_text_area_decode($pfb['dnsblconfig']['tldwhitelist'], TRUE, FALSE, TRUE);
|
|
$tld_whitelist = array();
|
|
}
|
|
|
|
$extdns_esc = escapeshellarg("@{$pfb['extdns']}");
|
|
|
|
if (!empty($tld_blacklist) && !empty($whitelist)) {
|
|
foreach ($whitelist as $domain) {
|
|
|
|
// Use user-defined IP address
|
|
if (strpos($domain, '|') !== FALSE) {
|
|
list($domain, $resolved_host) = array_map('trim', explode('|', $domain));
|
|
}
|
|
|
|
// Resolve Domain IP address
|
|
else {
|
|
$domain_esc = escapeshellarg($domain);
|
|
$resolved_host = exec("/usr/bin/drill {$extdns_esc} {$domain_esc} | grep -v '^;\|^\$' | head -1 | cut -f5 2>&1");
|
|
}
|
|
|
|
$tld = '';
|
|
if (strpos($domain, '.') !== FALSE && is_ipaddr($resolved_host)) {
|
|
$dparts = explode('.', $domain);
|
|
$dcnt = count($dparts);
|
|
$tld = end($dparts);
|
|
|
|
for ($i=($dcnt-1); $i > 0; $i--) {
|
|
$d_query = implode('.', array_slice($dparts, -$i, $i, TRUE));
|
|
if (isset($tlds[$tld][$d_query])) {
|
|
$tld = $d_query;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$resolved_host = pfb_filter($resolved_host, PFB_FILTER_IP, 'tld_analysis');
|
|
if (!empty($tld) && !empty($resolved_host)) {
|
|
if (!is_array($tld_whitelist[$tld])) {
|
|
$tld_whitelist[$tld] = array();
|
|
}
|
|
$tld_whitelist[$tld][] = array($domain, $resolved_host);
|
|
pfb_logger(" TLD Whitelist {$domain}|{$resolved_host}\n", 1);
|
|
} elseif (!empty($resolved_host)) {
|
|
pfb_logger("\n TLD Whitelist - Missing data | {$domain} | {$resolved_host} |\n", 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process TLD Blacklist(s). If configured the whole TLD will be blocked
|
|
if (!empty($tld_blacklist)) {
|
|
|
|
$tld_list = '';
|
|
$tld_cnt = 0;
|
|
$tld_segments = 0;
|
|
pfb_logger(" Blocking full TLD/Sub-Domain(s)... |", 1);
|
|
|
|
foreach ($tld_blacklist as $tld => $key) {
|
|
|
|
unset($tld_blacklist[$tld]); // Remove old entry
|
|
$tld = trim($tld, '.'); // Remove any leading/trailing dots
|
|
$tld_blacklist[$tld] = ''; // Add new TLD entry
|
|
|
|
// DNSBL python - TLD Blacklist (Set logging type to enabled '1')
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
$tld_cnt++;
|
|
$pfb_found = TRUE;
|
|
$tld_segments = @max((array((substr_count($tld, '.') +1), $tld_segments) ?: 1));
|
|
|
|
pfb_logger("{$tld}|", 1);
|
|
@fwrite($p_zone, ",{$tld},,1,DNSBL_TLD,DNSBL_TLD\n");
|
|
|
|
// Add TLD to remove file
|
|
@fwrite($p_tsp, ".{$tld},,\n");
|
|
|
|
// Collect List of TLDs and save to DNSBL folder
|
|
$tld_list .= ",{$tld},,\n";
|
|
|
|
// Remove any 'TLD Blacklists' from the 'TLD master list'
|
|
if (isset($tlds[$tld])) {
|
|
unset($tlds[$tld]);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// (Unbound mode only. Cannot have duplicate zones defined
|
|
elseif (!empty($pfb['safesearch_tlds']) && isset($pfb['safesearch_tlds'][$tld])) {
|
|
pfb_logger("\n{$tld}(Removed due to SafeSearch conflict)", 1);
|
|
continue;
|
|
}
|
|
|
|
$dnsbl_file = "{$pfb['dnsbl_tmpdir']}/DNSBL_{$tld}.txt";
|
|
if (!file_exists($dnsbl_file)) {
|
|
|
|
$tld_cnt++;
|
|
$pfb_found = TRUE;
|
|
$tld_segments = @max((array((substr_count($tld, '.') +1), $tld_segments) ?: 1));
|
|
|
|
// If a 'TLD Whitelist' exists, use 'static local-zone'
|
|
if (isset($tld_whitelist[$tld])) {
|
|
|
|
pfb_logger("{$tld}(static)|", 1);
|
|
$dnsbl_line = "local-zone: \"{$tld}\" \"static\"\n";
|
|
$whitelist = $tld_whitelist[$tld];
|
|
|
|
foreach ($whitelist as $list) {
|
|
$ip_type = is_ipaddr($list[1]);
|
|
$ip_esc = escapeshellarg($list[0]);
|
|
|
|
switch ($ip_type) {
|
|
case 4:
|
|
$dnsbl_line .= "local-data: \"{$list[0]} A {$list[1]}\"\n";
|
|
$tld_list .= "{$tld} | {$list[0]} A {$list[1]}\n";
|
|
if ($pfb['dnsbl_v6'] == 'on') {
|
|
$r = exec("/usr/bin/drill {$extdns_esc} AAAA {$ip_esc} | grep -v '^;\|^\$' | head -1 | cut -f5 2>&1");
|
|
if (is_ipaddrv6($r)) {
|
|
$dnsbl_line .= "local-data: \"{$list[0]} AAAA {$r}\"\n";
|
|
$tld_list .= "{$list[0]} AAAA {$r}\"\n";
|
|
}
|
|
}
|
|
break;
|
|
case 6:
|
|
$r = exec("/usr/bin/drill {$extdns_esc} A {$ip_esc} | grep -v '^;\|^\$' | head -1 | cut -f5 2>&1");
|
|
if (is_ipaddrv4($r)) {
|
|
$dnsbl_line .= "local-data: \"{$list[0]} A {$r}\"\n";
|
|
$tld_list .= "{$tld} | {$list[0]} A {$r}\"\n";
|
|
}
|
|
$dnsbl_line .= "local-data: \"{$list[0]} AAAA {$list[1]}\"\n";
|
|
$tld_list .= "{$tld} | {$list[0]} AAAA {$list[1]}\"\n";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create 'redirect' zone for whole TLD
|
|
else {
|
|
pfb_logger("{$tld}|", 1);
|
|
|
|
$ipv6_dnsbl = '';
|
|
if ($pfb['dnsbl_v6'] == 'on') {
|
|
$ipv6_dnsbl = " local-data: \"{$tld} 60 IN AAAA ::{$pfb['dnsbl_vip']}\"";
|
|
}
|
|
$dnsbl_line = "local-zone: \"{$tld}\" redirect local-data: \"{$tld} 60 IN A {$pfb['dnsbl_vip']}\"{$ipv6_dnsbl}\n";
|
|
|
|
// Collect List of TLDs and save to DNSBL folder
|
|
$tld_list .= "{$tld}\n";
|
|
}
|
|
|
|
@file_put_contents($dnsbl_file, $dnsbl_line, LOCK_EX);
|
|
|
|
// Add TLD to remove file (To be removed from 'transparent' zone)
|
|
@file_put_contents("{$pfb['dnsbl_tld_remove']}.tsp", ".{$tld} 60\n", FILE_APPEND | LOCK_EX);
|
|
|
|
// Remove any 'TLD Blacklists' from the 'TLD master list'
|
|
if (isset($tlds[$tld])) {
|
|
unset($tlds[$tld]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save a list of TLDs in DNSBL folder (DNSBL total line count verification)
|
|
if (!empty($tld_list)) {
|
|
@file_put_contents("{$pfb['dnsbl_tld_txt']}", $tld_list, LOCK_EX);
|
|
|
|
// Add 'TLD' to Alias/Feeds array
|
|
if (!is_array($pfb['tld_update']['DNSBL_TLD'])) {
|
|
$pfb['tld_update']['DNSBL_TLD'] = array();
|
|
}
|
|
|
|
$pfb['tld_update']['DNSBL_TLD']['feeds'] = array('DNSBL_TLD');
|
|
$pfb['tld_update']['DNSBL_TLD']['count'] = $tld_cnt;
|
|
$pfb['alias_dnsbl_all'][] = 'DNSBL_TLD';
|
|
}
|
|
else {
|
|
unlink_if_exists("{$pfb['dnsbl_tld_txt']}");
|
|
}
|
|
pfb_logger(" completed\n", 1);
|
|
}
|
|
else {
|
|
unlink_if_exists("{$pfb['dnsbl_tld_txt']}");
|
|
}
|
|
|
|
// Collect TLD Exclusion list and remove any 'TLD Exclusions' from the 'TLD master list'
|
|
$exclusion = pfbng_text_area_decode($pfb['dnsblconfig']['tldexclusion'], TRUE, FALSE, TRUE);
|
|
$tld_exclusion = array();
|
|
if (!empty($exclusion)) {
|
|
foreach ($exclusion as $key => $exclude) {
|
|
$exclude = trim($exclude, '.'); // Remove any leading/trailing dots
|
|
|
|
// Collect exclusion
|
|
if (strpos($exclude, '.') !== FALSE) {
|
|
$tld_exclusion[$exclude] = '';
|
|
}
|
|
|
|
// Remove Exclusion from TLDS array
|
|
if (isset($tlds[$exclude])) {
|
|
unset($tlds[$exclude]);
|
|
}
|
|
}
|
|
}
|
|
|
|
pfb_logger("TLD analysis", 1);
|
|
|
|
// [ $pfb['dnsbl_file']}.tsp ] Final DNSBL output file (using 'transparent' zone)
|
|
// [ $pfb['dnsbl_tld_remove'] ] File of Sub-Domains to be removed (from 'redirect' zone)
|
|
|
|
// DNSBL Unbound: Analyse DNSBL: 1) 'redirect' zone for whole Domain 2) 'transparent' zone only
|
|
// DNSBL python: Analyse DNSBL into local and zone files
|
|
|
|
if (($fhandle = @fopen("{$pfb['dnsbl_file']}.raw", 'r')) !== FALSE) {
|
|
while (($line = @fgets($fhandle)) !== FALSE) {
|
|
if (empty($line)) {
|
|
continue;
|
|
}
|
|
|
|
// Display progress indicator
|
|
if ($domain_cnt % 100000 == 0) {
|
|
// Memory limitation exceeded for 'redirect' zones
|
|
if ($domain_cnt >= $pfb['domain_max_cnt']) {
|
|
pfb_logger('x', 1);
|
|
} else {
|
|
pfb_logger('.', 1);
|
|
}
|
|
}
|
|
|
|
// DNSBL python blocking mode
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
$eparts = explode(',', $line, 3);
|
|
$domain = $eparts[1];
|
|
$dparts = explode('.', $domain);
|
|
$dcnt = count($dparts);
|
|
$tld = end($dparts);
|
|
$d_info = $eparts[2]; // Logging Type/Header/Alias group details
|
|
$dfound = '';
|
|
}
|
|
|
|
// DNSBL Unbound blocking mode
|
|
else {
|
|
$eparts = explode(' ', str_replace('"', '', $line), 3);
|
|
$domain = $eparts[1];
|
|
$s_info = trim($eparts[2]);
|
|
|
|
if ($pfb['dnsbl_v6'] == 'on') {
|
|
// Determine if DNSBL Logging is disabled and switch to '::0'
|
|
if (strpos($s_info, ' A 0.0.0.0') !== FALSE) {
|
|
$s_info6 = str_replace(' A 0.0.0.0', ' AAAA ::0', $s_info);
|
|
} else {
|
|
$s_info6 = str_replace(' A ', ' AAAA ::', $s_info);
|
|
}
|
|
}
|
|
|
|
$dparts = explode('.', $domain);
|
|
$dcnt = count($dparts);
|
|
$tld = end($dparts);
|
|
$dfound = '';
|
|
}
|
|
|
|
// Determine if TLD exists in TLD Blacklist (skip for DNSBL python)
|
|
if (!$pfb['dnsbl_py_blacklist'] && !empty($tld_blacklist)) {
|
|
|
|
// Determine minimum 'tld level' for loop efficiency
|
|
$min_cnt = @min(array($tld_segments, ($dcnt -1)));
|
|
|
|
for ($i=1; $i <= $min_cnt; $i++) {
|
|
$d_query = implode('.', array_slice($dparts, -$i, $i, TRUE));
|
|
if (isset($tld_blacklist[$d_query])) {
|
|
continue 2; // Whole TLD being blocked
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($domain_cnt <= $pfb['domain_max_cnt']) {
|
|
|
|
// Search TLD master list (Levels 1-4)
|
|
// If Domain is a Sub-Domain, create 'transparent' zone. Otherwise create 'redirect' zone
|
|
switch($dcnt) {
|
|
case ($dcnt > 5):
|
|
break;
|
|
case '5':
|
|
$dfound = tld_search($tld, $dparts, 4, 5);
|
|
break;
|
|
case '4':
|
|
$dfound = tld_search($tld, $dparts, 3, 4);
|
|
break;
|
|
case '3':
|
|
$dfound = tld_search($tld, $dparts, 2, 3);
|
|
break;
|
|
case '2':
|
|
$dfound = implode('.', array_slice($dparts, -2, 2, TRUE));
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If Domain is in the TLD Exclusion(s), use 'transparent zone'
|
|
if (!empty($domain) && isset($tld_exclusion[$domain])) {
|
|
$dfound = '';
|
|
}
|
|
|
|
// Create 'redirect' zone for Domain
|
|
if (!empty($dfound)) {
|
|
$pfb_found = TRUE;
|
|
|
|
// DNSBL python blocking mode
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
@fwrite($p_zone, ",{$dfound},{$d_info}");
|
|
|
|
// TLD remove files - See below for description
|
|
@fwrite($p_tsp, ".{$dfound},,\n");
|
|
}
|
|
else {
|
|
$ipv6_dnsbl = '';
|
|
if ($pfb['dnsbl_v6'] == 'on') {
|
|
$ipv6_dnsbl = " local-data: \"{$dfound} {$s_info6}\"";
|
|
}
|
|
$domain_line = "local-zone: \"{$dfound}\" redirect local-data: \"{$dfound} {$s_info}\"{$ipv6_dnsbl}\n";
|
|
@file_put_contents("{$pfb['dnsbl_file']}.tsp", $domain_line, FILE_APPEND | LOCK_EX);
|
|
|
|
// Add Domain to remove file for [ 1- 'redirect zone' Domains 2- Unbound memory domains ]
|
|
// This removes any of these domains and sub-domains
|
|
@file_put_contents("{$pfb['dnsbl_tld_remove']}", ".{$dfound} 60\n\"{$dfound} 60\n", FILE_APPEND | LOCK_EX);
|
|
|
|
// Add Domain to remove file for 'transparent zone' domains
|
|
// This removes any of these sub-domains
|
|
@file_put_contents("{$pfb['dnsbl_tld_remove']}.tsp", ".{$dfound} 60\n", FILE_APPEND | LOCK_EX);
|
|
}
|
|
}
|
|
|
|
// Create 'transparent zone' for Sub-Domain
|
|
else {
|
|
// DNSBL python blocking mode
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
@fwrite($p_data, ",{$domain},{$d_info}");
|
|
}
|
|
else {
|
|
if (!empty($tld)) {
|
|
$dnsbl_file = "{$pfb['dnsbl_tmpdir']}/DNSBL_{$tld}.txt";
|
|
|
|
// Create a temp file for each TLD. w/ 'transparent' header followed by each 'local-data' line
|
|
if (!file_exists($dnsbl_file)) {
|
|
$dnsbl_header = "local-zone: \"{$tld}\" \"transparent\"\n";
|
|
@file_put_contents($dnsbl_file, $dnsbl_header, LOCK_EX);
|
|
}
|
|
|
|
$ipv6_dnsbl = '';
|
|
if ($pfb['dnsbl_v6'] == 'on') {
|
|
$ipv6_dnsbl = " local-data: \"{$domain} {$s_info6}\"";
|
|
}
|
|
$domain_line = "local-data: \"{$domain} {$s_info}\"{$ipv6_dnsbl}\n";
|
|
@file_put_contents($dnsbl_file, $domain_line, FILE_APPEND | LOCK_EX);
|
|
}
|
|
else {
|
|
$oline = htmlentities($line);
|
|
pfb_logger("\nDebug: Missing TLD: {$oline}", 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Increment Domain counter
|
|
$domain_cnt++;
|
|
}
|
|
}
|
|
|
|
foreach (array($fhandle, $p_data, $p_zone, $p_tsp) as $handlex) {
|
|
if ($handlex) {
|
|
@fclose($handlex);
|
|
}
|
|
}
|
|
unset($tlds, $tld_blacklist, $tld_exclusion);
|
|
|
|
// TLD 'redirect zones' found. Finalize TLD function
|
|
if ($pfb_found) {
|
|
$log = " completed [ NOW ]\n";
|
|
// Print TLD exceedance error message
|
|
if ($domain_cnt >= $pfb['domain_max_cnt']) {
|
|
$log .= "\n ** TLD Domain count exceeded. [ {$pfb['domain_max_cnt']} ] All subsequent Domains listed as-is **\n\n";
|
|
}
|
|
$log .= "TLD finalize";
|
|
pfb_logger("{$log}", 1);
|
|
|
|
// Execute Domain De-duplication
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
exec("{$pfb['script']} domaintldpy >> {$pfb['log']} 2>&1");
|
|
}
|
|
else {
|
|
// Create a csv list of 'recently updated' DNSBL Feeds, as ordered by User
|
|
$dnsbl_feeds = '';
|
|
if (!empty($pfb['tld_update'])) {
|
|
foreach ($pfb['tld_update'] as $alias => $data) {
|
|
foreach ($data['feeds'] as $feed) {
|
|
$dnsbl_feeds .= "{$feed},";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty(pfb_filter($dnsbl_feeds, PFB_FILTER_CSV, 'tld_analysis'))) {
|
|
exec("{$pfb['script']} domaintld x x x {$dnsbl_feeds} >> {$pfb['log']} 2>&1");
|
|
} else {
|
|
pfb_logger("\nFailed to create list of DNSBL Feeds", 1);
|
|
}
|
|
}
|
|
pfb_logger("\nTLD finalize... completed [ NOW ]\n", 1);
|
|
|
|
// Update DNSBL Alias and Widget Stats
|
|
if (!empty($pfb['tld_update'])) {
|
|
foreach ($pfb['tld_update'] as $alias => $data) {
|
|
|
|
// Create Alias summary file for each DNSBL Alias
|
|
$lists_dnsbl_current = array();
|
|
foreach ($data['feeds'] as $feed) {
|
|
$lists_dnsbl_current[] = "{$feed}";
|
|
}
|
|
dnsbl_alias_update('update', $alias, $pfb['dnsdir'], $lists_dnsbl_current, $data['count']);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
pfb_logger(" no changes\n", 1);
|
|
}
|
|
|
|
// Save DNSBL Alias statistics
|
|
dnsbl_save_stats();
|
|
|
|
if ($pfb['dnsbl_py_blacklist'] && file_exists("{$pfb['dnsbl_file']}.raw")) {
|
|
unlink_if_exists("{$pfb['dnsbl_file']}.raw");
|
|
}
|
|
}
|
|
|
|
// Function to Start Unbound
|
|
function pfb_stop_start_unbound($type) {
|
|
global $g, $pfb;
|
|
|
|
$final = array();
|
|
if (file_exists("{$g['varrun_path']}/unbound.pid")) {
|
|
pfb_logger("\nStopping Unbound Resolver", 1);
|
|
sigkillbypid("{$g['varrun_path']}/unbound.pid", 'TERM');
|
|
}
|
|
|
|
// If unbound is still running, wait up to 30 seconds for it to terminate.
|
|
for ($i=1; $i <= 30; $i++) {
|
|
if (is_process_running('unbound')) {
|
|
pfb_logger('.', 1);
|
|
sleep(1);
|
|
} else {
|
|
pfb_logger("\nUnbound stopped in {$i} sec.", 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Add/Remove additional python mounts
|
|
if (file_exists('/var/unbound/pfb_unbound_include.inc')) {
|
|
$g['pfblockerng_include_verbose'] = TRUE;
|
|
pfb_logger("\nAdditional mounts{$type}:", 1);
|
|
require_once('/var/unbound/pfb_unbound_include.inc');
|
|
unset($g['pfblockerng_include_verbose']);
|
|
}
|
|
|
|
// Remove Unbound python mounts
|
|
if ($pfb['dnsbl_python_unmount']) {
|
|
pfb_unbound_python_unmount();
|
|
}
|
|
|
|
pfb_logger("\nStarting Unbound Resolver", 1);
|
|
exec("/usr/local/sbin/unbound -c /var/unbound/unbound.conf 2>&1", $final['result'], $final['retval']);
|
|
return $final;
|
|
}
|
|
|
|
|
|
// Reload Resolver
|
|
function pfb_reload_unbound($mode, $cache=FALSE, $pfbpython=FALSE) {
|
|
global $g, $pfb;
|
|
|
|
$final = array();
|
|
$type = '';
|
|
if ($mode == 'enabled' && $pfb['dnsbl_py_blacklist']) {
|
|
$type = ' (DNSBL python)';
|
|
}
|
|
|
|
if (!$pfb['dnsbl_py_blacklist'] && file_exists("{$pfb['dnsbl_file']}.raw")) {
|
|
@rename("{$pfb['dnsbl_file']}.raw", "{$pfb['dnsbl_file']}.conf");
|
|
}
|
|
|
|
$cache_dumpfile = tempnam('/var/tmp/', 'unbound_cache_');
|
|
if ($mode == 'enabled' && is_process_running('unbound') && !$pfb['dnsbl_python_unmount'] && !$pfbpython) {
|
|
|
|
$log = "\nReloading Unbound Resolver{$type}";
|
|
pfb_logger($log, 1);
|
|
|
|
if ($cache && $pfb['dnsbl_res_cache'] == 'on') {
|
|
exec("{$pfb['chroot_cmd']} dump_cache > " . escapeshellarg($cache_dumpfile) . " 2>&1");
|
|
pfb_logger('.', 1);
|
|
}
|
|
}
|
|
|
|
$final = pfb_stop_start_unbound($type);
|
|
pfb_logger('.', 1);
|
|
|
|
if ($final['retval'] != 0) {
|
|
|
|
@copy("{$pfb['dnsbldir']}/unbound.conf", "{$pfb['dnsbldir']}/unbound.conf.error");
|
|
if ($mode == 'enabled') {
|
|
if (!$pfb['dnsbl_py_blacklist']) {
|
|
$log = "\nDNSBL {$mode} FAIL - restoring Unbound conf *** Fix error(s) and a Force Reload required! ***\n";
|
|
|
|
// Try to restore previous DNSBL database
|
|
if (file_exists("{$pfb['dnsbl_file']}.bk")) {
|
|
@rename("{$pfb['dnsbl_file']}.bk", "{$pfb['dnsbl_file']}.conf");
|
|
}
|
|
|
|
// Wipe DNSBL database
|
|
else {
|
|
$log .= ' Restore previous database Failed!';
|
|
unlink_if_exists("{$pfb['dnsbl_file']}.conf");
|
|
touch("{$pfb['dnsbl_file']}.conf");
|
|
|
|
// Restore previous unbound.conf
|
|
if (file_exists("{$pfb['dnsbldir']}/unbound.bk")) {
|
|
@rename("{$pfb['dnsbldir']}/unbound.bk", "{$pfb['dnsbldir']}/unbound.conf");
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$log = "\nDNSBL {$mode} FAIL *** Fix error(s) and a Force Reload required! ***\n";
|
|
if (file_exists("{$pfb['dnsbldir']}/unbound.bk")) {
|
|
@rename("{$pfb['dnsbldir']}/unbound.bk", "{$pfb['dnsbldir']}/unbound.conf");
|
|
}
|
|
}
|
|
pfb_logger("{$log}", 2);
|
|
}
|
|
else {
|
|
$log = "\nDNSBL {$mode} - Unbound conf update FAIL *** Fix error(s) and a Force Reload required! ***\n";
|
|
pfb_logger("{$log}", 2);
|
|
}
|
|
|
|
$log = htmlspecialchars(implode("\n", $final['result']));
|
|
pfb_logger("\n\n====================\n\n{$log}\n\n====================\n\n", 2);
|
|
$final = pfb_stop_start_unbound($type);
|
|
}
|
|
|
|
// Confirm that Resolver is running
|
|
if (is_process_running('unbound')) {
|
|
pfb_logger('.', 1);
|
|
|
|
// $final['result'] will be appended with previous result above
|
|
exec("{$pfb['chroot_cmd']} status 2>&1", $final['result'], $final['retval']);
|
|
pfb_logger('.', 1);
|
|
if (preg_grep("/is running.../", $final['result'])) {
|
|
pfb_logger(" completed [ NOW ]", 1);
|
|
|
|
// Restore Resolver cache
|
|
if ($cache && $pfb['dnsbl_res_cache'] == 'on' && file_exists($cache_dumpfile) && filesize($cache_dumpfile) > 0) {
|
|
exec("{$pfb['chroot_cmd']} load_cache < " . escapeshellarg($cache_dumpfile) . " 2>&1");
|
|
$log = "\nResolver cache restored [ NOW ]";
|
|
pfb_logger($log, 1);
|
|
}
|
|
}
|
|
else {
|
|
$log = htmlspecialchars(implode("\n", $final['result']));
|
|
pfb_logger(" Not completed. [ NOW ]\n{$log}\n", 1);
|
|
}
|
|
}
|
|
else {
|
|
$log = htmlspecialchars(implode("\n", $final['result']));
|
|
pfb_logger(" Not completed. [ NOW ]\n{$log}\n", 1);
|
|
}
|
|
unlink_if_exists($cache_dumpfile);
|
|
}
|
|
|
|
|
|
// Function to clear Unbound/DNSBL work files
|
|
function pfb_unbound_clear_work_files() {
|
|
global $pfb;
|
|
|
|
foreach (array( $pfb['dnsbl_cache'],
|
|
"{$pfb['dnsbldir']}/unbound.bk",
|
|
"{$pfb['dnsbldir']}/unbound.tmp",
|
|
"{$pfb['dnsbl_file']}.bk",
|
|
"{$pfb['dnsbl_file']}.tsp",
|
|
"{$pfb['dnsbl_file']}.sync",
|
|
"/tmp/dnsbl_remove*",
|
|
"/tmp/dnsbl_add*",
|
|
"/tmp/dnsbl_tld*",
|
|
"{$pfb['unbound_py_data']}.raw",
|
|
"{$pfb['unbound_py_zone']}.raw",
|
|
"/var/tmp/unbound_cache_*") as $remove) {
|
|
unlink_if_exists($remove);
|
|
}
|
|
}
|
|
|
|
|
|
// Load new DNSBL updates to Unbound Resolver
|
|
function pfb_update_unbound($mode, $pfbupdate, $pfbpython) {
|
|
global $g, $pfb;
|
|
|
|
if ($mode == 'enabled') {
|
|
$ext = '.bk';
|
|
} else {
|
|
$ext = '.*'; // Remove all DNSBL Unbound files
|
|
}
|
|
|
|
// Execute TLD analysis, if configured
|
|
if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && !$pfb['save']) {
|
|
if ($pfb['dnsbl_tld']) {
|
|
tld_analysis();
|
|
} else {
|
|
unlink_if_exists("{$pfb['dnsbl_tld_txt']}");
|
|
}
|
|
}
|
|
|
|
// Create file marker to disable DNSBL Queries daemon to avoid unbound-control collisions
|
|
touch("{$pfb['dnsbl_file']}.sync");
|
|
|
|
// Marker file(s) to instruct Unbound to be reloaded
|
|
if ($pfb['reuse_dnsbl'] == 'on' ||
|
|
file_exists("{$pfb['dnsbl_file']}.reload") ||
|
|
file_exists("{$pfb['dnsbl_unlock']}")) {
|
|
|
|
$pfbupdate = TRUE;
|
|
unlink_if_exists("{$pfb['dnsbl_file']}.reload");
|
|
unlink_if_exists("{$pfb['dnsbl_unlock']}");
|
|
unlink_if_exists("{$pfb['dnsbl_unlock']}.data");
|
|
}
|
|
|
|
// Backup existing unbound.conf and rename new unbound.conf file
|
|
if (file_exists("{$pfb['dnsbldir']}/unbound.tmp")) {
|
|
@copy("{$pfb['dnsbldir']}/unbound.conf", "{$pfb['dnsbldir']}/unbound.bk");
|
|
@rename("{$pfb['dnsbldir']}/unbound.tmp", "{$pfb['dnsbldir']}/unbound.conf");
|
|
}
|
|
|
|
// When pfBlockerNG is disabled and 'keep blocklists' is disabled.
|
|
if ($pfb['enable'] == '' && $pfb['keep'] == '' && !$pfb['install']) {
|
|
unlink_if_exists("{$pfb['dnsbl_file']}{$ext}");
|
|
}
|
|
|
|
// Disable DNSBL
|
|
if (($pfb['enable'] != 'on' || $pfb['dnsbl'] != 'on') && !$pfb['install']) {
|
|
|
|
pfb_reload_unbound('disabled', FALSE, $pfbpython);
|
|
if (is_service_running('pfb_dnsbl')) {
|
|
pfb_logger("\nStop Service DNSBL", 1);
|
|
stop_service('pfb_dnsbl');
|
|
}
|
|
pfb_unbound_clear_work_files();
|
|
|
|
// Unmount Unbound python 'lib/bin' folders after Unbound has been reloaded with the python integration enabled
|
|
if ($pfb['dnsbl_python_unmount']) {
|
|
pfb_unbound_python_unmount();
|
|
unset($pfb['dnsbl_python_unmount']);
|
|
}
|
|
pfb_logger("\nDNSBL is disabled\n", 1);
|
|
return;
|
|
}
|
|
|
|
// Load new DNSBL updates
|
|
if (is_service_running('unbound')) {
|
|
|
|
// 'Live sync' new DNSBL updates utilizing unbound-control
|
|
if (!$pfb['dnsbl_py_blacklist'] && $pfb['dnsbl_sync'] && !$pfbpython &&
|
|
file_exists("{$pfb['dnsbl_file']}.conf") && filesize("{$pfb['dnsbl_file']}.conf") > 0) {
|
|
|
|
$sync_fail = FALSE;
|
|
|
|
pfb_logger("\nResolver Live Sync analysis", 1);
|
|
exec("{$pfb['script']} dnsbl_livesync >> {$pfb['log']} 2>&1");
|
|
pfb_logger(" completed [ NOW ]", 1);
|
|
|
|
$ucsync = array(array( 'dnsbl_remove_zone', 'local_zones_remove', 'Remove local-zone(s)' ),
|
|
array( 'dnsbl_remove_data', 'local_datas_remove', 'Remove local-data(s)' ),
|
|
array( 'dnsbl_add_zone', 'local_zones', 'Add local-zone(s)' ),
|
|
array( 'dnsbl_add_data', 'local_datas', 'Add local-data(s)' ));
|
|
|
|
// Disable zone updates when TLD is disabled
|
|
if (!$pfb['dnsbl_tld']) {
|
|
unset($ucsync[0], $ucsync[2]);
|
|
}
|
|
|
|
pfb_logger("\nResolver Live Sync finalizing:", 1);
|
|
foreach ($ucsync as $skey => $sync) {
|
|
$file = $pfb[$sync[0]];
|
|
|
|
if (filesize("{$file}") > 0) {
|
|
$result = array();
|
|
exec("{$pfb['chroot_cmd']} {$sync[1]} < {$file}", $result, $retval);
|
|
$result = implode("\n", $result);
|
|
$log = "\n\t{$sync[2]}:\t\t{$result}";
|
|
}
|
|
else {
|
|
$log = "\n\t{$sync[2]}:\t\tno changes";
|
|
}
|
|
pfb_logger("{$log}", 1);
|
|
|
|
if (!$sync_fail && !empty($retval)) {
|
|
$sync_fail = TRUE;
|
|
}
|
|
}
|
|
|
|
if ($sync_fail) {
|
|
pfb_logger("\nResolver Live Sync ... FAILED!", 1);
|
|
pfb_reload_unbound('reload', FALSE, $pfbpython);
|
|
}
|
|
}
|
|
|
|
// Do a full Reload of Unbound
|
|
else {
|
|
pfb_reload_unbound($mode, TRUE, $pfbpython);
|
|
}
|
|
}
|
|
|
|
// Start Unbound Service with new DNSBL Updates
|
|
else {
|
|
pfb_reload_unbound($mode, FALSE, $pfbpython);
|
|
}
|
|
|
|
if ($pfbpython) {
|
|
$log = "\nRestarting DNSBL Service (DNSBL python)";
|
|
pfb_logger("{$log}", 1);
|
|
restart_service('pfb_dnsbl');
|
|
}
|
|
|
|
$dnsbl_cnt = exec("/bin/cat {$pfb['dnsdir']}/*.txt | {$pfb['grep']} -c ^ 2>&1");
|
|
|
|
// Unbound blocking mode enabled
|
|
if (!$pfb['dnsbl_py_blacklist']) {
|
|
$final_cnt = exec("{$pfb['grep']} -v '\"transparent\"\|\"static\"' {$pfb['dnsbl_file']}.conf | {$pfb['grep']} -c ^ 2>&1");
|
|
if ($final_cnt == $dnsbl_cnt) {
|
|
$log = "\nDNSBL update [ {$final_cnt} | PASSED ]... completed [ NOW ]";
|
|
} else {
|
|
$log = "\n*** DNSBL update [ {$final_cnt} ] [ {$dnsbl_cnt} ] ... OUT OF SYNC ! *** [ NOW ]";
|
|
}
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
|
|
// Python blocking mode enabled
|
|
else {
|
|
$tld_cnt = @file_get_contents($pfb['unbound_py_count']);
|
|
$dnsbl_cnt = $dnsbl_cnt - $tld_cnt;
|
|
$final_cnt = exec("/usr/bin/find {$pfb['unbound_py_data']} {$pfb['unbound_py_zone']} -type f 2>/dev/null | xargs cat | {$pfb['grep']} -c ^ 2>&1");
|
|
if ($final_cnt == $dnsbl_cnt) {
|
|
$log = "\nDNSBL update [ {$final_cnt} | PASSED ]... completed [ NOW ]";
|
|
} else {
|
|
$log = "\n*** DNSBL update [ {$final_cnt} ] [ {$dnsbl_cnt} ] ... OUT OF SYNC ! *** [ NOW ]";
|
|
}
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
|
|
// DEBUG Live Sync
|
|
if (!$pfb['dnsbl_py_blacklist'] && $pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && $pfb['dnsbl_sync'] && !$pfbupdate && !$pfbpython && !$sync_fail) {
|
|
pfb_logger("\n\nDNSBL DEBUG", 1);
|
|
$datacnt = $zonecnt = 0;
|
|
|
|
exec("{$pfb['chroot_cmd']} list_local_data | {$pfb['grep']} '{$pfb['dnsbl_vip']}\|0\.0\.0\.0\$' | {$pfb['grep']} -v 'AAAA' | {$pfb['grep']} -c ^ 2>&1", $datacnt, $retval);
|
|
$datacnt = implode($datacnt);
|
|
pfb_logger('.', 1);
|
|
|
|
if ($pfb['dnsbl_tld']) {
|
|
exec("{$pfb['chroot_cmd']} list_local_zones | {$pfb['grep']} \"redirect\" | {$pfb['grep']} -c ^ 2>&1", $zonecnt, $retval);
|
|
pfb_logger('.', 1);
|
|
$zonecnt = implode($zonecnt);
|
|
|
|
$tldcnt = array('0');
|
|
if (file_exists('/var/db/pfblockerng/dnsbl/DNSBL_TLD.txt')) {
|
|
exec("{$pfb['grep']} -c ' A \| AAAA ' /var/db/pfblockerng/dnsbl/DNSBL_TLD.txt 2>&1", $tldcnt, $retval);
|
|
}
|
|
$tldcnt = implode($tldcnt);
|
|
$datacnt = $datacnt + $tldcnt;
|
|
}
|
|
pfb_logger("[ Data(s): {$datacnt}\tZone(s): {$zonecnt} | NOW ]", 1);
|
|
}
|
|
|
|
// Clear work files
|
|
pfb_unbound_clear_work_files();
|
|
pfb_logger("\n------------------------------------------------------------------------", 1);
|
|
}
|
|
|
|
|
|
// Process TOP1M database
|
|
function pfblockerng_top1m() {
|
|
global $pfb;
|
|
|
|
if (empty($pfb['dnsbl_alexa_inc'])) {
|
|
pfb_logger("\n TOP1M: No TLD Inclusions found.\n", 1);
|
|
return;
|
|
}
|
|
|
|
// Array of TLDs to include in Whitelist
|
|
$pfb_include = explode(',', $pfb['dnsbl_alexa_inc']);
|
|
if (!empty($pfb_include)) {
|
|
$pfb_include = array_flip($pfb_include);
|
|
}
|
|
|
|
$linecnt = $x = 0;
|
|
pfb_logger(" Building TOP1M Whitelist [", 1);
|
|
|
|
if (($handle = @fopen("{$pfb['dbdir']}/top-1m.csv", 'r')) !== FALSE) {
|
|
$pfb_output = @fopen("{$pfb['dbdir']}/pfbalexawhitelist.txt", 'w');
|
|
while (($line = @fgets($handle)) !== FALSE) {
|
|
|
|
if (strpos($line, '.') === FALSE || strpos($line, ',') === FALSE || empty($line)) {
|
|
continue;
|
|
}
|
|
|
|
// Display progress indicator
|
|
if ($linecnt % 100000 == 0) {
|
|
pfb_logger('.', 1);
|
|
}
|
|
|
|
// Collect Domain TLD
|
|
$csvline = str_getcsv($line);
|
|
$tld = substr($csvline[1], strrpos($csvline[1], '.') + 1);
|
|
|
|
if (isset($pfb_include[$tld])) {
|
|
// Whitelist both 'www.example.com' and 'example.com'
|
|
if (substr($csvline[1], 0, 4) == 'www.') {
|
|
$csvline[1] = substr($csvline[1], 4);
|
|
}
|
|
$x++;
|
|
|
|
// Create three whitelist options per TOP1M whitelisted Domain
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
@fwrite($pfb_output, ".{$csvline[1]},,\n,{$csvline[1]},,\n,www.{$csvline[1]},,\n");
|
|
} else {
|
|
@fwrite($pfb_output, ".{$csvline[1]} 60\n\"{$csvline[1]} 60\n\"www.{$csvline[1]} 60\n");
|
|
}
|
|
}
|
|
|
|
if ($x >= $pfb['dnsbl_alexa_cnt']) {
|
|
break;
|
|
}
|
|
$linecnt++;
|
|
}
|
|
pfb_logger("] [ Parsed {$linecnt} lines | Found {$x} of {$pfb['dnsbl_alexa_cnt']} ]...", 1);
|
|
}
|
|
else {
|
|
$log = "\nTOP1M conversion Failed. File: top-1m.csv, not found...";
|
|
pfb_logger("{$log}", 2);
|
|
}
|
|
|
|
if ($handle) {
|
|
@fclose($handle);
|
|
}
|
|
if ($pfb_output) {
|
|
@fclose($pfb_output);
|
|
}
|
|
|
|
// Remove Top1M update file marker
|
|
unlink_if_exists("{$pfb['dbdir']}/top-1m.update");
|
|
}
|
|
|
|
|
|
// Function to remove any leading zeros in octets and to exclude private/reserved addresses.
|
|
function sanitize_ipaddr($ipaddr, $custom, $pfbcidr) {
|
|
global $pfb;
|
|
|
|
list ($subnet, $mask) = explode('/', $ipaddr);
|
|
$iparr = explode('.', $subnet);
|
|
|
|
foreach ($iparr as $key => $octet) {
|
|
// Remove any leading zeros in octets
|
|
if ($octet == 0) {
|
|
$ip[$key] = 0;
|
|
} else {
|
|
$ip[$key] = ltrim($octet, '0');
|
|
}
|
|
|
|
if ($key == 3) {
|
|
// If mask is not defined and 4th octet is '0', set mask to '24'
|
|
if ($octet == 0 && empty($mask)) {
|
|
$mask = 24;
|
|
}
|
|
|
|
// If mask is '24', force 4th octet to '0'
|
|
if ($mask == 24 && $octet != 0) {
|
|
$ip[$key] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
$mask = str_replace('32', '', $mask); // Strip '/32' mask
|
|
$ip_final = implode('.', $ip);
|
|
|
|
// Exclude private/reserved IPs (bypass exclusion for custom lists)
|
|
if ($pfb['supp'] == 'on' && !$custom) {
|
|
|
|
// Remove 'loopback' and '0.0.0.0' IPs
|
|
if ($ip[0] == 127 || $ip[0] == 0 || empty($ip[0])) {
|
|
return;
|
|
}
|
|
|
|
// Advanced IPv4 Tunable (Set CIDR Block size limit)
|
|
if ($pfbcidr != 'Disabled' && !empty($mask) && $mask < $pfbcidr) {
|
|
pfb_logger("\n Suppression CIDR Limit: {$ip_final}/{$mask}", 1);
|
|
$mask = '32';
|
|
}
|
|
|
|
if ($mask > 32) {
|
|
$mask = '';
|
|
}
|
|
|
|
if (!filter_var($ip_final, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== FALSE) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!empty($mask)) {
|
|
return "{$ip_final}/{$mask}";
|
|
}
|
|
return "{$ip_final}";
|
|
}
|
|
|
|
|
|
// Validate IPv4 IP addresses
|
|
function validate_ipv4($ipaddr) {
|
|
if (strpos($ipaddr, '/') !== FALSE) {
|
|
return is_subnetv4($ipaddr);
|
|
}
|
|
return is_ipaddrv4($ipaddr);
|
|
}
|
|
|
|
|
|
// Validate IPv6 IP addresses
|
|
function validate_ipv6($ipaddr) {
|
|
if (strpos($ipaddr, '/') !== FALSE) {
|
|
return is_subnetv6($ipaddr);
|
|
}
|
|
return is_ipaddrv6($ipaddr);
|
|
}
|
|
|
|
|
|
// Validate IP addresses
|
|
function validate_ip($ipaddr) {
|
|
if (strpos($ipaddr, '/') !== FALSE) {
|
|
return is_subnet($ipaddr);
|
|
}
|
|
return is_ipaddr($ipaddr);
|
|
}
|
|
|
|
|
|
// Function to check for loopback addresses (IPv4 range: 127.0.0.0/8, excluding IPv6)
|
|
function FILTER_FLAG_NO_LOOPBACK_RANGE($value) {
|
|
// http://www.php.net/manual/en/filter.filters.flags.php
|
|
return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? $value : (((ip2long($value) & 0xff000000) == 0x7f000000) ? FALSE : $value);
|
|
}
|
|
|
|
|
|
// Explode IP for evaluations
|
|
function ip_explode($ip) {
|
|
|
|
$ix1 = '';
|
|
$ix = explode('.', $ip);
|
|
foreach ($ix as $key => $octet) {
|
|
if ($key != 3) {
|
|
$ix1 .= "{$octet}.";
|
|
}
|
|
}
|
|
array_unshift($ix, $ip);
|
|
$ix[] = "{$ix1}0/24";
|
|
$ix[] = "{$ix1}";
|
|
|
|
return $ix;
|
|
}
|
|
|
|
|
|
// Determine the header which Alerted an IP address and return the header name
|
|
function find_reported_header($ip, $pfbfolder, $geoip=FALSE) {
|
|
global $pfb;
|
|
|
|
// Find exact IP match
|
|
$q_ip = escapeshellarg(str_replace('.', '\.', "^{$ip}"));
|
|
$query = exec("{$pfb['grep']} -s {$q_ip} {$pfbfolder} 2>&1");
|
|
if (!empty($query)) {
|
|
$rx = pfb_parse_query($query);
|
|
return $rx;
|
|
}
|
|
else {
|
|
$v4_type = FALSE;
|
|
if (substr_count($ip, ':') > 1) {
|
|
$query = strstr($ip, ':', TRUE); // IPv6 Prefix
|
|
$query_esc = escapeshellarg("^{$query}:");
|
|
} else {
|
|
$query = strstr($ip, '.', TRUE); // IPv4 Octet #1
|
|
$query_esc = escapeshellarg("^{$query}\.");
|
|
$v4_type = TRUE;
|
|
}
|
|
|
|
$result = array();
|
|
if (!$geoip) {
|
|
exec("{$pfb['grep']} -s {$query_esc} {$pfbfolder} 2>&1", $result);
|
|
} else {
|
|
$geoip_list = "Africa\|Antarctica\|Asia\|Europe\|North_America\|South_America\|Oceania\|Proxy_and_Satellite\|Top_Spammers";
|
|
exec("{$pfb['grep']} -s {$query_esc} {$pfb['ccdir']}/*.txt | {$pfb['grep']} -v '{$geoip_list}' 2>&1", $result);
|
|
}
|
|
|
|
$cidrs = array();
|
|
if (!empty($result)) {
|
|
foreach ($result as $line) {
|
|
$rx = pfb_parse_query($line);
|
|
|
|
// Collect all CIDRs for analysis if Alert is from a CIDR
|
|
if (strpos($rx[1], '/') !== FALSE) {
|
|
$cidrs[] = $rx;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isset($result)) {
|
|
unset($result);
|
|
}
|
|
|
|
// Determine which CIDR alerted the IP address
|
|
if (!empty($cidrs)) {
|
|
foreach ($cidrs as $line) {
|
|
$validate = FALSE;
|
|
if ($v4_type) {
|
|
list($addr, $mask) = explode('/', $line[1]);
|
|
$mask = (0xffffffff << (32 - $mask)) & 0xffffffff;
|
|
$validate = ((ip2long($ip) & $mask) == (ip2long($addr) & $mask));
|
|
}
|
|
else {
|
|
/* Normalize IPv6 prefix to its start address to avoid PHP errors
|
|
* https://redmine.pfsense.org/issues/14256
|
|
*/
|
|
list($prefix, $length) = explode('/', $line[1]);
|
|
$prefix = gen_subnetv6($prefix, $length);
|
|
$subnet = "{$prefix}/{$length}";
|
|
|
|
$validate = (Net_IPv6::isInNetmask($ip, $subnet));
|
|
}
|
|
|
|
// Return header on CIDR match
|
|
if ($validate) {
|
|
unset($cidrs);
|
|
return $line;
|
|
}
|
|
}
|
|
unset($cidrs);
|
|
}
|
|
}
|
|
|
|
if (isset($result)) {
|
|
unset($result);
|
|
}
|
|
return array('Unknown', 'Unknown');
|
|
}
|
|
|
|
|
|
// Function to download feeds
|
|
function pfb_download($list_url, $file_dwn, $pflex=FALSE, $header, $format, $logtype, $vtype='', $timeout=300, $type='', $username='', $password='', $srcint=FALSE) {
|
|
global $pfb;
|
|
$http_status = '';
|
|
$elog = ">> {$pfb['log']} 2>&1";
|
|
|
|
// Remove any leading/trailing whitespace
|
|
$list_url = trim($list_url);
|
|
|
|
// Re-evaluate URL
|
|
if ($format == 'whois') {
|
|
if (empty(pfb_filter($list_url, PFB_FILTER_DOMAIN, 'pfb_download'))) {
|
|
pfb_logger("\n Failed", 2);
|
|
return FALSE;
|
|
}
|
|
}
|
|
elseif ($format == 'asn') {
|
|
if (empty(pfb_filter($list_url, PFB_FILTER_ALNUM, 'pfb_download'))) {
|
|
pfb_logger("\n Failed", 2);
|
|
return FALSE;
|
|
}
|
|
}
|
|
elseif (!pfb_filter($list_url, PFB_FILTER_URL, 'pfb_download_failure')) {
|
|
pfb_logger("\n Failed", 2);
|
|
return FALSE;
|
|
}
|
|
|
|
// Cron update function for md5 comparison
|
|
if ($type == 'md5') {
|
|
pfb_logger("\t\t\t\t( md5 feed )\t\t", 1);
|
|
}
|
|
|
|
$file_dwn_esc = escapeshellarg("{$file_dwn}.raw");
|
|
$file_org_esc = escapeshellarg("{$file_dwn}.orig");
|
|
$list_url_esc = escapeshellarg("{$list_url}");
|
|
$header_esc = escapeshellarg("{$header}");
|
|
|
|
$file_download = trim("{$file_dwn_esc}", "'");
|
|
$orig_download = trim("{$file_org_esc}", "'");
|
|
$list_download = trim("{$list_url_esc}", "'");
|
|
$head_download = trim("{$header_esc}", "'");
|
|
|
|
$md5_download = trim(escapeshellarg("{$file_dwn}.md5.raw"), "'");
|
|
|
|
// If the Cron update function 'md5 comparison' generated an md5 file, re-utilize instead of downloading twice
|
|
if (file_exists("{$md5_download}")) {
|
|
$list_download = "{$md5_download}";
|
|
pfb_logger(' ( md5 feed ) ', 1);
|
|
}
|
|
|
|
// Download RSYNC format
|
|
if ($format == 'rsync') {
|
|
$result = exec("/usr/local/bin/rsync --timeout=5 {$list_url_esc} {$file_dwn_esc}");
|
|
if ($result == 0) {
|
|
$http_status = '200';
|
|
} else {
|
|
$log = "\n RSYNC Failed...\n";
|
|
pfb_logger("{$log}", "{$logtype}");
|
|
return FALSE;
|
|
}
|
|
}
|
|
elseif ($format == 'whois' || $format == 'asn') {
|
|
// Convert a Domain name/AS into its respective IP addresses
|
|
exec("{$pfb['script']} whoisconvert {$header_esc} {$vtype} {$list_url_esc} {$elog}");
|
|
return TRUE;
|
|
}
|
|
else {
|
|
// Determine if URL is a pfSense localfile
|
|
$localfile = FALSE;
|
|
if (pfb_filter("{$list_download}", PFB_FILTER_URL, 'pfb_download', '', TRUE)) {
|
|
$localfile = TRUE;
|
|
}
|
|
|
|
// Download localfile format
|
|
if ($localfile) {
|
|
$file_data = @file_get_contents("{$list_download}");
|
|
if ($file_data === FALSE) {
|
|
$error = error_get_last();
|
|
$log = "\n[ {$header} ] {$error['message']}\n";
|
|
pfb_logger("{$log}", "{$logtype}");
|
|
return FALSE;
|
|
} else {
|
|
// Save original downloaded file
|
|
@file_put_contents("{$file_download}", $file_data, LOCK_EX);
|
|
$http_status = '200';
|
|
}
|
|
}
|
|
|
|
// Download using cURL
|
|
else {
|
|
$remote_stamp = -1;
|
|
if (($fhandle = @fopen("{$file_download}", 'w')) !== FALSE) {
|
|
if (!($ch = curl_init("{$list_download}"))) {
|
|
$log = "\nFailed to create cURL resource... Exiting...\n";
|
|
pfb_logger("{$log}", "{$logtype}");
|
|
return FALSE;
|
|
}
|
|
|
|
curl_setopt_array($ch, $pfb['curl_defaults']); // Load curl default settings
|
|
curl_setopt($ch, CURLOPT_FILE, $fhandle); // Add $fhandle setting to cURL
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); // Set cURL download timeout
|
|
curl_setopt($ch, CURLOPT_ENCODING, 'gzip'); // Request 'gzip' encoding from server if available
|
|
if ($srcint) {
|
|
curl_setopt($ch, CURLOPT_INTERFACE, $srcint); // Use a specific interface when downloading lists
|
|
pfb_logger("\nList: {$header} will be downloaded via interface: {$srcint}\n", 1);
|
|
}
|
|
|
|
if (!empty($username) && !empty($password)) {
|
|
curl_setopt($ch, CURLOPT_USERPWD, "{$username}:{$password}");
|
|
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
|
|
}
|
|
|
|
// Attempt 3 Downloads before failing.
|
|
for ($retries = 1; $retries <= 3; $retries++) {
|
|
if (curl_exec($ch)) {
|
|
// Collect remote timestamp.
|
|
$raw_filetime = curl_getinfo($ch, CURLINFO_FILETIME);
|
|
if ($raw_filetime == -1) {
|
|
$remote_stamp = -1;
|
|
} elseif (!empty(pfb_filter($raw_filetime, PFB_FILTER_NUM, 'pfb_download - remote timestamp'))) {
|
|
$remote_stamp = $raw_filetime;
|
|
}
|
|
break; // Break on success
|
|
}
|
|
|
|
$curl_error = curl_errno($ch);
|
|
if ($logtype != 3) {
|
|
pfb_logger(" cURL Error: {$curl_error} [ NOW ]\n", 1);
|
|
} else {
|
|
pfb_logger(" {$header}\t\tcURL Error: {$curl_error} [ NOW ]\n\n", 3);
|
|
}
|
|
|
|
/* 'Flex' Downgrade cURL errors - [ 35 - GET_SERVER_HELLO:sslv3 ]
|
|
[ 51 - NO alternative certificate ]
|
|
[ 60 - Local Issuer Certificate Subject ] */
|
|
|
|
// Allow downgrade of cURL settings 'Flex' after 1st failure, if user configured.
|
|
if ($retries == 1 && $pflex && in_array($curl_error, array( '35', '51', '60'))) {
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
|
curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'TLSv1.3, TLSv1.2, TLSv1.1, TLSv1, SSLv3');
|
|
$log = "\n Downgrading SSL settings (Flex)";
|
|
pfb_logger("{$log}", "{$logtype}");
|
|
}
|
|
else {
|
|
if ($retries == 3) {
|
|
$log = curl_error($ch) . " |{$head_download}|{$list_download}| Retry [{$retries}] in 5 seconds...\n";
|
|
} else {
|
|
$log = curl_error($ch) . " Retry [{$retries}] in 5 seconds...\n";
|
|
}
|
|
pfb_logger("{$log}", "{$logtype}");
|
|
sleep(5);
|
|
pfb_logger('.', "{$logtype}");
|
|
}
|
|
}
|
|
|
|
// Collect RFC7231 http status code
|
|
$http_status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
|
|
|
|
if (isset($pfb['rfc7231'][$http_status])) {
|
|
if ($logtype < 3) {
|
|
pfb_logger(". {$pfb['rfc7231'][$http_status]}", 2);
|
|
} else {
|
|
pfb_logger(" {$file_dwn}\t\t{$pfb['rfc7231'][$http_status]}\n", $logtype);
|
|
}
|
|
} else {
|
|
pfb_logger(". Unknown Failure Code [{$http_status}]", $logtype);
|
|
}
|
|
if ($ch) {
|
|
curl_close($ch);
|
|
}
|
|
}
|
|
if ($fhandle) {
|
|
@fclose($fhandle);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cron update function for md5 comparison
|
|
if ($type == 'md5') {
|
|
if ($http_status == '200') {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// Remove any downloaded files with md5 extension
|
|
unlink_if_exists("{$md5_download}");
|
|
|
|
// '304 not modified' - Utilize previously downloaded file if available
|
|
if ($http_status == '304' && file_exists("{$orig_download}")) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (file_exists($file_download) && ($http_status == '200' || $http_status == '221' || $http_status == '226')) {
|
|
|
|
if (isset($retval)) {
|
|
unset($retval);
|
|
}
|
|
|
|
// Validate File Mime-type
|
|
$file_type = pfb_filter(array("{$file_dwn_esc}", "{$file_download}", "{$list_download}"), PFB_FILTER_FILE_MIME, 'pfb_download');
|
|
if (!$file_type) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Create update file markers on new downloads available
|
|
switch($type) {
|
|
case 'geoip':
|
|
touch("{$pfb['dbdir']}/geoip.update");
|
|
break;
|
|
case 'top1m':
|
|
touch("{$pfb['dbdir']}/top-1m.update");
|
|
break;
|
|
case 'asn':
|
|
touch("{$pfb['dbdir']}/asn.update");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Set downloaded file timestamp to remote timestamp
|
|
if (isset($remote_stamp) && $remote_stamp != -1) {
|
|
@touch("{$file_download}", $remote_stamp);
|
|
}
|
|
|
|
// Decompress file if required
|
|
if ($file_type == 'application/x-gzip' || $file_type == 'application/gzip') {
|
|
if ($type == 'geoip') {
|
|
// Extras - MaxMind downloads
|
|
exec("/usr/bin/tar -xzf {$file_dwn_esc} --strip=1 -C {$pfb['geoipshare']} >/dev/null 2>&1");
|
|
unlink_if_exists($file_dwn_esc);
|
|
return TRUE;
|
|
}
|
|
elseif ($type == 'asn') {
|
|
exec("/usr/bin/gunzip -c {$file_dwn_esc} > {$header_esc}", $output, $retval);
|
|
|
|
// Update ASN Lookup table definitions
|
|
exec("{$pfb['script']} asn_table >> {$logtype} 2>&1");
|
|
|
|
unlink_if_exists($file_dwn_esc);
|
|
return TRUE;
|
|
}
|
|
elseif ($type == 'blacklist') {
|
|
// Extras - Blacklist downloads
|
|
@rename("{$file_download}", strstr("{$file_download}", '.raw', TRUE));
|
|
$file_esc = trim(escapeshellarg("{$file_dwn}"), "'");
|
|
$filename = basename("{$file_esc}", '.tar.gz');
|
|
|
|
if (!empty(pfb_filter($filename, PFB_FILTER_WORD, 'pfb_download category'))) {
|
|
rmdir_recursive("{$pfb['dbdir']}/{$filename}/");
|
|
safe_mkdir("{$pfb['dbdir']}/{$filename}/");
|
|
|
|
// Extract Blacklist categories from sub-folders into a single folder structure
|
|
$cmd = "--include='*domains' -s',.*/\\(.*\\)/\\(.*\\)/domains,{$filename}_\\1_\\2,' -s',.*/\\(.*\\)/domains,{$filename}_\\1,'";
|
|
$filename_esc = escapeshellarg("{$pfb['dbdir']}/{$filename}/");
|
|
exec("/usr/bin/tar -xf " . escapeshellarg("{$file_dwn}") . " {$cmd} -C {$filename_esc} >/dev/null 2>&1");
|
|
|
|
// Rename any Category files with dashes
|
|
$verifydir = "{$pfb['dbdir']}/{$filename}";
|
|
if (is_dir("{$verifydir}")) {
|
|
$list = glob("{$verifydir}/{$filename}*");
|
|
if (is_array($list) && !empty($list)) {
|
|
foreach ($list as $verify) {
|
|
if (strpos($verify, '-') !== FALSE) {
|
|
rename($verify, str_replace('-', '_', $verify));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create update file indicator for update process
|
|
touch("{$pfb['dbdir']}/{$filename}/{$filename}.update");
|
|
}
|
|
else {
|
|
pfb_logger("\n Invalid filename [{$filename}]", 1);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else {
|
|
if (!pfb_filter(array("{$file_dwn_esc}", "{$file_download}", "{$list_download}"), PFB_FILTER_FILE_MIME_COMPRESSED, 'pfb_download')) {
|
|
return FALSE;
|
|
}
|
|
pfb_logger('.', 1);
|
|
exec("/usr/bin/gunzip -c {$file_dwn_esc} > {$file_org_esc}", $output, $retval);
|
|
}
|
|
}
|
|
elseif ($file_type == 'application/x-bzip2') {
|
|
if (!pfb_filter(array("{$file_dwn_esc}", "{$file_download}", "{$list_download}"), PFB_FILTER_FILE_MIME_COMPRESSED, 'pfb_download')) {
|
|
return FALSE;
|
|
}
|
|
pfb_logger('.', 1);
|
|
exec("/usr/bin/bzip2 -dkc {$file_dwn_esc} > {$file_org_esc}", $output, $retval);
|
|
}
|
|
elseif ($file_type == 'application/zip') {
|
|
|
|
// Extras - MaxMind/TOP1M downloads
|
|
if ($type == 'geoip' || $type == 'top1m') {
|
|
// Determine if Zip contains multiple files
|
|
exec("/usr/bin/tar -tf {$file_dwn_esc} 2>&1", $archive_count, $retval);
|
|
if ($archive_count[0] == 'tar: Failed to set default locale') {
|
|
unset($archive_count[0]);
|
|
}
|
|
if (count($archive_count) > 1) {
|
|
exec("/usr/bin/tar -xf {$file_dwn_esc} --strip=1 -C {$header_esc} >/dev/null 2>&1");
|
|
} else {
|
|
exec("/usr/bin/tar -xOf {$file_dwn_esc} > {$header_esc}");
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
pfb_logger('.', 1);
|
|
|
|
/* TODO: FIX - Bypass ZIP Compression file mime type validation due to incompatability with ZIP files
|
|
if (!pfb_filter(array("{$file_dwn_esc}", "{$file_download}", "{$list_download}"), PFB_FILTER_FILE_MIME_COMPRESSED, 'pfb_download')) {
|
|
return FALSE;
|
|
}
|
|
*/
|
|
|
|
// Check if ZIP archive contains xlsx files
|
|
$xlsxtest = exec("/usr/bin/tar -tf {$file_dwn_esc}");
|
|
if (strpos($xlsxtest, '.xlsx') !== FALSE) {
|
|
unlink_if_exists("{$orig_download}");
|
|
exec("{$pfb['script']} xlsx {$header_esc} {$elog}");
|
|
if (file_exists("{$orig_download}")) {
|
|
$retval = 0;
|
|
}
|
|
} else {
|
|
pfb_logger('.', 1);
|
|
// Process ZIP file (SFS and hpHosts workaround)
|
|
exec("/usr/bin/tar -xOf {$file_dwn_esc} | /usr/bin/sed 's/,[[:space:]]/; /g' | /usr/bin/tr ',' '\n' > {$file_org_esc}", $output, $retval);
|
|
}
|
|
|
|
// TODO: Validate file contents after extraction (to be removed once ZIP compression file mime type is fixed above)
|
|
if (!pfb_filter(array("{$file_org_esc}", "{$file_download}", "{$list_download}"), PFB_FILTER_FILE_MIME, 'pfb_download')) {
|
|
unlink_if_exists("{$file_org_esc}");
|
|
return FALSE;
|
|
}
|
|
}
|
|
elseif ($file_type == 'application/x-7z-compressed') {
|
|
if (!pfb_filter(array("{$file_dwn_esc}", "{$file_download}", "{$list_download}"), PFB_FILTER_FILE_MIME_COMPRESSED, 'pfb_download')) {
|
|
return FALSE;
|
|
}
|
|
pfb_logger('.', 1);
|
|
exec("/usr/local/bin/7z e -so {$file_dwn_esc} > {$file_org_esc}", $output, $retval);
|
|
}
|
|
else {
|
|
// Uncompressed file format.
|
|
if ($type == 'geoip' || $type == 'asn') {
|
|
// Extras - MaxMind/TOP1M/ASN downloads
|
|
@rename("{$file_download}", "{$head_download}");
|
|
return TRUE;
|
|
}
|
|
elseif ($type == 'blacklist') {
|
|
$retval = 0;
|
|
}
|
|
else {
|
|
// Rename file to 'orig' format
|
|
@rename("{$file_download}", "{$orig_download}");
|
|
$retval = 0;
|
|
}
|
|
}
|
|
|
|
if ($retval == 0) {
|
|
// Set downloaded file timestamp to remote timestamp
|
|
if (isset($remote_stamp) && $remote_stamp != -1) {
|
|
@touch("{$orig_download}", $remote_stamp);
|
|
}
|
|
|
|
// Process Emerging Threats IQRisk if required
|
|
if (strpos($list_url, 'iprepdata.txt') !== FALSE) {
|
|
exec("{$pfb['script']} et {$header_esc} x x x x x {$pfb['etblock']} {$pfb['etmatch']} {$elog}");
|
|
}
|
|
return TRUE;
|
|
}
|
|
else {
|
|
$log = " Decompression Failed\n";
|
|
pfb_logger("{$log}", 2);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else {
|
|
// Download failed
|
|
unlink_if_exists("{$file_download}");
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// Determine reason for download failure
|
|
function pfb_download_failure($alias, $header, $pfbfolder, $list_url, $format, $vtype) {
|
|
global $pfb;
|
|
$pfbfound = FALSE;
|
|
|
|
// Re-evaluate URL
|
|
if ($format == 'whois') {
|
|
if (empty(pfb_filter($list_url, PFB_FILTER_DOMAIN, 'pfb_download_failure'))) {
|
|
pfb_logger("\n Invalid WHOIS. Terminating Download! [ {$list_url} ]\n", 1);
|
|
}
|
|
}
|
|
elseif ($format == 'asn') {
|
|
if (empty(pfb_filter($list_url, PFB_FILTER_ALNUM, 'pfb_download_failure'))) {
|
|
pfb_logger("\n Invalid ASN. Terminating Download! [ {$list_url} ]\n", 1);
|
|
}
|
|
}
|
|
elseif (!pfb_filter($list_url, PFB_FILTER_URL, 'pfb_download_failure')) {
|
|
pfb_logger("\n Invalid URL. Terminating Download! [ {$list_url} ]\n", 1);
|
|
}
|
|
else {
|
|
// Determine if URL is a localfile
|
|
$localfile = FALSE;
|
|
if (pfb_filter($list_url, PFB_FILTER_URL, 'pfb_download_failure', '', TRUE)) {
|
|
$localfile = TRUE;
|
|
}
|
|
|
|
// Log FAILED downloads and check if firewall or Snort/Suricata is blocking host
|
|
$log = "\n\n [ {$alias} - {$header} ] Download FAIL [ NOW ]\n";
|
|
pfb_logger("{$log}", 2);
|
|
|
|
// Only perform these checks if they are not 'localfiles'
|
|
if ($localfile) {
|
|
$log = " Local File Failure\n";
|
|
pfb_logger("{$log}", 2);
|
|
}
|
|
else {
|
|
// Determine if Firewall/IPS/DNSBL is blocking download.
|
|
$data = parse_url("{$list_url}");
|
|
|
|
$validate_list = array();
|
|
if (is_ipaddr($data['host'])) {
|
|
$validate_list = array(array('type' => 'IP', 'data' => $data['host']));
|
|
} else {
|
|
$validate_list = resolve_host_addresses($data['host']);
|
|
}
|
|
|
|
$validate_list_final = array();
|
|
if (!empty($validate_list) && is_array($validate_list)) {
|
|
foreach ($validate_list as $validate) {
|
|
if ($validate['type'] == 'CNAME') {
|
|
$cname_list = resolve_host_addresses($validate['data']);
|
|
if (!empty($cname_list) && is_array($cname_list)) {
|
|
foreach ($cname_list as $cname) {
|
|
if (!empty($cname['data'])) {
|
|
$validate_list_final[$cname['data']] = "Host:{$data['host']} | CNAME:{$cname['host']}";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
elseif (!empty($validate['data'])) {
|
|
$validate_list_final[$validate['data']] = $data['host'];
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
pfb_logger("\n Cannot Resolve Host:{$data['host']}", 1);
|
|
}
|
|
|
|
if (!empty($validate_list_final)) {
|
|
foreach ($validate_list_final as $ip => $host) {
|
|
if (is_ipaddr($ip)) {
|
|
|
|
// Query Firewall aliastables
|
|
$result = find_reported_header($ip, "{$pfbfolder}/*", FALSE);
|
|
if (!empty($result) && $result[0] != 'Unknown') {
|
|
$log = " [ {$ip} ] Firewall IP block found in: [ {$result[0]} | {$result[1]} ] for HOST:{$host}!\n";
|
|
pfb_logger("{$log}", 2);
|
|
$pfbfound = TRUE;
|
|
}
|
|
|
|
// Determine if Host is listed in DNSBL
|
|
if ($ip == $pfb['dnsbl_vip'] || $ip == "::{$pfb['dnsbl_vip']}" || $ip == '0.0.0.0') {
|
|
$log = " [ {$host} ] Domain blocked via DNSBL!\n";
|
|
pfb_logger("{$log}", 2);
|
|
$pfbfound = TRUE;
|
|
}
|
|
|
|
// Query Snort/Suricata snort2c IP block table
|
|
$ip_esc = escapeshellarg($ip);
|
|
$result = substr(exec("{$pfb['pfctl']} -t snort2c -T test {$ip_esc} 2>&1"), 0, 1);
|
|
if ($result > 0) {
|
|
$log = " [ {$ip} ] IDS IP block found for HOST:{$host}!\n";
|
|
pfb_logger("{$log}", 2);
|
|
$pfbfound = TRUE;
|
|
}
|
|
}
|
|
else {
|
|
pfb_logger("\nInvalid IP or NXDOMAIN found for HOST:{$host}", 2);
|
|
$pfbfound = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$pfbfound) {
|
|
$log = " DNSBL, Firewall, and IDS (Legacy mode only) are not blocking download.\n";
|
|
pfb_logger("{$log}", 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Call function to get all previous download fails
|
|
pfb_failures();
|
|
|
|
// On download failure, create file marker for subsequent download attempts. ('0' no download failure threshold)
|
|
if ($pfb['skipfeed'] == 0 || $pfb['failed'][$header] <= $pfb['skipfeed']) {
|
|
touch("{$pfbfolder}/{$header}.fail");
|
|
return;
|
|
}
|
|
|
|
unlink_if_exists("{$pfbfolder}/{$header}.fail");
|
|
return;
|
|
}
|
|
|
|
|
|
// Collect all previously failed daily download notices
|
|
function pfb_failures() {
|
|
global $pfb;
|
|
$pfb['failed'] = array();
|
|
|
|
if (file_exists("{$pfb['errlog']}")) {
|
|
$today_date = date('m/j/y', time());
|
|
exec("{$pfb['grep']} 'FAIL' {$pfb['errlog']} | {$pfb['grep']} {$today_date}", $results);
|
|
if (!empty($results)) {
|
|
foreach ($results as $result) {
|
|
$header = explode(' ', $result);
|
|
$pfb['failed'][$header[4]] += 1;
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
// Convert unique Alias details (via ascii table number) and return a 10 digit tracker ID
|
|
function pfb_tracker($alias, $int, $text) {
|
|
global $pfb;
|
|
|
|
$pfbtracker = 0;
|
|
$real_int = get_real_interface($int);
|
|
$ipaddr = get_interface_ip($int);
|
|
|
|
if (is_ipaddrv4($ipaddr)) {
|
|
$ipaddr = ip2long32($ipaddr);
|
|
$subnet = find_interface_subnet($real_int);
|
|
}
|
|
else {
|
|
$ipaddr = get_interface_ipv6($real_int);
|
|
$subnet = find_interface_subnetv6($real_int);
|
|
}
|
|
|
|
$search = array( '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' );
|
|
$replace = array( 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'zero' );
|
|
$line = "{$alias}{$int}{$text}{$real_int}{$ipaddr}{$subnet}";
|
|
$line = str_replace($search, $replace, $line);
|
|
|
|
for ($i = 0; $i < strlen($line); $i++) {
|
|
$pfbtracker += @ord($line[$i]);
|
|
}
|
|
|
|
$pfbtracker = '177' . str_pad($pfbtracker, 7, '0', STR_PAD_LEFT);
|
|
if (strlen($pfbtracker) > 10) {
|
|
$pfbtracker = substr($pfbtracker, 0, 10);
|
|
}
|
|
|
|
// If duplicate Tracker ID found, pre-define a Tracker ID (Starts at 1700000010)
|
|
if (in_array($pfbtracker, $pfb['trackerids'])) {
|
|
$pfbtracker = ($pfb['last_trackerid'] + 1);
|
|
|
|
// Increment prefix (digits 1&2) and reset Last_tracker ID after 10 digits
|
|
if (strlen($pfbtracker) > 10) {
|
|
$tracker_prefix = substr($pfbtracker, 0, 2);
|
|
$pfbtracker = ($tracker_prefix + 1) . '00000010';
|
|
}
|
|
$pfb['last_trackerid'] = $pfbtracker;
|
|
}
|
|
|
|
$pfb['trackerids'][] = $pfbtracker;
|
|
return (int)$pfbtracker;
|
|
}
|
|
|
|
|
|
// Define firewall rule settings
|
|
function pfb_firewall_rule($action, $pfb_alias, $vtype, $pfb_log, $agateway_in='default', $agateway_out='default',
|
|
$aaddrnot_in='', $adest_in='', $aports_in='', $aproto_in='', $anot_in='',
|
|
$aaddrnot_out='', $asrc_out='', $aports_out='', $aproto_out='', $anot_out='') {
|
|
|
|
global $pfb;
|
|
$rule = array();
|
|
|
|
switch ($action) {
|
|
case 'Deny_Both':
|
|
case 'Deny_Outbound':
|
|
$rule = $pfb['base_rule'];
|
|
$rule['type'] = "{$pfb['deny_action_outbound']}";
|
|
if ($vtype == '_v6') {
|
|
$rule['ipprotocol'] = 'inet6';
|
|
}
|
|
if ($pfb['float'] == 'on') {
|
|
$rule['direction'] = 'any';
|
|
}
|
|
$rule['descr'] = "{$pfb_alias}{$pfb['suffix']}";
|
|
if (!empty($asrc_out)) {
|
|
$rule['source'] = array('address' => "{$asrc_out}");
|
|
} else {
|
|
$rule['source'] = array('any' => '');
|
|
}
|
|
if (!empty($asrc_out) && $anot_out == 'on') {
|
|
$rule['source']['not'] = '';
|
|
}
|
|
if (!empty($aports_out)) {
|
|
$rule['destination'] = array('address' => "{$pfb_alias}", 'port' => "{$aports_out}");
|
|
} else {
|
|
$rule['destination'] = array('address' => "{$pfb_alias}");
|
|
}
|
|
if ($aaddrnot_out == 'on') {
|
|
$rule['destination']['not'] = '';
|
|
}
|
|
if (!empty($aproto_out)) {
|
|
$rule['protocol'] = "{$aproto_out}";
|
|
}
|
|
if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') {
|
|
$rule['log'] = '';
|
|
}
|
|
if (!empty($agateway_out) && $agateway_out != 'default') {
|
|
$rule['gateway'] = "{$agateway_out}";
|
|
if ($pfb['float'] == 'on') {
|
|
$rule['direction'] = 'out';
|
|
}
|
|
}
|
|
$rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto');
|
|
$pfb['deny_outbound'][] = $rule;
|
|
if ($action != 'Deny_Both') {
|
|
break;
|
|
}
|
|
case 'Deny_Inbound':
|
|
$rule = $pfb['base_rule'];
|
|
$rule['type'] = "{$pfb['deny_action_inbound']}";
|
|
if ($vtype == '_v6') {
|
|
$rule['ipprotocol'] = 'inet6';
|
|
}
|
|
if ($pfb['float'] == 'on') {
|
|
$rule['direction'] = 'any';
|
|
}
|
|
$rule['descr'] = "{$pfb_alias}{$pfb['suffix']}";
|
|
$rule['source'] = array('address' => "{$pfb_alias}");
|
|
if ($aaddrnot_in == 'on') {
|
|
$rule['source']['not'] = '';
|
|
}
|
|
if (!empty($adest_in) && !empty($aports_in)) {
|
|
$rule['destination'] = array('address' => "{$adest_in}", 'port' => "{$aports_in}");
|
|
} elseif (!empty($adest_in) && empty($aports_in)) {
|
|
$rule['destination'] = array('address' => "{$adest_in}");
|
|
} elseif (empty($adest_in) && !empty($aports_in)) {
|
|
$rule['destination'] = array('any' => '', 'port' => "{$aports_in}");
|
|
} else {
|
|
$rule['destination'] = array('any' => '');
|
|
}
|
|
if (!empty($adest_in) && $anot_in == 'on') {
|
|
$rule['destination']['not'] = '';
|
|
}
|
|
if (!empty($aproto_in)) {
|
|
$rule['protocol'] = "{$aproto_in}";
|
|
}
|
|
if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') {
|
|
$rule['log'] = '';
|
|
}
|
|
if (!empty($agateway_in) && $agateway_in != 'default') {
|
|
$rule['gateway'] = "{$agateway_in}";
|
|
if ($pfb['float'] == 'on') {
|
|
$rule['direction'] = 'in';
|
|
}
|
|
}
|
|
$rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto');
|
|
$pfb['deny_inbound'][] = $rule;
|
|
break;
|
|
case 'Permit_Both':
|
|
case 'Permit_Outbound':
|
|
$rule = $pfb['base_rule'];
|
|
$rule['type'] = 'pass';
|
|
if ($vtype == '_v6') {
|
|
$rule['ipprotocol'] = 'inet6';
|
|
}
|
|
if ($pfb['float'] == 'on') {
|
|
$rule['direction'] = 'any';
|
|
}
|
|
$rule['descr'] = "{$pfb_alias}{$pfb['suffix']}";
|
|
if (!empty($asrc_out)) {
|
|
$rule['source'] = array('address' => "{$asrc_out}");
|
|
} else {
|
|
$rule['source'] = array('any' => '');
|
|
}
|
|
if (!empty($asrc_out) && $anot_out == 'on') {
|
|
$rule['source']['not'] = '';
|
|
}
|
|
if (!empty($aports_out)) {
|
|
$rule['destination'] = array('address' => "{$pfb_alias}", 'port' => "{$aports_out}");
|
|
} else {
|
|
$rule['destination'] = array('address' => "{$pfb_alias}");
|
|
}
|
|
if ($aaddrnot_out == 'on') {
|
|
$rule['destination']['not'] = '';
|
|
}
|
|
if (!empty($aproto_out)) {
|
|
$rule['protocol'] = "{$aproto_out}";
|
|
}
|
|
if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') {
|
|
$rule['log'] = '';
|
|
}
|
|
if (!empty($agateway_out) && $agateway_out != 'default') {
|
|
$rule['gateway'] = "{$agateway_out}";
|
|
if ($pfb['float'] == 'on') {
|
|
$rule['direction'] = 'out';
|
|
}
|
|
}
|
|
$rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto');
|
|
$pfb['permit_outbound'][] = $rule;
|
|
if ($action != 'Permit_Both') {
|
|
break;
|
|
}
|
|
case 'Permit_Inbound':
|
|
$rule = $pfb['base_rule'];
|
|
$rule['type'] = 'pass';
|
|
if ($vtype == '_v6') {
|
|
$rule['ipprotocol'] = 'inet6';
|
|
}
|
|
if ($pfb['float'] == 'on') {
|
|
$rule['direction'] = 'any';
|
|
}
|
|
$rule['descr'] = "{$pfb_alias}{$pfb['suffix']}";
|
|
$rule['source'] = array('address' => "{$pfb_alias}");
|
|
if ($aaddrnot_in == 'on') {
|
|
$rule['source']['not'] = '';
|
|
}
|
|
if (!empty($adest_in) && !empty($aports_in)) {
|
|
$rule['destination'] = array('address' => "{$adest_in}", 'port' => "{$aports_in}");
|
|
} elseif (!empty($adest_in) && empty($aports_in)) {
|
|
$rule['destination'] = array('address' => "{$adest_in}");
|
|
} elseif (empty($adest_in) && !empty($aports_in)) {
|
|
$rule['destination'] = array('any' => '', 'port' => "{$aports_in}");
|
|
} else {
|
|
$rule['destination'] = array('any' => '');
|
|
}
|
|
if (!empty($adest_in) && $anot_in == 'on') {
|
|
$rule['destination']['not'] = '';
|
|
}
|
|
if (!empty($aproto_in)) {
|
|
$rule['protocol'] = "{$aproto_in}";
|
|
}
|
|
if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') {
|
|
$rule['log'] = '';
|
|
}
|
|
if (!empty($agateway_in) && $agateway_in != 'default') {
|
|
$rule['gateway'] = "{$agateway_in}";
|
|
if ($pfb['float'] == 'on') {
|
|
$rule['direction'] = 'in';
|
|
}
|
|
}
|
|
$rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto');
|
|
$pfb['permit_inbound'][] = $rule;
|
|
break;
|
|
case 'Match_Both':
|
|
case 'Match_Outbound':
|
|
$rule = $pfb['base_rule_float'];
|
|
$rule['type'] = 'match';
|
|
if ($vtype == '_v6') {
|
|
$rule['ipprotocol'] = 'inet6';
|
|
}
|
|
$rule['direction'] = 'any';
|
|
$rule['descr'] = "{$pfb_alias}{$pfb['suffix']}";
|
|
if (!empty($asrc_out)) {
|
|
$rule['source'] = array('address' => "{$asrc_out}");
|
|
} else {
|
|
$rule['source'] = array('any' => '');
|
|
}
|
|
if (!empty($asrc_out) && $anot_out == 'on') {
|
|
$rule['source']['not'] = '';
|
|
}
|
|
if (!empty($aports_out)) {
|
|
$rule['destination'] = array('address' => "{$pfb_alias}", 'port' => "{$aports_out}");
|
|
} else {
|
|
$rule['destination'] = array('address' => "{$pfb_alias}");
|
|
}
|
|
if ($aaddrnot_out == 'on') {
|
|
$rule['destination']['not'] = '';
|
|
}
|
|
if (!empty($aproto_out)) {
|
|
$rule['protocol'] = "{$aproto_out}";
|
|
}
|
|
if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') {
|
|
$rule['log'] = '';
|
|
}
|
|
if (!empty($agateway_out) && $agateway_out != 'default') {
|
|
$rule['gateway'] = "{$agateway_out}";
|
|
$rule['direction'] = 'out';
|
|
}
|
|
$rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto');
|
|
$pfb['match_outbound'][] = $rule;
|
|
if ($action != 'Match_Both') {
|
|
break;
|
|
}
|
|
case 'Match_Inbound':
|
|
$rule = $pfb['base_rule_float'];
|
|
$rule['type'] = 'match';
|
|
if ($vtype == '_v6') {
|
|
$rule['ipprotocol'] = 'inet6';
|
|
}
|
|
$rule['direction'] = 'any';
|
|
$rule['descr'] = "{$pfb_alias}{$pfb['suffix']}";
|
|
$rule['source'] = array('address' => "{$pfb_alias}");
|
|
if ($aaddrnot_in == 'on') {
|
|
$rule['source']['not'] = '';
|
|
}
|
|
if (!empty($adest_in) && !empty($aports_in)) {
|
|
$rule['destination'] = array('address' => "{$adest_in}", 'port' => "{$aports_in}");
|
|
} elseif (!empty($adest_in) && empty($aports_in)) {
|
|
$rule['destination'] = array('address' => "{$adest_in}");
|
|
} elseif (empty($adest_in) && !empty($aports_in)) {
|
|
$rule['destination'] = array('any' => '', 'port' => "{$aports_in}");
|
|
} else {
|
|
$rule['destination'] = array('any' => '');
|
|
}
|
|
if (!empty($adest_in) && $anot_in == 'on') {
|
|
$rule['destination']['not'] = '';
|
|
}
|
|
if (!empty($aproto_in)) {
|
|
$rule['protocol'] = "{$aproto_in}";
|
|
}
|
|
if ($pfb['global_log'] == 'on' || $pfb_log == 'enabled') {
|
|
$rule['log'] = '';
|
|
}
|
|
if (!empty($agateway_in) && $agateway_in != 'default') {
|
|
$rule['gateway'] = "{$agateway_in}";
|
|
$rule['direction'] = 'in';
|
|
}
|
|
$rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto');
|
|
$pfb['match_inbound'][] = $rule;
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
// Archive IP aliastables and DNSBL database. ( Ramdisk installations only )
|
|
function pfb_aliastables($mode) {
|
|
global $g, $pfb;
|
|
|
|
$msg = '';
|
|
$earlyshellcmd = '/usr/local/pkg/pfblockerng/pfblockerng.sh aliastables';
|
|
|
|
// Reload config.xml to get any recent changes
|
|
config_read_file(false, true);
|
|
|
|
$a_earlyshellcmd = config_get_path('system/earlyshellcmd', []);
|
|
$a_shellcmdsettings = config_get_path('installedpackages/shellcmdsettings/config', []);
|
|
|
|
// Only execute function if Ramdisks are used.
|
|
if (config_path_enabled('system', 'use_mfs_tmpvar')) {
|
|
|
|
// Archive aliastable folder
|
|
if ($mode == 'update') {
|
|
pfb_logger("\nArchiving Aliastable folder", 1);
|
|
|
|
$files_to_backup = '';
|
|
$files = glob("{{$pfb['aliasdir']}/pfB_*.txt,{$pfb['dnsbl_file']}.conf,/var/unbound/pfb_unbound*,/var/unbound/pfb_py_*}", GLOB_BRACE);
|
|
if (!empty($files)) {
|
|
$files_to_backup = implode(' ', array_map('escapeshellarg', array_filter($files)));
|
|
}
|
|
|
|
// Archive IP Aliastables/Unbound DNSBL Database as required.
|
|
if (!empty($files_to_backup)) {
|
|
exec("/usr/bin/tar -jcvf {$pfb['aliasarchive']} {$files_to_backup} >/dev/null 2>&1");
|
|
pfb_logger("\nArchiving selected pfBlockerNG files.\n", 1);
|
|
} else {
|
|
pfb_logger("\nNo Files to archive.\n", 1);
|
|
}
|
|
}
|
|
|
|
// Check conf file for earlyshellcmd/shellcmd package settings
|
|
elseif ($mode == 'conf') {
|
|
|
|
// Add earlyshellcmd settings
|
|
if (!empty($a_earlyshellcmd)) {
|
|
if (!preg_grep("/pfblockerng.sh aliastables/", $a_earlyshellcmd)) {
|
|
$a_earlyshellcmd[] = "{$earlyshellcmd}";
|
|
$msg = "\n** Adding earlyshellcmd settings **\n";
|
|
}
|
|
}
|
|
else {
|
|
$a_earlyshellcmd = "{$earlyshellcmd}";
|
|
$msg = "\n** Adding earlyshellcmd settings **\n";
|
|
}
|
|
|
|
// Add shellcmd package settings
|
|
$found = FALSE;
|
|
if (!empty($a_shellcmdsettings)) {
|
|
foreach ($a_shellcmdsettings as $key => $shellcmd) {
|
|
if (strpos($shellcmd['cmd'], 'pfblockerng.sh aliastables') !== FALSE) {
|
|
$found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$found) {
|
|
$add = array( 'cmd' => $earlyshellcmd,
|
|
'cmdtype' => 'earlyshellcmd',
|
|
'description' => 'pfBlockerNG earlyshellcmd. DO NOT EDIT/DELETE!');
|
|
|
|
$a_shellcmdsettings[] = $add;
|
|
$msg .= "\n** Adding shellcmd package settings **\n";
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Remove aliastables archive if found
|
|
if (file_exists("{$pfb['aliasarchive']}")) {
|
|
unlink_if_exists("{$pfb['aliasarchive']}");
|
|
}
|
|
|
|
// Remove earlyshellcmd settings
|
|
if (!empty($a_earlyshellcmd)) {
|
|
if (preg_grep("/pfblockerng.sh aliastables/", $a_earlyshellcmd)) {
|
|
$a_earlyshellcmd = preg_grep("/pfblockerng.sh aliastables/", $a_earlyshellcmd, PREG_GREP_INVERT);
|
|
$msg = "\n** Removing earlyshellcmd settings **\n";
|
|
}
|
|
}
|
|
|
|
// Remove shellcmd package settings
|
|
if (!empty($a_shellcmdsettings)) {
|
|
foreach ($a_shellcmdsettings as $key => $shellcmd) {
|
|
if (strpos($shellcmd['cmd'], 'pfblockerng.sh aliastables') !== FALSE) {
|
|
unset($a_shellcmdsettings[$key]);
|
|
$msg .= "\n** Removing shellcmd package settings**\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($msg)) {
|
|
pfb_logger("{$msg}", 1);
|
|
config_set_path('system/earlyshellcmd', $a_earlyshellcmd);
|
|
config_set_path('installedpackages/shellcmdsettings/config', $a_shellcmdsettings);
|
|
write_config('pfBlockerNG: saving earlyshellcmd');
|
|
}
|
|
}
|
|
|
|
|
|
// Collect pfBlockerNG rule names and Tracker IDs
|
|
function pfb_filterrules() {
|
|
global $pfb;
|
|
|
|
$rule_list = array();
|
|
$rule_list['id'] = array();
|
|
$rule_list['other'] = array();
|
|
$rule_list['int'] = array();
|
|
|
|
exec("{$pfb['pfctl']} -vvsr 2>&1", $results);
|
|
if (!empty($results)) {
|
|
foreach ($results as $result) {
|
|
if (substr($result, 0, 1) == '@') {
|
|
|
|
$type = strstr(ltrim(strstr($result, ' ', FALSE), ' '), ' ', TRUE);
|
|
if (in_array($type, array('block', 'pass', 'match'))) {
|
|
|
|
// Since pfSense CE 2.6 and pfSense Plus 22.01, pf rules use an 'ridentifier' string
|
|
if (strrpos($result, 'ridentifier') !== FALSE) {
|
|
$id_begin_delim = 'ridentifier ';
|
|
$id_end_delim = ' ';
|
|
} elseif (strpos($result, '(') !== FALSE && strpos($result, ')') !== FALSE) {
|
|
$id_begin_delim = '(';
|
|
$id_end_delim = ')';
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
// Get the rule ID
|
|
$id_begin_offset = strpos($result, $id_begin_delim) + strlen($id_begin_delim);
|
|
$id_end_offset = strpos($result, $id_end_delim, $id_begin_offset);
|
|
|
|
if ($id_end_offset !== FALSE) {
|
|
$id_length = $id_end_offset - $id_begin_offset;
|
|
} else {
|
|
$id_length = strlen($result) - $id_begin_offset;
|
|
}
|
|
$id = substr($result, $id_begin_offset, $id_length);
|
|
if (!is_numeric($id)) {
|
|
continue;
|
|
}
|
|
|
|
// Add the rule to the list
|
|
if (strpos($result, ' <pfB_') !== FALSE) {
|
|
$descr = ltrim(stristr($result, '<pfb_', FALSE), '<');
|
|
$descr = strstr($descr, ':', TRUE);
|
|
$type = strstr(ltrim(strstr($result, ' ', FALSE), ' '), ' ', TRUE);
|
|
if ($type == 'match') {
|
|
$type = 'unkn(%u)';
|
|
}
|
|
|
|
if (!is_array($rule_list[$id])) {
|
|
$rule_list[$id] = array();
|
|
}
|
|
|
|
$rule_list['id'][] = $id;
|
|
$rule_list[$id]['name'] = $descr;
|
|
$rule_list[$id]['type'] = $type;
|
|
|
|
$int = strstr(ltrim(strstr($result, ' on ', FALSE), ' on '), ' ', TRUE);
|
|
if (!empty($int)) {
|
|
$rule_list['int'][$int] = '';
|
|
}
|
|
}
|
|
else {
|
|
// All other non-pfBlockerNG Tracker IDs
|
|
$rule_list['other'][$id] = '';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $rule_list;
|
|
}
|
|
|
|
|
|
// Function to remove existing firewall states for IPs that are have been recently added to IP block/reject aliastables
|
|
function pfb_remove_states() {
|
|
global $pfb;
|
|
|
|
$log = "\n===[ Kill States ]==================================================\n";
|
|
pfb_logger("{$log}", 1);
|
|
|
|
$pfb_tables = array();
|
|
// Collect all 'pfB_' and 'pfb_' rules that are 'Block/Reject' and do not have bypass states enabled
|
|
foreach (config_get_path('aliases/alias', []) as $alias) {
|
|
if ($alias['type'] == 'urltable' && strpos($alias['name'], 'pfB_') !== FALSE && strpos($alias['descr'], '[s]') === FALSE) {
|
|
foreach (config_get_path('filter/rule', []) as $rule) {
|
|
if ($alias['name'] === $rule['source']['address'] || $alias['name'] === $rule['destination']['address']) {
|
|
|
|
if ($rule['type'] == 'block' ||
|
|
$rule['type'] == 'reject' ||
|
|
strpos($rule['descr'], '[ks]') !== FALSE) {
|
|
|
|
if (isset($rule['source']['address']) && !isset($rule['source']['not'])) {
|
|
$pfb_tables[] = $rule['source']['address'];
|
|
}
|
|
elseif (isset($rule['destination']['address']) && !isset($rule['destination']['not'])) {
|
|
$pfb_tables[] = $rule['destination']['address'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$pfb_tables = array_unique($pfb_tables);
|
|
|
|
// List of IPs to suppress
|
|
$pfb_local = $pfb_localsub = array();
|
|
|
|
// Collect IPv4 Suppression list IPs
|
|
$v4suppression = pfbng_text_area_decode($pfb['ipconfig']['v4suppression'], TRUE, FALSE);
|
|
foreach ($v4suppression as $line) {
|
|
if (strpos($line, '/32') != FALSE) {
|
|
$pfb_local[] = str_replace('/32', '', $line);
|
|
}
|
|
|
|
// Convert '/24' CIDRs
|
|
$pfb_suppcidr = array();
|
|
if (strpos($line, '/24') !== FALSE && is_ipaddrv4($line)) {
|
|
$pfb_suppcidr = subnetv4_expand($line);
|
|
$pfb_local = array_merge($pfb_local, $pfb_suppcidr);
|
|
}
|
|
}
|
|
|
|
if (!empty($pfb_local)) {
|
|
$pfb_local = array_flip($pfb_local);
|
|
}
|
|
|
|
// Collect Interface list that have pfB Rules assigned
|
|
$data = pfb_filterrules();
|
|
$pfb_int = $data['int'] ?: array();
|
|
|
|
// Collect local IPs
|
|
$data = pfb_collect_localip();
|
|
if (!empty($data[0])) {
|
|
$pfb_local = array_merge($pfb_local, $data[0]);
|
|
}
|
|
$pfb_localsub = $data[1] ?: array();
|
|
|
|
// Collect DNS servers to suppress
|
|
$pfb_dnsservers = get_dns_servers();
|
|
if (!empty($pfb_dnsservers)) {
|
|
$pfb_local = array_merge($pfb_local, $pfb_dnsservers);
|
|
}
|
|
|
|
// Remove any duplicate IPs
|
|
if (!empty($pfb_local)) {
|
|
$pfb_local = array_flip(array_unique($pfb_local));
|
|
}
|
|
|
|
// Collect any 'Permit' Customlist IPs to suppress
|
|
$custom_supp = array();
|
|
foreach (array('pfblockernglistsv4', 'pfblockernglistsv6') as $ip_type) {
|
|
foreach (config_get_path("installedpackages{$ip_type}/config", []) as $list) {
|
|
if (!empty($list['custom']) && strpos($list['action'], 'Permit_') !== FALSE) {
|
|
$custom = pfbng_text_area_decode($list['custom'], TRUE, FALSE);
|
|
$custom_supp = array_merge($custom_supp, $custom);
|
|
}
|
|
}
|
|
}
|
|
|
|
$custom_supp = array_unique(array_filter($custom_supp));
|
|
// Append '/32' CIDR as required
|
|
foreach ($custom_supp as &$custom) {
|
|
if (strpos($custom, '/') === FALSE) {
|
|
$custom = $custom . '/32';
|
|
}
|
|
}
|
|
|
|
// Collect firewall states and save to temp file
|
|
exec("{$pfb['pfctl']} -s state > {$pfb['states_tmp']} 2>&1");
|
|
|
|
$state_count = $all_states = 0;
|
|
$states = array();
|
|
$states[4] = array();
|
|
$states[6] = array();
|
|
|
|
if (($s_handle = @fopen("{$pfb['states_tmp']}", 'r')) !== FALSE) {
|
|
while (($sline = @fgets($s_handle)) !== FALSE) {
|
|
|
|
$all_states++;
|
|
|
|
// SAMPLE : em0 udp 93.15.36.22:6881 -> 192.168.0.3:681 MULTIPLE:MULTIPLE
|
|
// SAMPLE : pppoe0 udp 35.170.3.40:57197 (192.168.0.45:681) -> 22.41.123.206:1001 MULTIPLE:MULTIPLE
|
|
// SAMPLE : em0 tcp 2001:65c:1398:101:124[443] <- 2001:170:2f:3e:a4c4:7b23:fe5f:b36e[52725] FIN_WAIT_2:FIN_WAIT_2
|
|
|
|
if (!empty($sline)) {
|
|
|
|
$detail = array_filter(explode(' ', $sline));
|
|
|
|
// Validate states for pfB Interfaces only
|
|
if (!isset($pfb_int[$detail[0]])) {
|
|
continue;
|
|
}
|
|
|
|
$count = count($detail);
|
|
if ($count == 6) {
|
|
$orig_s_ip = $detail[2];
|
|
}
|
|
elseif ($count == 7) {
|
|
$orig_s_ip = $detail[5];
|
|
}
|
|
else {
|
|
continue; // Unknown state line
|
|
}
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
|
|
$ip_version = 4;
|
|
|
|
// Strip IPv6 port
|
|
if (strpos($orig_s_ip, '[') !== FALSE) {
|
|
list($s_ip, $s_port) = explode('[', $orig_s_ip);
|
|
$ip_version = 6;
|
|
}
|
|
|
|
// Strip IPv4 port
|
|
elseif (strpos($orig_s_ip, ':') !== FALSE && substr_count($orig_s_ip, ':') == 1) {
|
|
list($s_ip, $s_port) = explode(':', $orig_s_ip);
|
|
$ip_version = 4;
|
|
}
|
|
|
|
// No port listed
|
|
else {
|
|
$s_ip = $orig_s_ip;
|
|
$s_port = '';
|
|
|
|
if (is_ipaddrv6($s_ip)) {
|
|
$ip_version = 6;
|
|
}
|
|
}
|
|
|
|
// Exclude local and reserved IPs (Validate unique IPs only once)
|
|
if (!isset($states[$ip_version][$s_ip])) {
|
|
|
|
if ($ip_version == 4) {
|
|
if (isset($pfb_local[$s_ip]) ||
|
|
pfb_local_ip($s_ip, $pfb_localsub) ||
|
|
is_private_ip($s_ip) ||
|
|
substr($s_ip, 0, 2) == '0.' ||
|
|
substr($s_ip, 0, 4) == '127.' ||
|
|
substr($s_ip, 0, 3) >= 224) {
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
if (isset($pfb_local[$s_ip]) ||
|
|
pfb_local_ip($s_ip, $pfb_localsub) ||
|
|
!filter_var($s_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) ||
|
|
!filter_var($s_ip, FILTER_CALLBACK, array('options' => 'FILTER_FLAG_NO_LOOPBACK_RANGE'))) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Exclude any 'Permit' Customlist IPs
|
|
foreach ($custom_supp as $custom) {
|
|
if (ip_in_subnet($s_ip, $custom)) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
$state = rtrim($sline, "\x00..\x1F");
|
|
$state_count++;
|
|
|
|
// Collect IP for state removal verification
|
|
if (!is_array($states[$ip_version][$s_ip])) {
|
|
$states[$ip_version][$s_ip] = array();
|
|
}
|
|
|
|
if (!in_array($state, $states[$ip_version][$s_ip])) {
|
|
$states[$ip_version][$s_ip][] = $state;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
pfb_logger("\n No Firewall States found", 1);
|
|
}
|
|
|
|
if ($s_handle) {
|
|
@fclose($s_handle);
|
|
}
|
|
unlink_if_exists("{$pfb['states_tmp']}");
|
|
unset($pfb_local, $pfb_localsub, $custom_supp);
|
|
|
|
$pfbfound = FALSE;
|
|
foreach ($states as $ip_version => $details) {
|
|
|
|
if (!empty($details)) {
|
|
$log = "\nFirewall state(s) validation for [ " . count($details) . " ] IPv{$ip_version} address(es)...";
|
|
pfb_logger("{$log}", 1);
|
|
ksort($details, SORT_NATURAL);
|
|
}
|
|
|
|
foreach ($details as $s_ip => $state) {
|
|
foreach ($pfb_tables as $s_table) {
|
|
|
|
// Compare IP version and aliastable type
|
|
if ($ip_version == 4 && strpos($s_table, '_v4') !== FALSE ||
|
|
$ip_version == 6 && strpos($s_table, '_v6') !== FALSE) {
|
|
|
|
$s_table_esc = escapeshellarg($s_table);
|
|
$s_ip_esc = escapeshellarg($s_ip);
|
|
$result = substr(exec("{$pfb['pfctl']} -t {$s_table_esc} -T test {$s_ip_esc} 2>&1"), 0, 1);
|
|
if ($result > 0) {
|
|
|
|
$pfbfound = TRUE;
|
|
$log = "\n\n\t[ {$s_table} ] Removed " . count($state) . " state(s) for [ {$s_ip} ]\n\n";
|
|
pfb_logger("{$log}", 1);
|
|
|
|
foreach ($state as $line) {
|
|
pfb_logger("\t\t{$line}\n", 1);
|
|
}
|
|
|
|
// Kill all state entries originating from $s_ip
|
|
exec("{$pfb['pfctl']} -k {$s_ip_esc} 2>&1");
|
|
|
|
// Kill all state entries to the target $s_ip
|
|
exec("{$pfb['pfctl']} -k 0.0.0.0/0 -k {$s_ip_esc} 2>&1");
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
unset($states, $pfb_tables);
|
|
|
|
if ($pfbfound) {
|
|
pfb_logger("\n======================================================================\n", 1);
|
|
} else {
|
|
pfb_logger("\nNo matching states found\n\n======================================================================\n", 1);
|
|
}
|
|
}
|
|
|
|
|
|
// For subnet addresses - Determine if alert host 'dest' is within a local IP range.
|
|
function pfb_local_ip($subnet, $pfb_localsub) {
|
|
|
|
if (!empty($pfb_localsub)) {
|
|
foreach ($pfb_localsub as $line) {
|
|
if (ip_in_subnet($subnet, $line)) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// Collect local IP addresses
|
|
function pfb_collect_localip() {
|
|
$pfb_local = $pfb_localsub = array();
|
|
|
|
// Collect gateway IP addresses for inbound/outbound list matching
|
|
$int_gateway = get_interfaces_with_gateway();
|
|
if (isset($int_gateway)) {
|
|
foreach ($int_gateway as $gateway) {
|
|
$pfb_local[] = get_interface_ip($gateway) ?: 'Disabled';
|
|
}
|
|
}
|
|
|
|
// Collect virtual IP aliases for inbound/outbound list matching
|
|
foreach (config_get_path('virtualip/vip', []) as $list) {
|
|
if (!empty($list['subnet']) && !empty($list['subnet_bits']) && is_subnet("{$list['subnet']}/{$list['subnet_bits']}")) {
|
|
if (is_ipaddrv4($list['subnet'])) {
|
|
if ($list['subnet_bits'] >= 24) {
|
|
$pfb_local = array_merge(subnetv4_expand("{$list['subnet']}/{$list['subnet_bits']}"), $pfb_local);
|
|
} else {
|
|
$pfb_localsub[] = "{$list['subnet']}/{$list['subnet_bits']}";
|
|
}
|
|
}
|
|
elseif (is_ipaddrv6($list['subnet'])) {
|
|
$pfb_localsub[] = gen_subnetv6("{$list['subnet']}", "{$list['subnet_bits']}");
|
|
}
|
|
}
|
|
elseif (is_ipaddr($list['subnet'])) {
|
|
$pfb_local[] = "{$list['subnet']}";
|
|
}
|
|
}
|
|
|
|
// Collect NAT IP addresses for inbound/outbound list matching
|
|
foreach (config_get_path('nat/rule', []) as $natent) {
|
|
$pfb_local[] = $natent['target'];
|
|
}
|
|
|
|
// Collect 1:1 NAT IP addresses for inbound/outbound list matching
|
|
foreach (config_get_path('nat/onetoone', []) as $onetoone) {
|
|
$pfb_local[] = $onetoone['source']['address'];
|
|
}
|
|
|
|
// Convert any 'Firewall Aliases' to IP address format
|
|
$aliases = config_get_path('aliases/alias', []);
|
|
if (is_array($aliases)) {
|
|
for ($cnt = 0; $cnt <= count($pfb_local); $cnt++) {
|
|
foreach ($aliases as $i=> $alias) {
|
|
if (isset($alias['name']) && isset($pfb_local[$cnt])) {
|
|
if ($alias['name'] == $pfb_local[$cnt]) {
|
|
$pfb_local[$cnt] = $alias['address'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collect all interface addresses for inbound/outbound list matching
|
|
foreach (config_get_path('interfaces', []) as $int) {
|
|
if ($int['ipaddr'] != 'dhcp') {
|
|
if (!empty($int['ipaddr']) && !empty($int['subnet']) && is_subnet("{$int['ipaddr']}/{$int['subnet']}")) {
|
|
if (is_ipaddrv4($int['ipaddr'])) {
|
|
if ($int['subnet'] >= 24) {
|
|
$pfb_local = array_merge(subnetv4_expand("{$int['ipaddr']}/{$int['subnet']}"), $pfb_local);
|
|
} else {
|
|
$pfb_localsub[] = "{$int['ipaddr']}/{$int['subnet']}";
|
|
}
|
|
}
|
|
elseif (is_ipaddrv6($int['ipaddr'])) {
|
|
$pfb_localsub[] = gen_subnetv6("{$int['ipaddr']}", "{$int['subnet']}");
|
|
}
|
|
}
|
|
elseif (is_ipaddr($int['ipaddr'])) {
|
|
$pfb_local[] = "{$int['ipaddr']}";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove any duplicate IPs
|
|
if (!empty($pfb_local)) {
|
|
$pfb_local = array_flip(array_filter(array_unique($pfb_local)));
|
|
}
|
|
$pfb_localsub = array_unique($pfb_localsub);
|
|
|
|
return array($pfb_local, $pfb_localsub);
|
|
}
|
|
|
|
|
|
// Collect local hostnames
|
|
function pfb_collect_localhosts() {
|
|
global $g, $pfb;
|
|
|
|
// Collect DHCP hostnames/IPs
|
|
$local_hosts = array();
|
|
|
|
// Collect configured pfSense interfaces
|
|
$pf_int = get_configured_ip_addresses();
|
|
if (isset($pf_int)) {
|
|
$local_hosts = array_merge($local_hosts, array_flip(array_filter($pf_int)));
|
|
}
|
|
$pf_int = get_configured_ipv6_addresses();
|
|
if (isset($pf_int)) {
|
|
$local_hosts = array_merge($local_hosts, array_flip(array_filter($pf_int)));
|
|
}
|
|
|
|
// Collect dynamic DHCP hostnames/IPs
|
|
$leasesfile = "{$g['dhcpd_chroot_path']}/var/db/dhcpd.leases";
|
|
|
|
$end = $hostname = '';
|
|
if (file_exists("{$leasesfile}")) {
|
|
if (($l_handle = @fopen("{$leasesfile}", 'r')) !== FALSE) {
|
|
while (($line = @fgets($l_handle)) !== FALSE) {
|
|
if (strpos($line, '{') !== FALSE) {
|
|
$end = FALSE;
|
|
$data = explode(' ', $line);
|
|
$ip = $data[1];
|
|
}
|
|
if (strpos($line, 'client-hostname') !== FALSE) {
|
|
$data = explode(' ', $line);
|
|
$hostname = trim(str_replace(array('"', ';'), '', $data[3]));
|
|
}
|
|
if (strpos($line, '}') !== FALSE) {
|
|
$end = TRUE;
|
|
}
|
|
if ($end) {
|
|
if (!empty($ip)) {
|
|
$ip = pfb_filter($ip, PFB_FILTER_IP, 'Collect dynamic DHCP hostnames');
|
|
}
|
|
|
|
if (!empty($hostname)) {
|
|
$hostname = pfb_filter($hostname, PFB_FILTER_HOSTNAME, 'Collect dynamic DHCP hostnames');
|
|
}
|
|
|
|
if (!empty($ip) && !empty($hostname)) {
|
|
$local_hosts[$ip] = $hostname;
|
|
}
|
|
$ip = $hostname = '';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collect static DHCP hostnames/IPs
|
|
foreach (config_get_path('dhcpd', []) as $dhcp) {
|
|
if (isset($dhcp['staticmap']) && is_array($dhcp['staticmap'])) {
|
|
foreach ($dhcp['staticmap'] as $smap) {
|
|
$local_hosts[$smap['ipaddr']] = strtolower("{$smap['hostname']}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collect static DHCPv6 hostnames/IPs
|
|
foreach (config_get_path('dhcpdv6', []) as $dhcpv6) {
|
|
if (isset($dhcpv6['staticmap']) && is_array($dhcpv6['staticmap'])) {
|
|
foreach ($dhcpv6['staticmap'] as $smap) {
|
|
$local_hosts[$smap['ipaddrv6']] = strtolower("{$smap['hostname']}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collect Unbound Host overrides
|
|
$hosts = config_get_path('unbound/hosts', []);
|
|
foreach ($hosts as $host) {
|
|
$local_hosts[$host['ip']] = strtolower("{$host['descr']}");
|
|
}
|
|
|
|
// Collect NAT IP addresses by Target:Port
|
|
foreach (config_get_path('nat/rule', []) as $natent) {
|
|
$local_hosts["{$natent['target']}:{$natent['local-port']}"] = strtolower("{$natent['descr']}");
|
|
}
|
|
|
|
// Collect virtual IP aliases
|
|
foreach (config_get_path('virtualip/vip', []) as $list) {
|
|
if (!empty($list['subnet']) && !empty($list['subnet_bits'])) {
|
|
|
|
// Use pfSense hostname for DNSBL vip
|
|
if ($list['subnet'] == $pfb['dnsbl_vip']) {
|
|
$list['descr'] = config_get_path('system/hostname', 'pfSense') . '.' . (config_get_path('system/domain', 'localdomain'));
|
|
}
|
|
$local_hosts[$list['subnet']] = strtolower("{$list['descr']}");
|
|
}
|
|
}
|
|
|
|
// Add localhost hostname
|
|
if (!isset($local_hosts['127.0.0.1'])) {
|
|
$local_hosts['127.0.0.1'] = strtolower((config_get_path('system/hostname', 'pfSense')) . '.' . (config_get_path('system/domain', 'localdomain')));
|
|
}
|
|
|
|
return $local_hosts;
|
|
}
|
|
|
|
|
|
// Firewall filter.log parser daemon
|
|
function pfb_daemon_filterlog() {
|
|
global $pfb;
|
|
|
|
// ASN Reporting - cached setting
|
|
if ($pfb['asn_reporting'] != 'disabled') {
|
|
switch ($pfb['asn_reporting']) {
|
|
case '1hour':
|
|
$asn_cache = '-1 hour';
|
|
break;
|
|
case '4hour':
|
|
$asn_cache = '-4 hours';
|
|
break;
|
|
case '12hour':
|
|
$asn_cache = '-12 hours';
|
|
break;
|
|
case '24hour':
|
|
$asn_cache = '-24 hours';
|
|
break;
|
|
case 'week':
|
|
$asn_cache = '-1 week';
|
|
break;
|
|
default:
|
|
$asn_cache = '-24 hours';
|
|
}
|
|
}
|
|
|
|
// Application paths
|
|
if (file_exists('/usr/local/bin/mmdblookup') && file_exists("{$pfb['geoipshare']}/GeoLite2-Country.mmdb")) {
|
|
$pathgeoip = "/usr/local/bin/mmdblookup -f {$pfb['geoipshare']}/GeoLite2-Country.mmdb -i";
|
|
} else {
|
|
$pathgeoip = '';
|
|
}
|
|
|
|
// Proofpoint ET IQRisk header name reference
|
|
$et_header = config_get_path('installedpackages/pfblockerngreputation/config/0/et_header', '');
|
|
$et_enabled = TRUE;
|
|
if (empty($et_header)) {
|
|
$et_enabled = FALSE;
|
|
}
|
|
|
|
$p_entry = '';
|
|
$line_cnt = 0;
|
|
$rule_list = $rule_list['other'] = array();
|
|
|
|
if (file_exists('/usr/local/sbin/clog_pfb')) {
|
|
|
|
// Parse full filter.log on first run, otherwise only parse new filter.log events
|
|
if (!file_exists($pfb['ip_blocklog']) && !file_exists($pfb['ip_permitlog']) && !file_exists($pfb['ip_matchlog'])) {
|
|
$filter_cnt = 0;
|
|
} else {
|
|
$filter_cnt = max( exec("{$pfb['grep']} -c ^ /var/log/filter.log 2>&1") -1, 0) ?: 0;
|
|
}
|
|
$skip_cnt = $filter_cnt;
|
|
}
|
|
else {
|
|
$skip_cnt = $filter_cnt = 0;
|
|
}
|
|
|
|
/* filter.log reference: URL: https://docs.netgate.com/pfsense/en/latest/monitoring/logs/raw-filter-format.html
|
|
|
|
$line -> $f
|
|
|
|
[ BSD format ] [ syslog format ]
|
|
[0][1][2] [1] = Date/timestamp
|
|
[5] [7] = Event Details
|
|
|
|
$f[5] -> $d
|
|
|
|
[0] = Rule number
|
|
[1] = Sub-rule number
|
|
[2] = Anchor
|
|
[3] = Tracker ID
|
|
[4] = Real Interface
|
|
[5] = Reason
|
|
[6] = Action
|
|
[7] = Direction
|
|
[8] = IP version
|
|
[9] = IP Specific data
|
|
|
|
IPv4 IPv6
|
|
[10] [] =
|
|
[11] [] =
|
|
[12] [] =
|
|
[13] [] =
|
|
[14] [] =
|
|
[15] [13] = Protocol ID
|
|
[16] [12] = Protocol
|
|
[17] [] =
|
|
[18] [15] = SRC IP
|
|
[19] [16] = DST IP
|
|
[20] [17] = SRC Port
|
|
[21] [18] = DST Port
|
|
[22] [] =
|
|
[23] [20] = TCP Protocol Flags
|
|
|
|
Final output reference:
|
|
[0] = Date/Timestamp
|
|
[1] = Rulenum
|
|
[2] = Real Interface
|
|
[3] = Friendly Interface name
|
|
[4] = Action
|
|
[5] = Version
|
|
[6] = Protocol ID
|
|
[7] = Protocol
|
|
[8] = SRC IP
|
|
[9] = DST IP
|
|
[10] = SRC Port
|
|
[11] = DST Port
|
|
[12] = Direction
|
|
[13] = GeoIP code
|
|
[14] = IP Alias Name
|
|
[15] = IP evaluated
|
|
[16] = Feed Name
|
|
[17] = gethostbyaddr resolved hostname
|
|
[18] = Client Hostname
|
|
[19] = Duplicate ID indicator */
|
|
|
|
// Disable pfctl Tracker ID lookup on too many lookup failures
|
|
$max_retries = 0;
|
|
|
|
if (($s_handle = @fopen('php://stdin', 'r')) !== FALSE) {
|
|
syslog(LOG_NOTICE, '[pfBlockerNG] filterlog daemon started');
|
|
while (!feof($s_handle)) {
|
|
$line = @fgets($s_handle);
|
|
|
|
// Only parse new filter events
|
|
if ($filter_cnt > 0 && $skip_cnt > 0) {
|
|
$skip_cnt--;
|
|
continue;
|
|
}
|
|
|
|
$log_type = 'BSD';
|
|
$f_pos = 5;
|
|
if (substr($line, 0, 1) == '<') {
|
|
$log_type = 'syslog';
|
|
$f_pos = 7;
|
|
}
|
|
|
|
// Remove any '^M' characters
|
|
$line = htmlspecialchars(rtrim($line, "\x00..\x1F"));
|
|
|
|
$f = explode(' ', $line);
|
|
|
|
// Remove double space for single date entry nuance
|
|
if ($log_type == 'BSD' && empty($f[1])) {
|
|
array_splice($f, 1, 1);
|
|
}
|
|
$d = explode(',', $f[$f_pos]);
|
|
|
|
// Attempt to find the Tracker ID, if not found wait 5secs for filter_reload
|
|
$other = FALSE;
|
|
if (!empty($d[3])) {
|
|
for ($retries = 1; $retries <= 5; $retries++) {
|
|
|
|
// Skip known non-pfBlockerNG Tracker IDs
|
|
if (isset($rule_list['other'][$d[3]])) {
|
|
$other = TRUE;
|
|
break;
|
|
}
|
|
|
|
// Break on pfBlockerNG rule Tracker ID and Rule type (block|permit|match)
|
|
// If the user switched a manual rule from one type to another, the Tracker ID will stay the same
|
|
// So this comparison will refresh the data on changes
|
|
if (isset($rule_list[$d[3]]) && $rule_list[$d[3]]['type'] == $d[6]) {
|
|
break;
|
|
}
|
|
|
|
// Collect updated pfctl Rule data, Host and local IP data
|
|
else {
|
|
$rule_list = pfb_filterrules();
|
|
$data = pfb_collect_localip();
|
|
$pfb_local = $data[0] ?: array();
|
|
$pfb_localsub = $data[1] ?: array();
|
|
|
|
$local_hosts = pfb_collect_localhosts();
|
|
}
|
|
|
|
if ($retries < 5 && $max_retries < 30) {
|
|
$max_retries++;
|
|
sleep(5);
|
|
} else {
|
|
$other = TRUE;
|
|
}
|
|
}
|
|
|
|
if ($other) {
|
|
continue;
|
|
}
|
|
|
|
// Duplicate entry comparison: "Tracker ID/Action/SRC IP/DST IP/DST Port"
|
|
$dup_entry = '+';
|
|
if ($d[8] == 4 && "{$d[3]}{$d[6]}{$d[18]}{$d[19]}{$d[21]}" == $p_entry) {
|
|
$dup_entry = '-';
|
|
} elseif ($d[8] == 6 && "{$d[3]}{$d[6]}{$d[15]}{$d[16]}{$d[18]}" == $p_entry) {
|
|
$dup_entry = '-';
|
|
}
|
|
|
|
if ($dup_entry == '+') {
|
|
|
|
$int = convert_real_interface_to_friendly_descr($d[4]);
|
|
$pfb_alias = pfb_filter($rule_list[$d[3]]['name'], PFB_FILTER_WORD, 'pfb_daemon_filterlog', 'Unknown');
|
|
|
|
// Action setting variables
|
|
if ($d[6] == 'block') {
|
|
$folder = "{$pfb['denydir']}/* {$pfb['nativedir']}/*";
|
|
$iplog = "{$pfb['ip_blocklog']}";
|
|
$l_type = 'Block';
|
|
}
|
|
elseif ($d[6] == 'pass') {
|
|
$folder = "{$pfb['permitdir']}/* {$pfb['nativedir']}/*";
|
|
$iplog = "{$pfb['ip_permitlog']}";
|
|
$l_type = 'Permit';
|
|
}
|
|
elseif ($d[6] == 'unkn(%u)') {
|
|
$d[6] = 'match';
|
|
$folder = "{$pfb['matchdir']}/* {$pfb['nativedir']}/*";
|
|
$iplog = "{$pfb['ip_matchlog']}";
|
|
$l_type = 'Match';
|
|
}
|
|
|
|
if ($d[8] == 4) {
|
|
$srcip = $d[18] ?: 'Unknown';
|
|
$dstip = $d[19] ?: 'Unknown';
|
|
$tcp_flags = $d[16] == 'tcp' ? $d[23] : ''; // Keep protocol flags for TCP only
|
|
} else {
|
|
$srcip = $d[15] ?: 'Unknown';
|
|
$dstip = $d[16] ?: 'Unknown';
|
|
$tcp_flags = $d[12] == 'tcp' ? $d[20] : '';
|
|
}
|
|
|
|
// Determine if DST IP or SRC IP is the external host
|
|
if (isset($pfb_local[$dstip]) || pfb_local_ip($dstip, $pfb_localsub)) {
|
|
$dir = 'in';
|
|
$host = $srcip;
|
|
$client = $dstip;
|
|
$port = $d[21];
|
|
} else {
|
|
$dir = 'out';
|
|
$host = $dstip;
|
|
$client = $srcip;
|
|
$port = $d[20];
|
|
}
|
|
|
|
if (!is_ipaddr($host)) {
|
|
continue;
|
|
}
|
|
if (!is_ipaddr($client)) {
|
|
continue;
|
|
}
|
|
if (!is_port($port)) {
|
|
$port = '';
|
|
}
|
|
|
|
switch (TRUE) {
|
|
case isset($local_hosts["{$client}:{$port}"]) && !empty($local_hosts["{$client}:{$port}"]):
|
|
$hostname = $local_hosts["{$client}:{$port}"];
|
|
break;
|
|
case isset($local_hosts[$client]) && !empty($local_hosts[$client]):
|
|
$hostname = $local_hosts[$client];
|
|
break;
|
|
default:
|
|
$hostname = 'Unknown';
|
|
}
|
|
|
|
$ip_cache = FALSE;
|
|
$db_handle = pfb_open_sqlite(7, 'Query ip cache');
|
|
if ($db_handle) {
|
|
$db_update = "SELECT * FROM ipcache WHERE host = :host;";
|
|
$stmt = $db_handle->prepare($db_update);
|
|
if ($stmt) {
|
|
$stmt->bindValue(':host', $host, SQLITE3_TEXT);
|
|
$result = $stmt->execute();
|
|
if ($result) {
|
|
$ip_cache = $result->fetchArray(SQLITE3_ASSOC);
|
|
}
|
|
}
|
|
}
|
|
pfb_close_sqlite($db_handle);
|
|
|
|
if (!$ip_cache) {
|
|
|
|
// Find the header which alerted this host
|
|
$geoip_folder = FALSE;
|
|
$geoip_validate = substr($pfb_alias, 0, -3);
|
|
if (isset($pfb['continent_list'][$geoip_validate])) {
|
|
$geoip_folder = TRUE;
|
|
}
|
|
$pfb_query = find_reported_header($host, $folder, $geoip_folder);
|
|
|
|
// Report specific ET IQRisk details
|
|
if ($et_enabled && strpos($pfb_query[0], "{$et_header}") !== FALSE) {
|
|
$ET_orig = $pfb_query;
|
|
$pfb_query = find_reported_header($host, "{$pfb['etdir']}/*", FALSE);
|
|
|
|
// ET IQRisk category is unknown.
|
|
if ($pfb_query[1] == 'Unknown') {
|
|
$pfb_query = $ET_orig;
|
|
}
|
|
else {
|
|
// Prepend ET Header name
|
|
$pfb_query[0] = "{$et_header}:{$pfb_query[0]}";
|
|
}
|
|
}
|
|
|
|
// Determine GeoIP isocode of host
|
|
if (!empty($pathgeoip)) {
|
|
$geoip = exec("{$pathgeoip} " . escapeshellarg($host) . " country iso_code | grep -v '^\$' | cut -d '\"' -f2 2>&1");
|
|
$geoip = pfb_filter($geoip, PFB_FILTER_ALPHA, 'pfb_daemon_filterlog', 'Unk');
|
|
}
|
|
else {
|
|
$geoip = 'Unk';
|
|
}
|
|
|
|
$resolved_host = gethostbyaddr($host) ?: 'Unknown';
|
|
if ($host == $resolved_host || $resolved_host == 'Unknown') {
|
|
$resolved_host = 'Unknown';
|
|
}
|
|
|
|
// Save entry to IP cache
|
|
$db_update = "INSERT into ipcache ( host, q0, q1, geoip, resolved_host ) VALUES ( :host, :q0, :q1, :geoip, :resolved_host )";
|
|
$db_handle = pfb_open_sqlite(7, 'Add to IP cache');
|
|
if ($db_handle) {
|
|
$pfb_host = pfb_filter($host, PFB_FILTER_HTML, 'pfb_daemon_filterlog');
|
|
$pfb_query[0] = pfb_filter($pfb_query[0], PFB_FILTER_HTML, 'pfb_daemon_filterlog');
|
|
$pfb_query[1] = pfb_filter($pfb_query[1], PFB_FILTER_HTML, 'pfb_daemon_filterlog');
|
|
$geoip = pfb_filter($geoip, PFB_FILTER_HTML, 'pfb_daemon_filterlog');
|
|
$resolved_host = pfb_filter($resolved_host, PFB_FILTER_HOSTNAME, 'pfb_daemon_filterlog', 'Unknown');
|
|
|
|
$stmt = $db_handle->prepare($db_update);
|
|
if ($stmt) {
|
|
$stmt->bindValue(':host', $host, SQLITE3_TEXT);
|
|
$stmt->bindValue(':q0', $pfb_query[0], SQLITE3_TEXT);
|
|
$stmt->bindValue(':q1', $pfb_query[1], SQLITE3_TEXT);
|
|
$stmt->bindValue(':geoip', $geoip, SQLITE3_TEXT);
|
|
$stmt->bindValue(':resolved_host', $resolved_host, SQLITE3_TEXT);
|
|
$stmt->execute();
|
|
}
|
|
}
|
|
pfb_close_sqlite($db_handle);
|
|
}
|
|
|
|
// Use cached entries
|
|
else {
|
|
$host = htmlspecialchars($ip_cache['host']) ?: 'Unknown';
|
|
$pfb_query = array();
|
|
$pfb_query[0] = htmlspecialchars($ip_cache['q0']) ?: 'Unknown';
|
|
$pfb_query[1] = htmlspecialchars($ip_cache['q1']) ?: 'Unknown';
|
|
$geoip = htmlspecialchars($ip_cache['geoip']) ?: 'Unknown';
|
|
$resolved_host = htmlspecialchars($ip_cache['resolved_host']) ?: 'Unknown';
|
|
}
|
|
|
|
// Modify host for ASN query
|
|
if ($d[8] == 4) {
|
|
// For ASN, query for IP Network address (x.x.x.0)
|
|
$ix = ip_explode($host);
|
|
$ip = "{$ix['6']}0";
|
|
} else {
|
|
// For ASN, query for IP Network address with /48 subnet
|
|
$ip = gen_subnetv6($host, 48);
|
|
}
|
|
$ip = pfb_filter($ip, PFB_FILTER_IP, 'pfb_daemon_filterlog');
|
|
|
|
// Determine ASN number of host
|
|
$asn = 'null';
|
|
if (($pfb['asn_reporting'] != 'disabled' || !empty($pfb['asn_token'])) && !empty($ip)) {
|
|
|
|
$is_asn_cache = FALSE;
|
|
$db_handle = pfb_open_sqlite(5, 'Query ASN cache');
|
|
if ($db_handle) {
|
|
|
|
// Clear cached entries older than defined cache setting
|
|
$db_update = "DELETE FROM asncache WHERE timestamp <= datetime('now', 'localtime', :asn_cache)";
|
|
$stmt = $db_handle->prepare($db_update);
|
|
if ($stmt) {
|
|
$stmt->bindValue(':asn_cache', $asn_cache, SQLITE3_TEXT);
|
|
$stmt->execute();
|
|
}
|
|
|
|
// Query for cached Host IP
|
|
$db_update = "SELECT * FROM asncache WHERE host = :host;";
|
|
$stmt = $db_handle->prepare($db_update);
|
|
if ($stmt) {
|
|
$stmt->bindValue(':host', $ip, SQLITE3_TEXT);
|
|
$result = $stmt->execute();
|
|
if ($result) {
|
|
$is_asn_cache = $result->fetchArray(SQLITE3_ASSOC);
|
|
}
|
|
}
|
|
}
|
|
pfb_close_sqlite($db_handle);
|
|
|
|
// If Host IP is not in ASN cache, collect ASN from ASN database
|
|
if (!$is_asn_cache) {
|
|
$asn = exec("{$pfb['script']} iptoasn " . escapeshellarg($ip) . " 2>&1");
|
|
|
|
// Convert any IDN to ASCII
|
|
$asn_final = '';
|
|
if (!ctype_print($asn)) {
|
|
if (strpos($asn, '|') !== FALSE) {
|
|
$asn_ex = explode('|', $asn);
|
|
if (is_array($asn_ex) && !empty($asn_ex)) {
|
|
foreach ($asn_ex as $asn_element) {
|
|
if (strpos($asn_element, ':') !== FALSE) {
|
|
$asn_element_ex = explode(':', $asn_element);
|
|
if (is_array($asn_element_ex) && !empty($asn_element_ex)) {
|
|
if (!ctype_print($asn_element_ex[1])) {
|
|
$asn_element_ex[1] = mb_convert_encoding($asn_element_ex[1], 'UTF-8',
|
|
mb_detect_encoding($asn_element_ex[1], 'UTF-8, ASCII, ISO-8859-1'));
|
|
$asn_element_ex[1] = idn_to_ascii($asn_element_ex[1]);
|
|
}
|
|
$asn_final .= "{$asn_element_ex[0]}:{$asn_element_ex[1]}|";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$asn_final = $asn;
|
|
}
|
|
$asn = pfb_filter($asn_final, PFB_FILTER_HTML, 'pfb_daemon_filterlog - asn');
|
|
|
|
if (empty($asn) || strpos($asn, 'ASN:') === FALSE) {
|
|
$asn = 'Unknown';
|
|
}
|
|
|
|
// Save entry to ASN cache
|
|
else {
|
|
$db_update = "INSERT into asncache ( asn, host, timestamp ) VALUES ( :asn, :host, datetime('now', 'localtime'))";
|
|
$db_handle = pfb_open_sqlite(5, 'Add to ASN cache');
|
|
if ($db_handle) {
|
|
$stmt = $db_handle->prepare($db_update);
|
|
if ($stmt) {
|
|
$asn = "|{$asn}";
|
|
$stmt->bindValue(':asn', $asn, SQLITE3_TEXT);
|
|
$stmt->bindValue(':host', $ip, SQLITE3_TEXT);
|
|
$stmt->execute();
|
|
}
|
|
}
|
|
pfb_close_sqlite($db_handle);
|
|
}
|
|
}
|
|
|
|
// Use cached ASN Entry
|
|
else {
|
|
$asn = htmlspecialchars($is_asn_cache['asn']) ?: 'Unknown';
|
|
}
|
|
}
|
|
|
|
// Log: "Date timestamp/Tracker ID/Interface/Interface Name/Action/IP Version"
|
|
if ($log_type == 'BSD') {
|
|
$log = "{$f[0]} {$f[1]} {$f[2]},{$d[3]},{$d[4]},{$int},{$d[6]},{$d[8]},";
|
|
} else {
|
|
$ts = date('M j H:i:s', strtotime($f[1]));
|
|
$log = "{$ts},{$d[3]},{$d[4]},{$int},{$d[6]},{$d[8]},";
|
|
}
|
|
|
|
// Details: "Direction/GeoIP/Aliasname/IP evaluated/Feed Name/Resolved Hostname/Client Hostname/ASN/Duplicate status"
|
|
$details = "{$dir},{$geoip},{$pfb_alias},{$pfb_query[1]},{$pfb_query[0]},{$resolved_host},{$hostname},{$asn}";
|
|
|
|
// Reverse Match text
|
|
if ($d[6] == 'match') {
|
|
$d[6] = 'unkn(%u)';
|
|
}
|
|
|
|
if ($d[8] == 4) {
|
|
// Previous: "Tracker ID/Action/SRC IP/DST IP/DST Port"
|
|
$p_entry = "{$d[3]}{$d[6]}{$d[18]}{$d[19]}{$d[21]}";
|
|
$d[16] = str_replace('TCP', 'TCP-', strtoupper($d[16]), $d[16]) . $tcp_flags;
|
|
|
|
// Log: "Protocol ID/Protocol/SRC IP/DST IP/SRC Port/DST Port"
|
|
$log .= "{$d[15]},{$d[16]},{$d[18]},{$d[19]},{$d[20]},{$d[21]}";
|
|
}
|
|
else {
|
|
$p_entry = "{$d[3]}{$d[6]}{$d[15]}{$d[16]}{$d[18]}";
|
|
$d[12] = str_replace('TCP', 'TCP-', strtoupper($d[12]), $d[12]) . $tcp_flags;
|
|
$log .= "{$d[13]},{$d[12]},{$d[15]},{$d[16]},{$d[17]},{$d[18]}";
|
|
}
|
|
}
|
|
@file_put_contents("{$iplog}", "{$log},{$details},{$dup_entry}\n", FILE_APPEND | LOCK_EX);
|
|
|
|
// Write to Unified Log
|
|
if ($dup_entry == '+') {
|
|
@file_put_contents("{$pfb['unilog']}", "{$l_type},{$log},{$details},{$dup_entry}\n", FILE_APPEND | LOCK_EX);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
log_error('[pfBlockerNG] filterlog - Failed to read STDIN');
|
|
}
|
|
if ($s_handle) {
|
|
@fclose($s_handle);
|
|
}
|
|
}
|
|
|
|
|
|
// Function to parse grep output and return Aliasname and IP fields
|
|
function pfb_parse_query($line) {
|
|
$rx = explode('.txt:', $line);
|
|
$rx[0] = ltrim(strrchr($rx[0], '/'), '/');
|
|
return $rx;
|
|
}
|
|
|
|
|
|
// Function to output Alias/Feed name string
|
|
function pfb_parse_line($line) {
|
|
$match = strstr($line, ':local', TRUE);
|
|
if (strpos($match, '.txt') !== FALSE) {
|
|
$match = strstr($match, '.txt', TRUE);
|
|
}
|
|
$match = substr($match, strrpos($match, '/') + 1);
|
|
return $match;
|
|
}
|
|
|
|
|
|
// Functon to find which DNSBL Feed/Groupname blocked this event
|
|
function pfb_dnsbl_parse($mode='daemon', $domain, $src_ip, $req_agent) {
|
|
global $pfb;
|
|
|
|
$dnsbl_cache = FALSE;
|
|
$db_handle = pfb_open_sqlite(4, 'Query cache');
|
|
if ($db_handle) {
|
|
$db_update = "SELECT * FROM dnsblcache WHERE domain = :domain;";
|
|
$stmt = $db_handle->prepare($db_update);
|
|
if ($stmt) {
|
|
$stmt->bindValue(':domain', $domain, SQLITE3_TEXT);
|
|
$result = $stmt->execute();
|
|
if ($result) {
|
|
$dnsbl_cache = $result->fetchArray(SQLITE3_ASSOC);
|
|
}
|
|
}
|
|
}
|
|
pfb_close_sqlite($db_handle);
|
|
|
|
// If domain is not in DNSBL cache, query for blocked domain details
|
|
if (!$dnsbl_cache) {
|
|
$o_domain = $domain; // Store original domain for CNAME query
|
|
|
|
while (TRUE) {
|
|
|
|
$domainparse = str_replace('.', '\.', $domain);
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
$dquery_esc = escapeshellarg(",{$domainparse},,");
|
|
$pfb_feed = exec("{$pfb['grep']} -shm1 {$dquery_esc} {$pfb['unbound_py_data']} 2>&1");
|
|
} else {
|
|
$dquery_esc = escapeshellarg(" \"{$domainparse} 60");
|
|
$pfb_feed = pfb_parse_line(exec("{$pfb['grep']} -sHm1 {$dquery_esc} {$pfb['dnsdir']}/*.txt 2>&1"));
|
|
}
|
|
$pfb_group = $pfb_mode = $pfb_final = 'Unknown';
|
|
|
|
// Exact Domain match found
|
|
if (!empty($pfb_feed)) {
|
|
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
list($dummy, $pfb_final, $empty_placeholder, $log_type, $pfb_feed, $pfb_group) = explode(',', $pfb_feed);
|
|
$pfb_mode = 'DNSBL';
|
|
}
|
|
else {
|
|
$pfb_group = pfb_parse_line(exec("{$pfb['grep']} -sHm1 {$dquery_esc} {$pfb['dnsalias']}/* 2>&1"));
|
|
|
|
// Determine DNSBL Type
|
|
$pfb_mode = 'DNSBL';
|
|
$ip = gethostbyname("dnsbl.test.{$domain}");
|
|
if ($ip == $pfb['dnsbl_vip'] || $ip == "::{$pfb['dnsbl_vip']}" || $ip == '0.0.0.0') {
|
|
$pfb_mode = 'TLD';
|
|
}
|
|
$pfb_final = $domain;
|
|
}
|
|
}
|
|
|
|
else {
|
|
$dparts = explode('.', $domain);
|
|
if (!$pfb['dnsbl_py_blacklist']) {
|
|
unset($dparts[0]);
|
|
}
|
|
$dcnt = count($dparts);
|
|
|
|
for ($i=0; $i <= $dcnt; $i++) {
|
|
|
|
$domainparse = str_replace('.', '\.', implode('.', $dparts));
|
|
$domainparse_esc = escapeshellarg("^{$domainparse}$");
|
|
if (!$pfb['dnsbl_py_blacklist']) {
|
|
$dquery_esc = escapeshellarg(" \"{$domainparse} 60");
|
|
|
|
// Determine if TLD exists in TLD Blacklist
|
|
if (file_exists("{$pfb['dnsbl_tld_txt']}")) {
|
|
exec("/usr/bin/grep -l {$domainparse_esc} {$pfb['dnsbl_tld_txt']} 2>&1", $match);
|
|
if (!empty($match[0])) {
|
|
$pfb_group = $pfb_feed = $pfb_mode = 'DNSBL_TLD';
|
|
$pfb_final = $domainparse;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
$dquery_esc = escapeshellarg(",{$domainparse},,");
|
|
$pfb_feed = exec("{$pfb['grep']} -shm1 {$dquery_esc} {$pfb['unbound_py_zone']} 2>&1");
|
|
|
|
// Collect Alias Group name
|
|
if (!empty($pfb_feed)) {
|
|
list($dummy, $d_found, $empty_placeholder, $log_type, $pfb_feed, $pfb_group) = explode(',', $pfb_feed);
|
|
|
|
$pfb_final = str_replace('\.', '.', $domainparse);
|
|
$pfb_mode = 'TLD';
|
|
if ($pfb_feed == 'DNSBL_TLD') {
|
|
$pfb_mode = 'DNSBL_TLD';
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
$pfb_feed = pfb_parse_line(exec("{$pfb['grep']} -sHm1 {$dquery_esc} {$pfb['dnsdir']}/*.txt 2>&1"));
|
|
|
|
// Collect Alias Group name
|
|
if (!empty($pfb_feed)) {
|
|
$pfb_group = pfb_parse_line(exec("{$pfb['grep']} -sHm1 {$dquery_esc} {$pfb['dnsalias']}/* 2>&1"));
|
|
$pfb_mode = 'TLD';
|
|
$pfb_final = str_replace('\.', '.', $domainparse);
|
|
break;
|
|
}
|
|
}
|
|
unset($dparts[$i]);
|
|
}
|
|
}
|
|
|
|
// Query for CNAME(s)
|
|
if (empty($pfb_feed)) {
|
|
if (!isset($cnames)) {
|
|
$domain_esc = escapeshellarg($domain);
|
|
$extdns_esc = escapeshellarg("@{$pfb['extdns']}");
|
|
exec("/usr/bin/drill {$domain_esc} {$extdns_esc} | /usr/bin/awk '/CNAME/ {sub(\"\.\$\", \"\", \$5); print \$5;}'", $cnames);
|
|
if (is_array($cnames) && !empty($cnames)) {
|
|
foreach ($cnames as $key => $cname) {
|
|
// Remove invalid CNAMES
|
|
if (empty(pfb_filter($cname, PFB_FILTER_DOMAIN, 'pfb_dnsbl_parse'))) {
|
|
unset($cnames[$key]);
|
|
continue;
|
|
}
|
|
}
|
|
$cname_cnt = count($cnames);
|
|
}
|
|
}
|
|
if (is_array($cnames) && !empty($cnames)) {
|
|
$domain = array_shift($cnames);
|
|
}
|
|
|
|
if ($cname_cnt == 0) {
|
|
$domain = $o_domain; // No CNAME match found, revert back to original domain
|
|
break;
|
|
}
|
|
$cname_cnt--;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
$pfb_feed = $pfb_feed ?: 'Unknown';
|
|
|
|
if (isset($cnames)) {
|
|
unset($cnames);
|
|
if ($pfb_feed != 'Unknown') {
|
|
$pfb_mode = "{$pfb_mode}-CNAME";
|
|
}
|
|
}
|
|
|
|
// Save entry to DNSBL cache
|
|
$db_update = "INSERT into dnsblcache ( type, domain, groupname, final, feed ) VALUES ( :type, :domain, :groupname, :final, :feed )";
|
|
$db_handle = pfb_open_sqlite(4, 'Add to DNSBL cache');
|
|
if ($db_handle) {
|
|
$domain = pfb_filter($domain, PFB_FILTER_HTML, 'pfb_dnsbl_parse');
|
|
$pfb_group = pfb_filter($pfb_group, PFB_FILTER_HTML, 'pfb_dnsbl_parse');
|
|
$pfb_final = pfb_filter($pfb_final, PFB_FILTER_HTML, 'pfb_dnsbl_parse');
|
|
$pfb_feed = pfb_filter($pfb_feed, PFB_FILTER_HTML, 'pfb_dnsbl_parse');
|
|
|
|
$stmt = $db_handle->prepare($db_update);
|
|
if ($stmt) {
|
|
$stmt->bindValue(':type', $pfb_mode, SQLITE3_TEXT);
|
|
$stmt->bindValue(':domain', $domain, SQLITE3_TEXT);
|
|
$stmt->bindValue(':groupname', $pfb_group, SQLITE3_TEXT);
|
|
$stmt->bindValue(':final', $pfb_final, SQLITE3_TEXT);
|
|
$stmt->bindValue(':feed', $pfb_feed, SQLITE3_TEXT);
|
|
$stmt->execute();
|
|
}
|
|
}
|
|
pfb_close_sqlite($db_handle);
|
|
}
|
|
|
|
// Use cached entries
|
|
else {
|
|
$pfb_mode = htmlspecialchars($dnsbl_cache['type']) ?: 'Unknown';
|
|
$pfb_group = htmlspecialchars($dnsbl_cache['groupname']) ?: 'Unknown';
|
|
$pfb_final = htmlspecialchars($dnsbl_cache['final']) ?: 'Unknown';
|
|
$pfb_feed = htmlspecialchars($dnsbl_cache['feed']) ?: 'Unknown';
|
|
}
|
|
|
|
if ($mode == 'daemon') {
|
|
$details = "{$domain},{$src_ip},{$req_agent},{$pfb_mode},{$pfb_group},{$pfb_final},{$pfb_feed}";
|
|
return array ($pfb_group, $details);
|
|
} else {
|
|
return array ('pfb_mode' => $pfb_mode, 'pfb_group' => $pfb_group, 'pfb_final' => $pfb_final, 'pfb_feed' => $pfb_feed);
|
|
}
|
|
}
|
|
|
|
|
|
// DNSBL Lighttpd 'dnsbl_error.log' conditional log parser
|
|
function pfb_daemon_dnsbl() {
|
|
global $pfb;
|
|
|
|
if (($h_handle = @fopen('php://stdin', 'r')) !== FALSE) {
|
|
syslog(LOG_NOTICE, '[pfBlockerNG] DNSBL parser daemon started');
|
|
|
|
$chk_ver = FALSE;
|
|
$lighty_47 = FALSE;
|
|
$lighty_58 = FALSE;
|
|
$lighty_59 = FALSE;
|
|
$checkpos = 3; // Pre Lighttpd v1.4.47
|
|
|
|
while (!feof($h_handle)) {
|
|
$pfb_buffer = @fgets($h_handle);
|
|
|
|
if (!$chk_ver) {
|
|
|
|
// Lighttpd v1.4.58+ conditional error log with 'ssl.verifyclient.activate' to collect the domain name
|
|
if (!$lighty_47 && strpos($pfb_buffer, 'lighttpd/1.4.58') !== FALSE) {
|
|
$chk_ver = TRUE;
|
|
$lighty_58 = TRUE;
|
|
continue;
|
|
}
|
|
|
|
// Lighttpd v1.4.59+ syntax changes to IP line
|
|
elseif (!$lighty_47 &&
|
|
(strpos($pfb_buffer, 'lighttpd/1.4.59') !== FALSE ||
|
|
strpos($pfb_buffer, 'lighttpd/1.4.6') !== FALSE ||
|
|
strpos($pfb_buffer, 'lighttpd/1.4.7') !== FALSE ||
|
|
strpos($pfb_buffer, 'lighttpd/1.5') !== FALSE)) {
|
|
$chk_ver = TRUE;
|
|
$lighty_59 = TRUE;
|
|
continue;
|
|
}
|
|
|
|
// Lighttpd v1.4.47 uses mod_openssl with a different conditional log formatting
|
|
elseif (strpos($pfb_buffer, 'global/HTTPscheme') !== FALSE) {
|
|
$lighty_47 = TRUE;
|
|
$checkpos = 1;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ($lighty_58 || $lighty_59) {
|
|
|
|
// Verify HTTP["remoteip"]
|
|
if (empty($src_ip)) {
|
|
if ($lighty_58) {
|
|
if (strpos($pfb_buffer, '["re') !== FALSE) {
|
|
$src_ip = strstr($pfb_buffer, ') compare', TRUE);
|
|
$src_ip = ltrim(strstr($src_ip, '] (', FALSE), '] (');
|
|
$src_ip = (filter_var($src_ip, FILTER_VALIDATE_IP) !== FALSE) ? $src_ip : '';
|
|
}
|
|
} else {
|
|
if (strpos($pfb_buffer, '["re') !== FALSE && strpos($pfb_buffer, ' compare to ') !== FALSE) {
|
|
$src_ip = strstr($pfb_buffer, ' compare to ', FALSE);
|
|
$src_ip = trim(ltrim($src_ip, ' compare to '));
|
|
$src_ip = (filter_var($src_ip, FILTER_VALIDATE_IP) !== FALSE) ? $src_ip : '';
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!empty($src_ip) && strpos($pfb_buffer, "SSL: ") === FALSE) {
|
|
continue;
|
|
}
|
|
if (empty($domain) && strpos($pfb_buffer, "SSL: can't verify client without ssl.") !== FALSE) {
|
|
$domain = str_replace(' server name ', '', strstr(trim($pfb_buffer), ' server name ', FALSE));
|
|
}
|
|
|
|
if (!empty($domain) && !empty($src_ip)) {
|
|
|
|
$domain = pfb_filter($domain, PFB_FILTER_DOMAIN, 'pfb_daemon_dnsbl');
|
|
$src_ip = pfb_filter($src_ip, PFB_FILTER_IP, 'pfb_daemon_dnsbl');
|
|
if (!empty($domain) && !empty($src_ip)) {
|
|
|
|
// URL/Referer/URI/Agent String not available for HTTPS events
|
|
$req_agent = 'Unknown';
|
|
$type = 'DNSBL-HTTPS';
|
|
|
|
pfb_log_event($type, $domain, $src_ip, $req_agent);
|
|
$domain = $src_ip = '';
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Parse only HTTP["xxx"] log lines
|
|
if (strpos($pfb_buffer, 'HTTP[') === FALSE) {
|
|
continue;
|
|
}
|
|
|
|
// Verify only HTTPS lines
|
|
if (strpos($pfb_buffer, '( https') !== FALSE) {
|
|
if ($lighty_47) {
|
|
continue;
|
|
}
|
|
$checkpos = 0;
|
|
}
|
|
|
|
// Verify HTTP["remoteip"]
|
|
if ($checkpos == 1 && strpos($pfb_buffer, '["re') !== FALSE) {
|
|
$src_ip = strstr($pfb_buffer, ' ) compare', TRUE);
|
|
$src_ip = ltrim(strstr($src_ip, '] ( ', FALSE), '] ( ');
|
|
$src_ip = (filter_var($src_ip, FILTER_VALIDATE_IP) !== FALSE) ? $src_ip : '';
|
|
}
|
|
|
|
// Verify HTTP["host"]
|
|
elseif ($checkpos == 2 && strpos($pfb_buffer, '["ho') !== FALSE) {
|
|
$lighty_47 = FALSE;
|
|
$domain = strstr($pfb_buffer, ' ) compare', TRUE);
|
|
$domain = ltrim(strstr($domain, '] ( ', FALSE), '] ( ');
|
|
|
|
// URL/Referer/URI/Agent String not available for HTTPS events
|
|
$req_agent = 'Unknown';
|
|
$type = 'DNSBL-HTTPS';
|
|
|
|
// Log event and Increment SQLite DNSBL Group counter
|
|
if (!empty($domain) && !empty($src_ip)) {
|
|
|
|
$domain = pfb_filter($domain, PFB_FILTER_DOMAIN, 'pfb_daemon_dnsbl');
|
|
$src_ip = pfb_filter($src_ip, PFB_FILTER_IP, 'pfb_daemon_dnsbl');
|
|
if (!empty($domain) && !empty($src_ip)) {
|
|
|
|
// URL/Referer/URI/Agent String not available for HTTPS events
|
|
$req_agent = 'Unknown';
|
|
$type = 'DNSBL-HTTPS';
|
|
|
|
pfb_log_event($type, $domain, $src_ip, $req_agent);
|
|
$domain = $src_ip = '';
|
|
}
|
|
}
|
|
}
|
|
$checkpos++;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
log_error('[pfBlockerNG] DNSBL conditional log parser - Failed to open handle');
|
|
}
|
|
@fclose($h_handle);
|
|
}
|
|
|
|
|
|
// DNSBL Lighttpd 'index.php' event parser
|
|
function pfb_daemon_dnsbl_index() {
|
|
global $pfb;
|
|
|
|
// Replace any [',' or '|' ] in HTTP_REFERER, REQUEST_URI or HTTP_USER_AGENT fields
|
|
// Replace placeholder characters [ '!' and '*' ] to [ ',' and '|' ]
|
|
$p1 = array( ',', '!', '|', ' * ' );
|
|
$p2 = array( '', ',', '--', '|' );
|
|
|
|
if (($i_handle = @fopen('php://stdin', 'r')) !== FALSE) {
|
|
while (!feof($i_handle)) {
|
|
$pfb_buffer = @fgets($i_handle);
|
|
$pfb_buffer = str_replace($p1, $p2, $pfb_buffer);
|
|
|
|
if (substr($pfb_buffer, 0, 6) == 'INDEX,' && substr_count($pfb_buffer, ',') == 4) {
|
|
$csvline = str_getcsv($pfb_buffer, ',', '', '"');
|
|
|
|
// Determine blocked domain type (Full, 1x1 or JS)
|
|
if (isset($csvline[1]) && !empty($csvline[1])) {
|
|
$request = strstr($csvline[1], '/', FALSE);
|
|
$request = strstr($request, ' ', TRUE);
|
|
|
|
if (strlen($request) < 2) {
|
|
$type = 'DNSBL-Full';
|
|
}
|
|
else {
|
|
if (pathinfo($request, PATHINFO_EXTENSION) == 'js') {
|
|
$type = 'DNSBL-JS';
|
|
} else {
|
|
$type = 'DNSBL-1x1';
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$type = 'DNSBL-Unknown';
|
|
}
|
|
|
|
$csvline[2] = pfb_filter($csvline[2], PFB_FILTER_DOMAIN, 'pfb_daemon_dnsbl_index');
|
|
$csvline[3] = pfb_filter($csvline[3], PFB_FILTER_IP, 'pfb_daemon_dnsbl_index');
|
|
$csvline[4] = pfb_filter($csvline[4], PFB_FILTER_HTML, 'pfb_daemon_dnsbl_index');
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
|
|
// Log event and increment SQLite DNSBL Group counter
|
|
if (!empty($csvline[2]) && !empty($csvline[3])) {
|
|
pfb_log_event($type, $csvline[2], $csvline[3], $csvline[4]);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
log_error('[pfBlockerNG] DNSBL index event parser - Failed to open handle');
|
|
}
|
|
@fclose($i_handle);
|
|
}
|
|
|
|
|
|
// Function to create/open SQLite3 database(s)
|
|
function pfb_open_sqlite($table, $message) {
|
|
global $pfb;
|
|
|
|
if ($table == 1) {
|
|
$database = $pfb['dnsbl_info'];
|
|
$db_table = 'dnsbl';
|
|
$db_create = "CREATE TABLE IF NOT EXISTS dnsbl ( groupname TEXT, timestamp TEXT, entries INTEGER, counter INTEGER );";
|
|
} elseif ($table == 2) {
|
|
$database = $pfb['dnsbl_resolver'];
|
|
$db_table = 'lastevent';
|
|
$db_create = "CREATE TABLE IF NOT EXISTS lastevent ( row INTEGER, groupname TEXT, entry TEXT, details TEXT );";
|
|
} elseif ($table == 3) {
|
|
$database = $pfb['dnsbl_resolver'];
|
|
$db_table = 'resolver';
|
|
$db_create = "CREATE TABLE IF NOT EXISTS resolver ( row INTEGER, totalqueries INTEGER, queries INTEGER );";
|
|
} elseif ($table == 4) {
|
|
$database = $pfb['dnsbl_cache'];
|
|
$db_table = 'dnsblcache';
|
|
$db_create = "CREATE TABLE IF NOT EXISTS dnsblcache ( type TEXT, domain TEXT, groupname TEXT, final TEXT, feed TEXT );";
|
|
} elseif ($table == 5) {
|
|
$database = $pfb['asn_cache'];
|
|
$db_table = 'asncache';
|
|
$db_create = "CREATE TABLE IF NOT EXISTS asncache ( asn TEXT, host TEXT, timestamp TEXT );";
|
|
} elseif ($table == 6) {
|
|
$database = $pfb['dnsbl_resolver'];
|
|
$db_table = 'stats';
|
|
$db_create = "CREATE TABLE IF NOT EXISTS lastclear ( row INTEGER, lastipclear TEXT, lastdnsblclear TEXT );";
|
|
} elseif ($table == 7) {
|
|
$database = $pfb['ip_cache'];
|
|
$db_table = 'ipcache';
|
|
$db_create = "CREATE TABLE IF NOT EXISTS ipcache ( host TEXT, q0 TEXT, q1 TEXT, geoip TEXT, resolved_host TEXT );";
|
|
}
|
|
|
|
try {
|
|
$db_handle = new SQLite3($database);
|
|
$db_handle->busyTimeout("{$pfb['sqlite_timeout']}");
|
|
}
|
|
catch (Exception $e) {
|
|
@file_put_contents($pfb['errlog'], "\nDNSBL_SQL: Failed to open DB - {$message}", FILE_APPEND | LOCK_EX);
|
|
|
|
try {
|
|
$db_handle = new SQLite3($database);
|
|
$db_handle->busyTimeout("{$pfb['sqlite_timeout']}");
|
|
}
|
|
catch (Exception $e) {
|
|
@file_put_contents($pfb['errlog'], "\nDNSBL_SQL: Failed to open DB 2nd attempt - {$message}", FILE_APPEND | LOCK_EX);
|
|
}
|
|
}
|
|
|
|
if ($db_handle) {
|
|
|
|
// Validate database integrity
|
|
$validate = $pfb_validate = FALSE;
|
|
try {
|
|
$validate = $db_handle->query("PRAGMA integrity_check;");
|
|
}
|
|
catch (Exception $e) {
|
|
@file_put_contents($pfb['errlog'], "\nDNSBL_SQL: Failed to validate database - {$message}", FILE_APPEND | LOCK_EX);
|
|
}
|
|
|
|
if ($validate) {
|
|
while ($res = $validate->fetchArray(SQLITE3_ASSOC)) {
|
|
if ($res['integrity_check'] == 'ok') {
|
|
$pfb_validate = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$pfb_validate) {
|
|
log_error("[pfBlockerNG] DNSBL SQLite3 database [ {$db_table} ] corrupt. Table deletion/re-creation completed.");
|
|
@copy("{$database}", "{$database}.invalid");
|
|
$db_create = "DROP TABLE {$db_table}; {$db_create}";
|
|
}
|
|
|
|
try {
|
|
$db_handle->exec("BEGIN TRANSACTION; PRAGMA journal_mode = delete;"
|
|
. "{$db_create}"
|
|
. "END TRANSACTION;");
|
|
}
|
|
catch (Exception $e) {
|
|
@file_put_contents($pfb['errlog'], "\nDNSBL_SQL: Database failure - {$message}", FILE_APPEND | LOCK_EX);
|
|
return;
|
|
}
|
|
|
|
if ($table <= 4 && file_exists($database)) {
|
|
@chown($database, 'unbound');
|
|
@chgrp($database, 'unbound');
|
|
}
|
|
return $db_handle;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// Function to close SQLite3 database
|
|
function pfb_close_sqlite($db_handle) {
|
|
if (!empty($db_handle)) {
|
|
$db_handle->close();
|
|
unset($db_handle);
|
|
}
|
|
}
|
|
|
|
|
|
// Function to Log event, Increment SQLite 'dnsbl' database and save last event to SQLite 'lastevent' Database
|
|
function pfb_log_event($type, $domain, $src_ip, $req_agent) {
|
|
global $pfb;
|
|
|
|
$datereq = date('M j H:i:s', time());
|
|
$req_agent = str_replace("'", '', $req_agent);
|
|
|
|
// Collect lastevent without saving new event
|
|
$result = pfb_dnsbl_lastevent('', "{$domain}{$src_ip}", '');
|
|
$p_entry = $result['entry'];
|
|
$details = $result['details'];
|
|
|
|
// Duplicate entry comparison: "Domain/SRC IP"
|
|
if ("{$domain}{$src_ip}" == $p_entry) {
|
|
$dup_entry = '-';
|
|
$pfb_group = $result['groupname'];
|
|
}
|
|
|
|
// If not duplicate entry, determine TLD type, Group Name and Feed name
|
|
else {
|
|
$dup_entry = '+';
|
|
$data = pfb_dnsbl_parse('daemon', $domain, $src_ip, $req_agent);
|
|
$pfb_group = $data[0];
|
|
$details = $data[1];
|
|
$req_agent = $data[2];
|
|
|
|
// Save new lastevent
|
|
pfb_dnsbl_lastevent($pfb_group, "{$domain}{$src_ip}", $details);
|
|
}
|
|
|
|
$log = "{$type},{$datereq},{$details},{$dup_entry}\n";
|
|
@file_put_contents($pfb['dnslog'], "{$log}", FILE_APPEND | LOCK_EX);
|
|
|
|
// Write to Unified Log
|
|
@file_put_contents($pfb['unilog'], "{$log}", FILE_APPEND | LOCK_EX);
|
|
|
|
// Increment DNSBL Widget counter
|
|
if (!empty($pfb_group)) {
|
|
|
|
$db_handle = pfb_open_sqlite(1, 'Increment Counter');
|
|
if ($db_handle) {
|
|
|
|
$pfb_group = pfb_filter($pfb_group, PFB_FILTER_HTML, 'pfb_log_event');
|
|
$db_update = "UPDATE dnsbl SET counter = counter + 1 WHERE groupname = :pfb_group";
|
|
|
|
$stmt = $db_handle->prepare($db_update);
|
|
if ($stmt) {
|
|
$stmt->bindValue(':pfb_group', $pfb_group, SQLITE3_TEXT);
|
|
$stmt->execute();
|
|
}
|
|
}
|
|
pfb_close_sqlite($db_handle);
|
|
}
|
|
}
|
|
|
|
|
|
// Function to 1) Collect lastevent and 2) Save lastevent to SQLite 'lastevent' Database
|
|
function pfb_dnsbl_lastevent($p_group, $p_entry, $p_details) {
|
|
global $pfb;
|
|
|
|
$db_update = '';
|
|
$final = array();
|
|
|
|
$db_handle = pfb_open_sqlite(2, 'LastEvent');
|
|
if ($db_handle) {
|
|
$result = $db_handle->query("SELECT * FROM lastevent WHERE row = 0;");
|
|
if ($result) {
|
|
$final = $result->fetchArray(SQLITE3_ASSOC);
|
|
}
|
|
}
|
|
pfb_close_sqlite($db_handle);
|
|
|
|
// Collect or update existing row
|
|
if (!empty($final)) {
|
|
|
|
// Only collect lastevent entry
|
|
if (empty($p_group)) {
|
|
;
|
|
}
|
|
|
|
// Update lastevent entry
|
|
else {
|
|
$db_update = "UPDATE lastevent SET groupname=:p_group, entry=:p_entry, details=:p_details WHERE row = 0";
|
|
}
|
|
}
|
|
|
|
// Add new lastevent entry
|
|
else {
|
|
$db_update = "INSERT into lastevent (row, groupname, entry, details) VALUES (0, :p_group, :p_entry, :p_details )";
|
|
}
|
|
|
|
if (!empty($db_update)) {
|
|
$db_handle = pfb_open_sqlite(2, 'LastEvent');
|
|
if ($db_handle) {
|
|
|
|
$p_group = pfb_filter($p_group, PFB_FILTER_HTML, 'pfb_dnsbl_lastevent');
|
|
$p_entry = pfb_filter($p_entry, PFB_FILTER_HTML, 'pfb_dnsbl_lastevent');
|
|
$p_details = pfb_filter($p_details, PFB_FILTER_HTML, 'pfb_dnsbl_lastevent');
|
|
|
|
$stmt = $db_handle->prepare($db_update);
|
|
if ($stmt) {
|
|
$stmt->bindValue(':p_group', $p_group, SQLITE3_TEXT);
|
|
$stmt->bindValue(':p_entry', $p_entry, SQLITE3_TEXT);
|
|
$stmt->bindValue(':p_details', $p_details, SQLITE3_TEXT);
|
|
$stmt->execute();
|
|
}
|
|
}
|
|
pfb_close_sqlite($db_handle);
|
|
}
|
|
return $final;
|
|
}
|
|
|
|
|
|
// Function to collect and update the Unbound Resolver query/pid entries into SQLite3 database
|
|
function pfb_daemon_queries() {
|
|
global $g, $pfb;
|
|
|
|
$sleep_freq = config_get_path('installedpackages/pfblockerngglobal/widget-dnsblquery', 5);
|
|
|
|
$nice = '/usr/bin/nice -n20';
|
|
$unbound_pid = "{$g['varrun_path']}/unbound.pid";
|
|
|
|
while (TRUE) {
|
|
sleep($sleep_freq);
|
|
|
|
// If 'Live Sync' file marker exists skip DNSBL Queries daemon to avoid unbound-control collisions
|
|
if (platform_booting() || $g['pfblockerng_install'] || file_exists("{$pfb['dnsbl_file']}.sync")) {
|
|
continue;
|
|
}
|
|
|
|
// Collect Unbound Resolver pid
|
|
$pid = exec("{$nice} /usr/bin/pgrep -anx 'unbound' 2>&1");
|
|
if (!empty($pid)) {
|
|
$output = exec("{$pfb['chroot_cmd']} stats_noreset | {$nice} {$pfb['grep']} 'total.num.queries=' | cut -d '=' -f2 2>&1");
|
|
$query = intval($output);
|
|
if (!is_numeric($query)) {
|
|
$query = '';
|
|
}
|
|
|
|
// On an Unbound Reload, the query stat is reset, therefore reuse previous query value
|
|
if ($query < $p_query) {
|
|
$t_query = $query;
|
|
$query = $query + $p_query;
|
|
$pid = '';
|
|
}
|
|
}
|
|
|
|
// On initial daemon load
|
|
if (empty($p_pid)) {
|
|
$p_pid = $pid;
|
|
continue;
|
|
}
|
|
|
|
$pfb_found = FALSE;
|
|
$stats = array();
|
|
$stats['totalqueries'] = 0;
|
|
$stats['queries'] = 0;
|
|
|
|
$db_handle = pfb_open_sqlite(3, 'Resolver collect queries');
|
|
if ($db_handle) {
|
|
$result = $db_handle->query("SELECT * FROM resolver WHERE row = 0;");
|
|
if ($result) {
|
|
while ($res = $result->fetchArray(SQLITE3_ASSOC)) {
|
|
$stats = $res;
|
|
$pfb_found = TRUE;
|
|
}
|
|
}
|
|
|
|
// Create new row
|
|
if (!$pfb_found) {
|
|
$db_update = "INSERT INTO resolver ( row, totalqueries, queries ) VALUES ( 0, 0, 0 );";
|
|
$db_handle->exec("BEGIN TRANSACTION;"
|
|
. "{$db_update}"
|
|
. "END TRANSACTION;");
|
|
}
|
|
}
|
|
pfb_close_sqlite($db_handle);
|
|
|
|
// If Unbound Resolver pid has changed, clear SQLite database 'queries' entry, and update 'totalqueries/pid' entries
|
|
if ($pfb_found && $pid != $p_pid) {
|
|
$totalqueries = ($stats['totalqueries'] ?: 0) + ($query ?: 0);
|
|
pfBlockerNG_clearsqlite('update_totalqueries', $totalqueries);
|
|
}
|
|
else {
|
|
if ($query != '' && $query != $p_query && is_numeric($query)) {
|
|
|
|
// Update existing row
|
|
$db_handle = pfb_open_sqlite(3, 'Widget update queries');
|
|
if ($db_handle) {
|
|
$db_update = "UPDATE resolver SET queries = :query WHERE row = 0";
|
|
$stmt = $db_handle->prepare($db_update);
|
|
if ($stmt) {
|
|
$stmt->bindValue(':query', $query, SQLITE3_INTEGER);
|
|
$stmt->execute();
|
|
}
|
|
}
|
|
pfb_close_sqlite($db_handle);
|
|
}
|
|
}
|
|
|
|
$p_pid = $pid;
|
|
if (isset($t_query)) {
|
|
$p_query = $t_query;
|
|
unset($t_query);
|
|
} else {
|
|
$p_query = $query;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Read logfile in realtime (livetail)
|
|
// Reference: http://stackoverflow.com/questions/3218895/php-how-to-read-a-file-live-that-is-constantly-being-written-to
|
|
function pfb_livetail($logfile, $mode) {
|
|
global $pfb;
|
|
|
|
if (!file_exists("{$logfile}")) {
|
|
touch("{$logfile}");
|
|
}
|
|
|
|
$len = @filesize("{$logfile}"); // Start at EOF
|
|
$lastpos_old = $pfb_output = '';
|
|
|
|
if ($mode == 'view') {
|
|
// Start at EOF ( - 15000)
|
|
if ($len > 15000) {
|
|
$lastpos = ($len - 15000);
|
|
} else {
|
|
$lastpos = 0;
|
|
}
|
|
}
|
|
else {
|
|
$lastpos = $len;
|
|
}
|
|
|
|
while (TRUE) {
|
|
usleep(300000); //0.3s
|
|
clearstatcache(false, "{$logfile}");
|
|
$len = @filesize("{$logfile}");
|
|
|
|
if ($len < $lastpos) {
|
|
$lastpos = $len; // File deleted or reset
|
|
}
|
|
else {
|
|
$f = @fopen("{$logfile}", 'rb+');
|
|
if ($f === false) {
|
|
break;
|
|
}
|
|
@fseek($f, $lastpos);
|
|
|
|
while (!feof($f)) {
|
|
$pfb_buffer = @fread($f, 2048);
|
|
$pfb_output .= str_replace( array ("\r", "\")"), '', $pfb_buffer);
|
|
// Refresh on new lines only. This allows Scrolling.
|
|
if ($lastpos != $lastpos_old) {
|
|
pfbupdate_output($pfb_output);
|
|
}
|
|
$lastpos_old = $lastpos;
|
|
ob_flush();
|
|
flush();
|
|
}
|
|
|
|
$lastpos = @ftell($f);
|
|
if ($f) {
|
|
@fclose($f);
|
|
}
|
|
|
|
// Capture remaining output
|
|
if ($mode != 'view' && strpos($pfb_output, 'UPDATE PROCESS ENDED') !== FALSE) {
|
|
$f = @fopen($pfb['log'], 'rb');
|
|
@fseek($f, $lastpos);
|
|
$pfb_buffer = @fread($f, 2048);
|
|
$pfb_output .= str_replace( "\r", '', $pfb_buffer);
|
|
pfbupdate_output($pfb_output);
|
|
clearstatcache(false, $pfb['log']);
|
|
ob_flush();
|
|
flush();
|
|
if ($f) {
|
|
@fclose($f);
|
|
}
|
|
|
|
// Call log mgmt function
|
|
pfb_log_mgmt();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Load/convert Feeds (w/alternative aliasname(s), if user-configured) and return as array
|
|
function convert_feeds_json() {
|
|
global $pfb;
|
|
|
|
$aconfig = config_get_path('installedpackages/pfblockerngglobal', []);
|
|
$pfb['feeds_list'] = $merge_feeds = $feed_info = array();
|
|
|
|
$feed_info_raw = json_decode(@file_get_contents("{$pfb['feeds']}"), TRUE);
|
|
if (json_last_error() !== JSON_ERROR_NONE || !is_array($feed_info_raw)) {
|
|
return array('blank' => '');
|
|
}
|
|
|
|
$feed_count = array();
|
|
foreach ($feed_info_raw as $type => $info) {
|
|
|
|
if (!is_array($info) || (isset($info[0]) && $info[0] == '*')) {
|
|
continue;
|
|
}
|
|
|
|
$feed_count[$type] = 0;
|
|
foreach ($info as $aliasname => $data) {
|
|
$l_aliasname = strtolower($aliasname);
|
|
|
|
$feed_count[$type] = $feed_count[$type] + count($data['feeds']);
|
|
foreach ($data['feeds'] as $key => $feed) {
|
|
|
|
// Remove discontinued Feeds
|
|
if (isset($feed['status']) && $feed['status'] == 'discontinued') {
|
|
unset($data['feeds'][$key]);
|
|
$feed_count[$type]--;
|
|
continue;
|
|
}
|
|
|
|
if (isset($feed['alternate'])) {
|
|
foreach ($feed['alternate'] as $alternate) {
|
|
$feed_count[$type]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!array_key_exists($type, $pfb['feeds_list'])) {
|
|
$pfb['feeds_list'][$type] = array();
|
|
}
|
|
if (!array_key_exists($type, $feed_info)) {
|
|
$feed_info[$type] = array();
|
|
}
|
|
|
|
// Use alternative Aliasname(s) and/or merge multiple aliasname Feeds together (if user configured)
|
|
if (!empty($aconfig['feed_' . $l_aliasname])) {
|
|
|
|
$alt_feed = $aconfig['feed_' . $l_aliasname];
|
|
$pfb['feeds_list'][$type][$aliasname] = $alt_feed; // Global list of all known Feed aliasnames
|
|
|
|
if (!is_array($merge_feeds[$alt_feed])) {
|
|
$merge_feeds[$alt_feed] = array();
|
|
}
|
|
$merge_feeds[$alt_feed] = array_merge( $merge_feeds[$alt_feed], (array)$data['feeds'] );
|
|
|
|
if (!isset($feed_info[$type][$alt_feed])) {
|
|
|
|
// Modify 'info' and 'description' fields to reference user-defined aliasname
|
|
foreach (array('info', 'description') as $atype) {
|
|
$match = strpos($data[$atype], $aliasname);
|
|
if ($match !== FALSE) {
|
|
$data[$atype] = substr_replace($data[$atype], $aconfig['feed_' . $l_aliasname], $match, strlen($aliasname));
|
|
}
|
|
}
|
|
$feed_info[$type][$aconfig['feed_' . $l_aliasname]] = $data;
|
|
}
|
|
$feed_info[$type][$alt_feed]['feeds'] = $merge_feeds[$alt_feed];
|
|
}
|
|
else {
|
|
$pfb['feeds_list'][$type][$aliasname] = $aliasname;
|
|
$feed_info[$type][$aliasname] = $data;
|
|
}
|
|
}
|
|
}
|
|
$feed_info['count'] = $feed_count;
|
|
return $feed_info;
|
|
}
|
|
|
|
|
|
// Define Alerts Tab 'default GET request' (Top row)
|
|
function pfb_alerts_default_page() {
|
|
global $pfb;
|
|
|
|
if (isset($pfb['config_global']) &&
|
|
isset($pfb['config_global']['pfbpageload'])) {
|
|
switch($pfb['config_global']['pfbpageload']) {
|
|
case 'dnsbl_stat':
|
|
return '?view=dnsbl_stat';
|
|
case 'dnsbl_reply_stat':
|
|
return '?view=dnsbl_reply_stat';
|
|
case 'ip_block_stat':
|
|
return '?view=ip_block_stat';
|
|
case 'ip_permit_stat':
|
|
return '?view=ip_permit_stat';
|
|
case 'ip_match_stat':
|
|
return '?view=ip_match_stat';
|
|
case 'reply':
|
|
return '?view=reply';
|
|
case 'unified':
|
|
return '?view=unified';
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
|
|
// Clear IP Alias Packet Counts (widget)
|
|
function pfBlockerNG_clearip() {
|
|
global $pfb;
|
|
|
|
exec("{$pfb['pfctl']} -z 2>&1");
|
|
|
|
/* TODO: Clear only pfB counters
|
|
$pfb_tables = array();
|
|
exec("{$pfb['pfctl']} -sTables | {$pfb['grep']} 'pfB_' 2>&1", $pfb_tables);
|
|
if (!empty($pfb_tables)) {
|
|
foreach ($pfb_tables as $table) {
|
|
exec("{$pfb['pfctl']} -t {$table} -T zero");
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
// Clear DNSBL SQLite database statistics/queries as required
|
|
function pfBlockerNG_clearsqlite($mode, $totalqueries = 0) {
|
|
global $g, $pfb;
|
|
|
|
// Format of todo array: database, error message, SQLite command
|
|
$todo = array();
|
|
|
|
// Clear SQLite database 'queries' entry and update totalqueries (+queries), if Unbound Resolver PID changed (Reload)
|
|
if ($mode == 'update_totalqueries') {
|
|
$todo[] = array(3, 'Clear Resolver queries', 'UPDATE resolver SET totalqueries = :totalqueries, queries = 0 WHERE row = 0;');
|
|
}
|
|
elseif ($mode == 'clearip') {
|
|
$lastipclear = date('M j H:i:s', time());
|
|
$todo[] = array(6, 'Reset IP last clear timestamp', 'UPDATE lastclear SET lastipclear = :lastipclear WHERE row = 0;');
|
|
}
|
|
elseif ($mode == 'cleardnsbl') {
|
|
$lastdnsblclear = date('M j H:i:s', time());
|
|
$todo[] = array(1, 'Clear Widget counters', 'UPDATE dnsbl SET counter = 0;');
|
|
$todo[] = array(3, 'Clear Resolver queries', 'UPDATE resolver SET totalqueries = 0, queries = 0;');
|
|
$todo[] = array(6, 'Reset DNSBL last clear timestamp', 'UPDATE lastclear SET lastdnsblclear = :lastdnsblclear WHERE row = 0;');
|
|
}
|
|
|
|
// Clear Unbound Resolver statistics
|
|
if ($mode != 'clearip' && is_process_running('unbound')) {
|
|
exec("{$pfb['chroot_cmd']} flush_stats 2>&1");
|
|
}
|
|
|
|
if (!empty($todo)) {
|
|
foreach ($todo as $data) {
|
|
$db_handle = pfb_open_sqlite($data[0], $data[1]);
|
|
|
|
if ($db_handle) {
|
|
if ($mode == 'update_totalqueries') {
|
|
if (is_numeric($totalqueries)) {
|
|
$stmt = $db_handle->prepare($data[2]);
|
|
if ($stmt) {
|
|
$stmt->bindValue(':totalqueries', $totalqueries, SQLITE3_INTEGER);
|
|
$stmt->execute();
|
|
}
|
|
}
|
|
}
|
|
elseif ($mode == 'clearip') {
|
|
$stmt = $db_handle->prepare($data[2]);
|
|
if ($stmt) {
|
|
$stmt->bindValue(':lastipclear', $lastipclear, SQLITE3_TEXT);
|
|
$stmt->execute();
|
|
}
|
|
}
|
|
elseif ($mode == 'cleardnsbl') {
|
|
$stmt = $db_handle->prepare($data[2]);
|
|
if ($stmt) {
|
|
$stmt->bindValue(':lastdnsblclear', $lastdnsblclear, SQLITE3_TEXT);
|
|
$stmt->execute();
|
|
}
|
|
}
|
|
else {
|
|
$db_handle->exec("BEGIN TRANSACTION;"
|
|
. "{$data[2]}"
|
|
. "END TRANSACTION;");
|
|
}
|
|
}
|
|
pfb_close_sqlite($db_handle);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Function to read/lock/unlock IP/Domains from Aliastables/DNSBL (Called via Alerts Page)
|
|
function pfb_unlock($mode, $type, $remove='', $r_type='', $filename_unlock) {
|
|
global $pfb;
|
|
|
|
if ($type == 'ip') {
|
|
$filename = $pfb['ip_unlock'];
|
|
} elseif ($type == 'dnsbl') {
|
|
$filename = $pfb['dnsbl_unlock'];
|
|
} elseif ($type == 'dnsbl_data') {
|
|
$filename = "{$pfb['dnsbl_unlock']}.data";
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
if ($mode == 'read') {
|
|
$filename_unlock = array();
|
|
if (($handle = @fopen("{$filename}", 'r')) !== FALSE) {
|
|
while (($line = @fgetcsv($handle)) !== FALSE) {
|
|
if (!empty($line)) {
|
|
$filename_unlock[$line[0]] = $line[1];
|
|
}
|
|
}
|
|
}
|
|
if ($handle) {
|
|
@fclose($handle);
|
|
}
|
|
|
|
if (empty($filename_unlock)) {
|
|
unlink_if_exists("{$filename}");
|
|
}
|
|
return $filename_unlock;
|
|
}
|
|
elseif ($mode == 'dnsbl_data') {
|
|
$filename_unlock = '';
|
|
$data = array();
|
|
if (($handle = @fopen("{$filename}", 'r')) !== FALSE) {
|
|
while (($line = @fgets($handle)) !== FALSE) {
|
|
if (strpos($line, $remove) !== FALSE) {
|
|
$data = explode(',', $line);
|
|
} else {
|
|
$filename_unlock .= "{$line}";
|
|
}
|
|
}
|
|
}
|
|
if ($handle) {
|
|
@fclose($handle);
|
|
}
|
|
|
|
if (empty($filename_unlock)) {
|
|
unlink_if_exists("{$filename}");
|
|
} else {
|
|
@file_put_contents("{$filename}", "{$filename_unlock}", LOCK_EX);
|
|
}
|
|
return $data;
|
|
}
|
|
elseif ($mode == 'unlock' && isset($filename_unlock[$remove])) {
|
|
return;
|
|
}
|
|
elseif (empty($remove)) {
|
|
return;
|
|
}
|
|
|
|
// Add/Remove IP/Domain in unlock file
|
|
if (($pfb_output = @fopen("{$filename}", 'w')) !== FALSE) {
|
|
foreach ($filename_unlock as $key => $line) {
|
|
|
|
// 'Remove locked IP/Domains' or 'Add existing unlocked IP/Domain' in unlock file
|
|
if ($mode == 'unlock' || ($mode == 'lock' && $key != $remove)) {
|
|
@fwrite($pfb_output, "{$key},{$line}\n");
|
|
}
|
|
}
|
|
|
|
// Add IP/Domain to unlock file
|
|
if ($mode == 'unlock') {
|
|
$filename_unlock[$remove] = $r_type;
|
|
@fwrite($pfb_output, "{$remove},{$r_type}\n");
|
|
}
|
|
}
|
|
if ($pfb_output) {
|
|
@fclose($pfb_output);
|
|
}
|
|
|
|
if (empty($filename_unlock)) {
|
|
unlink_if_exists("{$filename}");
|
|
}
|
|
}
|
|
|
|
|
|
// Function to clear pfBlockerNG folder/files
|
|
function pfb_clear_contents() {
|
|
global $pfb;
|
|
|
|
unlink_if_exists("{$pfb['dbdir']}/masterfile");
|
|
unlink_if_exists("{$pfb['dbdir']}/mastercat");
|
|
unlink_if_exists("{$pfb['supptxt']}");
|
|
unlink_if_exists("{$pfb['dnsbl_supptxt']}");
|
|
unlink_if_exists("{$pfb['dnsbl_info']}");
|
|
unlink_if_exists("{$pfb['dnsbl_resolver']}");
|
|
unlink_if_exists("{$pfb['dnsbl_cache']}");
|
|
unlink_if_exists("/var/tmp/unbound_cache_*");
|
|
unlink_if_exists("{$pfb['asn_cache']}");
|
|
unlink_if_exists("{$pfb['ip_cache']}");
|
|
rmdir_recursive("{$pfb['origdir']}");
|
|
rmdir_recursive("{$pfb['matchdir']}");
|
|
rmdir_recursive("{$pfb['permitdir']}");
|
|
rmdir_recursive("{$pfb['denydir']}");
|
|
rmdir_recursive("{$pfb['nativedir']}");
|
|
rmdir_recursive("{$pfb['etdir']}");
|
|
rmdir_recursive("{$pfb['dnsdir']}");
|
|
rmdir_recursive("{$pfb['dnsorigdir']}");
|
|
rmdir_recursive("{$pfb['dnsalias']}");
|
|
}
|
|
|
|
|
|
// Main pfBlockerNG function
|
|
function sync_package_pfblockerng($cron='') {
|
|
global $g, $pfb, $pfbarr;
|
|
pfb_global();
|
|
|
|
$pfb['conf_mod'] = FALSE; // Flag to check for mods to the config.xml file. ('$pfb_config' array to hold changes)
|
|
$pfb['filter_configure'] = FALSE; // Flag to call filter_configure once
|
|
|
|
// Detect boot process or package installation
|
|
if (platform_booting() || $g['pfblockerng_install']) {
|
|
// Create DNSBL NAT, VIP, Lighttpd service and certs if required on reboot.
|
|
if ($pfb['dnsbl'] == 'on') {
|
|
pfb_create_dnsbl('enabled');
|
|
}
|
|
$log = 'Sync terminated during boot process.';
|
|
pfb_logger("\n{$log}\nUPDATE PROCESS ENDED [ NOW ]\n", 1);
|
|
log_error("[pfBlockerNG] {$log}");
|
|
return;
|
|
}
|
|
|
|
// Reloads existing lists without downloading new lists when defined 'on'
|
|
$pfb['reuse'] = $pfb['config']['pfb_reuse'];
|
|
$pfb['reuse_dnsbl'] = '';
|
|
|
|
// Define update process (update or reload)
|
|
switch ($cron) {
|
|
case 'noupdates':
|
|
// Force update - Set 'save' variable when 'No updates' found.
|
|
$pfb['save'] = TRUE;
|
|
break;
|
|
case 'cron':
|
|
if ($pfb['reuse'] == 'on') {
|
|
$pfb['reuse_dnsbl'] = 'on';
|
|
unlink_if_exists("{$pfb['dbdir']}/masterfile");
|
|
unlink_if_exists("{$pfb['dbdir']}/mastercat");
|
|
}
|
|
break;
|
|
case 'updatednsbl':
|
|
$pfb['reuse'] = '';
|
|
$pfb['reuse_dnsbl'] = 'on';
|
|
$pfb['updatednsbl'] = TRUE;
|
|
break;
|
|
case 'updateip':
|
|
$pfb['reuse'] = 'on';
|
|
$pfb['reuse_dnsbl'] = '';
|
|
unlink_if_exists("{$pfb['dbdir']}/masterfile");
|
|
unlink_if_exists("{$pfb['dbdir']}/mastercat");
|
|
break;
|
|
}
|
|
|
|
// Start of pfBlockerNG logging to 'pfblockerng.log'
|
|
if ($pfb['enable'] == 'on' && !$pfb['save']) {
|
|
$log = " UPDATE PROCESS START [ " . pfb_pkg_ver() . " ] [ NOW ]\n";
|
|
pfb_logger("{$log}", 1);
|
|
} else {
|
|
if ($cron != 'noupdates') {
|
|
$log = "\n**Saving configuration [ NOW ]**\n";
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
}
|
|
|
|
// Call function for Ramdisk processes.
|
|
pfb_aliastables('conf');
|
|
|
|
// If table limit not defined, set default to 2M
|
|
if (empty(config_get_path('system/maximumtableentries'))) {
|
|
config_set_path('system/maximumtableentries', '2000000');
|
|
write_config('pfBlockerNG: save max Firewall table entries limit', false);
|
|
}
|
|
$pfb['table_limit'] = config_get_path('system/maximumtableentries');
|
|
|
|
// Collect local web gui configuration
|
|
$pfb['weblocal'] = config_get_path('system/webgui/protocol', 'http');
|
|
$pfb['port'] = config_get_path('system/webgui/port');
|
|
if (empty($pfb['port'])) {
|
|
if (config_get_path('system/webgui/protocol') == 'http') {
|
|
$pfb['port'] = '80';
|
|
} else {
|
|
$pfb['port'] = '443';
|
|
}
|
|
}
|
|
$pfb['weblocal'] .= "://127.0.0.1:{$pfb['port']}/pfblockerng/pfblockerng.php";
|
|
|
|
// Define Inbound/Outbound action is not user selected.
|
|
$pfb['deny_action_inbound'] = $pfb['ipconfig']['inbound_deny_action'] ?: 'block';
|
|
$pfb['deny_action_outbound'] = $pfb['ipconfig']['outbound_deny_action'] ?: 'reject';
|
|
|
|
$pfb['float'] = $pfb['ipconfig']['enable_float']; // Enable/Disable floating autorules
|
|
$pfb['dup'] = $pfb['ipconfig']['enable_dup']; // Enable remove of duplicate IPs utilizing grepcidr
|
|
$pfb['agg'] = $pfb['ipconfig']['enable_agg']; // Enable aggregation of CIDRs
|
|
$pfb['order'] = $pfb['ipconfig']['pass_order']; // Order of the autorules
|
|
$pfb['global_log'] = $pfb['ipconfig']['enable_log']; // Enable Global IP logging
|
|
$pfb['suffix'] = $pfb['ipconfig']['autorule_suffix']; // Suffix used for autorules
|
|
$pfb['kstates'] = $pfb['ipconfig']['killstates']; // Firewall states removal
|
|
|
|
$pfb['ip_ph'] = pfb_filter($pfb['ipconfig']['ip_placeholder'], PFB_FILTER_IPV4, 'Placeholder IP Address', '127.1.7.7'); // Placeholder IP Address
|
|
|
|
// DNSBL settings
|
|
$pfb['dnsbl_ip'] = $pfb['dnsblconfig']['action'] ?: 'Disabled'; // Enable/Disable IP blocking from DNSBL lists
|
|
$pfb['dnsbl_rule'] = $pfb['dnsblconfig']['pfb_dnsbl_rule'] ?: 'Disabled'; // Auto create a Floating Pass Rule for other Lan subnets
|
|
$pfb['dnsbl_alexa_cnt'] = $pfb['dnsblconfig']['alexa_count'] ?: '1000'; // TOP1M whitelist domain setting
|
|
$pfb['dnsbl_alexa_inc'] = $pfb['dnsblconfig']['alexa_inclusion']?: ''; // TOP1M TLDs inclusions for whitelisting
|
|
$pfb['dnsbl_tld'] = $pfb['dnsblconfig']['pfb_tld']; // Enable TLD Function
|
|
$pfb['dnsbl_control'] = $pfb['dnsblconfig']['pfb_control'] ?: ''; // Python Control integration
|
|
|
|
// Validate pfB Script variable
|
|
$pfb['dnsbl_alexa_inc'] = pfb_filter($pfb['dnsbl_alexa_inc'], PFB_FILTER_CSV, 'Validate pfB Script variable');
|
|
|
|
// Reputation config variables
|
|
$pfb['config_rep'] = config_get_path('installedpackages/pfblockerngreputation/config/0', []);
|
|
|
|
// Validate pfB Script variables
|
|
foreach (array(
|
|
'enable_rep' => 'rep', // Enable/Disable 'Max' Reputation
|
|
'enable_pdup' => 'prep', // Enable/Disable 'pRep' Reputation
|
|
'enable_dedup' => 'drep', // Enable/Disable 'dRep' Reputation
|
|
'et_update' => 'etupdate', // Perform a Force Update on ET categories
|
|
'ccwhite' => 'ccwhite', // Action for whitelist Country category
|
|
'ccblack' => 'ccblack', // Action for blacklist Country category
|
|
'etblock' => 'etblock', // Emerging Threats IQRisk block categories
|
|
'etmatch' => 'etmatch', // Emerging Threats IQRisk match categories
|
|
'p24_max_var' => 'max', // 'Max' variable setting for Reputation
|
|
'p24_dmax_var' => 'dmax', // 'dMax' variable setting for Reputation
|
|
'p24_pmax_var' => 'pmax', // 'pMax' variable setting for Reputation
|
|
'ccexclude' => 'ccexclude' // List of Countries to whitelist
|
|
) as $conf_value => $pfb_value) {
|
|
|
|
$pfb_variable = $pfb['config_rep'][$conf_value] ?: 'x';
|
|
if (empty(pfb_filter($pfb_variable, PFB_FILTER_CSV, 'Validate pfB Script variables'))) {
|
|
$pfb[$pfb_value] = 'x';
|
|
} else {
|
|
$pfb[$pfb_value] = $pfb_variable;
|
|
}
|
|
}
|
|
|
|
// Starting variable to skip Reputation functions, if no changes are required
|
|
$pfb['repcheck'] = FALSE;
|
|
// $pfb['save'] is used to determine if user pressed "save" button to avoid collision with CRON.
|
|
|
|
// For 'script' calls using exec() (used to shorten length of line)
|
|
$elog = ">> {$pfb['log']} 2>&1";
|
|
|
|
|
|
#################################
|
|
# Configure ARRAYS #
|
|
#################################
|
|
|
|
$new_aliases = array(); // An array of aliases (full details)
|
|
$new_aliases_list = array(); // An array of alias names
|
|
$pfb_alias_lists = array(); // An array of aliases that have updated lists via CRON/force update. ('Reputation' disabled)
|
|
$pfb_alias_lists_all = array(); // An array of all active aliases. ('Reputation' enabled)
|
|
|
|
$ip_types = array( 'pfblockernglistsv4' => '_v4', 'pfblockernglistsv6' => '_v6');
|
|
$cont_types = array( 'countries4' => '_v4', 'countries6' => '_v6');
|
|
|
|
#################################
|
|
# Tracker IDs #
|
|
#################################
|
|
|
|
$pfb['trackerids'] = array(); // An array of pfBlockerNG Firewall rule Tracker IDs.
|
|
$pfb['last_trackerid'] = 1700000009; // Pre-defined 'starting' Tracker ID (Only used if duplicates found)
|
|
|
|
|
|
#########################################
|
|
# Configure Rule Suffix #
|
|
#########################################
|
|
|
|
// Discover if any rules are autorules (If no autorules found, $pfb['autorules'] is FALSE, skip rules re-order )
|
|
// To configure auto rule suffix. pfBlockerNG must be disabled to change suffix and to avoid duplicate rules
|
|
$pfb['autorules'] = FALSE;
|
|
$action = array('Deny_Both', 'Deny_Inbound', 'Deny_Outbound', 'Match_Both', 'Match_Inbound',
|
|
'Match_Outbound', 'Permit_Both', 'Permit_Inbound', 'Permit_Outbound');
|
|
|
|
foreach ($pfb['continents'] as $continent => $pfb_alias) {
|
|
$cont_key = 'pfblockerng' . strtolower(str_replace(' ', '', $continent));
|
|
if (!empty(config_get_path("installedpackages/{$cont_key}/config"))) {
|
|
$continent_config = config_get_path("installedpackages/{$cont_key}/config/0");
|
|
if ($continent_config['action'] != 'Disabled' && in_array($continent_config['action'], $action)) {
|
|
$pfb['autorules'] = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$pfb['autorules']) {
|
|
foreach ($ip_types as $ip_type => $vtype) {
|
|
foreach(config_get_path("installedpackages/{$ip_type}/config", []) as $list) {
|
|
if ($list['action'] != 'Disabled' && in_array($list['action'], $action)) {
|
|
$pfb['autorules'] = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if DNSBL auto permit rule or DNSBL 'Auto Deny' rules for DNSBL IPs are defined
|
|
if (!empty($pfb['dnsblconfig']['dnsbl_allow_int']) || strpos($pfb['dnsblconfig']['action'], 'Deny_') !== FALSE) {
|
|
$pfb['autorules'] = TRUE;
|
|
}
|
|
|
|
// Configure auto rule suffix.
|
|
$pfbfound = FALSE;
|
|
$pfb_suffix_match = '';
|
|
foreach (config_get_path('filter/rule', []) as $rule) {
|
|
|
|
// Query for previous IPv4 pfBlockerNG 'alias type' aliasnames which are not in the new '_v4' suffix format
|
|
foreach (array('source', 'destination') as $rtype) {
|
|
if (substr($rule[$rtype]['address'], 0, 4) == 'pfB_' &&
|
|
substr($rule[$rtype]['address'], -3) != '_v4' &&
|
|
$rule['ipprotocol'] == 'inet') {
|
|
$pfb['autorules'] = TRUE; // Set flag to re-configure Firewall rules and add missing '_v4' suffix
|
|
}
|
|
}
|
|
|
|
// Collect any pre-existing suffix
|
|
if (!empty($pfb_suffix_match) && preg_match('/^pfB_\w+(\s.*)/', $rule['descr'], $pfb_suffix_real)) {
|
|
$pfb_suffix_match = $pfb_suffix_real[1];
|
|
}
|
|
}
|
|
|
|
// Change suffix only if no pfB rules found and autorules are enabled.
|
|
if ($pfb['autorules'] && !$pfbfound) {
|
|
switch ($pfb['suffix']) {
|
|
case 'autorule':
|
|
$pfb['suffix'] = ' auto rule';
|
|
break;
|
|
case 'standard':
|
|
$pfb['suffix'] = '';
|
|
break;
|
|
case 'ar':
|
|
$pfb['suffix'] = ' AR';
|
|
break;
|
|
}
|
|
} else {
|
|
$pfb['suffix'] = '';
|
|
if ($pfb['autorules']) {
|
|
$pfb['suffix'] = $pfb_suffix_match; // Use existing suffix match
|
|
}
|
|
}
|
|
|
|
|
|
#########################################################
|
|
# Configure INBOUND/OUTBOUND INTERFACES #
|
|
#########################################################
|
|
|
|
// Collect pfSense interface order
|
|
$ifaces = pfb_build_if_list(TRUE, FALSE);
|
|
|
|
foreach (array('inbound', 'outbound') as $type) {
|
|
$pfb["{$type}_interfaces"] = $pfb["{$type}_floating"] = array();
|
|
|
|
if (!empty($pfb['ipconfig']["{$type}_interface"])) {
|
|
$interfaces = explode(',', $pfb['ipconfig']["{$type}_interface"]);
|
|
|
|
// CSV string for 'pfB_' match rules
|
|
$pfb["{$type}_floating"] = ltrim(implode(',', $interfaces), ',');
|
|
|
|
// Assign base rule/interfaces
|
|
if ($pfb['float'] == 'on') {
|
|
$pfb['base_rule'] = $pfb['base_rule_float'];
|
|
$pfb["{$type}_interfaces"] = explode(' ', $pfb["{$type}_floating"]);
|
|
} else {
|
|
$pfb['base_rule'] = $pfb['base_rule_reg'];
|
|
$pfb["{$type}_interfaces"] = $interfaces;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine max Domain count available for DNSBL TLD analysis (Avoid Unbound memory exhaustion)
|
|
$pfs_memory = (round(get_single_sysctl('hw.physmem') / (1024*1024)) ?: 1000);
|
|
|
|
$pfb['pfs_mem'] = [
|
|
'0' => '100000',
|
|
'1500' => '150000',
|
|
'2000' => '200000',
|
|
'2500' => '250000',
|
|
'3000' => '400000',
|
|
'4000' => '600000',
|
|
'5000' => '1000000',
|
|
'6000' => '1500000',
|
|
'7000' => '2000000',
|
|
'8000' => '2500000',
|
|
'12000' => '3000000',
|
|
'16000' => '4000000',
|
|
'32000' => '8000000'
|
|
];
|
|
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
array_walk($pfb['pfs_mem'], function (&$value) {
|
|
$value = $value * 3;
|
|
});
|
|
}
|
|
|
|
foreach ($pfb['pfs_mem'] as $pfb_mem => $domain_max) {
|
|
if ($pfs_memory >= $pfb_mem) {
|
|
$pfb['domain_max_cnt'] = $domain_max;
|
|
}
|
|
}
|
|
|
|
|
|
#################################################
|
|
# Clear Removed Lists from Masterfiles #
|
|
#################################################
|
|
|
|
$pfb['sync_master'] = TRUE; // Process to keep IP Masterfiles in sync with valid Lists from config.conf file
|
|
$pfb['remove'] = FALSE; // Flag to execute pfctl and rules ordering or reload of DNSBL domains
|
|
$pfb['summary'] = FALSE; // Execute final summary as a list was removed
|
|
|
|
// Don't execute this function when pfBlockerNG is disabled and 'keep blocklists' is enabled.
|
|
if ($pfb['enable'] == '' && $pfb['keep'] == 'on') {
|
|
$pfb['sync_master'] = FALSE;
|
|
}
|
|
|
|
if ($pfb['sync_master']) {
|
|
|
|
// Find all enabled Continents lists
|
|
foreach ($pfb['continents'] as $continent => $pfb_alias) {
|
|
$cont_key = 'pfblockerng' . strtolower(str_replace(' ', '', $continent));
|
|
if (!empty(config_get_path("installedpackages/{$cont_key}/config")) && $pfb['enable'] == 'on') {
|
|
$continent_config = config_get_path("installedpackages/{$cont_key}/config/0");
|
|
if ($continent_config['action'] != 'Disabled') {
|
|
foreach ($cont_types as $c_type => $vtype) {
|
|
if (!empty($continent_config[$c_type])) {
|
|
|
|
// Force 'Alias Native' setting to any Alias with 'Advanced Inbound/Outbound -Invert src/dst' settings.
|
|
// This will bypass Deduplication and Reputation features.
|
|
if ($continent_config['autoaddrnot_in'] == 'on' ||
|
|
$continent_config['autoaddrnot_out'] == 'on') {
|
|
$pfb['existing']['native'][] = "{$pfb_alias}{$vtype}";
|
|
}
|
|
else {
|
|
if (strpos($continent_config['action'], 'Match') !== FALSE) {
|
|
$pfb['existing']['match'][] = "{$pfb_alias}{$vtype}";
|
|
}
|
|
elseif (strpos($continent_config['action'], 'Permit') !== FALSE) {
|
|
$pfb['existing']['permit'][] = "{$pfb_alias}{$vtype}";
|
|
}
|
|
elseif (strpos($continent_config['action'], 'Deny') !== FALSE) {
|
|
$pfb['existing']['deny'][] = "{$pfb_alias}{$vtype}";
|
|
}
|
|
elseif ($continent_config['action'] == 'Alias_Native') {
|
|
$pfb['existing']['native'][] = "{$pfb_alias}{$vtype}";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find all enabled IPv4/IPv6 lists and DNSBL lists
|
|
// Find all enabled IPv4 'Custom List' header names and check if 'Emerging Threats Update' and 'Custom List Update' needs force updating
|
|
$list_types = array( 'pfblockernglistsv4' => '_v4',
|
|
'pfblockernglistsv6' => '_v6',
|
|
'pfblockerngdnsbl' => '_v4'
|
|
);
|
|
|
|
$pfb_invalid = FALSE;
|
|
foreach ($list_types as $ltype => $vtype) {
|
|
$lists = array();
|
|
|
|
$type_config = config_get_path("installedpackages/{$ltype}/config");
|
|
if (!empty($type_config) && $pfb['enable'] == 'on') {
|
|
foreach ($type_config as $list) {
|
|
|
|
// If only the 'customlist' is defined. Remove the 'List row' data.
|
|
if (isset($list['row']) && empty($list['row'][0]['url'])) {
|
|
unset($list['row']);
|
|
}
|
|
|
|
if (!empty($list['custom'])) {
|
|
$list['row'][] = array( 'header' => "{$list['aliasname']}_custom",
|
|
'custom' => $list['custom'],
|
|
'state' => 'Enabled',
|
|
'url' => 'custom'
|
|
);
|
|
}
|
|
$lists[] = $list;
|
|
}
|
|
}
|
|
|
|
// ADD DNSBL IP
|
|
if ($ltype == 'pfblockernglistsv4' && $pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && $pfb['dnsbl_ip'] != 'Disabled') {
|
|
$list = array();
|
|
$list['action'] = "{$pfb['dnsbl_ip']}";
|
|
$list['row'][] = array('format' => 'auto',
|
|
'state' => 'Enabled',
|
|
'url' => "{$pfb['dbdir']}/DNSBLIP{$vtype}.txt",
|
|
'header' => 'DNSBLIP');
|
|
$lists[] = $list;
|
|
}
|
|
|
|
if (!empty($lists)) {
|
|
foreach ($lists as $key => $list) {
|
|
|
|
// Remove any spaces or special characters in existing Aliasnames
|
|
if (preg_match("/\W/", $list['aliasname'])) {
|
|
$pfb_invalid = TRUE;
|
|
$list['aliasname'] = preg_replace("/\W/", '', $list['aliasname']);
|
|
config_set_path("installedpackages/{$ltype}/config/{$key}/aliasname", $list['aliasname']);
|
|
}
|
|
|
|
if (isset($list['row']) && $list['action'] != 'Disabled') {
|
|
|
|
// Force 'Alias Native' setting to any Alias with 'Advanced Inbound/Outbound -Invert src/dst' settings.
|
|
// This will bypass Deduplication and Reputation features.
|
|
if ($list['action'] != 'unbound' && ($list['autoaddrnot_in'] == 'on' ||
|
|
$list['autoaddrnot_out'] == 'on')) {
|
|
$list['action'] = 'Alias_Native';
|
|
}
|
|
|
|
foreach ($list['row'] as $hkey => $row) {
|
|
|
|
// Remove any spaces or special characters in existing Header names
|
|
if (preg_match("/\W/", $row['header'])) {
|
|
$pfb_invalid = TRUE;
|
|
$row['header'] = preg_replace("/\W/", '', $row['header']);
|
|
config_set_path("installedpackages/{$ltype}/config/{$key}/row/{$hkey}/header", $row['header']);
|
|
}
|
|
|
|
if ($ltype == 'pfblockerngdnsbl') {
|
|
$header = "{$row['header']}";
|
|
} else {
|
|
$header = "{$row['header']}{$vtype}";
|
|
}
|
|
|
|
// Collect enabled lists
|
|
if (!empty($row['url']) && $row['state'] != 'Disabled') {
|
|
if (strpos($list['action'], 'Match') !== FALSE) {
|
|
$pfb['existing']['match'][] = "{$header}";
|
|
}
|
|
elseif (strpos($list['action'], 'Permit') !== FALSE) {
|
|
$pfb['existing']['permit'][] = "{$header}";
|
|
}
|
|
elseif (strpos($list['action'], 'Deny') !== FALSE) {
|
|
$pfb['existing']['deny'][] = "{$header}";
|
|
}
|
|
elseif ($list['action'] == 'Alias_Native') {
|
|
$pfb['existing']['native'][] = "{$header}";
|
|
}
|
|
elseif ($list['action'] == 'unbound') {
|
|
$pfb['existing']['dnsbl'][] = "{$header}";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save any existing Alias/Header names that have spaces or special characters
|
|
if ($pfb_invalid) {
|
|
write_config('pfBlockerNG: Remove spaces/special characters in Alias/Header names');
|
|
}
|
|
|
|
// If 'TLD' enabled and TLD Blacklists are defined, add to enabled DNSBL lists
|
|
if ($pfb['dnsbl_tld']) {
|
|
$tld_blacklist = pfbng_text_area_decode($pfb['dnsblconfig']['tldblacklist'], TRUE, FALSE, TRUE);
|
|
if (!empty($tld_blacklist)) {
|
|
$pfb['existing']['dnsbl'][] = 'DNSBL_TLD';
|
|
}
|
|
}
|
|
|
|
// Add 'Reputation - ccwhite Action' if found
|
|
if ($pfb['ccwhite'] == 'match' && file_exists("{$pfb['matchdir']}/matchdedup_v4.txt")) {
|
|
$pfb['existing']['match'][] = 'matchdedup_v4';
|
|
}
|
|
|
|
// Add enabled 'DNSBL Blacklist categories'
|
|
if (isset($pfb['blconfig']) &&
|
|
$pfb['blconfig']['blacklist_enable'] != 'Disable' &&
|
|
!empty($pfb['blconfig']['blacklist_selected'])) {
|
|
|
|
$selected = array_flip(explode(',', $pfb['blconfig']['blacklist_selected'])) ?: array();
|
|
foreach ($pfb['blconfig']['item'] as $item) {
|
|
|
|
if (isset($selected[$item['xml']]) && !empty($item['selected'])) {
|
|
$categories = explode(',', $item['selected']) ?: array();
|
|
foreach ($categories as $category) {
|
|
if (!empty($category)) {
|
|
$category = str_replace('-', '_', $category);
|
|
$pfb['existing']['dnsbl'][] = "{$item['title']}_{$category}";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collect all .txt file names for each list type
|
|
$list_types = array( 'match' => $pfb['matchdir'], 'permit' => $pfb['permitdir'], 'deny' => $pfb['denydir'],
|
|
'native' => $pfb['nativedir'], 'dnsbl' => $pfb['dnsdir']);
|
|
|
|
// Collect all previouly downloaded filename headers
|
|
foreach ($list_types as $pftype => $pfbfolder) {
|
|
|
|
$pfb_files = glob("{$pfbfolder}/*.txt");
|
|
foreach ($pfb_files as $pfb_list) {
|
|
$pfb['actual'][$pftype][] = basename($pfb_list, '.txt');
|
|
}
|
|
|
|
$results = array_diff($pfb['actual'][$pftype], $pfb['existing'][$pftype]);
|
|
|
|
// Remove any DNSBL Orig files that are not referenced
|
|
if ($pftype == 'dnsbl') {
|
|
$orig_list = glob("{$pfb['dnsorigdir']}/*.orig");
|
|
if (!empty($orig_list) && is_array($orig_list)) {
|
|
$orig_list_final = array();
|
|
foreach ($orig_list as $pfb_orig) {
|
|
$orig_list_final[] = basename($pfb_orig, '.orig');
|
|
}
|
|
|
|
$results_flip = array_flip($pfb['actual'][$pftype]);
|
|
if (!empty($results_flip) && is_array($results_flip)) {
|
|
foreach ($orig_list_final as $orig) {
|
|
if (!isset($results_flip[$orig])) {
|
|
unlink_if_exists("{$pfb['dnsorigdir']}/{$orig}.*");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (empty($results)) {
|
|
continue; // No changes required
|
|
}
|
|
|
|
$f_result = implode(',', $results);
|
|
if (empty(pfb_filter($f_result, PFB_FILTER_CSV, 'Failed to complete sync of Feeds'))) {
|
|
pfb_logger("\nFailed to complete sync of Feeds!", 1);
|
|
continue;
|
|
}
|
|
|
|
$log = "\n[ Removing '{$pftype}' \tList(s) : {$f_result} ]";
|
|
pfb_logger("{$log}", 1);
|
|
|
|
// Process to remove lists from IP Masterfile/DB folder if they are not referenced any longer
|
|
switch ($pftype) {
|
|
case 'deny':
|
|
// Script to Remove un-associated List(s)
|
|
exec("{$pfb['script']} remove x x x {$f_result} {$elog}");
|
|
$pfb['summary'] = $pfb['remove'] = TRUE;
|
|
break;
|
|
case 'match':
|
|
case 'permit':
|
|
case 'native':
|
|
foreach ($results as $pfb_result) {
|
|
if (!empty(pfb_filter($pfb_result, PFB_FILTER_WORD, "Remove un-associated List - {$pftype}"))) {
|
|
unlink_if_exists("{$pfbfolder}/{$pfb_result}.txt");
|
|
unlink_if_exists("{$pfb['origdir']}/{$pfb_result}.*");
|
|
} else {
|
|
pfb_logger("\nFailed to remove Native Feed [$pfb_result}]", 1);
|
|
}
|
|
}
|
|
$pfb['summary'] = $pfb['remove'] = TRUE;
|
|
break;
|
|
case 'dnsbl':
|
|
foreach ($results as $pfb_result) {
|
|
if (!empty(pfb_filter($pfb_result, PFB_FILTER_WORD, "Remove un-associated List - {$pftype}"))) {
|
|
unlink_if_exists("{$pfb['dnsorigdir']}/{$pfb_result}.*");
|
|
} else {
|
|
pfb_logger("\nFailed to remove DNSBL Feed [{$pfb_result}]", 1);
|
|
}
|
|
}
|
|
|
|
rmdir_recursive("{$pfb['dnsdir']}");
|
|
safe_mkdir("{$pfb['dnsdir']}");
|
|
|
|
pfb_logger("\n ** DNSBL Changes found, Reloading...\n", 1);
|
|
$pfb['reuse_dnsbl'] = 'on';
|
|
break;
|
|
}
|
|
|
|
// Allow rebuilding of changed Alias to purge 'SKIP' Lists (when pfBlockerNG is enabled)
|
|
if ($pfb['enable'] == 'on' && $pftype != 'dnsbl') {
|
|
foreach ($ip_types as $ltype => $vtype) {
|
|
foreach ($results as $removed_header) {
|
|
foreach (config_get_path("installedpackages/{$ltype}/config", []) as $list) {
|
|
if (!empty($list['row'])) {
|
|
foreach ($list['row'] as $row) {
|
|
$removed = rtrim($removed_header, ',');
|
|
if ($row['header'] == $removed) {
|
|
$pfb['summary'] = $pfb['remove'] = TRUE;
|
|
|
|
// Add Alias to update array
|
|
$pfb_alias_lists[] = "pfB_{$list['aliasname']}{$vtype}";
|
|
$pfb_alias_lists_all[] = "pfB_{$list['aliasname']}{$vtype}";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#########################################################
|
|
# Clear Match/Pass/ET/Original Files/Folders #
|
|
#########################################################
|
|
|
|
// When pfBlockerNG is Disabled and 'Keep Blocklists' is Disabled.
|
|
if ($pfb['enable'] == '' && $pfb['keep'] == '' && !$pfb['install']) {
|
|
$log = "\n Removing DB Files/Folders \n";
|
|
pfb_logger("{$log}", 1);
|
|
pfb_clear_contents();
|
|
}
|
|
|
|
#################################################
|
|
# Create IP Suppression Txt File #
|
|
#################################################
|
|
|
|
if ($pfb['enable'] == 'on' && $pfb['supp'] == 'on') {
|
|
pfb_create_suppression_file();
|
|
}
|
|
|
|
#########################################
|
|
# DNSBL - Processes #
|
|
#########################################
|
|
|
|
if (!$pfb['save']) {
|
|
$log = "\n===[ DNSBL Process ]================================================\n";
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
|
|
// Define DNSBL arrays and variables
|
|
$pfb['alias_dnsbl_all'] = array(); // Array of all DNSBL aliases
|
|
$pfb['tld_update'] = array(); // Array of all DNSBL Aliases/Feeds used for TLD Function
|
|
$pfb['domain_update'] = FALSE; // Flag to signal update Unbound
|
|
$pfb['updateip'] = FALSE; // Flag to signal updates to DNSBL IP lists
|
|
|
|
$dnsbl_error = FALSE;
|
|
if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && !$pfb['save']) {
|
|
|
|
// Terminate if DNSBL VIP is empty
|
|
if (empty($pfb['dnsbl_vip']) || empty($pfb['dnsbl_port']) || empty($pfb['dnsbl_port_ssl'])) {
|
|
$log = "\n\n===[ DNSBL Virtual IP and/or Ports are not defined. Exiting ]======\n";
|
|
pfb_logger("{$log}", 1);
|
|
$dnsbl_error = TRUE;
|
|
}
|
|
}
|
|
|
|
if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && !$pfb['save'] && !$dnsbl_error) {
|
|
if ((config_get_path('installedpackages/pfblockerngdnsbl/config') != null) ||
|
|
(config_get_path('installedpackages/pfblockerngblacklist/blacklist_enable') == 'Enable')) {
|
|
|
|
$dnsbl_missing = FALSE;
|
|
|
|
// Collect existing DNSBL group statistics
|
|
// SQLite3 Database format [ group name, updated timestamp, total domain count, total blocked count ]
|
|
|
|
$pfb['dnsbl_info_stats'] = array();
|
|
if (file_exists("{$pfb['dnsbl_info']}")) {
|
|
pfb_logger("\n Loading DNSBL Statistics...", 1);
|
|
|
|
$db_handle = pfb_open_sqlite(1, 'Reading DNSBL database');
|
|
if ($db_handle) {
|
|
$result = $db_handle->query("SELECT * FROM dnsbl;");
|
|
if ($result) {
|
|
while ($res = $result->fetchArray(SQLITE3_ASSOC)) {
|
|
$pfb['dnsbl_info_stats'][] = $res;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
pfb_logger(" FAILED", 1);
|
|
unlink_if_exists("{$pfb['dnsbl_info']}");
|
|
$dnsbl_missing = TRUE;
|
|
}
|
|
|
|
pfb_close_sqlite($db_handle);
|
|
pfb_logger(" completed", 1);
|
|
}
|
|
else {
|
|
$dnsbl_missing = TRUE;
|
|
}
|
|
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
|
|
// When DNSBL python blocking mode is enabled, zone should not exist, and data should exist
|
|
if (!$pfb['dnsbl_tld']) {
|
|
if (file_exists($pfb['unbound_py_zone'])) {
|
|
$dnsbl_missing = TRUE;
|
|
}
|
|
if (!file_exists($pfb['unbound_py_data'])) {
|
|
$dnsbl_missing = TRUE;
|
|
}
|
|
}
|
|
|
|
// When DNSBL python blocking mode is enabled, atleast one 'data or zone' file must exist
|
|
else {
|
|
$found = FALSE;
|
|
if (file_exists($pfb['unbound_py_data'])) {
|
|
$found = TRUE;
|
|
}
|
|
if (file_exists($pfb['unbound_py_zone'])) {
|
|
$found = TRUE;
|
|
}
|
|
if (!$found) {
|
|
$dnsbl_missing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if DNSBL database is missing, when DNSBL python blocking mode is not enabled
|
|
if (!$pfb['dnsbl_py_blacklist'] && !file_exists("{$pfb['dnsbl_file']}.conf")) {
|
|
$dnsbl_missing = TRUE;
|
|
}
|
|
|
|
if ($dnsbl_missing) {
|
|
$log = "\n Missing DNSBL stats and/or Unbound DNSBL files - Rebuilding\n";
|
|
pfb_logger("{$log}", 1);
|
|
$pfb['reuse_dnsbl'] = 'on';
|
|
touch("{$pfb['dnsbl_file']}.reload");
|
|
}
|
|
|
|
// SafeSearch
|
|
pfb_logger("\n Loading DNSBL SafeSearch...", 1);
|
|
$safesearch_hosts = array();
|
|
$pfb['safesearch_tlds'] = array();
|
|
$safesearch_types = array( array( 'safesearch_enable', 'Enable', 'safesearch'),
|
|
array( 'safesearch_youtube', 'Strict', 'youtube_restrict'),
|
|
array( 'safesearch_youtube', 'Moderate', 'youtube_restrictmoderate'),
|
|
array( 'safesearch_doh', 'Enable', 'doh')
|
|
);
|
|
|
|
$safesearch_update = $pfb_found = FALSE;
|
|
$CNAME_LOG = '';
|
|
|
|
// Remove deprecated firefox conf file
|
|
if (file_exists("{$pfb['dnsbldir']}/pfb_dnsbl.firefoxdoh.conf")) {
|
|
unlink_if_exists("{$pfb['dnsbldir']}/pfb_dnsbl.firefoxdoh.conf");
|
|
$safesearch_update = TRUE;
|
|
}
|
|
|
|
foreach ($safesearch_types as $safe_type) {
|
|
if ($pfb[$safe_type[0]] == $safe_type[1]) {
|
|
$pfb_found = TRUE;
|
|
|
|
// SafeSearch DoH validation (Add/remove comment '#' to DoH entries)
|
|
if ($safe_type[0] == 'safesearch_doh') {
|
|
$doh_file = file($pfb["dnsbl_{$safe_type[2]}"]);
|
|
if (!empty($doh_file)) {
|
|
$doh_file_final = '';
|
|
foreach ($doh_file as $host) {
|
|
if (strpos($host, '#') !== FALSE) {
|
|
$host = ltrim(str_replace('#', '', $host));
|
|
}
|
|
|
|
$doh_found = FALSE;
|
|
if (!empty($pfb['safesearch_doh_list'])) {
|
|
foreach ($pfb['safesearch_doh_list'] as $doh) {
|
|
if (strpos($host, $doh) !== FALSE) {
|
|
$doh_found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!$doh_found) {
|
|
$doh_file_final .= "# {$host}";
|
|
} else {
|
|
$doh_file_final .= "{$host}";
|
|
}
|
|
}
|
|
if (!empty($doh_file_final)) {
|
|
@file_put_contents($pfb["dnsbl_{$safe_type[2]}"], $doh_file_final, LOCK_EX);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
|
|
// Python Mode determine if user manually entered these SafeSearch CNAMES to avoid duplicate zone errors
|
|
if ($safe_type[0] == 'safesearch_enable') {
|
|
$DDG = $PIX = $ss_cname = FALSE;
|
|
if (file_exists('/var/unbound/unbound.conf')) {
|
|
$pfb_py_conf_ex = @file_get_contents('/var/unbound/unbound.conf');
|
|
if (strstr($pfb_py_conf_ex, 'duckduckgo.com')) {
|
|
$CNAME_LOG = "\n *** Found manual Resolver entry for duckduckgo.com. This manual entry should be removed in future!\n";
|
|
$DDG = TRUE;
|
|
}
|
|
if (strstr($pfb_py_conf_ex, 'pixabay.com')) {
|
|
$CNAME_LOG .= "\n *** Found manual Resolver entry for pixabay.com. This manual entry should be removed in future!\n";
|
|
$PIX = TRUE;
|
|
}
|
|
}
|
|
$ss_ex = @file_get_contents("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf");
|
|
}
|
|
unlink_if_exists("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf"); // Remove Unbound mode file
|
|
|
|
$safesearch_file = file($pfb["dnsbl_{$safe_type[2]}"]);
|
|
if (!empty($safesearch_file)) {
|
|
foreach ($safesearch_file as $host) {
|
|
|
|
if (substr($host, 0, 1) == '#') {
|
|
continue;
|
|
}
|
|
|
|
$line = str_replace('"', '', strstr($host, '"', False));
|
|
$host_ip = trim(str_replace('A ', '', strstr($line, 'A ', FALSE)));
|
|
$domain = strstr($line, ' ', TRUE);
|
|
if (substr($domain, 0, 4) == 'www.') {
|
|
$domain = substr($domain, 4);
|
|
}
|
|
|
|
if (strpos($line, ' A ') !== FALSE) {
|
|
$safesearch_hosts[$domain]['A'] = $host_ip;
|
|
} elseif (strpos($line, ' AAAA ') !== FALSE) {
|
|
$safesearch_hosts[$domain]['AAAA'] = $host_ip;
|
|
|
|
# CNAME SafeSearch is not compatible in Python Mode, use Unbound mode for now
|
|
} elseif (strpos($line, ' CNAME ') !== FALSE) {
|
|
$ss_cname = TRUE;
|
|
if (!$DDG && strpos($line, 'duckduckgo.com') !== FALSE) {
|
|
@file_put_contents("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf", "{$host}", FILE_APPEND);
|
|
}
|
|
if (!$PIX && strpos($line, 'pixabay.com') !== FALSE) {
|
|
@file_put_contents("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf", "{$host}", FILE_APPEND);
|
|
}
|
|
# $cname = trim(str_replace('CNAME ', '', strstr($line, 'CNAME', FALSE)));
|
|
# $safesearch_hosts[$domain]['A'] = 'cname';
|
|
# $safesearch_hosts[$domain]['AAAA'] = $cname;
|
|
} elseif (strpos($line, 'always_nxdomain') !== FALSE) {
|
|
$safesearch_hosts[$domain]['nxdomain'] = '';
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($safe_type[2] == 'safesearch' && ($ss_cname && $ss_ex !== @file_get_contents("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf")) ||
|
|
(($DDG || $PIX) && !$ss_cname)) {
|
|
$safesearch_update = TRUE;
|
|
}
|
|
}
|
|
else {
|
|
unlink_if_exists("/var/unbound/pfb_py_ss.*"); // Remove Python mode file
|
|
$ss_cname = FALSE;
|
|
|
|
// Determine if user manually entered these SafeSearch CNAMES to avoid duplicate zone errors
|
|
if ($safe_type[0] == 'safesearch_enable') {
|
|
|
|
// Step one - See if DDG and PIX are manually configured
|
|
$DDG = $PIX = FALSE;
|
|
if (file_exists('/var/unbound/unbound.conf')) {
|
|
$pfb_py_conf_ex = @file_get_contents('/var/unbound/unbound.conf');
|
|
if (strstr($pfb_py_conf_ex, 'duckduckgo.com')) {
|
|
$CNAME_LOG = "\n *** Found manual Resolver entry for duckduckgo.com. This manual entry should be removed in future!\n";
|
|
$DDG = TRUE;
|
|
}
|
|
if (strstr($pfb_py_conf_ex, 'pixabay.com')) {
|
|
$CNAME_LOG .= "\n *** Found manual Resolver entry for pixabay.com. This manual entry should be removed in future!\n";
|
|
$PIX = TRUE;
|
|
}
|
|
}
|
|
|
|
// Step two - If they are configured rebuild SafeSearch file
|
|
$safesearch_file = file($pfb["dnsbl_{$safe_type[2]}"]);
|
|
$ss_lines = '';
|
|
if (!empty($safesearch_file)) {
|
|
foreach ($safesearch_file as $host) {
|
|
if ($DDG && strpos($host, 'duckduckgo.com') !== FALSE) {
|
|
$ss_lines .= "# {$host}";
|
|
$ss_cname = TRUE;
|
|
}
|
|
elseif ($PIX && strpos($host, 'pixabay.com') !== FALSE) {
|
|
$ss_lines .= "# {$host}";
|
|
$ss_cname = TRUE;
|
|
}
|
|
else {
|
|
$ss_lines .= "{$host}";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Step three - Compare previous SafeSearch file to new and update if changes are found
|
|
if ($ss_cname) {
|
|
if (file_exists("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf")) {
|
|
$ss_lines_ex = @file_get_contents("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf");
|
|
|
|
if ($ss_lines !== $ss_lines_ex) {
|
|
@file_put_contents("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf", $ss_lines, LOCK_EX);
|
|
$safesearch_update = TRUE;
|
|
}
|
|
}
|
|
else {
|
|
@file_put_contents("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf", $ss_lines, LOCK_EX);
|
|
$safesearch_update = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy file if not exists or has been updated
|
|
if (!file_exists("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf") ||
|
|
@md5_file($pfb["dnsbl_{$safe_type[2]}"]) !==
|
|
@md5_file("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf")) {
|
|
|
|
// Temp Workaround for Duplicate CNAME Zone issue:
|
|
if (!$ss_cname) {
|
|
@copy($pfb["dnsbl_{$safe_type[2]}"], "{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf");
|
|
$safesearch_update = TRUE;
|
|
}
|
|
}
|
|
|
|
// Collect SafeSearch domains for wildcard whitelisting
|
|
exec("cut -d '\"' -f2 {$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf | cut -d ' ' -f1", $safesearch_hosts);
|
|
|
|
// Collect all Domains/TLDs for 'TLD Blacklist' validation (Unbound mode only. Cannot have duplicate zones)
|
|
if (!$pfb['dnsbl_py_blacklist'] && !empty($safesearch_hosts)) {
|
|
foreach ($safesearch_hosts as $host) {
|
|
$safesearch_tlds[$host] = '';
|
|
for ($i= substr_count($host, '.'); $i > 0; $i--) {
|
|
$host = ltrim(strstr($host, '.', FALSE), '.');
|
|
$pfb['safesearch_tlds'][$host] = '';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
elseif (file_exists("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf")) {
|
|
unlink("{$pfb['dnsbldir']}/pfb_dnsbl.{$safe_type[2]}.conf");
|
|
$safesearch_update = TRUE;
|
|
}
|
|
}
|
|
|
|
// Python mode create a CSV list of SafeSearch hosts
|
|
if ($pfb['dnsbl_py_blacklist'] && !empty($safesearch_hosts)) {
|
|
foreach ($safesearch_hosts as $host => $data) {
|
|
if (isset($data['nxdomain'])) {
|
|
$line = "{$host},nxdomain,nxdomain\n";
|
|
} else {
|
|
$line = "{$host}," . ($data['A'] ?: '') . ',' . ($data['AAAA'] ?: '') . "\n"
|
|
. "www.{$host}," . ($data['A'] ?: '') . ',' . ($data['AAAA'] ?: '') . "\n";
|
|
}
|
|
@file_put_contents('/var/unbound/pfb_py_ss.raw', $line, FILE_APPEND);
|
|
}
|
|
|
|
// Copy file if not exists or has been updated
|
|
if (!file_exists("{$pfb['unbound_py_ss']}") ||
|
|
@md5_file("{$pfb['unbound_py_ss']}") !==
|
|
@md5_file('/var/unbound/pfb_py_ss.raw')) {
|
|
@rename('/var/unbound/pfb_py_ss.raw', "{$pfb['unbound_py_ss']}");
|
|
$safesearch_update = TRUE;
|
|
touch("{$pfb['dnsbl_file']}.reload");
|
|
}
|
|
unlink_if_exists('/var/unbound/pfb_py_ss.raw');
|
|
}
|
|
else {
|
|
unlink_if_exists("/var/unbound/pfb_py_ss.*");
|
|
}
|
|
|
|
// Wildcard whitelist SafeSearch domains (Unbound mode only)
|
|
$pfb_whitelist = '';
|
|
if (!$pfb['dnsbl_py_blacklist'] && !empty($safesearch_hosts)) {
|
|
$safesearch_hosts = array_unique($safesearch_hosts);
|
|
foreach ($safesearch_hosts as $line) {
|
|
if (!empty($line)) {
|
|
|
|
// Remove 'www.' prefix
|
|
if (substr($line, 0, 4) == 'www.') {
|
|
$line = substr($line, 4);
|
|
}
|
|
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
$pfb_whitelist .= ".{$line},,\n,{$line},,\n";
|
|
} else {
|
|
$pfb_whitelist .= ".{$line} 60\n\"{$line} 60\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$s_log = ' disabled';
|
|
if ($pfb_found) {
|
|
$s_log = ' enabled';
|
|
}
|
|
pfb_logger("{$s_log}{$CNAME_LOG}", 1);
|
|
|
|
// Collect Whitelist, create string, and save to file (for grep -vF -f cmd)
|
|
pfb_logger("\n Loading DNSBL Whitelist...", 1);
|
|
$pfb_white = pfbng_text_area_decode($pfb['dnsblconfig']['suppression'], TRUE, FALSE, TRUE);
|
|
if (!empty($pfb_white)) {
|
|
foreach ($pfb_white as $line) {
|
|
if (!empty($line)) {
|
|
$wildcard = FALSE;
|
|
if (substr($line, 0, 1) == '.') {
|
|
$line = ltrim($line, '.');
|
|
$wildcard = TRUE;
|
|
}
|
|
|
|
// Remove 'www.' prefix
|
|
if (substr($line, 0, 4) == 'www.') {
|
|
$line = substr($line, 4);
|
|
}
|
|
|
|
if ($wildcard) {
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
$pfb_whitelist .= ".{$line},,\n,{$line},,\n";
|
|
} else {
|
|
$pfb_whitelist .= ".{$line} 60\n\"{$line} 60\n";
|
|
}
|
|
} else {
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
$pfb_whitelist .= ",{$line},,\n,www.{$line},,\n";
|
|
} else {
|
|
$pfb_whitelist .= "\"{$line} 60\n\"www.{$line} 60\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Added due to SWC Feed
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
$pfb_whitelist .= ",localhost.localdomain,,\n";
|
|
} else {
|
|
$pfb_whitelist .= "\"localhost.localdomain 60\n";
|
|
}
|
|
|
|
@file_put_contents("{$pfb['dnsbl_supptxt']}", $pfb_whitelist, LOCK_EX);
|
|
pfb_logger(" completed", 1);
|
|
|
|
if (!$pfb['dnsbl_py_blacklist'] && $safesearch_update) {
|
|
$log = "\n DNSBL - SafeSearch changes found - Rebuilding!\n";
|
|
pfb_logger("{$log}", 1);
|
|
$pfb['reuse_dnsbl'] = 'on';
|
|
touch("{$pfb['dnsbl_file']}.reload");
|
|
}
|
|
|
|
// Call TOP1M whitelist process
|
|
if ($pfb['dnsbl_alexa'] == 'on') {
|
|
pfb_logger("\n Loading TOP1M Whitelist...", 1);
|
|
|
|
// Check if TOP1M database exists
|
|
if (!file_exists("{$pfb['dbdir']}/top-1m.csv")) {
|
|
// Check if TOP1M download already in progress
|
|
exec('/bin/ps -wax', $result_cron);
|
|
if (!preg_grep("/pfblockerng[.]php\s+al/", $result_cron)) {
|
|
$log = "\n TOP1M Database downloading ( approx 21MB ) ... Please wait ...\n";
|
|
pfb_logger("{$log}", 1);
|
|
exec('/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php al');
|
|
}
|
|
else {
|
|
$log = "\n TOP1M download already in process...\n";
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
}
|
|
|
|
// Process TOP1M database
|
|
if (!file_exists("{$pfb['dbdir']}/pfbalexawhitelist.txt") ||
|
|
file_exists("{$pfb['dbdir']}/top-1m.update")) {
|
|
pfblockerng_top1m();
|
|
$pfb['reuse_dnsbl'] = 'on';
|
|
touch("{$pfb['dnsbl_file']}.reload");
|
|
pfb_logger("\n DNSBL - TOP1M changes found - Rebuilding!\n", 1);
|
|
}
|
|
pfb_logger(" completed", 1);
|
|
}
|
|
pfb_logger("\n", 1);
|
|
|
|
// List of invalid Domains to skip parsed failed logging function
|
|
$dnsbl_skip = array_flip (array('broadcasthost',
|
|
'local',
|
|
'localhost',
|
|
'<pre>',
|
|
'Vault',
|
|
'Site',
|
|
'list',
|
|
'::1',
|
|
':',
|
|
'ip6-localhost',
|
|
'ip6-localnet',
|
|
'ip6-mcastprefix',
|
|
'ip6-allnodes',
|
|
'ip6-allrouters',
|
|
'ip6-allhosts'
|
|
));
|
|
|
|
// List of Alienvault OTX Indicator Types
|
|
$alienvault_types = array_flip(array('domain', 'hostname', 'URL'));
|
|
|
|
// Collect feeds and custom list configuration and format into one array ($lists).
|
|
$lists = array();
|
|
|
|
// Add DNSBL Category to '$lists array'
|
|
if (isset($pfb['blconfig']) &&
|
|
$pfb['blconfig']['blacklist_enable'] != 'Disable' &&
|
|
!empty($pfb['blconfig']['blacklist_selected'])) {
|
|
|
|
$bl_count = 0;
|
|
$bl_validate = FALSE;
|
|
|
|
$selected = array_flip(explode(',', $pfb['blconfig']['blacklist_selected'])) ?: array();
|
|
foreach ($pfb['blconfig']['item'] as $item) {
|
|
$type = "{$item['xml']}";
|
|
|
|
if (isset($selected[$type]) && !empty($item['selected'])) {
|
|
|
|
$bl_count++;
|
|
$categories = explode(',', $item['selected']) ?: array();
|
|
|
|
$list = array();
|
|
$list['aliasname'] = "{$item['title']}";
|
|
$list['action'] = 'unbound';
|
|
$list['logging'] = $pfb['blconfig']['blacklist_logging'] ?: 'enabled';
|
|
$list['filter_alexa'] = '';
|
|
|
|
$feedname = strtolower($item['title']);
|
|
$update_flag = "{$pfb['dbdir']}/{$feedname}/{$feedname}.update";
|
|
|
|
foreach ($categories as $category) {
|
|
|
|
// Replace dash to underscore in Header name
|
|
$category = str_replace('-', '_', $category);
|
|
|
|
if (!empty($category)) {
|
|
$list['row'][] = array( 'format' => 'auto',
|
|
'state' => 'Enabled',
|
|
'url' => "{$pfb['dbdir']}/{$type}/{$type}_{$category}",
|
|
'header' => "{$item['title']}_{$category}"
|
|
);
|
|
|
|
// If update available set Update flag for each selected Category
|
|
if (file_exists("{$update_flag}")) {
|
|
touch("{$pfb['dnsdir']}/{$item['title']}_{$category}.update");
|
|
}
|
|
}
|
|
}
|
|
unlink_if_exists("{$update_flag}");
|
|
|
|
if (isset($list['row'])) {
|
|
$lists[] = $list;
|
|
}
|
|
|
|
// Check if Blacklist database has not been previously downloaded
|
|
if (!is_dir("{$pfb['dbdir']}/{$type}") ||
|
|
is_dir("{$pfb['dbdir']}/{$type}") && (count(scandir("{$pfb['dbdir']}/{$type}")) <= 2)) {
|
|
if (!is_array($bl_validate)) {
|
|
$bl_validate = array();
|
|
}
|
|
$bl_validate[$type] = $item['size'];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Download Blacklist databases that are not previously downloaded
|
|
if ($bl_validate) {
|
|
|
|
// Create commandline arguments for download script
|
|
$bl_string = $bl_sources = '';
|
|
foreach ($bl_validate as $type => $size) {
|
|
$bl_string .= ",{$type}";
|
|
$bl_sources .= " {$type} (~{$size}MB) |";
|
|
}
|
|
|
|
if (!empty(pfb_filter($bl_string, PFB_FILTER_CSV, 'Blacklist database commandline arguments'))) {
|
|
$bl_string = ltrim($bl_string, ',');
|
|
$bl_sources = rtrim($bl_sources, ' |');
|
|
|
|
// Check if Blacklist download already in progress
|
|
exec('/bin/ps -wax', $result_cron);
|
|
if (!preg_grep("/pfblockerng[.]php\s+?(bl|bls)/", $result_cron)) {
|
|
|
|
$log = "\nDownloading Blacklist Database(s) [{$bl_sources} ] ... Please wait ...\n";
|
|
pfb_logger("{$log}", 1);
|
|
exec("/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php bls {$bl_string} 2>&1", $pfb_return);
|
|
|
|
if (is_array($pfb_return)) {
|
|
foreach ($pfb_return as $key => $return_output) {
|
|
|
|
pfb_logger("{$return_output}\n", 1);
|
|
|
|
// On download failure, remove associated Blacklist category configuration
|
|
if (strpos($return_output, 'Failed') !== FALSE) {
|
|
unset($lists[$key]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$log = "\nBlacklist Database download already in process... Try again later...\n";
|
|
pfb_logger("{$log}", 1);
|
|
|
|
// Remove Blacklist Category updates until database download is completed
|
|
while ($bl_count != 0) {
|
|
array_pop($lists);
|
|
$bl_count--;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
pfb_logger("\n Invalid Blacklist arguments [{$bl_string}]", 1);
|
|
}
|
|
}
|
|
else {
|
|
pfb_logger(" Blacklist database(s) ... exists.\n", 1);
|
|
}
|
|
}
|
|
|
|
foreach (config_get_path('installedpackages/pfblockerngdnsbl/config', []) as $list) {
|
|
// If only the 'customlist' is defined. Remove the 'List row' data.
|
|
if (isset($list['row']) && empty($list['row'][0]['url'])) {
|
|
unset($list['row']);
|
|
}
|
|
|
|
if (!empty($list['custom'])) {
|
|
$list['row'][] = array( 'header' => "{$list['aliasname']}_custom",
|
|
'custom' => $list['custom'],
|
|
'state' => 'Enabled',
|
|
'url' => 'custom'
|
|
);
|
|
}
|
|
|
|
// Move DNSBL Group to primary position before the Blacklist settings
|
|
if ($list['order'] == 'primary') {
|
|
$list_primary = array();
|
|
$list_primary[] = $list;
|
|
$lists = array_merge($list_primary, $lists);
|
|
} else {
|
|
$lists[] = $list;
|
|
}
|
|
}
|
|
|
|
foreach ($lists as $list) {
|
|
|
|
// Reset variables once per alias
|
|
$lists_dnsbl_current = array(); // Array of all active Lists in current alias
|
|
$pfb['aliasupdate'] = FALSE; // Flag to signal changes to alias
|
|
$pfb['domain_clear'] = FALSE; // Flag to signal no Aliases defined or all Aliases disabled.
|
|
$alias_cnt = 0;
|
|
|
|
if ($list['action'] != 'Disabled' && isset($list['row'])) {
|
|
$alias = "DNSBL_{$list['aliasname']}";
|
|
if (empty(pfb_filter($alias, PFB_FILTER_WORD, 'DNSBL - Processes'))) {
|
|
pfb_logger("\n Invalid Aliasname:{$list['aliasname']}, *skipping*", 1);
|
|
continue;
|
|
}
|
|
$pfb['alias_dnsbl_all'][] = "{$alias}";
|
|
|
|
foreach ($list['row'] as $key => $row) {
|
|
if (!empty($row['url']) && $row['state'] != 'Disabled') {
|
|
|
|
// Empty header field validation check
|
|
$header = pfb_filter($row['header'], PFB_FILTER_WORD, 'DNSBL - Processes');
|
|
if (empty($header)) {
|
|
$log = "\n[ {$row['url']} ]{$logtab} Header Field cannot be empty. *Skipping* \n";
|
|
pfb_logger("{$log}", 2);
|
|
continue;
|
|
}
|
|
$header_esc = escapeshellarg($header);
|
|
|
|
$liteparser = FALSE; // Minimal DNSBL Parser
|
|
$rev_format = FALSE; // Host style format is reversed
|
|
$domain_data_ip = array(); // Array of IPs found in feed
|
|
$domain_data = ''; // List of Domains found in feed
|
|
|
|
// If row is a custom_list, set flag.
|
|
if (isset($row['custom'])) {
|
|
$custom = TRUE;
|
|
} else {
|
|
$custom = FALSE;
|
|
}
|
|
|
|
// Global DNSBL Logging/Blocking mode
|
|
if (!empty($pfb['dnsbl_global_log'])) {
|
|
$list['logging'] = $pfb['dnsbl_global_log'];
|
|
}
|
|
|
|
// Force Unbound mode Logging for 'disabled_log' to 'enabled'
|
|
if (!$pfb['dnsbl_py_blacklist'] && $list['logging'] == 'disabled_log') {
|
|
$list['logging'] = 'enabled';
|
|
}
|
|
|
|
// If Null Blocking mode is selected, use '0.0.0.0|::0', otherwise utilize DNSBL WebServer/DNSBL VIP
|
|
if ($list['logging'] == 'disabled') {
|
|
$sinkhole_type4 = '0.0.0.0';
|
|
$sinkhole_type6 = '::0';
|
|
$logging_type = '2'; // Null Blocking no logging
|
|
}
|
|
elseif ($list['logging'] == 'disabled_log') {
|
|
$logging_type = '0'; // Null Blocking logging
|
|
} else {
|
|
$sinkhole_type4 = "{$pfb['dnsbl_vip']}";
|
|
$sinkhole_type6 = "::{$pfb['dnsbl_vip']}";
|
|
$logging_type = '1'; // DNSBL VIP logging
|
|
}
|
|
|
|
// Determine 'list' details (return array $pfbarr)
|
|
pfb_determine_list_detail($list['action'], $header, 'pfblockerngdnsblsettings', '0');
|
|
$pfbadv = $pfbarr['adv'];
|
|
$pfbfolder = $pfbarr['folder'];
|
|
$pfborig = $pfbarr['orig'];
|
|
$pfbreuse = $pfbarr['reuse'];
|
|
$logtab = $pfbarr['logtab'];
|
|
|
|
if (file_exists("{$pfbfolder}/{$header}.txt") &&
|
|
!file_exists("{$pfbfolder}/{$header}.update") &&
|
|
!file_exists("{$pfbfolder}/{$header}.fail") &&
|
|
$pfbreuse == '') {
|
|
|
|
|
|
if ($row['state'] == 'Hold') {
|
|
$log = "\n[ {$header} ]{$logtab} static hold. [ NOW ]";
|
|
} else {
|
|
$log = "\n[ {$header} ]{$logtab} exists. [ NOW ]";
|
|
}
|
|
pfb_logger("{$log}", 1);
|
|
|
|
// Collect existing list stats
|
|
$lists_dnsbl_all[] = "{$row['header']}.txt";
|
|
$lists_dnsbl_current[] = "{$row['header']}";
|
|
$file_esc = escapeshellarg("{$pfbfolder}/{$header}.txt");
|
|
$list_cnt = exec("{$pfb['grep']} -c ^ {$file_esc}");
|
|
$alias_cnt = $alias_cnt + $list_cnt;
|
|
}
|
|
else {
|
|
if ($pfbreuse == 'on' && file_exists("{$pfborig}/{$header}.orig")) {
|
|
$log = "\n[ {$header} ]{$logtab} Reload [ NOW ]";
|
|
} else {
|
|
$log = "\n[ {$header} ]{$logtab} Downloading update [ NOW ]";
|
|
}
|
|
pfb_logger("{$log}", 1);
|
|
$file_dwn = "{$pfborig}/{$header}";
|
|
|
|
if (!$custom) {
|
|
pfb_logger(' .', 1);
|
|
|
|
// Allow cURL SSL downgrade 'Flex' if user configured.
|
|
$pflex = FALSE;
|
|
if ($row['state'] == 'Flex') {
|
|
$pflex = TRUE;
|
|
}
|
|
|
|
// Determine if list needs to be downloaded or reuse previously downloaded file.
|
|
if ($pfbreuse == 'on' && file_exists("{$file_dwn}.orig")) {
|
|
// File exists/reuse
|
|
pfb_logger(' completed .', 1);
|
|
} else {
|
|
// Download file
|
|
if (!pfb_download($row['url'], $file_dwn, $pflex, $header,
|
|
$row['format'], 1, '', '', '', '', '', $srcint)) {
|
|
|
|
// Determine reason for download failure
|
|
pfb_download_failure($alias, $header, $pfbfolder, $row['url'], $row['format'], '');
|
|
|
|
// Utilize previously download file (If 'fail' marker exists)
|
|
if (file_exists("{$pfbfolder}/{$header}.fail") &&
|
|
file_exists("{$file_dwn}.orig")) {
|
|
pfb_logger("\n Restoring previously downloaded file\n ", 2);
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
// Clear any previous download fail marker
|
|
unlink_if_exists("{$pfbfolder}/{$header}.fail");
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Collect custom list data.
|
|
$custom_list = pfbng_text_area_decode($row['custom'], FALSE, TRUE, TRUE);
|
|
@file_put_contents("{$file_dwn}.orig", $custom_list, LOCK_EX);
|
|
unset($custom_list);
|
|
$liteparser = TRUE;
|
|
}
|
|
|
|
// Variables for Easylists
|
|
$easylist = $validate_header = FALSE;
|
|
$e_replace = array( '||', '.^', '^' );
|
|
|
|
$run_once = $csv_parser = FALSE;
|
|
$csv_type = '';
|
|
$ipcount = $ip_cnt = 0;
|
|
|
|
// Parse downloaded file for Domain names
|
|
if (($fhandle = @fopen("{$file_dwn}.orig", 'r')) !== FALSE) {
|
|
if (($dhandle = @fopen("{$pfbfolder}/{$header}.bk", 'w')) !== FALSE) {
|
|
while (($line = @fgets($fhandle)) !== FALSE) {
|
|
|
|
// Collect original line
|
|
$oline = $line;
|
|
|
|
// Validate EasyList/AdBlock/uBlock/ADGuard Feeds
|
|
if (!$validate_header) {
|
|
if (strpos($line, '[Adblock Plus ') !== FALSE ||
|
|
strpos($line, '[Adblock Plus]') !== FALSE ||
|
|
strpos($line, '[uBlock Origin') !== FALSE ||
|
|
strpos($line, '! Title: AdGuard') !== FALSE) {
|
|
$easylist = $validate_header = TRUE;
|
|
continue;
|
|
}
|
|
elseif (substr($line, 0, 1) === '!') {
|
|
continue;
|
|
}
|
|
else {
|
|
$validate_header = TRUE;
|
|
}
|
|
}
|
|
|
|
// Remove any '^M' characters
|
|
if (strpos($line, "\r") !== FALSE) {
|
|
$line = rtrim($line, "\x00..\x1F");
|
|
}
|
|
|
|
// Remove invalid charaters
|
|
$line = trim($line, " \t\n\r\0\x0B\xC2\xA0");
|
|
|
|
if ($easylist) {
|
|
if (substr($line, 0, 2) !== '||' ||
|
|
substr($line, -1) !== '^' ||
|
|
strpos($line, '$') !== FALSE ||
|
|
strpos($line, '*') !== FALSE ||
|
|
strpos($line, '/') !== FALSE) {
|
|
continue;
|
|
}
|
|
|
|
$lite = TRUE;
|
|
$line = str_replace($e_replace, '', $line);
|
|
}
|
|
else {
|
|
// If 'tab' character found, replace with whitespace
|
|
if (strpos($line, "\x09") !== FALSE) {
|
|
$line = str_replace("\x09", ' ', $line);
|
|
}
|
|
|
|
// If '%20' found, remove.
|
|
if (strpos($line, '%20') !== FALSE) {
|
|
$line = str_replace('%20', '', $line);
|
|
}
|
|
|
|
// Remove comment lines and special format considerations
|
|
if (substr($line, 0, 1) == '#') {
|
|
// Exit (hpHosts) when end of domain names found.
|
|
if (strpos($line, 'Append critical updates below') !== FALSE) {
|
|
break;
|
|
}
|
|
|
|
// Spamhaus format validation
|
|
if (strpos($line, 'The Spamhaus Project Ltd') !== FALSE) {
|
|
$rev_format = TRUE;
|
|
}
|
|
|
|
if ($line == '#family,type,url,status,first_seen,'
|
|
. 'first_active,last_active,last_update') {
|
|
$csv_type = 'h3x';
|
|
$csv_parser = TRUE;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Remove slash comment lines
|
|
if (substr($line, 0, 2) == '//') {
|
|
continue;
|
|
}
|
|
|
|
// Remove any 'End of line' comments (Some contains commas)
|
|
if (strpos($line, ' #') !== FALSE) {
|
|
$line = strstr($line, ' #', TRUE);
|
|
}
|
|
|
|
// Convert CSV line into array
|
|
if ($csv_parser) {
|
|
$csvline = str_getcsv($line, ',', '', '"');
|
|
}
|
|
elseif (!$run_once) {
|
|
if (substr_count($line, ',') >= 2) {
|
|
$csvline = str_getcsv($line, ',', '', '"');
|
|
$csv_parser = TRUE;
|
|
}
|
|
$run_once = TRUE;
|
|
}
|
|
}
|
|
|
|
// Remove blank lines
|
|
if (empty($line)) {
|
|
continue;
|
|
}
|
|
|
|
// CSV parser
|
|
if ($csv_parser) {
|
|
|
|
$csv_found = FALSE;
|
|
$csv_count = count($csvline);
|
|
|
|
switch ($csv_type) {
|
|
case 'pt':
|
|
if ($csv_count == 8) {
|
|
if (strpos($csvline[1], ' ') !== FALSE) {
|
|
$line = str_replace(' ', '', $csvline[1]);
|
|
} else {
|
|
$line = $csvline[1];
|
|
}
|
|
$csv_found = TRUE;
|
|
}
|
|
break;
|
|
case 'bbc':
|
|
if ($csv_count == 4) {
|
|
$line = $csvline[0];
|
|
$csv_found = TRUE;
|
|
}
|
|
break;
|
|
case 'h3x':
|
|
if ($csv_count == 8) {
|
|
$line = $csvline[2];
|
|
if (strpos($line, 'btc://') !== FALSE) {
|
|
continue 2;
|
|
}
|
|
$csv_found = TRUE;
|
|
}
|
|
break;
|
|
case 'otx':
|
|
if ($csv_count == 3) {
|
|
if (isset($alienvault_types[$csvline[0]])) {
|
|
$line = $csvline[1];
|
|
$csv_found = TRUE;
|
|
} else {
|
|
continue 2;
|
|
}
|
|
}
|
|
break;
|
|
case 'pon':
|
|
if ($csv_count == 9) {
|
|
$line = $csvline[2];
|
|
$csv_found = TRUE;
|
|
|
|
// Collect additional IP csv entry
|
|
if (is_ipaddrv4($csvline[0]) &&
|
|
$pfb['dnsbl_ip'] != 'Disabled') {
|
|
$parsed = sanitize_ipaddr($line, $custom, 'Disabled');
|
|
if (validate_ipv4($parsed)) {
|
|
$domain_data_ip[] = $parsed;
|
|
$pfb['updateip'] = TRUE;
|
|
$ipcount++;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 'et':
|
|
if ($csv_count == 3) {
|
|
$line = $csvline[0];
|
|
$csv_found = TRUE;
|
|
}
|
|
break;
|
|
default:
|
|
|
|
// Parse Phishtank Feed
|
|
if (strpos($line, 'phish_id,url,'
|
|
. 'phish_detail_url') !== FALSE) {
|
|
$csv_type = 'pt';
|
|
continue 2;
|
|
}
|
|
|
|
// Parse Bambenek Consulting Feed
|
|
elseif (strpos($csvline[3], 'osint.'
|
|
. 'bambenekconsulting.com') !== FALSE) {
|
|
$csv_type = 'bbc';
|
|
$line = $csvline[0];
|
|
$csv_found = TRUE;
|
|
$liteparser = TRUE;
|
|
}
|
|
|
|
// Parse Alienvault OTX pulse Feed
|
|
elseif ($line == 'Indicator type,Indicator,'
|
|
. 'Description') {
|
|
$csv_type = 'otx';
|
|
$liteparser = FALSE;
|
|
continue 2;
|
|
}
|
|
|
|
// Parse Ponomocup Feed
|
|
elseif (strpos($csvline[0], 'timestamp') !== FALSE) {
|
|
$csv_type = 'pon';
|
|
$liteparser = TRUE;
|
|
continue 2;
|
|
}
|
|
|
|
// Parse Proofpoint/ET IQRisk IPRep Feed
|
|
elseif ($line == 'domain, category, score') {
|
|
$csv_type = 'et';
|
|
$liteparser = TRUE;
|
|
continue 2;
|
|
}
|
|
|
|
// Reset variables for CSV determination
|
|
else {
|
|
$csv_parser = $run_once = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Record Failed CSV Parse event
|
|
if (!$csv_found || empty($csv_type)) {
|
|
pfb_parsed_fail($header, '', $oline, $pfb['dnsbl_parse_err']);
|
|
continue;
|
|
}
|
|
}
|
|
$line = trim($line);
|
|
|
|
if (!$easylist) {
|
|
|
|
// Typical Host Feed format - Remove characters before space
|
|
if (!$rev_format && strpos($line, ' ') !== FALSE) {
|
|
$line = trim(strstr($line, ' ', FALSE));
|
|
}
|
|
|
|
// Remove characters after space
|
|
if (strpos($line, ' ') !== FALSE) {
|
|
$line = strstr($line, ' ', TRUE);
|
|
}
|
|
|
|
// Determine if line contains only an alpha-numeric Domain name
|
|
if (!$liteparser) {
|
|
|
|
$lite = FALSE;
|
|
if (strpos($line, '.') !== FALSE &&
|
|
ctype_alnum(str_replace('.', '', $line))) {
|
|
$lite = TRUE;
|
|
}
|
|
}
|
|
else {
|
|
$lite = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!$lite) {
|
|
|
|
// If 'http|https|telnet|ftp://' found, remove
|
|
if (strpos($line, '://') !== FALSE) {
|
|
$line = substr($line, strpos($line, '://') + 3);
|
|
}
|
|
|
|
// If '/' character found, remove characters after '/'
|
|
if (strpos($line, '/') !== FALSE) {
|
|
$line = strstr($line, '/', TRUE);
|
|
}
|
|
|
|
// If '#' character found, remove characters after '#'
|
|
if (strpos($line, '#') !== FALSE) {
|
|
$line = strstr($line, '#', TRUE);
|
|
}
|
|
|
|
// If '?' character found, remove characters after '?'
|
|
if (strpos($line, '?') !== FALSE) {
|
|
$line = strstr($line, '?', TRUE);
|
|
}
|
|
|
|
// If special characters found, parse line for host
|
|
if (strpos($line, ';') !== FALSE) {
|
|
$host = parse_url($line);
|
|
if (isset($host['host'])) {
|
|
$line = $host['host'];
|
|
} else {
|
|
$line = strstr($line, ';', TRUE);
|
|
}
|
|
}
|
|
|
|
// Remove any Port numbers at end of line
|
|
if (strpos($line, ':') !== FALSE) {
|
|
$line = preg_replace("/:[0-9]{1,5}$/", '', $line);
|
|
}
|
|
}
|
|
$line = trim($line);
|
|
|
|
// Collect any IPs found in domain feed
|
|
if (is_ipaddrv4($line)) {
|
|
if ($pfb['dnsbl_ip'] != 'Disabled') {
|
|
$parsed = sanitize_ipaddr($line, $custom, 'Disabled');
|
|
if (validate_ipv4($parsed)) {
|
|
$domain_data_ip[] = $parsed;
|
|
$pfb['updateip'] = TRUE;
|
|
$ipcount++;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Convert IDN (Unicode domains) to ASCII (punycode)
|
|
if (!ctype_print($line)) {
|
|
|
|
// Convert encodings to UTF-8
|
|
$line = mb_convert_encoding($line, 'UTF-8',
|
|
mb_detect_encoding($line, 'UTF-8, ASCII, ISO-8859-1'));
|
|
|
|
$log = "\n IDN converted: [ {$line} ]\t";
|
|
$line = idn_to_ascii($line);
|
|
if (!empty($line)) {
|
|
pfb_logger("{$log} [ {$line} ]", 1);
|
|
}
|
|
else {
|
|
// Record failed parsed line
|
|
pfb_parsed_fail($header, '', $oline, $pfb['dnsbl_parse_err']);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Remove leading/trailing dots
|
|
$line = trim(trim($line), '.');
|
|
|
|
// Domain Validation
|
|
if (empty(pfb_filter($line, PFB_FILTER_DOMAIN, 'DNSBL_Download'))) {
|
|
|
|
// Reset lite parser
|
|
$liteparser = FALSE;
|
|
|
|
// Skip yHost '@' prefixed lines
|
|
if (substr($line, 0, 1) == '@') {
|
|
continue;
|
|
}
|
|
|
|
// Log invalid Domains
|
|
if (!isset($dnsbl_skip[$line])) {
|
|
pfb_parsed_fail($header, $line, $oline, $pfb['dnsbl_parse_err']);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// For DNSBL python, save domain and Logging type
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
$domain_data = ',' . strtolower($line)
|
|
. ",,{$logging_type},{$header},{$alias}\n";
|
|
}
|
|
else {
|
|
$ipv6_dnsbl = "\n";
|
|
if ($pfb['dnsbl_v6'] == 'on' && !$pfb['dnsbl_tld']) {
|
|
$ipv6_dnsbl = " local-data: \"" . strtolower($line)
|
|
. " 60 IN AAAA {$sinkhole_type6}\"\n";
|
|
}
|
|
$domain_data = "local-data: \"" . strtolower($line)
|
|
. " 60 IN A {$sinkhole_type4}\"{$ipv6_dnsbl}";
|
|
}
|
|
@fwrite($dhandle, $domain_data);
|
|
}
|
|
}
|
|
if ($dhandle) {
|
|
@fclose($dhandle);
|
|
}
|
|
}
|
|
if ($fhandle) {
|
|
@fclose($fhandle);
|
|
}
|
|
if (isset($csvline)) {
|
|
unset($csvline);
|
|
}
|
|
|
|
// Remove duplicates and save any IPs found in domain feed
|
|
if (!empty($domain_data_ip)) {
|
|
$domain_data_ip = implode("\n", array_unique($domain_data_ip)) . "\n";
|
|
@file_put_contents("{$pfbfolder}/{$header}_v4.ip", $domain_data_ip, LOCK_EX);
|
|
$ip_cnt = exec("{$pfb['grep']} -c ^ " . escapeshellarg("{$pfbfolder}/{$header}_v4.ip"));
|
|
}
|
|
else {
|
|
// Remove previous IP feed
|
|
unlink_if_exists("{$pfbfolder}/{$header}_v4.ip");
|
|
}
|
|
|
|
// Validate feed with Unbound-checkconf
|
|
if (!empty($domain_data)) {
|
|
$conf = "server:\n";
|
|
$conf .= "chroot: {$pfb['dnsbldir']}\n";
|
|
$conf .= "username: \"unbound\"\n";
|
|
$conf .= "directory: \"{$pfb['dnsbldir']}\"\n";
|
|
$conf .= "pidfile: \"/var/run/unbound.pid\"\n";
|
|
$conf .= "server:include: {$pfbfolder}/{$header}.bk";
|
|
@file_put_contents("{$pfb['dnsbldir']}/check.conf", $conf, LOCK_EX);
|
|
|
|
pfb_logger(".\n", 1);
|
|
|
|
// Bypass TOP1M whitelist, if user configured
|
|
$pfb_alexa = 'Disabled';
|
|
if ($pfb['dnsbl_alexa'] == 'on' &&
|
|
$list['filter_alexa'] == 'on' &&
|
|
file_exists("{$pfb['dbdir']}/pfbalexawhitelist.txt")) {
|
|
$pfb_alexa = 'on';
|
|
}
|
|
|
|
// DNSBL python requires a different deduplication process
|
|
$dup_mode = '';
|
|
if ($pfb['dnsbl_py_blacklist']) {
|
|
$dup_mode = 'python';
|
|
}
|
|
|
|
// Call script to process DNSBL 'De-Duplication / Whitelisting / TOP1M Whitelisting'
|
|
exec("{$pfb['script']} dnsbl_scrub {$header_esc} {$pfb_alexa} {$dup_mode} {$elog}");
|
|
|
|
if ($ip_cnt > 0) {
|
|
pfb_logger(" IPv4 count={$ip_cnt}\n", 1);
|
|
}
|
|
|
|
if (!$pfb['dnsbl_py_blacklist']) {
|
|
$result = array();
|
|
exec("/usr/local/sbin/unbound-checkconf {$pfb['dnsbldir']}/check.conf 2>&1", $result);
|
|
} else {
|
|
$result = array('unbound-checkconf: no errors');
|
|
}
|
|
unlink_if_exists("{$pfb['dnsbldir']}/check.conf");
|
|
}
|
|
else {
|
|
$log = "\n No Domains Found! Ensure only domain based Feeds are used for DNSBL!\n";
|
|
pfb_logger("{$log}", 1);
|
|
|
|
// Copy downloaded file to /tmp for debugging
|
|
$ts = date('M_j', time());
|
|
@copy("{$file_dwn}.orig", "/tmp/Error_{$header}_{$ts}.orig");
|
|
|
|
unlink_if_exists("{$pfbfolder}/{$header}.bk");
|
|
$result = array('unbound-checkconf: no errors');
|
|
}
|
|
|
|
// If parse error found, use previously downloaded file if available
|
|
if (!$pfb['dnsbl_py_blacklist'] && !preg_grep("/unbound-checkconf: no errors/", $result)) {
|
|
unlink_if_exists("{$pfbfolder}/{$header}.bk");
|
|
|
|
pfb_logger("\n DNSBL FAIL - Skipped! Use previous data, if found:\n", 2);
|
|
$log = htmlspecialchars(implode("\n", $result));
|
|
pfb_logger("{$log}\n", 1);
|
|
|
|
// Create failed marker file
|
|
touch("{$pfbfolder}/{$header}.fail");
|
|
}
|
|
|
|
// Save DNSBL feed info for next steps
|
|
$pfb['domain_update'] = $pfb['aliasupdate'] = $pfb['summary'] = TRUE;
|
|
$lists_dnsbl_all[] = "{$row['header']}.txt";
|
|
$lists_dnsbl_current[] = "{$row['header']}";
|
|
|
|
// Rename newly downloaded file to final location
|
|
if (file_exists("{$pfbfolder}/{$header}.bk")) {
|
|
@rename("{$pfbfolder}/{$header}.bk", "{$pfbfolder}/{$header}.txt");
|
|
}
|
|
|
|
// Create empty placeholder file
|
|
if (!file_exists("{$pfbfolder}/{$header}.txt")) {
|
|
touch("{$pfbfolder}/{$header}.txt");
|
|
}
|
|
|
|
$list_cnt = exec("{$pfb['grep']} -c ^ " . escapeshellarg("{$pfbfolder}/{$header}.txt"));
|
|
$alias_cnt = $alias_cnt + $list_cnt;
|
|
|
|
// Remove update file indicator
|
|
unlink_if_exists("{$pfbfolder}/{$header}.update");
|
|
}
|
|
}
|
|
}
|
|
|
|
// If changes found update DNSBL alias and TLD disabled, call function to update DNSBL alias
|
|
if ($pfb['aliasupdate'] && !$pfb['dnsbl_tld']) {
|
|
dnsbl_alias_update('update', $alias, $pfbfolder, $lists_dnsbl_current, $alias_cnt);
|
|
}
|
|
|
|
// Collect Alias/Feeds for post TLD function
|
|
if ($pfb['dnsbl_tld']) {
|
|
if (!is_array($pfb['tld_update'][$alias])) {
|
|
$pfb['tld_update'][$alias] = array();
|
|
}
|
|
$pfb['tld_update'][$alias]['feeds'] = $lists_dnsbl_current;
|
|
$pfb['tld_update'][$alias]['count'] = $alias_cnt;
|
|
}
|
|
}
|
|
else {
|
|
dnsbl_alias_update('disabled', $alias, '', '', '');
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Remove any unused DNSBL aliases
|
|
$daliases = glob("{$pfb['dnsalias']}/*");
|
|
if (!empty($daliases)) {
|
|
foreach ($daliases as $dlist) {
|
|
if (!in_array(basename($dlist), $pfb['alias_dnsbl_all'])) {
|
|
unlink_if_exists ("{$dlist}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add DNSBL Python options to widget statistics
|
|
if ($pfb['dnsbl_mode'] == 'dnsbl_python') {
|
|
if ($pfb['dnsbl_idn'] == 'on') {
|
|
$idn_cnt = 1;
|
|
$pfb['alias_dnsbl_all'][] = 'DNSBL_IDN';
|
|
dnsbl_alias_update('update', 'DNSBL_IDN', '', '', $idn_cnt);
|
|
}
|
|
if ($pfb['dnsbl_pytld'] == 'on') {
|
|
$pytld_cnt = 0;
|
|
foreach (array('gtld', 'cctld', 'itld', 'bgtld') as $pytld) {
|
|
if (isset($pfb['dnsblconfig']['pfb_pytlds_' . $pytld]) && !empty($pfb['dnsblconfig']['pfb_pytlds_' . $pytld])) {
|
|
$p_cnt = count(explode(',', $pfb['dnsblconfig']['pfb_pytlds_' . $pytld]));
|
|
if (is_numeric($p_cnt) && $p_cnt > 0) {
|
|
$pytld_cnt += $p_cnt;
|
|
}
|
|
}
|
|
}
|
|
|
|
$pfb['alias_dnsbl_all'][] = 'DNSBL_TLD_Allow';
|
|
dnsbl_alias_update('update', 'DNSBL_TLD_Allow', '', '', $pytld_cnt);
|
|
}
|
|
if ($pfb['dnsbl_regex'] == 'on') {
|
|
$regex_cnt = 0;
|
|
if (isset($pfb['dnsbl_regex_list'])) {
|
|
$regex_cnt = count(pfbng_text_area_decode($pfb['dnsbl_regex_list'], TRUE, FALSE, FALSE)) ?: 0;
|
|
}
|
|
$pfb['alias_dnsbl_all'][] = 'DNSBL_Regex';
|
|
dnsbl_alias_update('update', 'DNSBL_Regex', '', '', $regex_cnt);
|
|
}
|
|
}
|
|
|
|
// Save DNSBL Alias statistics (Not for TLD mode)
|
|
if ($pfb['domain_update'] && !$pfb['dnsbl_tld']) {
|
|
dnsbl_save_stats();
|
|
}
|
|
}
|
|
|
|
// Collect all DNSBL IP feeds (IPv4 only) into DNSBLIP_v4.txt
|
|
if ($pfb['dnsbl_ip'] != 'Disabled' && ($pfb['updateip'] || !file_exists("{$pfb['dbdir']}/DNSBLIP_v4.txt"))) {
|
|
|
|
$dnsbl_ip = glob("{$pfb['dnsdir']}/*_v4.ip");
|
|
if (!empty($dnsbl_ip)) {
|
|
$pfb_ips = @fopen("{$pfb['dbdir']}/DNSBLIP_v4.txt", 'w');
|
|
foreach ($dnsbl_ip as $d_ip) {
|
|
if (($handle = @fopen("{$d_ip}", 'r')) !== FALSE) {
|
|
while (($line = @fgets($handle)) !== FALSE) {
|
|
@fwrite($pfb_ips, $line);
|
|
}
|
|
}
|
|
if ($handle) {
|
|
@fclose($handle);
|
|
}
|
|
}
|
|
if ($pfb_ips) {
|
|
@fclose($pfb_ips);
|
|
}
|
|
}
|
|
|
|
// Add empty placeholder IP
|
|
else {
|
|
@file_put_contents("{$pfb['dbdir']}/DNSBLIP_v4.txt", "{$pfb['ip_ph']}\n", LOCK_EX);
|
|
}
|
|
unlink_if_exists($pfb['ip_cache']);
|
|
touch("{$pfb['denydir']}/DNSBLIP_v4.update");
|
|
}
|
|
|
|
// Remove DNSBL IP feed, if disabled
|
|
if ($pfb['dnsbl_ip'] == 'Disabled') {
|
|
unlink_if_exists("{$pfb['dbdir']}/DNSBLIP_v4.txt");
|
|
unlink_if_exists("{$pfb['denydir']}/DNSBLIP_v4.*");
|
|
}
|
|
|
|
#########################################
|
|
# UPDATE Unbound DNS Database #
|
|
#########################################
|
|
|
|
if ($pfb['domain_update']) {
|
|
if (!empty($lists_dnsbl_all)) {
|
|
pfb_logger("\n------------------------------------------------------------------------\n", 1);
|
|
|
|
pfb_logger('Assembling DNSBL database...', 1);
|
|
unlink_if_exists("{$pfb['dnsbl_file']}.raw");
|
|
$pfb_output = @fopen("{$pfb['dnsbl_file']}.raw", 'w');
|
|
foreach ($lists_dnsbl_all as $current_list) {
|
|
if (($handle = @fopen("{$pfb['dnsdir']}/{$current_list}", 'r')) !== FALSE) {
|
|
while (($line = @fgets($handle)) !== FALSE) {
|
|
@fwrite($pfb_output, $line);
|
|
}
|
|
}
|
|
if ($handle) {
|
|
@fclose($handle);
|
|
}
|
|
}
|
|
if ($pfb_output) {
|
|
@fclose($pfb_output);
|
|
}
|
|
pfb_logger("... completed [ NOW ]", 1);
|
|
|
|
// DNSBL Python blocking mode, if TLD is not enabled
|
|
if ($pfb['dnsbl_py_blacklist'] && !$pfb['dnsbl_tld']) {
|
|
unlink_if_exists($pfb['unbound_py_data']);
|
|
unlink_if_exists($pfb['unbound_py_zone']);
|
|
unlink_if_exists($pfb['unbound_py_count']);
|
|
rename("{$pfb['dnsbl_file']}.raw", $pfb['unbound_py_data']);
|
|
}
|
|
}
|
|
|
|
else {
|
|
$log = "\nDNSBL not Updated!\n";
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
}
|
|
else {
|
|
if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on') {
|
|
|
|
// When DNSBL is enabled and no Aliases are defined, or all Aliases are Disabled
|
|
if (empty($lists_dnsbl_all) && !$pfb['save']) {
|
|
pfb_logger("\nClearing all DNSBL Feeds", 1);
|
|
$pfb['domain_clear'] = TRUE;
|
|
|
|
// Clear out Unbound pfb_dnsbl.conf file
|
|
if (!$pfb['dnsbl_py_blacklist']) {
|
|
$pfb_output = @fopen("{$pfb['dnsbl_file']}.conf", 'w');
|
|
@fwrite($pfb_output, '');
|
|
@fclose($pfb_output);
|
|
}
|
|
|
|
// Remove DNSBL Python files
|
|
else {
|
|
unlink_if_exists($pfb['unbound_py_data']);
|
|
unlink_if_exists($pfb['unbound_py_zone']);
|
|
unlink_if_exists($pfb['unbound_py_wh']);
|
|
unlink_if_exists($pfb['unbound_py_count']);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
foreach (array("{$pfb['dnsbl_file']}.conf", $pfb['unbound_py_data'], $pfb['unbound_py_zone']) as $pfb_file) {
|
|
if (file_exists($pfb_file)) {
|
|
$pfb['domain_clear'] = TRUE;
|
|
@unlink($pfb_file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#################################
|
|
# UNBOUND INTEGRATION #
|
|
#################################
|
|
|
|
$pfbupdate = $pfbpython = FALSE;
|
|
if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && $pfb['unbound_state'] == 'on') {
|
|
$mode = 'enabled';
|
|
}
|
|
elseif (($pfb['enable'] == '' || $pfb['dnsbl'] == '') && !$pfb['install']) {
|
|
$mode = 'disabled';
|
|
}
|
|
|
|
// Modify Unbound python configuration and mount lib/bin folders, as required
|
|
$pfbpython = pfb_unbound_python($mode);
|
|
|
|
// Modify Unbound.conf file, as required
|
|
$pfbupdate = pfb_unbound_dnsbl($mode);
|
|
|
|
// Modify DNSBL NAT and VIP and lighttpd web server conf, as required.
|
|
pfb_create_dnsbl($mode);
|
|
|
|
// Load new DNSBL updates to Unbound Resolver, as required
|
|
if ($pfb['domain_update'] || $pfbupdate || $pfbpython ||$pfb['domain_clear'] || $safesearch_update) {
|
|
|
|
// Create backup of existing DNSBL Unbound database
|
|
if (!$pfb['dnsbl_py_blacklist'] && file_exists("{$pfb['dnsbl_file']}.conf")) {
|
|
@copy("{$pfb['dnsbl_file']}.conf", "{$pfb['dnsbl_file']}.bk");
|
|
}
|
|
|
|
pfb_update_unbound($mode, $pfbupdate, $pfbpython);
|
|
}
|
|
|
|
|
|
#################################
|
|
# Assign Countries #
|
|
#################################
|
|
|
|
if (!$pfb['save']) {
|
|
$log = "\n\n===[ GeoIP Process ]============================================\n";
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
|
|
// Download MaxMind Databases if not found
|
|
$maxmind_verify = FALSE;
|
|
if (!empty($pfb['maxmind_key']) && !empty($pfb['maxmind_account'])) {
|
|
|
|
$maxmind_verify = TRUE;
|
|
if (!file_exists("{$pfb['geoipshare']}/GeoLite2-Country.mmdb") ||
|
|
!file_exists("{$pfb['geoipshare']}/GeoLite2-Country-Blocks-IPv4.csv") ||
|
|
!file_exists("{$pfb['dbdir']}/geoip.txt") ||
|
|
!file_exists("{$pfb['ccdir']}/Top_Spammers_v4.info")) {
|
|
|
|
// Check if MaxMind download already in progress
|
|
exec('/bin/ps -wax', $result_cron);
|
|
if (!preg_grep("/pfblockerng[.]php\s+dc/", $result_cron)) {
|
|
$log = "\nMaxMind Database downloading and processing ( approx 4MB ) ... Please wait ...\n";
|
|
pfb_logger("{$log}", 1);
|
|
exec("/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php dc >> {$pfb['log']} 2>&1");
|
|
restart_service('pfb_filter');
|
|
}
|
|
else {
|
|
$log = "\nMaxMind download already in process...\n";
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
$maxmind_run_once = TRUE;
|
|
foreach ($pfb['continents'] as $continent => $pfb_alias) {
|
|
$cont_key = 'pfblockerng' . strtolower(str_replace(' ', '', $continent));
|
|
if (!empty(config_get_path("installedpackages/{$cont_key}/config"))) {
|
|
$continent_config = config_get_path("installedpackages/{$cont_key}/config/0");
|
|
$cc_name = 'pfblockerng' . strtolower(str_replace(' ', '', $continent));
|
|
if (isset($continent_config['action']) && $continent_config['action'] != 'Disabled' && $pfb['enable'] == 'on') {
|
|
|
|
// Maxmind License Key verification and user notification
|
|
if ($maxmind_run_once && !$maxmind_verify) {
|
|
$mmsg = 'MaxMind now requires a License Key! Review the IP tab: MaxMind settings for more information.';
|
|
pfb_logger("\n\nURGENT:\n {$mmsg}\n", 1);
|
|
file_notice('pfBlockerNG MaxMind', $mmsg, 'pfBlockerNG', '/pfblockerng/pfblockerng_ip.php', 2);
|
|
$maxmind_run_once = FALSE;
|
|
}
|
|
|
|
$urlvalue = ''; // Firewall: Aliases value field
|
|
|
|
// Determine if Continent lists require action (IPv4 and IPv6)
|
|
foreach ($cont_types as $c_type => $vtype) {
|
|
|
|
$cc_alias = "{$pfb_alias}{$vtype}";
|
|
|
|
// Determine 'list' details (return array $pfbarr)
|
|
pfb_determine_list_detail($continent_config['action'], "{$cc_alias}", $cc_name, '0');
|
|
$pfbadv = $pfbarr['adv'];
|
|
$pfbdescr = $pfbarr['descr'];
|
|
$pfbfolder = $pfbarr['folder'];
|
|
$pfborig = $pfbarr['orig'];
|
|
$logtab = $pfbarr['logtab'];
|
|
|
|
if (!empty($continent_config[$c_type])) {
|
|
|
|
// Collect selected GeoIP ISOs
|
|
if (($pfb_output = @fopen("{$pfb['geoip_tmp']}", 'w')) !== FALSE) {
|
|
foreach (explode(',', $continent_config[$c_type]) as $iso) {
|
|
|
|
$urlvalue .= "{$iso},";
|
|
$isofile = "{$pfb['ccdir']}/{$iso}{$vtype}.txt";
|
|
if (($handle = @fopen("{$isofile}", 'r')) !== FALSE) {
|
|
while (($line = @fgets($handle)) !== FALSE) {
|
|
@fwrite($pfb_output, $line);
|
|
}
|
|
}
|
|
else {
|
|
pfb_logger("\nCould not open ISO [ {$iso}{$vtype} ]\n", 1);
|
|
}
|
|
if ($handle) {
|
|
@fclose($handle);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
pfb_logger("\n[ {$cc_alias} ] Could not create GeoIP file handle\n", 1);
|
|
}
|
|
if ($pfb_output) {
|
|
@fclose($pfb_output);
|
|
}
|
|
|
|
// Collect md5 of new Continent data
|
|
$continent = 'md5_0';
|
|
if (file_exists("{$pfb['geoip_tmp']}")) {
|
|
$continent = @md5_file("{$pfb['geoip_tmp']}");
|
|
}
|
|
|
|
// Collect md5 of existing Continent data
|
|
$continent_ex = 'md5_1';
|
|
if (file_exists("{$pfborig}/{$cc_alias}.orig")) {
|
|
$continent_ex = @md5_file("{$pfborig}/{$cc_alias}.orig");
|
|
}
|
|
|
|
// Check if pfBlockerNG pfctl Continent tables are empty (pfBlockerNG was disabled w/ "keep", then re-enabled)
|
|
$pfctlck = exec("{$pfb['pfctl']} -vvsTables | {$pfb['grep']} -A1 {$cc_alias} | {$pfb['awk']} '/Addresses/ {s+=\$2}; END {print s}'");
|
|
|
|
if (empty($pfctlck) && file_exists("{$pfbfolder}/{$cc_alias}.txt")) {
|
|
@copy("{$pfbfolder}/{$cc_alias}.txt", "{$pfb['aliasdir']}/{$cc_alias}.txt");
|
|
// Collect updated alias lists ('Reputation' disabled)
|
|
$pfb_alias_lists[] = "{$cc_alias}";
|
|
}
|
|
|
|
// Collect active alias lists (Used for pfctl update when 'Reputation' is enabled).
|
|
$pfb_alias_lists_all[] = "{$cc_alias}";
|
|
|
|
// Compare existing (original file) and new Continent data
|
|
if ($continent == $continent_ex && !empty($pfctlck)
|
|
&& file_exists("{$pfbfolder}/{$cc_alias}.txt") && $pfb['reuse'] == ''
|
|
&& !file_exists("{$pfb['dbdir']}/geoip.update")) {
|
|
if (!$pfb['save']) {
|
|
$log = "\n[ {$cc_alias} ]{$logtab} exists. [ NOW ]";
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
} else {
|
|
// Do not proceed with changes on user 'save'
|
|
if (!$pfb['save']) {
|
|
$log = "\n[ {$cc_alias} ]{$logtab} Changes found... Updating\n";
|
|
pfb_logger("{$log}", 1);
|
|
|
|
// Execute Reputation functions, when changes are found.
|
|
if ($pfbadv && $vtype == '_v4') {
|
|
$pfb['repcheck'] = TRUE;
|
|
}
|
|
|
|
// Collect updated alias lists ('Reputation' disabled)
|
|
$pfb_alias_lists[] = "{$cc_alias}";
|
|
|
|
if ($continent != 'md5_0') {
|
|
@rename("{$pfb['geoip_tmp']}", "{$pfborig}/{$cc_alias}.orig");
|
|
@copy("{$pfborig}/{$cc_alias}.orig", "{$pfbfolder}/{$cc_alias}.txt");
|
|
|
|
// Call Aggregate process
|
|
if ($pfb['agg'] == 'on' && $vtype == '_v4') {
|
|
exec("{$pfb['script']} cidr_aggregate {$cc_alias} {$pfbfolder} {$elog}");
|
|
}
|
|
|
|
// Call Duplication process
|
|
if ($pfb['dup'] == 'on' && $vtype == '_v4' && $pfbadv) {
|
|
exec("{$pfb['script']} continent {$cc_alias} {$elog}");
|
|
}
|
|
|
|
// Save Continent data to aliastable folder
|
|
@copy("{$pfbfolder}/{$cc_alias}.txt", "{$pfb['aliasdir']}/{$cc_alias}.txt");
|
|
}
|
|
|
|
// Check if file exists and is > 0 in size and save alias file
|
|
$file_chk = 0;
|
|
$cont_chk = "{$pfbfolder}/{$cc_alias}.txt";
|
|
if (file_exists($cont_chk) && @filesize($cont_chk) > 0) {
|
|
$file_chk = exec("{$pfb['grep']} -cv '^#\|^\$' {$cont_chk}");
|
|
}
|
|
|
|
if ($file_chk <= 1) {
|
|
if ($vtype == '_v6') {
|
|
$p_ip = "::{$pfb['ip_ph']}";
|
|
} else {
|
|
$p_ip = $pfb['ip_ph'];
|
|
}
|
|
|
|
@file_put_contents("{$pfbfolder}/{$cc_alias}.txt", "{$p_ip}\n", LOCK_EX);
|
|
@copy("{$pfbfolder}/{$cc_alias}.txt", "{$pfb['aliasdir']}/{$cc_alias}.txt");
|
|
$log = "[ {$cc_alias} ] Found no unique IPs, adding '{$p_ip}' to avoid empty file\n";
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (file_exists("{$pfbfolder}/{$cc_alias}.txt")) {
|
|
// Create alias config
|
|
$new_aliases_list[] = "{$cc_alias}";
|
|
$new_aliases[] = array( 'name' => "{$cc_alias}",
|
|
'url' => "{$pfb['weblocal']}?pfb={$cc_alias}",
|
|
'updatefreq' => '32',
|
|
'address' => '',
|
|
'descr' => "pfBlockerNG {$pfbdescr} GeoIP Alias [ {$urlvalue} ]",
|
|
'type' => 'urltable',
|
|
'detail' => 'DO NOT EDIT THIS ALIAS'
|
|
);
|
|
|
|
// Define firewall rule settings
|
|
pfb_firewall_rule($continent_config['action'], $cc_alias, $vtype, $continent_config['aliaslog'],
|
|
$pfbarr['agateway_in'], $pfbarr['agateway_out'], $pfbarr['aaddrnot_in'], $pfbarr['aaddr_in'],
|
|
$pfbarr['aports_in'], $pfbarr['aproto_in'], $pfbarr['anot_in'], $pfbarr['aaddrnot_out'],
|
|
$pfbarr['aaddr_out'], $pfbarr['aports_out'], $pfbarr['aproto_out'], $pfbarr['anot_out']);
|
|
}
|
|
else {
|
|
// unlink Continent list
|
|
unlink_if_exists("{$pfb['aliasdir']}/{$cc_alias}.txt");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove temp file
|
|
unlink_if_exists("{$pfb['geoip_tmp']}");
|
|
|
|
#################################################
|
|
# Download and Collect IPv4/IPv6 lists #
|
|
#################################################
|
|
|
|
// IPv4 REGEX Definitions
|
|
$pfb['range'] = '/((?:(?:25[0-5]|2[0-4]\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?))/';
|
|
$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)?/';
|
|
|
|
// IPv6 REGEX Definitions - Reference: http://labs.spritelink.net/regex
|
|
$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)?/';
|
|
|
|
if ($pfb['enable'] == 'on' && !$pfb['save']) {
|
|
|
|
$pfb['supp_update'] = FALSE;
|
|
$runonce_v4 = $runonce_v6 = TRUE;
|
|
$lists = array();
|
|
|
|
// Collect lists and custom list configuration and format into one array ($lists).
|
|
foreach ($ip_types as $ip_type => $vtype) {
|
|
foreach (config_get_path("installedpackages/{$ip_type}/config", []) as $key => $list) {
|
|
if (!is_array($list)) {
|
|
$list = array();
|
|
}
|
|
if (!is_array($list['row'])) {
|
|
$list['row'] = array();
|
|
}
|
|
|
|
$list['vtype'] = "{$vtype}"; // Collect list IP type
|
|
$list['key'] = "{$key}"; // Collect list array key location
|
|
|
|
// If only the 'customlist' is defined. Remove the 'List row' data.
|
|
if (empty(array_get_path($list, 'row/0/url'))) {
|
|
unset($list['row']);
|
|
}
|
|
|
|
if (!empty($list['custom'])) {
|
|
array_init_path($list, 'row');
|
|
$list['row'][] = array( 'header' => "{$list['aliasname']}_custom",
|
|
'custom' => $list['custom'],
|
|
'state' => 'Enabled',
|
|
'url' => 'custom'
|
|
);
|
|
}
|
|
$lists[] = $list;
|
|
}
|
|
|
|
// Add DNSBLIP, if configured (IPv4 only)
|
|
if ($pfb['dnsbl'] == 'on' && $pfb['dnsbl_ip'] != 'Disabled' && $vtype == '_v4') {
|
|
|
|
$list = array( 'aliasname' => 'DNSBLIP',
|
|
'vtype' => "{$vtype}",
|
|
'key' => 0,
|
|
'dnsblip' => '',
|
|
'action' => "{$pfb['dnsbl_ip']}",
|
|
);
|
|
|
|
$list['row'][] = array( 'format' => 'auto',
|
|
'state' => 'Enabled',
|
|
'url' => "{$pfb['dbdir']}/DNSBLIP{$vtype}.txt",
|
|
'header' => 'DNSBLIP');
|
|
$lists[] = $list;
|
|
}
|
|
}
|
|
|
|
$maxmind_run_once = $asn_run_once = TRUE;
|
|
foreach ($lists as $list) {
|
|
if ($runonce_v4 && $list['vtype'] == '_v4') {
|
|
$runonce_v4 = FALSE;
|
|
$log = "\n\n===[ IPv4 Process ]=================================================\n";
|
|
pfb_logger("{$log}", 1);
|
|
} elseif ($runonce_v6 && $list['vtype'] == '_v6') {
|
|
$runonce_v6 = FALSE;
|
|
$log = "\n\n===[ IPv6 Process ]=================================================\n";
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
|
|
if ($list['action'] != 'Disabled' && isset($list['row'])) {
|
|
$alias = "pfB_{$list['aliasname']}{$list['vtype']}"; // Capture Alias name
|
|
if (empty(pfb_filter($alias, PFB_FILTER_WORD, 'Download and Collect IPv4/IPv6 lists'))) {
|
|
pfb_logger("\n Invalid Aliasname:{$list['aliasname']}{$list['vtype']}, *skipping*", 1);
|
|
continue;
|
|
}
|
|
|
|
foreach ($list['row'] as $row) {
|
|
if (!empty($row['url']) && $row['state'] != 'Disabled') {
|
|
$header = "{$row['header']}{$list['vtype']}"; // Capture Header/Label name
|
|
if (empty(pfb_filter($header, PFB_FILTER_WORD, 'Download and Collect IPv4/IPv6 lists'))) {
|
|
pfb_logger("\n Invalid Aliasname:{$list['aliasname']}{$list['vtype']} | Header:{$row['header']}{$list['vtype']}, *skipping*", 1);
|
|
continue;
|
|
}
|
|
$header_esc = escapeshellarg($header);
|
|
|
|
// If row is a custom_list, set flag.
|
|
if (isset($row['custom'])) {
|
|
$custom = TRUE;
|
|
} else {
|
|
$custom = FALSE;
|
|
}
|
|
|
|
// Maxmind License Key verification
|
|
if ($maxmind_run_once && $row['format'] == 'geoip') {
|
|
$mmsg = 'MaxMind now requires an Account ID and License Key! Review the IP tab: MaxMind settings for more information.';
|
|
if (empty($pfb['maxmind_key']) || empty($pfb['maxmind_account'])) {
|
|
pfb_logger("\n\nURGENT:\n {$mmsg}.\n", 1);
|
|
file_notice('pfBlockerNG MaxMind', $mmsg, 'pfBlockerNG', '/pfblockerng/pfblockerng_ip.php', 2);
|
|
}
|
|
$maxmind_run_once = FALSE;
|
|
}
|
|
|
|
// IPinfo ASN Token verification
|
|
if ($asn_run_once && $row['format'] == 'asn') {
|
|
$mmsg = 'To utilize the ASN functionality, you must register for a free IPinfo Account. Review IP Tab for more information.';
|
|
if (empty($pfb['asn_token'])) {
|
|
pfb_logger($mmsg, $logtype);
|
|
file_notice('pfBlockerNG ASN', $mmsg, 'pfBlockerNG', '/pfblockerng/pfblockerng_ip.php', 2);
|
|
}
|
|
$asn_run_once = FALSE;
|
|
}
|
|
|
|
// IPv4 Advanced Tunables
|
|
$pfbcidr = 'Disabled';
|
|
if (isset($list['suppression_cidr']) && $list['suppression_cidr'] != 'Disabled' && is_numeric($list['suppression_cidr'])) {
|
|
$pfbcidr = "{$list['suppression_cidr']}";
|
|
}
|
|
|
|
// cURL Source Interface (sets CURLOPT_INTERFACE)
|
|
$srcint = $list['srcint'] ?: FALSE;
|
|
|
|
// IP v4/6 Advanced Tunable - (Pre/Post Script processing)
|
|
$pfb_script_pre = FALSE;
|
|
if (isset($list['script_pre']) && !empty($list['script_pre'])) {
|
|
$script_pre = basename($list['script_pre']);
|
|
if (file_exists("/usr/local/pkg/pfblockerng/{$script_pre}")) {
|
|
$pfb_script_pre = "/usr/local/pkg/pfblockerng/{$script_pre}";
|
|
}
|
|
}
|
|
|
|
$pfb_script_post = FALSE;
|
|
if (isset($list['script_post']) && !empty($list['script_post'])) {
|
|
$script_post = basename($list['script_post']);
|
|
if (file_exists("/usr/local/pkg/pfblockerng/{$script_post}")) {
|
|
$pfb_script_post = "/usr/local/pkg/pfblockerng/{$script_post}";
|
|
}
|
|
}
|
|
|
|
// Determine 'list' details (return array $pfbarr)
|
|
if (isset($list['dnsblip'])) {
|
|
$list_type = 'pfblockerngdnsblsettings';
|
|
} else {
|
|
$list_type = "{$ip_type}";
|
|
}
|
|
|
|
pfb_determine_list_detail($list['action'], $header, $list_type, $list['key']);
|
|
$pfbadv = $pfbarr['adv'];
|
|
$pfbfolder = $pfbarr['folder'];
|
|
$pfborig = $pfbarr['orig'];
|
|
$pfbreuse = $pfbarr['reuse'];
|
|
$logtab = $pfbarr['logtab'];
|
|
|
|
// Collect active alias list (Used for pfctl update when 'Reputation' is enabled.
|
|
$pfb_alias_lists_all[] = "{$alias}";
|
|
|
|
// Set update flags on new downloads available for GeoIP and ASN
|
|
if ($row['format'] == 'geoip' && file_exists("{$pfb['dbdir']}/geoip.update")) {
|
|
touch("{$pfbfolder}/{$header}.update");
|
|
}
|
|
if ($row['format'] == 'asn' && file_exists("{$pfb['dbdir']}/asn.update")) {
|
|
touch("{$pfbfolder}/{$header}.update");
|
|
}
|
|
|
|
if (file_exists("{$pfbfolder}/{$header}.txt") &&
|
|
!file_exists("{$pfbfolder}/{$header}.update") &&
|
|
!file_exists("{$pfbfolder}/{$header}.fail") &&
|
|
$pfbreuse == '') {
|
|
|
|
if ($row['state'] == 'Hold') {
|
|
$log = "\n[ {$header} ]{$logtab} static hold. [ NOW ]";
|
|
} else {
|
|
$log = "\n[ {$header} ]{$logtab} exists. [ NOW ]";
|
|
}
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
else {
|
|
if ($pfbreuse == 'on' && file_exists("{$pfborig}/{$header}.orig")) {
|
|
$log = "\n[ {$header} ]{$logtab} Reload [ NOW ]";
|
|
} else {
|
|
$log = "\n[ {$header} ]{$logtab} Downloading update [ NOW ]";
|
|
}
|
|
pfb_logger("{$log}", 1);
|
|
$file_dwn = "{$pfborig}/{$header}";
|
|
|
|
// Force 'Alias Native' setting to any Alias with 'Advanced Inbound/Outbound -Invert src/dst' settings.
|
|
// This will bypass Deduplication and Reputation features.
|
|
if ($pfbarr['aaddrnot_in'] == 'on' || $pfbarr['aaddrnot_out'] == 'on') {
|
|
pfb_logger("Using Alias Native\n", 1);
|
|
}
|
|
|
|
if (!$custom) {
|
|
pfb_logger(' .', 1);
|
|
|
|
// Allow cURL SSL downgrade 'Flex' if user configured.
|
|
$pflex = FALSE;
|
|
if ($row['state'] == 'Flex') {
|
|
$pflex = TRUE;
|
|
}
|
|
|
|
// Adjust 'geoip' format to GeoIP path location
|
|
if ($row['format'] == 'geoip') {
|
|
if (strpos($row['url'], ' ') !== FALSE) {
|
|
$row['url'] = strstr($row['url'], ' ', TRUE);
|
|
}
|
|
if (!empty(pfb_filter($row['url'], PFB_FILTER_WORD, 'Adjust geoip format to GeoIP path location'))) {
|
|
$row['url'] = "/usr/local/share/GeoIP/cc/{$row['url']}{$list['vtype']}.txt";
|
|
} else {
|
|
$row['url'] = '';
|
|
}
|
|
}
|
|
|
|
// Remove 'whois' source field description
|
|
elseif ($row['format'] == 'asn') {
|
|
if (strpos($row['url'], ' ') !== FALSE) {
|
|
$row['url'] = strstr($row['url'], ' ', TRUE);
|
|
}
|
|
}
|
|
|
|
// Determine if list needs to be downloaded or reuse previously downloaded file.
|
|
if ($pfbreuse == 'on' && file_exists("{$file_dwn}.orig")) {
|
|
// File exists/reuse
|
|
|
|
// Process Emerging Threats IQRisk if required
|
|
if (strpos($row['url'], 'iprepdata.txt') !== FALSE) {
|
|
if (file_exists("{$file_dwn}.raw")) {
|
|
$file_dwn_esc = escapeshellarg("{$file_dwn}.raw");
|
|
$file_org_esc = escapeshellarg("{$file_dwn}.orig");
|
|
exec("/usr/bin/gunzip -c {$file_dwn_esc} > {$file_org_esc}");
|
|
}
|
|
exec("{$pfb['script']} et {$header_esc} x x x x x {$pfb['etblock']} {$pfb['etmatch']} {$elog}");
|
|
}
|
|
}
|
|
else {
|
|
// Download list
|
|
if (!pfb_download($row['url'], $file_dwn, $pflex, $header, $row['format'],
|
|
1, $list['vtype'], '', '', '', '', $srcint)) {
|
|
|
|
// Determine reason for download failure
|
|
pfb_download_failure($alias, $header, $pfbfolder, $row['url'], $row['format'], $list['vtype']);
|
|
|
|
// Utilize previously download file (If 'fail' marker exists)
|
|
if (file_exists("{$pfbfolder}/{$header}.fail") &&
|
|
file_exists("{$file_dwn}.orig")) {
|
|
pfb_logger("\n Restoring previously downloaded file contents...", 2);
|
|
}
|
|
else {
|
|
if ($pfbadv) {
|
|
// Script to Remove failed lists from masterfile
|
|
exec("{$pfb['script']} remove x x x {$header_esc} {$elog}");
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
// Clear any previous download fail marker
|
|
unlink_if_exists("{$pfbfolder}/{$header}.fail");
|
|
pfb_logger('.', 1);
|
|
}
|
|
}
|
|
pfb_logger(' completed .', 1);
|
|
}
|
|
else {
|
|
if ($list['whois_convert'] == 'on') {
|
|
// Process Domain/AS based custom list
|
|
$custom_list = str_replace("\n", ',', pfbng_text_area_decode($list['custom'], FALSE, TRUE, TRUE));
|
|
if (!empty(pfb_filter($custom_list, PFB_FILTER_CSV_WHOIS, 'Process Domain/AS based custom list'))) {
|
|
exec("{$pfb['script']} whoisconvert {$header_esc} {$list['vtype']} {$custom_list} {$elog}");
|
|
} else {
|
|
pfb_logger("\nFailed to process customlist [AS/Whois convert | " . htmlspecialchars($custom_list) . " ]", 1);
|
|
}
|
|
}
|
|
else {
|
|
// Process IP based custom list
|
|
$custom_list = pfbng_text_area_decode($list['custom'], FALSE, TRUE, FALSE);
|
|
@file_put_contents("{$file_dwn}.orig", $custom_list, LOCK_EX);
|
|
}
|
|
pfb_logger(' . completed .', 1);
|
|
}
|
|
|
|
$ip_data = ''; // IPs collected from feed
|
|
$parse_fail = 0; // Failed parsed lines from feed
|
|
pfb_logger('.', 1);
|
|
|
|
// Set 'auto' format for all lists, except for lists that require 'regex' parsing.
|
|
if ($row['format'] == 'regex') {
|
|
$pftype = 'regex';
|
|
}
|
|
else {
|
|
$url = pathinfo($row['url']);
|
|
|
|
// Strip any text after '?'
|
|
if (strpos($url['extension'], '?') !== FALSE) {
|
|
$url['extension'] = strstr($url['extension'], '?', TRUE);
|
|
}
|
|
|
|
// Determine if list is an IBlock list
|
|
if (strpos($url['dirname'], 'iblocklist') !== FALSE) {
|
|
$url['extension'] = 'iblock';
|
|
}
|
|
|
|
// Use 'regex' IP parser for non-standard IP lists.
|
|
if (in_array($url['extension'], array('html', 'htm', 'php', 'aspx', 'cgi', 'csv', 'rules', ''))) {
|
|
$pftype = 'regex';
|
|
} else {
|
|
$pftype = 'auto';
|
|
}
|
|
}
|
|
|
|
// IPv4/6 Advanced Tunable - (Pre Script processing)
|
|
if ($pfb_script_pre && file_exists("{$pfb_script_pre}")) {
|
|
pfb_logger("\nExecuting pre-script: {$list['script_pre']}\n", 1);
|
|
$file_dwn_esc = escapeshellarg("{$file_dwn}.orig");
|
|
@copy("{$file_dwn}.orig", "{$file_dwn}.orig.pre"); // Save original file for restoration
|
|
exec("{$pfb_script_pre} {$file_dwn_esc} {$list['vtype']} {$elog}");
|
|
}
|
|
|
|
if (($fhandle = @fopen("{$file_dwn}.orig", 'r')) !== FALSE) {
|
|
while (($line = @fgets($fhandle)) !== FALSE) {
|
|
// Record original line for regex matching, if required.
|
|
$oline = $line;
|
|
|
|
// Remove any leading/trailing whitespaces
|
|
$line = trim($line);
|
|
|
|
// Remove commentlines and blank lines
|
|
if (substr($line, 0, 1) == '#' || empty($line)) {
|
|
continue;
|
|
}
|
|
|
|
$parse_error = FALSE;
|
|
if ($list['vtype'] == '_v4' && $pftype == 'auto') {
|
|
|
|
// IBlock - parser sample ( JKS Media, LLC:4.53.2.12-4.53.2.15 )
|
|
// Remove leading domain name details
|
|
if (strpos($line, '-') !== FALSE && strpos($line, ':') !== FALSE) {
|
|
$line = str_replace(':', '', strstr($line, ':', FALSE));
|
|
}
|
|
|
|
// If 'space' character found, remove characters after space
|
|
if (strpos($line, ' ') !== FALSE) {
|
|
$line = strstr($line, ' ', TRUE);
|
|
}
|
|
|
|
// If '#' character found, remove characters after '#'
|
|
if (strpos($line, '#') !== FALSE) {
|
|
$line = str_replace('#', '', strstr($line, '#', TRUE));
|
|
}
|
|
|
|
// Remove any leading/trailing whitespaces
|
|
$line = trim($line);
|
|
|
|
// Range parser
|
|
if (strpos($line, '-') !== FALSE) {
|
|
$matches = explode('-', $line);
|
|
if (count($matches) == 2) {
|
|
$a_cidr = ip_range_to_subnet_array($matches[0],$matches[1]);
|
|
if (!empty($a_cidr)) {
|
|
foreach ($a_cidr as $cidr) {
|
|
$cidr = sanitize_ipaddr($cidr, $custom, $pfbcidr);
|
|
if (!empty($cidr)) {
|
|
if (validate_ipv4($cidr)) {
|
|
$ip_data .= $cidr . "\n";
|
|
}
|
|
else {
|
|
$parse_error = TRUE;
|
|
}
|
|
}
|
|
}
|
|
if (!$parse_error) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$parse_error = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!$parse_error) {
|
|
// Single address parser
|
|
$parsed = sanitize_ipaddr($line, $custom, $pfbcidr);
|
|
if (validate_ipv4($parsed)) {
|
|
$ip_data .= $parsed . "\n";
|
|
continue;
|
|
}
|
|
else {
|
|
$parse_error = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($list['vtype'] == '_v4' && ($pftype == 'regex' || $parse_error)) {
|
|
|
|
// Use regex as last alternative.
|
|
|
|
if (strpos($oline, '-') !== FALSE && strpos($oline, '.') !== FALSE) {
|
|
// Network range 192.168.0.0-192.168.0.254
|
|
if (preg_match($pfb['range'], $oline, $matches)) {
|
|
$a_cidr = ip_range_to_subnet_array($matches[1], $matches[2]);
|
|
if (!empty($a_cidr)) {
|
|
foreach ($a_cidr as $cidr) {
|
|
$cidr = sanitize_ipaddr($cidr, $custom, $pfbcidr);
|
|
if (validate_ipv4($cidr)) {
|
|
$ip_data .= $cidr . "\n";
|
|
}
|
|
else {
|
|
$parse_fail++;
|
|
}
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// IPv4/CIDR format 192.168.0.0 | 192.168.0.0/16
|
|
if (preg_match_all($pfb['ipv4'], $oline, $matches)) {
|
|
$matches = array_unique($matches[0]);
|
|
foreach ($matches as $match) {
|
|
|
|
// Workaround to skip cloudflare error pages
|
|
if (strpos($oline, 'cf-footer-item') === FALSE) {
|
|
$parsed = sanitize_ipaddr($match, $custom, $pfbcidr);
|
|
if (validate_ipv4($parsed)) {
|
|
$ip_data .= $parsed . "\n";
|
|
}
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ($list['vtype'] == '_v6') {
|
|
// Auto IPv6 parser
|
|
if ($pftype == 'auto') {
|
|
if (strpos($line, ':') !== FALSE) {
|
|
|
|
// Remove any comments
|
|
if (strpos($line, '#') !== FALSE) {
|
|
$line = str_replace('#', '', strstr($line, '#', TRUE));
|
|
}
|
|
|
|
if (validate_ipv6($line)) {
|
|
$ip_data .= $line . "\n";
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Range parser
|
|
if (strpos($line, '-') !== FALSE && strpos($line, ':') !== FALSE) {
|
|
$matches = explode('-', $line);
|
|
if (count($matches) == 2) {
|
|
$a_cidr = ip_range_to_subnet_array($matches[0],$matches[1]);
|
|
if (!empty($a_cidr)) {
|
|
foreach ($a_cidr as $cidr) {
|
|
if (!empty($cidr)) {
|
|
if (validate_ipv6($cidr)) {
|
|
$ip_data .= $cidr . "\n";
|
|
}
|
|
else {
|
|
$parse_error = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!$parse_error) {
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
$parse_error = TRUE;
|
|
}
|
|
}
|
|
|
|
// IPv6 Regex parser
|
|
if (preg_match_all($pfb['ipv6'], $oline, $matches)) {
|
|
$matches = array_unique($matches[0]);
|
|
foreach ($matches as $match) {
|
|
|
|
// Remove any comments
|
|
if (strpos($match, '#') !== FALSE) {
|
|
$match = str_replace('#', '', strstr($match, '#', TRUE));
|
|
}
|
|
|
|
// Workaround to skip cloudflare error pages
|
|
if (strpos($oline, 'cf-footer-item') === FALSE) {
|
|
if (validate_ipv6($match)) {
|
|
$ip_data .= $match . "\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for parse failures
|
|
if (!empty($line) && !preg_match('/[a-zA-Z,;|\"\'?]/', $line)) {
|
|
$parse_fail++;
|
|
$log = "[!] Parse Errors [ {$parse_fail} ]\n";
|
|
pfb_logger("{$log}", 2);
|
|
}
|
|
}
|
|
if ($fhandle) {
|
|
@fclose($fhandle);
|
|
}
|
|
pfb_logger("\n", 1);
|
|
|
|
// IP v4/6 Advanced Tunable - (Post Script processing)
|
|
if ($pfb_script_post && file_exists("{$pfb_script_post}")) {
|
|
pfb_logger("\nExecuting post-script: {$list['script_pre']}\n", 1);
|
|
$file_org_esc = escapeshellarg("{$file_dwn}.orig");
|
|
@copy("{$file_dwn}.orig", "{$file_dwn}.orig.post"); // Save original file for restoration
|
|
exec("{$pfb_script_post} {$file_org_esc} {$list['vtype']} {$elog}");
|
|
}
|
|
|
|
if (!$custom) {
|
|
// Check to see if list actually failed download or has no IPs listed.
|
|
$file_chk = '';
|
|
if (file_exists("{$file_dwn}.orig") && @filesize("{$file_dwn}.orig") > 0) {
|
|
$file_org_esc = escapeshellarg("{$file_dwn}.orig");
|
|
$file_chk = exec("{$pfb['grep']} -cv '^#\|^\$' {$file_org_esc}");
|
|
}
|
|
|
|
if ($file_chk == 0) {
|
|
if ($list['vtype'] == '_v6') {
|
|
$p_ip = "::{$pfb['ip_ph']}";
|
|
} else {
|
|
$p_ip = $pfb['ip_ph'];
|
|
}
|
|
|
|
$ip_data = "{$p_ip}\n";
|
|
$log = " Empty file, Adding '{$p_ip}' to avoid download failure.\n";
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
}
|
|
|
|
if (!empty($ip_data)) {
|
|
// Save List to '.txt' format in appropriate folder
|
|
@file_put_contents("{$pfbfolder}/{$header}.txt", "{$ip_data}", LOCK_EX);
|
|
|
|
// Call 'shell script' functions (Deny Actions only)
|
|
if ($pfbadv && $list['vtype'] == '_v4') {
|
|
$args = '';
|
|
// Call Process255
|
|
if ($pfb['dup'] == 'on' || $pfb['agg'] == 'on') {
|
|
$args = '_255';
|
|
}
|
|
// Call Aggregate process
|
|
if ($pfb['agg'] == 'on') {
|
|
$args .= '_agg';
|
|
}
|
|
// Call Reputation Max process
|
|
if ($pfb['rep'] == 'on') {
|
|
$args .= '_rep';
|
|
}
|
|
// Call Duplication process
|
|
if ($pfb['dup'] == 'on') {
|
|
$args .= '_dup';
|
|
}
|
|
if (!empty($args)) {
|
|
exec("{$pfb['script']} {$args} {$header_esc} {$pfb['max']} {$pfb['drep']} {$pfb['ccexclude']} {$pfb['ccwhite']} {$pfb['ccblack']} {$elog}");
|
|
}
|
|
}
|
|
|
|
if (!$pfbadv && $list['vtype'] == '_v4') {
|
|
// Call Aggregate process
|
|
if ($pfb['agg'] == 'on') {
|
|
exec("{$pfb['script']} cidr_aggregate {$header_esc} {$pfbfolder} {$elog}");
|
|
}
|
|
}
|
|
|
|
// Collect updated alias lists ('Reputation' disabled)
|
|
$pfb_alias_lists[] = "{$alias}";
|
|
|
|
if ($pfbadv && $list['vtype'] == '_v4') {
|
|
// Execute Reputation functions, when changes are found.
|
|
$pfb['repcheck'] = TRUE;
|
|
|
|
// Enable suppression process due to updates
|
|
if ($pfb['supp'] == 'on') {
|
|
$pfb['supp_update'] = TRUE;
|
|
}
|
|
}
|
|
} else {
|
|
if (!$custom) {
|
|
$log = "[ {$alias} {$header} ] No IPs found! Ensure only IP based Feeds are used! ]\n";
|
|
} else {
|
|
$log = "[ {$alias} {$header} ] Custom List: No IPs found! Ensure only IP based Feeds are used! ]\n";
|
|
}
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
unset($ip_data);
|
|
|
|
// Remove update file indicator
|
|
unlink_if_exists("{$pfbfolder}/{$header}.update");
|
|
|
|
// Restore Original Downloaded file after Post Script function
|
|
if (file_exists("{$file_dwn}.orig.post")) {
|
|
@rename("{$file_dwn}.orig.post", "{$file_dwn}.orig");
|
|
}
|
|
// Restore Original Downloaded file after Pre Script function
|
|
if (file_exists("{$file_dwn}.orig.pre")) {
|
|
@rename("{$file_dwn}.orig.pre", "{$file_dwn}.orig");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove database update file markers
|
|
unlink_if_exists("{$pfb['dbdir']}/geoip.update");
|
|
unlink_if_exists("{$pfb['dbdir']}/asn.update");
|
|
|
|
#################################
|
|
# REPUTATION PROCESSES #
|
|
#################################
|
|
|
|
// IP Reputation processes (pMax and dMax)
|
|
if ($pfb['prep'] == 'on' && $pfb['repcheck'] && !$pfb['save'] && $pfb['enable'] == 'on') {
|
|
// Script to run prep process
|
|
exec("{$pfb['script']} pmax x {$pfb['pmax']} {$elog}");
|
|
}
|
|
if ($pfb['drep'] == 'on' && $pfb['repcheck'] && !$pfb['save'] && $pfb['enable'] == 'on') {
|
|
// Script to run drep process
|
|
exec("{$pfb['script']} dmax x {$pfb['dmax']} {$pfb['drep']} {$pfb['ccexclude']} {$pfb['ccwhite']} {$pfb['ccblack']} {$elog}");
|
|
}
|
|
|
|
#################################################
|
|
# CONFIGURE ALIASES AND FIREWALL RULES #
|
|
#################################################
|
|
|
|
foreach ($ip_types as $ip_type => $vtype) {
|
|
$lists = config_get_path("installedpackages/{$ip_type}/config");
|
|
|
|
// Add DNSBLIP, if configured (IPv4 only)
|
|
if ($pfb['dnsbl'] == 'on' && $pfb['dnsbl_ip'] != 'Disabled' && $vtype == '_v4') {
|
|
|
|
$list = array( 'aliasname' => 'DNSBLIP',
|
|
'vtype' => "{$vtype}",
|
|
'key' => 0,
|
|
'dnsblip' => '',
|
|
'action' => "{$pfb['dnsbl_ip']}",
|
|
'aliaslog' => "{$pfb['dnsblconfig']['aliaslog']}");
|
|
|
|
$list['row'][] = array( 'format' => 'auto',
|
|
'state' => 'Enabled',
|
|
'url' => "{$pfb['dbdir']}/DNSBLIP{$vtype}.txt",
|
|
'header' => 'DNSBLIP');
|
|
$lists[] = $list;
|
|
}
|
|
|
|
if (!empty($lists) && $pfb['enable'] == 'on') {
|
|
$pfbrunonce = TRUE;
|
|
foreach ($lists as $key => $list) {
|
|
$alias = "pfB_{$list['aliasname']}{$vtype}";
|
|
if (empty(pfb_filter($alias, PFB_FILTER_WORD, 'Configure Aliases and Firewall Rules'))) {
|
|
pfb_logger("\n Invalid Aliasname:{$list['aliasname']}{$vtype}, *skipping*", 1);
|
|
continue;
|
|
}
|
|
$alias_esc = escapeshellarg($alias);
|
|
|
|
// Skip any Alias that are 'enabled' but Lists/customlists are not defined.
|
|
if (empty($list['row'][0]['url']) && empty($list['custom'])) {
|
|
exec("{$pfb['pfctl']} -t {$alias_esc} -T kill 2>&1", $result);
|
|
continue;
|
|
}
|
|
|
|
if (isset($list['dnsblip'])) {
|
|
$list_type = 'pfblockerngdnsblsettings';
|
|
} else {
|
|
$list_type = "{$ip_type}";
|
|
}
|
|
|
|
// Determine 'list' details (return array $pfbarr)
|
|
pfb_determine_list_detail($list['action'], '', $list_type, $key);
|
|
$pfbadv = $pfbarr['adv'];
|
|
$pfbdescr = $pfbarr['descr'];
|
|
$pfbfolder = $pfbarr['folder'];
|
|
|
|
// Only Save aliases that have been updated.
|
|
// When 'Reputation' is used, all aliases need to be updated.
|
|
$final_alias = array();
|
|
if ($pfb['drep'] == 'on' || $pfb['prep'] == 'on') {
|
|
if (!empty($pfb_alias_lists_all)) {
|
|
$final_alias = array_unique($pfb_alias_lists_all);
|
|
}
|
|
}
|
|
else {
|
|
if (!empty($pfb_alias_lists)) {
|
|
$final_alias = array_unique($pfb_alias_lists);
|
|
}
|
|
}
|
|
|
|
if ($list['action'] != 'Disabled') {
|
|
$pfbupdate = FALSE;
|
|
$alias_ips = ''; // IP Collection of all Lists in the Alias
|
|
$urlvalue = ''; // Firewall: Aliases value field
|
|
|
|
if (isset($list['row'])) {
|
|
foreach ($list['row'] as $row) {
|
|
if (!empty($row['url']) && $row['state'] != 'Disabled') {
|
|
|
|
$header = "{$row['header']}{$vtype}";
|
|
if (empty(pfb_filter($header, PFB_FILTER_WORD, 'Configure Aliases and Firewall Rules'))) {
|
|
pfb_logger("\n Invalid Aliasname:{$list['aliasname']}{$vtype} | Header:{$row['header']}{$vtype}, *skipping*", 1);
|
|
continue;
|
|
}
|
|
$header_esc = escapeshellarg($header);
|
|
$urlvalue .= "{$header},";
|
|
|
|
$pfctlck = exec("{$pfb['pfctl']} -vvsTables | {$pfb['grep']} -A1 {$alias_esc} | {$pfb['awk']} '/Addresses/ {s+=\$2}; END {print s}'");
|
|
|
|
// Update alias if list file exists and its been updated or if the alias URL table is empty.
|
|
if (file_exists("{$pfbfolder}/{$header}.txt") && (in_array($alias, $final_alias) || empty($pfctlck))) {
|
|
// Script to run suppression process (print header only)
|
|
if ($pfbrunonce && $pfb['supp'] == 'on' && $vtype == '_v4' && $pfb['supp_update']) {
|
|
exec("{$pfb['script']} suppress suppressheader {$elog}");
|
|
$pfbrunonce = FALSE;
|
|
}
|
|
// Script to run suppression process (body)
|
|
if ($pfb['supp'] == 'on' && $vtype == '_v4' && $pfb['supp_update'] && $pfbadv) {
|
|
if ($pfb['dup'] == 'on') {
|
|
exec("{$pfb['script']} suppress {$header_esc} {$pfbfolder} on {$elog}");
|
|
} else {
|
|
exec("{$pfb['script']} suppress {$header_esc} {$pfbfolder} off {$elog}");
|
|
}
|
|
}
|
|
$alias_ips .= file_get_contents("{$pfbfolder}/{$header}.txt");
|
|
$pfbupdate = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// check custom network list
|
|
$aliasname = "{$list['aliasname']}_custom{$vtype}";
|
|
|
|
// Update alias if list file exists and its been updated or if the alias URL table is empty.
|
|
$alias_esc = escapeshellarg($alias);
|
|
$pfctlck = exec("{$pfb['pfctl']} -vvsTables | {$pfb['grep']} -A1 {$alias_esc} | {$pfb['awk']} '/Addresses/ {s+=\$2}; END {print s}'");
|
|
if (!empty($list['custom'])) {
|
|
$urlvalue .= "{$aliasname},";
|
|
if (file_exists("{$pfbfolder}/{$aliasname}.txt") && in_array($alias, $final_alias) ||
|
|
file_exists("{$pfbfolder}/{$aliasname}.txt") && empty($pfctlck)) {
|
|
$alias_ips .= file_get_contents("{$pfbfolder}/{$aliasname}.txt");
|
|
$pfbupdate = TRUE;
|
|
}
|
|
}
|
|
|
|
// Determine validity of alias URL tables/rules. ie: Don't create empty URL tables or aliases
|
|
if (empty($alias_ips) && empty($pfctlck)) {
|
|
unlink_if_exists("{$pfb['aliasdir']}/{$alias}.txt");
|
|
}
|
|
else {
|
|
// Save only aliases that have been updated.
|
|
if ($pfbupdate) {
|
|
@file_put_contents("{$pfb['aliasdir']}/{$alias}.txt", $alias_ips, LOCK_EX);
|
|
}
|
|
|
|
// Add '[s]' to Alias descriptions (Bypass States removal feature)
|
|
$adescr = "pfBlockerNG {$pfbdescr} Alias";
|
|
if ($list['stateremoval'] == 'disabled') {
|
|
$adescr = "pfBlockerNG {$pfbdescr} Alias [s]";
|
|
}
|
|
|
|
// Create alias
|
|
$new_aliases_list[] = "{$alias}";
|
|
$new_aliases[] = array( 'name' => "{$alias}",
|
|
'url' => "{$pfb['weblocal']}?pfb={$alias}",
|
|
'updatefreq' => '32',
|
|
'address' => '',
|
|
'descr' => "{$adescr} [ {$urlvalue} ]",
|
|
'type' => 'urltable',
|
|
'detail' => 'DO NOT EDIT THIS ALIAS'
|
|
);
|
|
|
|
// Define firewall rule settings
|
|
pfb_firewall_rule($list['action'], $alias, $vtype, $list['aliaslog'], $pfbarr['agateway_in'], $pfbarr['agateway_out'],
|
|
$pfbarr['aaddrnot_in'], $pfbarr['aaddr_in'], $pfbarr['aports_in'], $pfbarr['aproto_in'], $pfbarr['anot_in'],
|
|
$pfbarr['aaddrnot_out'], $pfbarr['aaddr_out'], $pfbarr['aports_out'], $pfbarr['aproto_out'], $pfbarr['anot_out']);
|
|
}
|
|
}
|
|
else {
|
|
// unlink previous pfblockerNG alias list
|
|
unlink_if_exists("{$pfb['aliasdir']}/{$alias}.txt");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Clear variables
|
|
$alias_ips = '';
|
|
|
|
// Define DNSBL VIP Ports alias
|
|
if ($pfb['dnsbl_rule'] != 'Disabled' && !empty($pfb['dnsbl_port']) && !empty($pfb['dnsbl_port_ssl'])
|
|
&& !empty($pfb['dnsblconfig']['dnsbl_allow_int']) && isset($pfb['dnsbl_vip'])) {
|
|
|
|
$new_aliases_list[] = 'pfB_DNSBL_Ports';
|
|
$new_aliases[] = array( 'name' => 'pfB_DNSBL_Ports',
|
|
'address' => $pfb['dnsbl_iface'] != 'lo0' ? "{$pfb['dnsbl_port']} {$pfb['dnsbl_port_ssl']}" : '80 443',
|
|
'descr' => 'pfBlockerNG DNSBL VIP Ports',
|
|
'type' => 'port',
|
|
'detail' => 'DO NOT EDIT THIS PORT||DO NOT EDIT THIS PORT'
|
|
);
|
|
|
|
if ($pfb['dnsbl_v6'] == 'on') {
|
|
$new_aliases_list[] = 'pfB_DNSBL_VIPs';
|
|
$new_aliases[] = array( 'name' => 'pfB_DNSBL_VIPs',
|
|
'address' => "{$pfb['dnsbl_vip']} ::{$pfb['dnsbl_vip']}",
|
|
'descr' => 'pfBlockerNG DNSBL VIPs',
|
|
'type' => 'host',
|
|
'detail' => 'DO NOT EDIT THIS HOST||DO NOT EDIT THIS HOST'
|
|
);
|
|
}
|
|
}
|
|
|
|
#########################################
|
|
# UPDATE pfSense ALIAS TABLES #
|
|
#########################################
|
|
|
|
// Reload config.xml to get any recent changes
|
|
config_read_file(false, true);
|
|
|
|
$exist_aliases = config_get_path('aliases/alias', []);
|
|
foreach ($exist_aliases as $cbalias) {
|
|
|
|
if (substr($cbalias['name'], 0, 4) == 'pfB_') {
|
|
// Remove unreferenced pfB aliastable files
|
|
if (!in_array($cbalias['name'], $new_aliases_list)) {
|
|
unlink_if_exists("{$pfb['aliasdir']}/{$cbalias['name']}.*");
|
|
}
|
|
}
|
|
else {
|
|
$new_aliases[] = $cbalias;
|
|
}
|
|
}
|
|
|
|
// Update config.xml, if changes required
|
|
if ($exist_aliases !== $new_aliases) {
|
|
config_set_path('aliases/alias', $new_aliases);
|
|
write_config('pfBlockerNG: saving Aliases');
|
|
}
|
|
unset($new_aliases, $exist_aliases);
|
|
|
|
#########################
|
|
# Assign Rules #
|
|
#########################
|
|
|
|
// Only execute if autorules are defined or if an alias has been removed.
|
|
if ($pfb['autorules'] || $pfb['enable'] == '' || $pfb['remove']) {
|
|
$message = '';
|
|
if (!empty($pfb['deny_inbound']) || !empty($pfb['permit_inbound']) || !empty($pfb['match_inbound'])) {
|
|
if (empty($pfb['inbound_interfaces'])) {
|
|
$message = " Unable to apply rules. Inbound interface option not configured.";
|
|
}
|
|
}
|
|
if (!empty($pfb['deny_outbound']) || !empty($pfb['permit_outbound']) || !empty($pfb['match_outbound'])) {
|
|
if (empty($pfb['outbound_interfaces'])) {
|
|
$message .= "\n Unable to apply rules. Outbound interface option not configured.";
|
|
}
|
|
}
|
|
|
|
if (empty($message)) {
|
|
$new_rules = $permit_rules = $match_rules = $other_rules = $fpermit_rules = $fmatch_rules = $fother_rules = array();
|
|
|
|
// Reload config.xml to get any recent changes
|
|
config_read_file(false, true);
|
|
|
|
// New vs old rules array comparison
|
|
$orig_rules_nocreated = $new_rules_nocreated = array();
|
|
|
|
// Collect all existing rules
|
|
$rules = config_get_path('filter/rule', []);
|
|
|
|
// Collect existing pfSense rules 'pass', 'match' and 'other' pfSense rules into new arrays.
|
|
if (!empty($rules)) {
|
|
foreach ($rules as $rule) {
|
|
|
|
// Remove all existing rules that start with 'pfB_' in the Rule Description
|
|
if (substr($rule['descr'], 0, 4) != 'pfB_') {
|
|
|
|
// Upgrade previous IPv4 pfBlockerNG 'alias type' aliasnames to new '_v4' suffix format
|
|
foreach (array('source', 'destination') as $rtype) {
|
|
if (substr($rule[$rtype]['address'], 0, 4) == 'pfB_' &&
|
|
substr($rule[$rtype]['address'], -3) != '_v4' &&
|
|
$rule['ipprotocol'] == 'inet') {
|
|
|
|
// Add '_v4' suffix
|
|
$rule[$rtype]['address'] = "{$rule[$rtype]['address']}_v4";
|
|
}
|
|
}
|
|
|
|
// Floating rules collection 'Floating Pass/Match', balance to 'other'
|
|
if ($pfb['float'] == 'on') {
|
|
if ($pfb['order'] == 'order_0' && $rule['floating'] == 'yes') {
|
|
$fother_rules[] = $rule;
|
|
}
|
|
else {
|
|
if ($rule['type'] == 'pass' && $rule['floating'] == 'yes') {
|
|
$fpermit_rules[] = $rule;
|
|
} elseif ($rule['type'] == 'match' && $rule['floating'] == 'yes') {
|
|
$fmatch_rules[] = $rule;
|
|
} elseif ($rule['floating'] == 'yes') {
|
|
$fother_rules[] = $rule;
|
|
} else {
|
|
$other_rules[] = $rule;
|
|
}
|
|
}
|
|
} else {
|
|
// Collect only 'selected inbound and outbound interfaces'. balance to 'other'
|
|
if (in_array($rule['interface'], $pfb['inbound_interfaces']) ||
|
|
in_array($rule['interface'], $pfb['outbound_interfaces'])) {
|
|
// Floating rules 'off'. Collect 'floating other', pass, balance to 'other'
|
|
if ($rule['floating'] == 'yes') {
|
|
$fother_rules[] = $rule;
|
|
} elseif ($rule['type'] == 'pass' || isset($rule['associated-rule-id'])) {
|
|
if ($pfb['order'] == 'order_0') {
|
|
$other_rules[] = $rule;
|
|
} else {
|
|
$permit_rules[] = $rule;
|
|
}
|
|
} else {
|
|
$other_rules[] = $rule;
|
|
}
|
|
} else {
|
|
if ($rule['floating'] == 'yes') {
|
|
$fother_rules[] = $rule;
|
|
} else {
|
|
$other_rules[] = $rule;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove 'created' tag
|
|
if (isset($rule['created'])) {
|
|
unset($rule['created']);
|
|
}
|
|
$orig_rules_nocreated[] = $rule;
|
|
}
|
|
}
|
|
|
|
#################################################################################
|
|
# IP FIREWALL RULES ORDER #
|
|
# ORDER 0 | pfB (p/m/b/r) | All other | #
|
|
# ORDER 1 | pfSense (p/m) | pfB (p/m) | pfB (b/r) | pfSense (b/r) #
|
|
# ORDER 2 | pfB (p/m) | pfSense (p/m) | pfB (b/r) | pfSense (b/r) #
|
|
# ORDER 3 | pfB (p/m) | pfB (b/r) | pfSense (p/m) | pfSense (b/r) #
|
|
# ORDER 4 | pfB (p/m) | pfB (b/r) | pfSense (b/r) | pfSense (p/m) #
|
|
#################################################################################
|
|
|
|
|
|
if ($pfb['float'] == '' && $pfb['order'] == 'order_1' && !empty($fother_rules)) {
|
|
foreach ($fother_rules as $cb_rules) {
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
if ($pfb['float'] == 'on' && $pfb['order'] == 'order_1') {
|
|
foreach (array($fpermit_rules, $fmatch_rules) as $rtype) {
|
|
if (!empty($rtype)) {
|
|
foreach ($rtype as $cb_rules) {
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Define DNSBL 'Floating' pass rule for selected 'OPT' segments to be able to access the LAN DNSBL VIP
|
|
if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && $pfb['dnsbl_rule'] != 'Disabled'
|
|
&& !empty($pfb['dnsbl_port']) && !empty($pfb['dnsbl_port_ssl'])
|
|
&& !empty($pfb['dnsblconfig']['dnsbl_allow_int']) && isset($pfb['dnsbl_vip'])) {
|
|
|
|
$rule = $pfb['base_rule_float'];
|
|
$rule['tracker'] = pfb_tracker('pfB_DNSBL_Ping', '', '');
|
|
$rule['type'] = 'pass';
|
|
$rule['direction'] = 'any';
|
|
$rule['interface'] = "{$pfb['dnsblconfig']['dnsbl_allow_int']}";
|
|
$rule['ipprotocol'] = ($pfb['dnsbl_v6'] == 'on' ? $rule['ipprotocol'] = 'inet46' : $rule['ipprotocol'] = 'inet');
|
|
$rule['descr'] = "pfB_DNSBL_Ping{$pfb['suffix']}";
|
|
$rule['protocol'] = 'icmp';
|
|
$rule['icmptype'] = 'echoreq';
|
|
$rule['source'] = array('any' => '');
|
|
$rule['destination'] = array('address' => ($pfb['dnsbl_v6'] == 'on' ? 'pfB_DNSBL_VIPs' : $pfb['dnsbl_vip']));
|
|
$rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto');
|
|
$new_rules[] = $rule;
|
|
|
|
$rule = $pfb['base_rule_float'];
|
|
$rule['tracker'] = pfb_tracker('pfB_DNSBL_Permit', '', '');
|
|
$rule['type'] = 'pass';
|
|
$rule['direction'] = 'any';
|
|
$rule['interface'] = "{$pfb['dnsblconfig']['dnsbl_allow_int']}";
|
|
$rule['ipprotocol'] = ($pfb['dnsbl_v6'] == 'on' ? $rule['ipprotocol'] = 'inet46' : $rule['ipprotocol'] = 'inet');
|
|
$rule['descr'] = "pfB_DNSBL_Permit{$pfb['suffix']}";
|
|
$rule['protocol'] = 'tcp/udp';
|
|
$rule['source'] = array('any' => '');
|
|
$rule['destination'] = array('address' => ($pfb['dnsbl_v6'] == 'on' ? 'pfB_DNSBL_VIPs' : $pfb['dnsbl_vip']),
|
|
'port' => 'pfB_DNSBL_Ports');
|
|
$rule['created'] = array('time' => (int)microtime(true), 'username' => 'Auto');
|
|
$new_rules[] = $rule;
|
|
}
|
|
|
|
// Define inbound interface rules
|
|
if (!empty($pfb['inbound_interfaces'])) {
|
|
$pfbrunonce = TRUE;
|
|
foreach ($pfb['inbound_interfaces'] as $inbound_interface) {
|
|
if ($pfb['order'] == 'order_1' && !empty($permit_rules)) {
|
|
foreach ($permit_rules as $cb_rules) {
|
|
if ($cb_rules['interface'] == $inbound_interface) {
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
}
|
|
if (!empty($pfb['permit_inbound'])) {
|
|
foreach ($pfb['permit_inbound'] as $cb_rules) {
|
|
$cb_rules['interface'] = $inbound_interface;
|
|
$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $inbound_interface, 'permit_in');
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
// Match inbound rules defined as floating only.
|
|
if ($pfbrunonce && !empty($pfb['match_inbound'])) {
|
|
foreach ($pfb['match_inbound'] as $cb_rules) {
|
|
$cb_rules['interface'] = $pfb['inbound_floating'];
|
|
$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $inbound_interface, 'match_in');
|
|
$new_rules[] = $cb_rules;
|
|
$pfbrunonce = FALSE;
|
|
}
|
|
}
|
|
if ($pfb['order'] == 'order_2') {
|
|
foreach (array($fpermit_rules, $fmatch_rules) as $rtype) {
|
|
if (!empty($rtype)) {
|
|
foreach ($rtype as $cb_rules) {
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
}
|
|
if (!empty($permit_rules)) {
|
|
foreach ($permit_rules as $cb_rules) {
|
|
if ($cb_rules['interface'] == $inbound_interface) {
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!empty($pfb['deny_inbound'])) {
|
|
foreach ($pfb['deny_inbound'] as $cb_rules) {
|
|
$cb_rules['interface'] = $inbound_interface;
|
|
$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $inbound_interface, 'deny_in');
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Define outbound interface rules
|
|
if (!empty($pfb['outbound_interfaces'])) {
|
|
$pfbrunonce = TRUE;
|
|
foreach ($pfb['outbound_interfaces'] as $outbound_interface) {
|
|
if ($pfb['order'] == 'order_1' && !empty($permit_rules)) {
|
|
foreach ($permit_rules as $cb_rules) {
|
|
if ($cb_rules['interface'] == $outbound_interface) {
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
}
|
|
if (!empty($pfb['permit_outbound'])) {
|
|
foreach ($pfb['permit_outbound'] as $cb_rules) {
|
|
$cb_rules['interface'] = $outbound_interface;
|
|
$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $outbound_interface, 'permit_out');
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
// Match outbound rules defined as floating only.
|
|
if ($pfbrunonce && !empty($pfb['match_outbound'])) {
|
|
foreach ($pfb['match_outbound'] as $cb_rules) {
|
|
$cb_rules['interface'] = $pfb['outbound_floating'];
|
|
$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $outbound_interface, 'match_out');
|
|
$new_rules[] = $cb_rules;
|
|
$pfbrunonce = FALSE;
|
|
}
|
|
}
|
|
if ($pfb['order'] == 'order_2' && !empty($permit_rules)) {
|
|
foreach ($permit_rules as $cb_rules) {
|
|
if ($cb_rules['interface'] == $outbound_interface) {
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
}
|
|
if (!empty($pfb['deny_outbound'])) {
|
|
foreach ($pfb['deny_outbound'] as $cb_rules) {
|
|
$cb_rules['interface'] = $outbound_interface;
|
|
$cb_rules['tracker'] = pfb_tracker($cb_rules['descr'], $outbound_interface, 'deny_out');
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($pfb['float'] == 'on' && in_array($pfb['order'], array('order_0', 'order_3', 'order_4'))) {
|
|
if ($pfb['order'] != 'order_3') {
|
|
$rule_order = array($fother_rules, $fpermit_rules, $fmatch_rules);
|
|
} else {
|
|
$rule_order = array($fpermit_rules, $fmatch_rules, $fother_rules);
|
|
}
|
|
foreach ($rule_order as $rtype) {
|
|
if (!empty($rtype)) {
|
|
foreach ($rtype as $cb_rules) {
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($pfb['float'] == 'on' && in_array($pfb['order'], array('order_1', 'order_2')) && !empty($fother_rules)) {
|
|
foreach ($fother_rules as $cb_rules) {
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
if ($pfb['float'] == '' && $pfb['order'] != 'order_1' && !empty($fother_rules)) {
|
|
foreach ($fother_rules as $cb_rules) {
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
if ($pfb['order'] == 'order_4' && !empty($other_rules)) {
|
|
foreach ($other_rules as $cb_rules) {
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
if ($pfb['order'] == 'order_4' && !empty($permit_rules)) {
|
|
foreach ($permit_rules as $cb_rules) {
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
if ($pfb['order'] == 'order_3' && !empty($permit_rules)) {
|
|
foreach ($permit_rules as $cb_rules) {
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
if ($pfb['order'] != 'order_4' && !empty($other_rules)) {
|
|
foreach ($other_rules as $cb_rules) {
|
|
$new_rules[] = $cb_rules;
|
|
}
|
|
}
|
|
|
|
unset($pfb['permit_inbound'], $pfb['permit_outbound'], $pfb['deny_inbound'],
|
|
$pfb['deny_outbound'], $pfb['match_inbound'], $pfb['match_outbound']);
|
|
unset($cb_rules, $other_rules, $fother_rules, $permit_rules, $fpermit_rules, $match_rules, $fmatch_rules);
|
|
|
|
// Remove 'created' tag (New vs old rules array comparison)
|
|
foreach ($new_rules as $rule) {
|
|
if (isset($rule['created'])) {
|
|
unset($rule['created']);
|
|
}
|
|
$new_rules_nocreated[] = $rule;
|
|
}
|
|
|
|
// Update config.xml, if changes required
|
|
if ($orig_rules_nocreated != $new_rules_nocreated) {
|
|
config_set_path('filter/rule', $new_rules);
|
|
write_config('pfBlockerNG: saving Firewall rules');
|
|
}
|
|
}
|
|
else {
|
|
$log = "\n\n{$message}\n";
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
}
|
|
|
|
#################################
|
|
# pfSense Integration #
|
|
#################################
|
|
|
|
// If 'Rule Changes' are found, utilize the 'filter_configure()' function, if not, utilize 'pfctl replace' command
|
|
if ($pfb['autorules'] && $orig_rules_nocreated != $new_rules_nocreated || $pfb['enable'] == '' || $pfb['remove']) {
|
|
|
|
if (!$pfb['save']) {
|
|
$log = "\n===[ Aliastables / Rules ]================================\n\n";
|
|
pfb_logger("{$log}", 1);
|
|
|
|
$log = "Firewall rule changes found, applying Filter Reload\n";
|
|
syslog(LOG_NOTICE, "[pfBlockerNG] {$log}");
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
|
|
// Remove all pfB aliastables
|
|
exec("{$pfb['pfctl']} -s Tables | {$pfb['grep']} '^pfB_'", $pfb_tables);
|
|
if (isset($pfb_tables)) {
|
|
foreach ($pfb_tables as $pfb_table) {
|
|
$pfb_table_esc = escapeshellarg($pfb_table);
|
|
exec("{$pfb['pfctl']} -t {$pfb_table_esc} -T kill 2>&1", $result);
|
|
}
|
|
}
|
|
|
|
$pfb['filter_configure'] = TRUE; // Set flag for filter_configure which will create the pfctl tables
|
|
|
|
// Call function for Ramdisk processes.
|
|
pfb_aliastables('update');
|
|
}
|
|
else {
|
|
// Don't execute on user 'save'
|
|
if (!$pfb['save']) {
|
|
$log = "\n\n===[ Aliastables / Rules ]==========================================\n\n";
|
|
pfb_logger("{$log}", 1);
|
|
|
|
$log = "No changes to Firewall rules, skipping Filter Reload\n";
|
|
syslog(LOG_NOTICE, "[pfBlockerNG] {$log}");
|
|
pfb_logger("{$log}", 1);
|
|
|
|
// Remove Alerts IP unlock file and force Reload of all Aliastables
|
|
if (file_exists("{$pfb['ip_unlock']}")) {
|
|
unlink_if_exists("{$pfb['ip_unlock']}");
|
|
$pfb['repcheck'] = TRUE;
|
|
}
|
|
|
|
// Only Save Aliases that have been updated.
|
|
// When 'Reputation' is used, all aliases need to be updated when any alias has been updated.
|
|
$final_alias = array();
|
|
if ($pfb['repcheck'] && ($pfb['drep'] == 'on' || $pfb['prep'] == 'on')) {
|
|
if (!empty($pfb_alias_lists_all)) {
|
|
$final_alias = array_unique($pfb_alias_lists_all);
|
|
}
|
|
} else {
|
|
if (!empty($pfb_alias_lists)) {
|
|
$final_alias = array_unique($pfb_alias_lists);
|
|
}
|
|
}
|
|
|
|
if (!empty($final_alias)) {
|
|
foreach ($final_alias as $final) {
|
|
$log = "\n Updating: {$final}\n";
|
|
pfb_logger("{$log}", 1);
|
|
$result = '';
|
|
if (file_exists("{$pfb['aliasdir']}/{$final}.txt")) {
|
|
$final_esc = escapeshellarg($final);
|
|
$final_path_esc = escapeshellarg("{$pfb['aliasdir']}/{$final}.txt");
|
|
exec("{$pfb['pfctl']} -t {$final_esc} -T replace -f {$final_path_esc} 2>&1", $result);
|
|
$log = implode($result);
|
|
} else {
|
|
$log = "Aliastable file not found\n";
|
|
}
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
pfb_logger("\n", 1);
|
|
|
|
// Call function for Ramdisk processes.
|
|
pfb_aliastables('update');
|
|
} else {
|
|
$log = "No Changes to Aliases, Skipping pfctl Update\n";
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
}
|
|
}
|
|
unset($rules, $new_rules, $orig_rules_nocreated, $new_rules_nocreated);
|
|
|
|
|
|
#################################
|
|
# SAVE CONFIGURATION #
|
|
#################################
|
|
|
|
// Uncheck reusing existing downloads check box
|
|
if (!$pfb['save'] && $pfb['enable'] == 'on' && $pfb['config']['pfb_reuse'] == 'on') {
|
|
$pfb_config['installedpackages']['pfblockerng']['config'][0]['pfb_reuse'] = '';
|
|
$pfb['conf_mod'] = TRUE;
|
|
}
|
|
|
|
// Only save config.xml, if changes are found.
|
|
if ($pfb['conf_mod'] && isset($pfb_config)) {
|
|
pfb_logger("\nSaving config changes", 1);
|
|
|
|
// Reload config.xml to get any recent changes and merge/save changes.
|
|
config_read_file(false, true);
|
|
config_set_path('', array_replace_recursive(config_get_path(''), $pfb_config));
|
|
write_config('pfBlockerNG: save settings');
|
|
|
|
pfb_logger("... completed", 1);
|
|
}
|
|
|
|
|
|
// Query NAT Rules for previous IPv4 pfBlockerNG aliasnames which are not in the new '_v4' suffix format
|
|
$pfb_found = FALSE;
|
|
config_read_file(false, true);
|
|
|
|
foreach (config_get_path('nat/rule', []) as $key => $ex_nat) {
|
|
foreach (array('source', 'destination') as $rtype) {
|
|
if (isset($ex_nat[$rtype]['address']) && substr($ex_nat[$rtype]['address'], 0, 4) == 'pfB_') {
|
|
$pfb_suffix = substr($ex_nat[$rtype]['address'], -3);
|
|
|
|
if ($pfb_suffix == '_v6') {
|
|
continue;
|
|
}
|
|
|
|
// Add '_v4' suffix if missing
|
|
elseif ($pfb_suffix != '_v4') {
|
|
config_set_path("nat/rule/{$key}/{$rtype}/address", "{$ex_nat[$rtype]['address']}_v4");
|
|
$pfb_found = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($pfb_found) {
|
|
write_config("pfBlockerNG: update NAT rule(s) aliasnames to include '_v4' suffix");
|
|
}
|
|
|
|
#################################
|
|
# Call filter_configure once #
|
|
#################################
|
|
|
|
$log = '';
|
|
if ($pfb['filter_configure']) {
|
|
|
|
// Remove any IPs in Alerts unlock feature
|
|
unlink_if_exists("{$pfb['ip_unlock']}");
|
|
|
|
// Remove IP Cache file
|
|
unlink_if_exists($pfb['ip_cache']);
|
|
|
|
require_once('filter.inc');
|
|
filter_configure();
|
|
|
|
// Stop/Restart pfb_filter service for new rule changes
|
|
if ($pfb['enable'] == '') {
|
|
if (is_service_running('pfb_filter')) {
|
|
$log = 'Stopping firewall filter daemon';
|
|
stop_service('pfb_filter');
|
|
}
|
|
}
|
|
else {
|
|
$log = 'Restarting firewall filter daemon';
|
|
restart_service('pfb_filter');
|
|
}
|
|
}
|
|
else {
|
|
// Stop/Start Firewall filter daemon
|
|
if ($pfb['enable'] == '' && is_service_running('pfb_filter')) {
|
|
$log = 'Stopping firewall filter daemon';
|
|
stop_service('pfb_filter');
|
|
}
|
|
elseif ($pfb['enable'] == 'on' && !is_service_running('pfb_filter')) {
|
|
$log = 'Starting firewall filter daemon';
|
|
start_service('pfb_filter');
|
|
}
|
|
}
|
|
|
|
if (!empty($log)) {
|
|
pfb_logger("\n\n** {$log} **\n", 1);
|
|
syslog(LOG_NOTICE, "[pfBlockerNG] {$log}");
|
|
}
|
|
|
|
|
|
#################################
|
|
# KILL STATES #
|
|
#################################
|
|
|
|
if (!$pfb['save'] && $pfb['kstates'] && !$pfb['filter_configure']) {
|
|
pfb_remove_states();
|
|
}
|
|
|
|
|
|
#########################################
|
|
# XMLRPC - sync process #
|
|
#########################################
|
|
|
|
if (!platform_booting() && !$g['pfblockerng_install']) {
|
|
pfblockerng_sync_on_changes();
|
|
}
|
|
|
|
#########################################
|
|
# Define/Apply CRON Jobs #
|
|
#########################################
|
|
|
|
// Replace CRON job with any user changes to $pfb_min
|
|
if ($pfb['enable'] == 'on' && $pfb['interval'] != 'Disabled') {
|
|
// Define pfBlockerNG CRON job
|
|
$pfb_cmd = "/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php cron >> {$pfb['log']} 2>&1";
|
|
// $pfb['min'] ( User defined variable. Variable defined at start of script )
|
|
|
|
// Define CRON hour (CRON interval & start hour)
|
|
if ($pfb['interval'] == 1) {
|
|
$pfb_hour = '*';
|
|
} elseif ($pfb['interval'] == 24) {
|
|
$pfb_hour = $pfb['24hour'];
|
|
} else {
|
|
$pfb_hour = implode(',', pfb_cron_base_hour($pfb['interval']));
|
|
}
|
|
|
|
if ($pfb_hour == 0 || !empty(pfb_filter($pfb_hour, PFB_FILTER_CSV_CRON, 'Define pfBlockerNG CRON job'))) {
|
|
$pfb_mday = '*';
|
|
$pfb_month = '*';
|
|
$pfb_wday = '*';
|
|
$pfb_who = 'root';
|
|
|
|
// Determine if CRON job requires updating
|
|
if (!pfblockerng_cron_exists($pfb_cmd, $pfb['min'], $pfb_hour, $pfb_mday, $pfb_wday)) {
|
|
install_cron_job('pfblockerng.php cron', false);
|
|
install_cron_job($pfb_cmd, true, $pfb['min'], $pfb_hour, $pfb_mday, $pfb_month, $pfb_wday, $pfb_who);
|
|
}
|
|
}
|
|
else {
|
|
pfb_logger("\n Failed to create pfBlockerNG cron task [{$pfb_hour}]", 1);
|
|
}
|
|
}
|
|
else {
|
|
// Clear any existing pfBlockerNG CRON jobs
|
|
install_cron_job('pfblockerng.php cron', false);
|
|
}
|
|
|
|
// Define pfBlockerNG MaxMind/TOP1M/ASN Cron job
|
|
if ($pfb['enable'] == 'on') {
|
|
$pfb_gcmd = "/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php dcc >> {$pfb['extraslog']} 2>&1";
|
|
// CRON hour is randomized between 0-23 Hour to minimize effect on Servers
|
|
$pfb_gmin = '0';
|
|
$pfb_ghour = rand(0,23);
|
|
$pfb_gmday = '*';
|
|
$pfb_gmonth = '*';
|
|
$pfb_gwday = '*';
|
|
$pfb_gwho = 'root';
|
|
|
|
// Determine if CRON job requires updating
|
|
if (!pfblockerng_cron_exists($pfb_gcmd, $pfb_gmin, 'random', $pfb_gmday, $pfb_gwday)) {
|
|
install_cron_job('pfblockerng.php dcc', false);
|
|
install_cron_job($pfb_gcmd, true, $pfb_gmin, $pfb_ghour, $pfb_gmday, $pfb_gmonth, $pfb_gwday, $pfb_gwho);
|
|
}
|
|
}
|
|
else {
|
|
// Clear any existing pfBlockerNG CRON jobs
|
|
install_cron_job('pfblockerng.php dcc', false);
|
|
}
|
|
|
|
|
|
// Define pfBlockerNG Blacklist CRON job
|
|
if ($pfb['enable'] == 'on' && $pfb['dnsbl'] == 'on' && $pfb['blconfig'] &&
|
|
$pfb['blconfig']['blacklist_enable'] != 'Disable' &&
|
|
$pfb['blconfig']['blacklist_freq'] != 'Never' &&
|
|
!empty($pfb['blconfig']['blacklist_selected']) &&
|
|
isset($pfb['blconfig']['item'])) {
|
|
|
|
$bl_string = '';
|
|
$selected = array_flip(explode(',', $pfb['blconfig']['blacklist_selected'])) ?: array();
|
|
foreach ($pfb['blconfig']['item'] as $item) {
|
|
if (isset($selected[$item['xml']]) && !empty($item['selected'])) {
|
|
$bl_string .= ",{$item['xml']}";
|
|
}
|
|
}
|
|
$bl_string = pfb_filter(ltrim($bl_string, ','), PFB_FILTER_CSV, 'Define pfBlockerNG Blacklist CRON job');
|
|
|
|
if (!empty($bl_string)) {
|
|
$pfb_bcmd = "/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php bl {$bl_string} >> {$pfb['extraslog']} 2>&1";
|
|
|
|
$pfb_bmin = '0';
|
|
$pfb_bhour = rand(0,23);
|
|
$pfb_bmday = '*';
|
|
$pfb_bmonth = '*';
|
|
$pfb_bwday = ($pfb['blconfig']['blacklist_freq'] == 'Weekly' ? '7' : '*');
|
|
$pfb_bwho = 'root';
|
|
|
|
// Determine if CRON job requires updating
|
|
if (!pfblockerng_cron_exists($pfb_bcmd, $pfb_bmin, 'random', $pfb_bmday, $pfb_bwday)) {
|
|
install_cron_job('pfblockerng.php bl', false);
|
|
install_cron_job($pfb_bcmd, true, $pfb_bmin, $pfb_bhour, $pfb_bmday, $pfb_bmonth, $pfb_bwday, $pfb_bwho);
|
|
}
|
|
}
|
|
else {
|
|
pfb_logger("\n Failed to create pfBlockerNG Blacklist cron task [{$bl_string}]", 1);
|
|
}
|
|
}
|
|
else {
|
|
// Clear any existing pfBlockerNG Blacklist CRON jobs
|
|
install_cron_job('pfblockerng.php bl', false);
|
|
}
|
|
|
|
// Define pfBlockerNG clear [ dnsbl and/or IP ] counter CRON job
|
|
foreach (array( 'clearip', 'cleardnsbl') as $type) {
|
|
$pfb_cmd = "/usr/local/bin/php /usr/local/www/pfblockerng/pfblockerng.php {$type} >/dev/null 2>&1";
|
|
if (config_get_path("installedpackages/pfblockerngglobal/widget-{$type}") !== null) {
|
|
if (config_get_path("installedpackages/pfblockerngglobal/widget-{$type}") != 'never') {
|
|
$pfb_day = '*';
|
|
if (config_get_path("installedpackages/pfblockerngglobal/widget-{$type}") == 'weekly') {
|
|
$pfb_day = '7';
|
|
}
|
|
|
|
// Remove unreferenced 'daily' or 'weekly' cron job
|
|
$pfb_other = ($pfb_day == '*') ? '7' : '*';
|
|
if (pfblockerng_cron_exists($pfb_cmd, '0', '0', '*', $pfb_other)) {
|
|
install_cron_job("pfblockerng.php {$type}", false);
|
|
}
|
|
|
|
if (!pfblockerng_cron_exists($pfb_cmd, '0', '0', '*', $pfb_day)) {
|
|
install_cron_job($pfb_cmd, true, '0', '0', '*', '*', $pfb_day, 'root');
|
|
}
|
|
}
|
|
else {
|
|
if (pfblockerng_cron_exists($pfb_cmd, '0', '0', '*', '*')) {
|
|
install_cron_job("pfblockerng.php {$type}", false);
|
|
}
|
|
if (pfblockerng_cron_exists($pfb_cmd, '0', '0', '*', '7')) {
|
|
install_cron_job("pfblockerng.php {$type}", false);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (pfblockerng_cron_exists($pfb_cmd, '0', '0', '*', '*')) {
|
|
install_cron_job("pfblockerng.php {$type}", false);
|
|
}
|
|
if (pfblockerng_cron_exists($pfb_cmd, '0', '0', '*', '7')) {
|
|
install_cron_job("pfblockerng.php {$type}", false);
|
|
}
|
|
}
|
|
}
|
|
|
|
#################################
|
|
# FINAL REPORTING #
|
|
#################################
|
|
|
|
// Only run with CRON or Force invoked process
|
|
if ((!$pfb['save'] && $pfb['repcheck'] && $pfb['enable'] == 'on') || $pfb['summary']) {
|
|
// Script to run final script processes.
|
|
if ($pfb['dup'] == 'on') {
|
|
exec("{$pfb['script']} closing on {$elog}");
|
|
}
|
|
}
|
|
|
|
if ($pfb['enable'] == 'on' && !$pfb['save'] || $pfb['summary']) {
|
|
$log = "\n UPDATE PROCESS ENDED [ NOW ]\n";
|
|
pfb_logger("{$log}", 1);
|
|
}
|
|
}
|
|
|
|
|
|
// Function to De-Install pfBlockerNG
|
|
function pfblockerng_php_pre_deinstall_command() {
|
|
require_once('config.inc');
|
|
global $g, $pfb;
|
|
|
|
// Set these two variables to disable pfBlockerNG on de-install
|
|
$pfb['save'] = $pfb['install'] = TRUE;
|
|
|
|
update_status("Removing pfBlockerNG...");
|
|
sync_package_pfblockerng();
|
|
|
|
// Maintain pfBlockerNG settings and database files if $pfb['keep'] is ON.
|
|
if ($pfb['keep'] != 'on') {
|
|
update_status(" Removing all customizations/data...");
|
|
// Remove pfBlockerNG log and DB folder
|
|
rmdir_recursive("{$pfb['dbdir']}");
|
|
rmdir_recursive("{$pfb['logdir']}");
|
|
|
|
// Remove all pfB aliastables
|
|
exec("{$pfb['pfctl']} -s Tables | {$pfb['grep']} '^pfB_'", $pfb_tables);
|
|
if (isset($pfb_tables)) {
|
|
foreach ($pfb_tables as $pfb_table) {
|
|
$pfb_table_esc = escapeshellarg($pfb_table);
|
|
exec("{$pfb['pfctl']} -t {$pfb_table_esc} -T kill 2>&1", $result);
|
|
}
|
|
}
|
|
|
|
// Remove aliastables archive and earlyshellcmd if found.
|
|
@unlink_if_exists("{$pfb['aliasarchive']}");
|
|
if (config_get_path('system/earlyshellcmd') !== null) {
|
|
$a_earlyshellcmd = config_get_path('system/earlyshellcmd', '');
|
|
if (preg_grep("/pfblockerng.sh aliastables/", $a_earlyshellcmd)) {
|
|
config_set_path('system/earlyshellcmd',
|
|
preg_grep("/pfblockerng.sh aliastables/", $a_earlyshellcmd, PREG_GREP_INVERT));
|
|
}
|
|
}
|
|
foreach (config_get_path('installedpackages/shellcmdsettings/config', []) as $key => $shellcmd) {
|
|
if (strpos($shellcmd['cmd'], 'pfblockerng.sh aliastables') !== FALSE) {
|
|
config_del_path("installedpackages/shellcmdsettings/config/{$key}");
|
|
}
|
|
}
|
|
|
|
// Remove settings from config.xml
|
|
pfb_remove_config_settings();
|
|
|
|
unlink_if_exists("{$pfb['dnsbl_conf']}");
|
|
unlink_if_exists("{$pfb['dnsbl_cert']}");
|
|
unlink_if_exists("{$pfb['aliasarchive']}");
|
|
unlink_if_exists("{$pfb['dnsbl_info']}");
|
|
unlink_if_exists("{$pfb['dnsbl_resolver']}");
|
|
|
|
unlink_if_exists("{$g['unbound_chroot_path']}/pfb_unbound.py");
|
|
unlink_if_exists("{$g['unbound_chroot_path']}/pfb_unbound_include.inc");
|
|
unlink_if_exists("{$g['unbound_chroot_path']}/pfb_py_hsts.txt");
|
|
|
|
// Remove widget (code from Snort deinstall)
|
|
$pfb['widgets'] = config_get_path('widgets/sequence', '');
|
|
if (!empty($pfb['widgets'])) {
|
|
$widgetlist = explode(',', $pfb['widgets']);
|
|
foreach ($widgetlist as $key => $widget) {
|
|
if (strpos($widget, 'pfblockerng') !== FALSE) {
|
|
unset($widgetlist[$key]);
|
|
}
|
|
}
|
|
config_set_path('widgets/sequence', implode(',', $widgetlist));
|
|
write_config('pfBlockerNG: Remove widget', false);
|
|
}
|
|
}
|
|
else {
|
|
update_status(" All customizations/data will be retained...");
|
|
}
|
|
|
|
unlink_if_exists('/usr/local/sbin/lighttpd_pfb');
|
|
unlink_if_exists('/usr/local/bin/php_pfb');
|
|
unlink_if_exists('/usr/local/sbin/clog_pfb');
|
|
unlink_if_exists('/usr/bin/tail_pfb');
|
|
unlink_if_exists('/usr/local/etc/rc.d/pfb_filter.sh');
|
|
unlink_if_exists('/usr/local/etc/rc.d/pfb_dnsbl.sh');
|
|
|
|
unlink_if_exists("{$pfb['dnsbl_cache']}");
|
|
unlink_if_exists("/var/tmp/unbound_cache_*");
|
|
unlink_if_exists("{$pfb['ip_cache']}");
|
|
|
|
// Remove incorrect xml setting
|
|
config_del_path('installedpackages/pfblockerngantartica');
|
|
|
|
update_status(" done.\n");
|
|
}
|
|
|
|
|
|
// Remove settings from config.xml
|
|
function pfb_remove_config_settings() {
|
|
global $pfb;
|
|
|
|
foreach (array( 'pfblockerng',
|
|
'pfblockerngglobal',
|
|
'pfblockerngsync',
|
|
'pfblockerngreputation',
|
|
'pfblockerngipsettings',
|
|
'pfblockernglistsv4',
|
|
'pfblockernglistsv6',
|
|
'pfblockerngdnsbl',
|
|
'pfblockerngdnsblsettings',
|
|
'pfblockerngsafesearch',
|
|
'pfblockerngblacklist',
|
|
'pfblockerngafrica',
|
|
'pfblockerngantarctica',
|
|
'pfblockerngasia',
|
|
'pfblockerngeurope',
|
|
'pfblockerngnorthamerica',
|
|
'pfblockerngoceania',
|
|
'pfblockerngsouthamerica',
|
|
'pfblockerngtopspammers',
|
|
'pfblockerngproxyandsatellite' ) as $type) {
|
|
|
|
config_del_path("installedpackages/{$type}");
|
|
}
|
|
}
|
|
|
|
|
|
/* Uses XMLRPC to synchronize the changes to a remote node */
|
|
function pfblockerng_sync_on_changes() {
|
|
// Create array of sync settings and exit if sync is disabled.
|
|
$pfb_sync = config_get_path('installedpackages/pfblockerngsync/config/0', []);
|
|
if (!empty($pfb_sync)) {
|
|
if ($pfb_sync['varsynconchanges'] == 'disabled' || empty($pfb_sync['varsynconchanges'])) {
|
|
return;
|
|
}
|
|
$synctimeout = $pfb_sync['varsynctimeout'] ?: 150;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
pfb_logger("\n===[ XMLRPC Sync ]===================================================\n", 1);
|
|
syslog(LOG_NOTICE, '[pfBlockerNG] XMLRPC sync is starting.');
|
|
|
|
if (config_get_path('installedpackages/pfblockerngsync/config') != null) {
|
|
switch ($pfb_sync['varsynconchanges']) {
|
|
case 'manual':
|
|
if (isset($pfb_sync['row'])) {
|
|
$rs = $pfb_sync['row'];
|
|
} else {
|
|
log_error('[pfBlockerNG] Manual XMLRPC sync is enabled but there are no replication targets configured.');
|
|
return;
|
|
}
|
|
break;
|
|
case 'auto':
|
|
$hasync = config_get_path('hasync');
|
|
if ($hasync != null) {
|
|
$system_carp = $hasync;
|
|
$rs[0]['varsyncipaddress'] = $system_carp['synchronizetoip'];
|
|
$rs[0]['varsyncusername'] = $system_carp['username'];
|
|
$rs[0]['varsyncpassword'] = $system_carp['password'];
|
|
$rs[0]['varsyncdestinenable'] = FALSE;
|
|
|
|
// XMLRPC sync is currently only supported over connections using the same protocol and port as this system
|
|
if (config_get_path('system/webgui/protocol') == 'http') {
|
|
$rs[0]['varsyncprotocol'] = 'http';
|
|
$rs[0]['varsyncport'] = config_get_path('system/webgui/port', '80');
|
|
} else {
|
|
$rs[0]['varsyncprotocol'] = 'https';
|
|
$rs[0]['varsyncport'] = config_get_path('system/webgui/port', '443');
|
|
}
|
|
|
|
if (empty($system_carp['synchronizetoip'])) {
|
|
log_error('[pfBlockerNG] Auto XMLRPC sync is enabled but there is no sync IP address configured.');
|
|
return;
|
|
} else {
|
|
$rs[0]['varsyncdestinenable'] = TRUE;
|
|
}
|
|
} else {
|
|
log_error('[pfBlockerNG] Auto XMLRPC sync is enabled but there are no replication targets configured.');
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
return;
|
|
break;
|
|
}
|
|
if (isset($rs)) {
|
|
foreach ($rs as $sh) {
|
|
// Only sync enabled replication targets
|
|
if ($sh['varsyncdestinenable']) {
|
|
$sync_to_ip = $sh['varsyncipaddress'];
|
|
$port = $sh['varsyncport'];
|
|
$password = $sh['varsyncpassword'];
|
|
$protocol = $sh['varsyncprotocol'];
|
|
$username = $sh['varsyncusername'] ?: 'admin';
|
|
|
|
$validate = TRUE;
|
|
$error = '| ';
|
|
|
|
if (empty($password)) {
|
|
$error .= 'Password parameter missing. | ';
|
|
$validate = FALSE;
|
|
}
|
|
if (!is_ipaddr($sync_to_ip) && !is_hostname($sync_to_ip) && !is_domain($sync_to_ip)) {
|
|
$error .= 'Mis-configured Target IP/Host address. | ';
|
|
$validate = FALSE;
|
|
}
|
|
if (!is_port($port)) {
|
|
$error .= 'Mis-configured Target Port setting. |';
|
|
$validate = FALSE;
|
|
}
|
|
|
|
if ($validate) {
|
|
pfb_logger("\n Sync with [ {$protocol}://{$sync_to_ip}:{$port} ] ...", 1);
|
|
$success = pfblockerng_do_xmlrpc_sync($sync_to_ip, $port, $protocol, $username, $password, $synctimeout);
|
|
|
|
if ($success) {
|
|
pfb_logger(" done.\n", 1);
|
|
syslog(LOG_NOTICE, "[pfBlockerNG] XMLRPC sync to [ {$sync_to_ip}:{port} ] completed successfully.");
|
|
} else {
|
|
pfb_logger(" Failed!\n", 1);
|
|
}
|
|
} else {
|
|
pfb_logger(" terminated due to the following error(s): {$error}", 1);
|
|
log_error("[pfBlockerNG] XMLRPC sync to [ {$sync_to_ip}:{port} ] terminated due to the following error(s): {$error}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pfb_logger("\n======================================================================\n", 1);
|
|
}
|
|
|
|
|
|
/* Do the actual XMLRPC sync */
|
|
function pfblockerng_do_xmlrpc_sync($sync_to_ip, $port, $protocol, $username, $password, $synctimeout) {
|
|
global $g;
|
|
$success = TRUE;
|
|
|
|
// Take care of IPv6 literal address
|
|
if (is_ipaddrv6($sync_to_ip)) {
|
|
$sync_to_ip = "[{$sync_to_ip}]";
|
|
}
|
|
|
|
/* xml will hold the sections to sync */
|
|
$xml = array();
|
|
|
|
// If User Disabled, remove 'General/IP/DNSBL Tab Customizations' from Sync
|
|
if (config_get_path('installedpackages/pfblockerngsync/config/0/syncinterfaces') != 'on') {
|
|
if (config_get_path('installedpackages/pfblockerng') != null) {
|
|
$xml['pfblockerng'] = config_get_path('installedpackages/pfblockerng');
|
|
}
|
|
if (config_get_path('installedpackages/pfblockerngipsettings') != null) {
|
|
$xml['pfblockerngipsettings'] = config_get_path('installedpackages/pfblockerngipsettings');
|
|
}
|
|
if (config_get_path('installedpackages/pfblockerngdnsblsettings') != null) {
|
|
$xml['pfblockerngdnsblsettings']= config_get_path('installedpackages/pfblockerngdnsblsettings');
|
|
|
|
// Increase CARP Advskew value, see https://redmine.pfsense.org/issues/11964
|
|
if (isset($xml['pfblockerngdnsblsettings']['config'][0]['pfb_dnsvip_skew'])) {
|
|
$advskew = intval($xml['pfblockerngdnsblsettings']['config'][0]['pfb_dnsvip_skew']);
|
|
$advskew += 100;
|
|
if ($advskew > 254) {
|
|
$advskew = 254;
|
|
}
|
|
$xml['pfblockerngdnsblsettings']['config'][0]['pfb_dnsvip_skew'] = $advskew;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (config_get_path('installedpackages/pfblockernglistsv4') !== null)
|
|
$xml['pfblockernglistsv4'] = config_get_path('installedpackages/pfblockernglistsv4');
|
|
if (config_get_path('installedpackages/pfblockernglistsv6') !== null)
|
|
$xml['pfblockernglistsv6'] = config_get_path('installedpackages/pfblockernglistsv6');
|
|
if (config_get_path('installedpackages/pfblockerngreputation') !== null)
|
|
$xml['pfblockerngreputation'] = config_get_path('installedpackages/pfblockerngreputation');
|
|
if (config_get_path('installedpackages/pfblockerngtopspammers') !== null)
|
|
$xml['pfblockerngtopspammers'] = config_get_path('installedpackages/pfblockerngtopspammers');
|
|
if (config_get_path('installedpackages/pfblockerngafrica') !== null)
|
|
$xml['pfblockerngafrica'] = config_get_path('installedpackages/pfblockerngafrica');
|
|
if (config_get_path('installedpackages/pfblockerngantarctica') !== null)
|
|
$xml['pfblockerngantarctica'] = config_get_path('installedpackages/pfblockerngantarctica');
|
|
if (config_get_path('installedpackages/pfblockerngasia') !== null)
|
|
$xml['pfblockerngasia'] = config_get_path('installedpackages/pfblockerngasia');
|
|
if (config_get_path('installedpackages/pfblockerngeurope') !== null)
|
|
$xml['pfblockerngeurope'] = config_get_path('installedpackages/pfblockerngeurope');
|
|
if (config_get_path('installedpackages/pfblockerngnorthamerica') !== null)
|
|
$xml['pfblockerngnorthamerica'] = config_get_path('installedpackages/pfblockerngnorthamerica');
|
|
if (config_get_path('installedpackages/pfblockerngoceania') !== null)
|
|
$xml['pfblockerngoceania'] = config_get_path('installedpackages/pfblockerngoceania');
|
|
if (config_get_path('installedpackages/pfblockerngsouthamerica') !== null)
|
|
$xml['pfblockerngsouthamerica'] = config_get_path('installedpackages/pfblockerngsouthamerica');
|
|
if (config_get_path('installedpackages/pfblockerngproxyandsatellite') !== null)
|
|
$xml['pfblockerngproxyandsatellite'] = config_get_path('installedpackages/pfblockerngproxyandsatellite');
|
|
if (config_get_path('installedpackages/pfblockerngdnsbl') !== null)
|
|
$xml['pfblockerngdnsbl'] = config_get_path('installedpackages/pfblockerngdnsbl');
|
|
if (config_get_path('installedpackages/pfblockerngblacklist') !== null)
|
|
$xml['pfblockerngblacklist'] = config_get_path('installedpackages/pfblockerngblacklist');
|
|
if (config_get_path('installedpackages/pfblockerngglobal') !== null)
|
|
$xml['pfblockerngglobal'] = config_get_path('installedpackages/pfblockerngglobal');
|
|
if (config_get_path('installedpackages/pfblockerngsafesearch') !== null)
|
|
$xml['pfblockerngsafesearch'] = config_get_path('installedpackages/pfblockerngsafesearch');
|
|
|
|
|
|
// Execute applicable XMLRPC code as per pfSense version
|
|
if (substr(trim(file_get_contents('/etc/version')), 0, 3) < '2.4') {
|
|
|
|
require_once('xmlrpc.inc');
|
|
require_once('xmlrpc_client.inc');
|
|
|
|
$url = "{$protocol}://{$sync_to_ip}";
|
|
|
|
/* assemble xmlrpc payload */
|
|
$params = array(XML_RPC_encode($password), XML_RPC_encode($xml));
|
|
|
|
/* set a few variables needed for sync code borrowed from filter.inc */
|
|
$msg = new XML_RPC_Message('pfsense.merge_installedpackages_section_xmlrpc', $params);
|
|
$cli = new XML_RPC_Client('/xmlrpc.php', $url, $port);
|
|
$cli->setCredentials($username, $password);
|
|
if ($g['debug']) {
|
|
$cli->setDebug(1);
|
|
}
|
|
|
|
/* send our XMLRPC message and timeout after defined sync timeout value */
|
|
$resp = $cli->send($msg, $synctimeout);
|
|
|
|
if (!$resp) {
|
|
log_error("[pfBlockerNG] XMLRPC communications error occurred while attempting sync with {$url}:{$port}.");
|
|
file_notice('pfBlockerNG Sync settings', $error, 'pfBlockerNG', '/pfblockerng/pfblockerng_sync.php', 2);
|
|
$success = FALSE;
|
|
} elseif ($resp->faultCode()) {
|
|
$cli->setDebug(1);
|
|
$resp = $cli->send($msg, $synctimeout);
|
|
log_error("[pfBlockerNG] XMLRPC errors syncing with {$url}:{$port} - Code " . $resp->faultCode() . ": " . $resp->faultString());
|
|
file_notice('pfBlockerNG Sync settings', $error, 'pfBlockerNG', '/pfblockerng/pfblockerng_sync.php', 2);
|
|
$success = FALSE;
|
|
}
|
|
return $success;
|
|
}
|
|
else {
|
|
require_once('xmlrpc_client.inc');
|
|
|
|
// xmlrpc cannot encode NULL objects/arrays
|
|
foreach ($xml as $xmlkey => $xmlvalue) {
|
|
if (gettype($xmlvalue) == 'NULL') {
|
|
$xml[$xmlkey] = array();
|
|
}
|
|
}
|
|
|
|
$synctimeout = intval($synctimeout);
|
|
$rpc_client = new pfsense_xmlrpc_client();
|
|
$rpc_client->setConnectionData($sync_to_ip, $port, $username, $password, $protocol);
|
|
$resp = $rpc_client->xmlrpc_method('merge_installedpackages_section', $xml, $synctimeout);
|
|
|
|
if (!isset($resp)) {
|
|
return FALSE;
|
|
} else {
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|