diff --git a/Makefile b/Makefile index 743f50544..022c51b26 100644 --- a/Makefile +++ b/Makefile @@ -135,7 +135,6 @@ CORE_DEPENDS?= ${CORE_DEPENDS_${CORE_ARCH}} \ rrdtool \ samplicator \ squid \ - sshlockout_pf \ strongswan \ sudo \ syslog-ng${CORE_SYSLOGNG:S/.//g} \ diff --git a/plist b/plist index fb61f37fa..6b381dfd1 100644 --- a/plist +++ b/plist @@ -495,6 +495,7 @@ /usr/local/opnsense/mvc/app/views/OPNsense/Cron/index.volt /usr/local/opnsense/mvc/app/views/OPNsense/Diagnostics/arp.volt /usr/local/opnsense/mvc/app/views/OPNsense/Diagnostics/fw_log.volt +/usr/local/opnsense/mvc/app/views/OPNsense/Diagnostics/fw_stats.volt /usr/local/opnsense/mvc/app/views/OPNsense/Diagnostics/log.volt /usr/local/opnsense/mvc/app/views/OPNsense/Diagnostics/ndp.volt /usr/local/opnsense/mvc/app/views/OPNsense/Diagnostics/netflow.volt @@ -710,6 +711,8 @@ /usr/local/opnsense/scripts/suricata/rule-updater.py /usr/local/opnsense/scripts/suricata/setup.sh /usr/local/opnsense/scripts/syslog/list_applications.php +/usr/local/opnsense/scripts/syslog/lockout_handler +/usr/local/opnsense/scripts/syslog/log_archive /usr/local/opnsense/scripts/system/dh_parameters.sh /usr/local/opnsense/scripts/system/list_interrupts.py /usr/local/opnsense/scripts/system/rfc5246_cipher_suites.csv @@ -834,11 +837,33 @@ /usr/local/opnsense/service/templates/OPNsense/Sample/sub2/+TARGETS /usr/local/opnsense/service/templates/OPNsense/Sample/sub2/example_sub2.txt /usr/local/opnsense/service/templates/OPNsense/Syslog/+TARGETS +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/README +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/configd.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/dhcpd.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/dnsmasq.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/filter.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/gateways.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/ipsec.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/lighttpd.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/ntpd.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/openvpn.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/pkg.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/portalauth.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/ppps.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/relayd.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/resolver.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/routing.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/squid_access.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/suricata.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/vpn.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/local/wireless.conf /usr/local/opnsense/service/templates/OPNsense/Syslog/newsyslog.conf /usr/local/opnsense/service/templates/OPNsense/Syslog/rc.conf.d /usr/local/opnsense/service/templates/OPNsense/Syslog/sources/001-local.conf /usr/local/opnsense/service/templates/OPNsense/Syslog/syslog-ng-destinations.conf /usr/local/opnsense/service/templates/OPNsense/Syslog/syslog-ng-legacy.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/syslog-ng-local.conf +/usr/local/opnsense/service/templates/OPNsense/Syslog/syslog-ng-lockout.conf /usr/local/opnsense/service/templates/OPNsense/Syslog/syslog-ng.conf /usr/local/opnsense/service/templates/OPNsense/Unbound/core/+TARGETS /usr/local/opnsense/service/templates/OPNsense/Unbound/core/dnsbl.inc @@ -1580,7 +1605,6 @@ /usr/local/www/diag_ipsec_sad.php /usr/local/www/diag_ipsec_spd.php /usr/local/www/diag_logs_common.inc -/usr/local/www/diag_logs_filter_summary.php /usr/local/www/diag_logs_settings.php /usr/local/www/diag_packet_capture.php /usr/local/www/diag_pf_info.php diff --git a/src/etc/inc/authgui.inc b/src/etc/inc/authgui.inc index eb4eef0f7..0ef4a7cef 100644 --- a/src/etc/inc/authgui.inc +++ b/src/etc/inc/authgui.inc @@ -64,6 +64,11 @@ function set_language() function session_auth(&$Login_Error) { global $config; + function auth_log($message) { + openlog("webgui", LOG_ODELAY, LOG_AUTH); + log_error($message); + closelog(); + } // Handle HTTPS httponly and secure flags $currentCookieParams = session_get_cookie_params(); @@ -111,7 +116,7 @@ function session_auth(&$Login_Error) $_SESSION['user_shouldChangePassword'] = true; } if (!isset($config['system']['webgui']['quietlogin'])) { - log_error(sprintf("Successful login for user '%s' from: %s", $_POST['usernamefld'], $_SERVER['REMOTE_ADDR'])); + auth_log(sprintf("Successful login for user '%s' from: %s", $_POST['usernamefld'], $_SERVER['REMOTE_ADDR'])); } if (!empty($_GET['url'])) { $tmp_url_parts = parse_url($_GET['url']); @@ -137,7 +142,7 @@ function session_auth(&$Login_Error) } exit; } else { - log_error("Web GUI authentication error for '{$_POST['usernamefld']}' from {$_SERVER['REMOTE_ADDR']}"); + auth_log("Web GUI authentication error for '{$_POST['usernamefld']}' from {$_SERVER['REMOTE_ADDR']}"); $Login_Error = true; } } @@ -169,9 +174,9 @@ function session_auth(&$Login_Error) /* user hit the logout button */ if (isset($_GET['logout'])) { if (isset($_SESSION['Logout'])) { - log_error(sprintf("Session timed out for user '%s' from: %s", $_SESSION['Username'], $_SERVER['REMOTE_ADDR'])); + auth_log(sprintf("Session timed out for user '%s' from: %s", $_SESSION['Username'], $_SERVER['REMOTE_ADDR'])); } else { - log_error(sprintf("User logged out for user '%s' from: %s", $_SESSION['Username'], $_SERVER['REMOTE_ADDR'])); + auth_log(sprintf("User logged out for user '%s' from: %s", $_SESSION['Username'], $_SERVER['REMOTE_ADDR'])); } /* wipe out $_SESSION */ diff --git a/src/etc/inc/plugins.inc.d/core.inc b/src/etc/inc/plugins.inc.d/core.inc index 6f3e1b124..58c2b63d3 100644 --- a/src/etc/inc/plugins.inc.d/core.inc +++ b/src/etc/inc/plugins.inc.d/core.inc @@ -143,6 +143,7 @@ function core_cron() $jobs[]['autocron'] = array('/usr/local/sbin/expiretable -v -t 3600 sshlockout', '2'); $jobs[]['autocron'] = array('/usr/local/sbin/expiretable -v -t 3600 virusprot', '3'); $jobs[]['autocron'] = array('/usr/local/etc/rc.expireaccounts', '5'); + $jobs[]['autocron'] = array('/usr/local/opnsense/scripts/syslog/log_archive ', '4'); $jobs[]['autocron'] = array('/usr/local/sbin/ping_hosts.sh', '*/4'); diff --git a/src/etc/inc/system.inc b/src/etc/inc/system.inc index 4d272abb0..fff598ff4 100644 --- a/src/etc/inc/system.inc +++ b/src/etc/inc/system.inc @@ -641,7 +641,6 @@ news.err;local0.none;local3.none;local4.none {$log_directive}/var/log/system.l local7.none {$log_directive}/var/log/system.log security.* {$log_directive}/var/log/system.log auth.info;authpriv.info;daemon.info {$log_directive}/var/log/system.log -auth.info;authpriv.info;user.* |exec /usr/local/sbin/sshlockout_pf 15 *.emerg * EOD; diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/Api/FirewallController.php b/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/Api/FirewallController.php index 6e5acb84d..1d665776a 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/Api/FirewallController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/Api/FirewallController.php @@ -30,6 +30,7 @@ namespace OPNsense\Diagnostics\Api; use OPNsense\Base\ApiControllerBase; use OPNsense\Core\Backend; +use OPNsense\Core\Config; /** * Class FirewallController @@ -56,4 +57,63 @@ class FirewallController extends ApiControllerBase return null; } } + + /** + * retrieve firewall stats + * @return array + */ + public function statsAction() + { + if ($this->request->isGet()) { + $this->sessionClose(); // long running action, close session + $limit = empty($this->request->get('limit')) ? 5000 : $this->request->get('limit'); + $group_by = empty($this->request->get('group_by')) ? "interface" : $this->request->get('group_by'); + $records = json_decode((new Backend())->configdpRun("filter read log", array($limit)), true); + $response = array(); + if (!empty($records)) { + $tmp_stats = array(); + foreach ($records as $record) { + if (isset($record[$group_by])) { + if (!isset($tmp_stats[$record[$group_by]])) { + $tmp_stats[$record[$group_by]] = 0; + } + $tmp_stats[$record[$group_by]]++; + } + } + arsort($tmp_stats); + $label_map = array(); + switch ($group_by) { + case 'interface': + $label_map["lo0"] = gettext("loopback"); + if (Config::getInstance()->object()->interfaces->count() > 0) { + foreach (Config::getInstance()->object()->interfaces->children() as $k => $n) { + $label_map[(string)$n->if] = !empty((string)$n->descr) ? (string)$n->descr : $k; + } + } + break; + case 'proto': + // proto + break; + } + $recno = $top_cnt = 0; + foreach ($tmp_stats as $key => $value) { + // top 10 + other + if ($recno < 10) { + $response[] = [ + "label" => !empty($label_map[$key]) ? $label_map[$key] : $key, + "value" => $value + ]; + $top_cnt += $value; + } else { + $response[] = ["label" => gettext("other"), "value" => count($records) - $top_cnt]; + break; + } + $recno++; + } + } + return $response; + } else { + return null; + } + } } diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/FirewallController.php b/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/FirewallController.php index e0ac2a1e1..0b29f418b 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/FirewallController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Diagnostics/FirewallController.php @@ -45,4 +45,11 @@ class FirewallController extends IndexController { $this->view->pick('OPNsense/Diagnostics/fw_log'); } + /** + * firewall statistical view + */ + public function statsAction() + { + $this->view->pick('OPNsense/Diagnostics/fw_stats'); + } } diff --git a/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml b/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml index afea9f3db..312d1f3e3 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml @@ -312,7 +312,8 @@ Diagnostics: Logs: Firewall: Summary View - diag_logs_filter_summary.php* + ui/diagnostics/firewall/stats* + api/diagnostics/firewall/stats* diff --git a/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml b/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml index e9d1e12b7..64674de44 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml @@ -192,7 +192,7 @@ - + diff --git a/src/opnsense/mvc/app/views/OPNsense/Diagnostics/fw_stats.volt b/src/opnsense/mvc/app/views/OPNsense/Diagnostics/fw_stats.volt new file mode 100644 index 000000000..957e94a9c --- /dev/null +++ b/src/opnsense/mvc/app/views/OPNsense/Diagnostics/fw_stats.volt @@ -0,0 +1,136 @@ +{# + +OPNsense® is Copyright © 2020 by Deciso B.V. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +#} + + + + + + + + + + + + + + + +
+
+
+
+
+ +
+ + + + + + + + + +
#
+
+
+
+
diff --git a/src/opnsense/scripts/filter/read_log.py b/src/opnsense/scripts/filter/read_log.py index d57498ae1..e00b3a094 100755 --- a/src/opnsense/scripts/filter/read_log.py +++ b/src/opnsense/scripts/filter/read_log.py @@ -31,6 +31,7 @@ import os import sys import re +import glob from hashlib import md5 import argparse import ujson @@ -39,7 +40,6 @@ sys.path.insert(0, "/usr/local/opnsense/site-python") from log_helper import reverse_log_reader, fetch_clog from params import update_params -filter_log = '/var/log/filter.log' # define log layouts, every endpoint contains all options # source : https://github.com/opnsense/ports/blob/master/opnsense/filterlog/files/description.txt @@ -109,57 +109,72 @@ if __name__ == '__main__': running_conf_descr = fetch_rule_details() result = list() - for record in reverse_log_reader(fetch_clog(filter_log)): - if record['line'].find('filterlog') > -1: - rule = dict() - metadata = dict() - # rule metadata (unique hash, hostname, timestamp) - log_ident = re.split('filterlog[^:]*:', record['line']) - tmp = log_ident[0].split() - metadata['__digest__'] = md5(record['line'].encode()).hexdigest() - metadata['__host__'] = tmp.pop() - metadata['__timestamp__'] = ' '.join(tmp) - rulep = log_ident[1].strip().split(',') - update_rule(rule, metadata, rulep, fields_general) + filter_logs = [] + if os.path.isdir('/var/log/filter'): + filter_logs = list(sorted(glob.glob("/var/log/filter/filter_*.log"), reverse=True)) + if os.path.isfile('/var/log/filter.log'): + filter_logs.append('/var/log/filter.log') - if 'action' not in rule: - # not a filter log line, skip - continue - elif 'version' in rule: - if rule['version'] == '4': - update_rule(rule, metadata, rulep, fields_ipv4) - if 'proto' in rule: - if rule['proto'] == '17': # UDP - update_rule(rule, metadata, rulep, fields_ipv4_udp) - elif rule['proto'] == '6': # TCP - update_rule(rule, metadata, rulep, fields_ipv4_tcp) - elif rule['proto'] == '112': # CARP - update_rule(rule, metadata, rulep, fields_ipv4_carp) - elif rule['version'] == '6': - update_rule(rule, metadata, rulep, fields_ipv6) - if 'proto' in rule: - if rule['proto'] == '17': # UDP - update_rule(rule, metadata, rulep, fields_ipv6_udp) - elif rule['proto'] == '6': # TCP - update_rule(rule, metadata, rulep, fields_ipv6_tcp) - elif rule['proto'] == '112': # CARP - update_rule(rule, metadata, rulep, fields_ipv6_carp) + for filter_log in filter_logs: + do_exit = False + try: + filename = fetch_clog(filter_log) + except Exception as e: + filename = filter_log + for record in reverse_log_reader(filename): + if record['line'].find('filterlog') > -1: + rule = dict() + metadata = dict() + # rule metadata (unique hash, hostname, timestamp) + log_ident = re.split('filterlog[^:]*:', record['line']) + tmp = log_ident[0].split() + metadata['__digest__'] = md5(record['line'].encode()).hexdigest() + metadata['__host__'] = tmp.pop() + metadata['__timestamp__'] = ' '.join(tmp) + rulep = log_ident[1].strip().split(',') + update_rule(rule, metadata, rulep, fields_general) - rule.update(metadata) - if 'rulenr' in rule and rule['rulenr'] in running_conf_descr: - if rule['action'] in ['pass', 'block']: - rule['label'] = running_conf_descr[rule['rulenr']]['label'] - rule['rid'] = running_conf_descr[rule['rulenr']]['rid'] - elif rule['action'] not in ['pass', 'block']: - rule['label'] = "%s rule" % rule['action'] + if 'action' not in rule: + # not a filter log line, skip + continue + elif 'version' in rule: + if rule['version'] == '4': + update_rule(rule, metadata, rulep, fields_ipv4) + if 'proto' in rule: + if rule['proto'] == '17': # UDP + update_rule(rule, metadata, rulep, fields_ipv4_udp) + elif rule['proto'] == '6': # TCP + update_rule(rule, metadata, rulep, fields_ipv4_tcp) + elif rule['proto'] == '112': # CARP + update_rule(rule, metadata, rulep, fields_ipv4_carp) + elif rule['version'] == '6': + update_rule(rule, metadata, rulep, fields_ipv6) + if 'proto' in rule: + if rule['proto'] == '17': # UDP + update_rule(rule, metadata, rulep, fields_ipv6_udp) + elif rule['proto'] == '6': # TCP + update_rule(rule, metadata, rulep, fields_ipv6_tcp) + elif rule['proto'] == '112': # CARP + update_rule(rule, metadata, rulep, fields_ipv6_carp) - result.append(rule) + rule.update(metadata) + if 'rulenr' in rule and rule['rulenr'] in running_conf_descr: + if rule['action'] in ['pass', 'block']: + rule['label'] = running_conf_descr[rule['rulenr']]['label'] + rule['rid'] = running_conf_descr[rule['rulenr']]['rid'] + elif rule['action'] not in ['pass', 'block']: + rule['label'] = "%s rule" % rule['action'] - # handle exit criteria, row limit or last digest - if parameters['limit'] != 0 and len(result) >= parameters['limit']: - break - elif parameters['digest'].strip() != '' and parameters['digest'] == rule['__digest__']: - break + result.append(rule) + # handle exit criteria, row limit or last digest + if parameters['limit'] != 0 and len(result) >= parameters['limit']: + do_exit = True + elif parameters['digest'].strip() != '' and parameters['digest'] == rule['__digest__']: + do_exit = True + if do_exit: + break + if do_exit: + break print (ujson.dumps(result)) diff --git a/src/opnsense/scripts/syslog/lockout_handler b/src/opnsense/scripts/syslog/lockout_handler new file mode 100755 index 000000000..462323cfb --- /dev/null +++ b/src/opnsense/scripts/syslog/lockout_handler @@ -0,0 +1,104 @@ +#!/usr/local/bin/python3 + +""" + Copyright (c) 2020 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +""" +import sys +import re +import argparse +import datetime +import ipaddress +import syslog +import subprocess +import time +from select import select + +all_rules = { + '.*Accepted.*': True, + '.*Successful login.*': True, + '.*Web GUI authentication error.*': False, + '.*Invalid user.*': False, + '.*Illegal user.*': False, + '.*Postponed keyboard-interactive for invalid user.*': False, + '.*authentication error for illegal user.*': False +} + +if __name__ == '__main__': + # handle parameters + parser = argparse.ArgumentParser() + parser.add_argument('--attempts', help='maximum number of attempts', type=int, default=5) + parser.add_argument('--grace_period', help='keep stats for max number of seconds', type=int, default=3600) + parser.add_argument('--pf_table' ,help='pf table to add failed attempts in', default='sshlockout') + inputargs = parser.parse_args() + + suspects = dict() + suspects_lastseen = dict() + + while True: + rlist, _, _ = select([sys.stdin], [], [], 0.5) + if rlist: + line = sys.stdin.readline() + ip = None + for part in line.split(): + if re.match('^[0-9.]+$', part) or re.match('^[a-fA-F0-9:]+$', part): + try: + ip = ipaddress.ip_address(part) + break + except ValueError: + ip = None + if ip: + # cleanup entries after grace period + for cleanup_ip in list(suspects_lastseen): + if time.time() - suspects_lastseen[cleanup_ip] > inputargs.grace_period: + del suspects_lastseen[cleanup_ip] + del suspects[cleanup_ip] + + allowed = None + for rule in all_rules: + if re.match(rule, line): + allowed = all_rules[rule] + break + + if allowed is True: + # reset counter when login was successful + if ip in suspects: + del suspects[ip] + del suspects_lastseen[ip] + elif allowed is False: + suspects_lastseen[ip] = time.time() + if ip not in suspects: + suspects[ip] = list() + + ts = datetime.datetime.strptime(line[0:15].split()[-1], "%H:%M:%S") + if len(suspects[ip]) == 0 or abs((ts - suspects[ip][-1]).total_seconds()) > 2: + # a single attempt can lead to multiple log entries, suppress likely duplicates + suspects[ip].append(ts) + if len(suspects[ip]) > inputargs.attempts: + syslog.syslog(syslog.LOG_NOTICE, "lockout %s [using table %s] after %d attempts" % ( + ip, inputargs.pf_table, len(suspects[ip]) + )) + subprocess.run(['/sbin/pfctl', '-t', inputargs.pf_table, '-T', 'add', str(ip)], + capture_output=True) diff --git a/src/opnsense/scripts/syslog/log_archive b/src/opnsense/scripts/syslog/log_archive new file mode 100755 index 000000000..8cdedb416 --- /dev/null +++ b/src/opnsense/scripts/syslog/log_archive @@ -0,0 +1,62 @@ +#!/usr/local/bin/php +isFile()) { + $log_subject = basename($file->getPath()); + if (strpos($file->getFilename(), $log_subject . "_") === 0 + && $file->getExtension() == "log" + && ctype_digit(substr($file->getFilename(), strlen($log_subject)+1, 8))) { + if (!isset($relevant_logs[$log_subject])) { + $relevant_logs[$log_subject] = []; + } + $relevant_logs[$log_subject][] = (string)$file; + } + } +} +// remove expired logs +foreach ($relevant_logs as $log_subject => $items) { + if (count($items) > $preserve_logs) { + rsort($items); + foreach (array_slice($items, $preserve_logs) as $filename) { + @unlink($filename); + } + } +} diff --git a/src/opnsense/scripts/systemhealth/clearlog b/src/opnsense/scripts/systemhealth/clearlog index 28bdcc516..fb3715f0c 100755 --- a/src/opnsense/scripts/systemhealth/clearlog +++ b/src/opnsense/scripts/systemhealth/clearlog @@ -34,7 +34,6 @@ require_once("interfaces.inc"); require_once("plugins.inc"); $opts = getopt('hm:f:', array(), $optind); - if (isset($opts['h']) || empty($opts['f']) || empty($opts['m'])) { echo "Usage: clearlog [-h] [-m] [-f]\n\n"; echo "\t-h show this help text and exit\n"; @@ -46,10 +45,19 @@ if (isset($opts['m']) && isset($opts['f'])) { $mname = basename($opts['m']); $fname = basename($opts['f']); if ($mname == 'core') { - $filename = "/var/log/{$fname}.log"; + $basename = "/var/log/{$fname}"; } else { - $filename = "/var/log/{$mname}/{$fname}.log"; + $basename = "/var/log/{$mname}/{$fname}"; } + $filename = "{$basename}.log"; + + if (is_dir($basename)) { + foreach (glob("{$basename}/{$fname}_*.log") as $filename) { + @unlink($filename); + } + system_syslogd_start(); + } + if (is_file($filename)) { $size = filesize($filename); $handle = fopen($filename, "r"); diff --git a/src/opnsense/scripts/systemhealth/queryLog.py b/src/opnsense/scripts/systemhealth/queryLog.py index e2b6b9618..5833f012e 100755 --- a/src/opnsense/scripts/systemhealth/queryLog.py +++ b/src/opnsense/scripts/systemhealth/queryLog.py @@ -36,6 +36,7 @@ import re import sre_constants import ujson import datetime +import glob from logformats import FormatContainer sys.path.insert(0, "/usr/local/opnsense/site-python") from log_helper import reverse_log_reader, fetch_clog @@ -54,12 +55,19 @@ if __name__ == '__main__': result = {'filters': filter, 'rows': [], 'total_rows': 0, 'origin': os.path.basename(inputargs.filename)} if inputargs.filename != "": + log_filenames = list() if inputargs.module == 'core': - log_filename = "/var/log/%s.log" % os.path.basename(inputargs.filename) + log_basename = "/var/log/%s" % os.path.basename(inputargs.filename) else: - log_filename = "/var/log/%s/%s.log" % ( + log_basename = "/var/log/%s/%s" % ( os.path.basename(inputargs.module), os.path.basename(inputargs.filename) ) + if os.path.isdir(log_basename): + # new syslog-ng local targets use an extra directory level + for filename in sorted(glob.glob("%s/*.log" % log_basename), reverse=True): + log_filenames.append(filename) + # legacy log output is always stiched last + log_filenames.append("%s.log" % log_basename) limit = int(inputargs.limit) if inputargs.limit.isdigit() else 0 offset = int(inputargs.offset) if inputargs.offset.isdigit() else 0 @@ -73,27 +81,30 @@ if __name__ == '__main__': # remove illegal expression filter_regexp = re.compile('.*') - if os.path.exists(log_filename): - format_container = FormatContainer(log_filename) - try: - filename = fetch_clog(log_filename) - except Exception as e: - filename = log_filename - for record in reverse_log_reader(filename): - if record['line'] != "" and filter_regexp.match(('%s' % record['line']).lower()): - result['total_rows'] += 1 - if (len(result['rows']) < limit or limit == 0) and result['total_rows'] >= offset: - record['timestamp'] = None - record['parser'] = None - frmt = format_container.get_format(record['line']) - if frmt: - record['timestamp'] = frmt.timestamp(record['line']) - record['line'] = frmt.line(record['line']) - record['parser'] = frmt.name - result['rows'].append(record) - elif result['total_rows'] > offset + limit: - # do not fetch data until end of file... - break + for log_filename in log_filenames: + if os.path.exists(log_filename): + format_container = FormatContainer(log_filename) + try: + filename = fetch_clog(log_filename) + except Exception as e: + filename = log_filename + for record in reverse_log_reader(filename): + if record['line'] != "" and filter_regexp.match(('%s' % record['line']).lower()): + result['total_rows'] += 1 + if (len(result['rows']) < limit or limit == 0) and result['total_rows'] >= offset: + record['timestamp'] = None + record['parser'] = None + frmt = format_container.get_format(record['line']) + if frmt: + record['timestamp'] = frmt.timestamp(record['line']) + record['line'] = frmt.line(record['line']) + record['parser'] = frmt.name + result['rows'].append(record) + elif result['total_rows'] > offset + limit: + # do not fetch data until end of file... + break + if result['total_rows'] > offset + limit: + break # output results print(ujson.dumps(result)) diff --git a/src/opnsense/service/templates/OPNsense/Syslog/+TARGETS b/src/opnsense/service/templates/OPNsense/Syslog/+TARGETS index 47e85adee..19115d463 100644 --- a/src/opnsense/service/templates/OPNsense/Syslog/+TARGETS +++ b/src/opnsense/service/templates/OPNsense/Syslog/+TARGETS @@ -3,3 +3,5 @@ newsyslog.conf:/etc/newsyslog.conf syslog-ng.conf:/usr/local/etc/syslog-ng.conf syslog-ng-legacy.conf:/usr/local/etc/syslog-ng.conf.d/legacy.conf syslog-ng-destinations.conf:/usr/local/etc/syslog-ng.conf.d/syslog-ng-destinations.conf +syslog-ng-local.conf:/usr/local/etc/syslog-ng.conf.d/syslog-ng-local.conf +syslog-ng-lockout.conf:/usr/local/etc/syslog-ng.conf.d/syslog-ng-lockout.conf diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/README b/src/opnsense/service/templates/OPNsense/Syslog/local/README new file mode 100644 index 000000000..920411b33 --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/README @@ -0,0 +1,12 @@ +Configuration of local syslog-ng targets. + +These configuration files should use a strict format in order to be properly supported and should only contain filter criteria. + +The filter clause itself, defines which local log traffic should be captured. +A strict format is required here. f_local_[filename without .conf]. (e.g. f_local_configd for configd.conf) + +All of these files are combined into a large configuration file defining a target per local file and a system log +file containing all not catched in the local definitions. +A local target named system is therefor not allowed here. + +Underscores will be replaced as path reference, app_file.conf will result in a file /var/log/app/file/file_[XX].log diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/configd.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/configd.conf new file mode 100644 index 000000000..51382fc02 --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/configd.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [configd]. +################################################################### +filter f_local_configd { + program("configd.py"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/dhcpd.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/dhcpd.conf new file mode 100644 index 000000000..759ff46de --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/dhcpd.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [dhcpd]. +################################################################### +filter f_local_dhcpd { + facility(local7) or program("dhcpd") or program("dhcrelay"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/dnsmasq.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/dnsmasq.conf new file mode 100644 index 000000000..071e7c2a8 --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/dnsmasq.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [dnsmasq]. +################################################################### +filter f_local_dnsmasq { + program("dnsmasq"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/filter.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/filter.conf new file mode 100644 index 000000000..2c83864f1 --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/filter.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [filter]. +################################################################### +filter f_local_filter { + program("filterlog"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/gateways.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/gateways.conf new file mode 100644 index 000000000..986e01532 --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/gateways.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [gateways]. +################################################################### +filter f_local_gateways { + program("dpinger"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/ipsec.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/ipsec.conf new file mode 100644 index 000000000..4fb565ca3 --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/ipsec.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [ipsec]. +################################################################### +filter f_local_ipsec { + program("charon"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/lighttpd.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/lighttpd.conf new file mode 100644 index 000000000..58231dc2c --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/lighttpd.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [lighttpd]. +################################################################### +filter f_local_lighttpd { + program("lighttpd"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/ntpd.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/ntpd.conf new file mode 100644 index 000000000..94c0437bc --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/ntpd.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [ntpd]. +################################################################### +filter f_local_ntpd { + program("ntp") or program("ntpd") or program("ntpdate"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/openvpn.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/openvpn.conf new file mode 100644 index 000000000..125eeffe1 --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/openvpn.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [openvpn]. +################################################################### +filter f_local_openvpn { + program("openvpn"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/pkg.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/pkg.conf new file mode 100644 index 000000000..ac84ddc3a --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/pkg.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [pkg]. +################################################################### +filter f_local_pkg { + program("pkg") or program("pkg-static"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/portalauth.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/portalauth.conf new file mode 100644 index 000000000..4ebde909a --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/portalauth.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [portalauth]. +################################################################### +filter f_local_portalauth { + facility(local4) or program("captiveportal"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/ppps.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/ppps.conf new file mode 100644 index 000000000..1a7d1029d --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/ppps.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [ppps]. +################################################################### +filter f_local_ppps { + program("ppp"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/relayd.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/relayd.conf new file mode 100644 index 000000000..9affca7df --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/relayd.conf @@ -0,0 +1,7 @@ +################################################################### +# Local syslog-ng configuration filter definition [relayd]. +################################################################### +filter f_local_relayd { + program("haproxy") or + program("relayd"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/resolver.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/resolver.conf new file mode 100644 index 000000000..779735edf --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/resolver.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [resolver]. +################################################################### +filter f_local_resolver { + program("unbound"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/routing.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/routing.conf new file mode 100644 index 000000000..2b030b2cb --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/routing.conf @@ -0,0 +1,13 @@ +################################################################### +# Local syslog-ng configuration filter definition [routing]. +################################################################### +filter f_local_routing { + program("radvd") or + program("routed") or + program("rtsold") or + program("olsrd") or + program("zebra") or + program("ospfd") or + program("bgpd") or + program("miniupnpd"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/squid_access.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/squid_access.conf new file mode 100644 index 000000000..0f742e2a1 --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/squid_access.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [squid_access]. +################################################################### +filter f_local_squid_access { + program("(squid-1)"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/suricata.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/suricata.conf new file mode 100644 index 000000000..a14a2d8e2 --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/suricata.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [suricata]. +################################################################### +filter f_local_suricata { + program("suricata"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/vpn.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/vpn.conf new file mode 100644 index 000000000..15685b28b --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/vpn.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [vpn]. +################################################################### +filter f_local_vpn { + facility(local3); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/local/wireless.conf b/src/opnsense/service/templates/OPNsense/Syslog/local/wireless.conf new file mode 100644 index 000000000..8027f0b40 --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/local/wireless.conf @@ -0,0 +1,6 @@ +################################################################### +# Local syslog-ng configuration filter definition [wireless]. +################################################################### +filter f_local_wireless { + program("hostapd"); +}; diff --git a/src/opnsense/service/templates/OPNsense/Syslog/syslog-ng-legacy.conf b/src/opnsense/service/templates/OPNsense/Syslog/syslog-ng-legacy.conf index 08ac479aa..d5227b7c4 100644 --- a/src/opnsense/service/templates/OPNsense/Syslog/syslog-ng-legacy.conf +++ b/src/opnsense/service/templates/OPNsense/Syslog/syslog-ng-legacy.conf @@ -3,6 +3,7 @@ # send all received local events to platform standard syslogd # +{% if helpers.empty('syslog.disable_clog') %} destination legacy_dst { unix-dgram("/var/run/legacy_log" template("<$PRI>${MSGHDR}${MESSAGE}\n")); }; @@ -11,3 +12,5 @@ log { source(s_all); destination(legacy_dst); }; + +{% endif %} diff --git a/src/opnsense/service/templates/OPNsense/Syslog/syslog-ng-local.conf b/src/opnsense/service/templates/OPNsense/Syslog/syslog-ng-local.conf new file mode 100644 index 000000000..f5e902586 --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/syslog-ng-local.conf @@ -0,0 +1,48 @@ +{% if not helpers.empty('syslog.disable_clog') %} +{% set all_filters = [] %} +{% for sfilename in helpers.glob("OPNsense/Syslog/local/*.conf") %}{% + include sfilename without context +%} {% + set local_config = sfilename.split('/')[-1].replace('.conf', '') +%} {% + set local_config_filter = "f_local_" + local_config +%} {% + do all_filters.append(local_config_filter) +%} + +destination d_local_{{ local_config }} { + file( + "/var/log/{{local_config.replace('_', '/')}}/{{local_config.split('_')[-1]}}_${YEAR}${MONTH}${DAY}.log" + create-dirs(yes) + ); +}; +log { + source(s_all); + filter({{local_config_filter}}); + destination(d_local_{{ local_config }}); +}; + +{% endfor %} + +################################################################################ +# not captured elsewhere, but relevant, send to system[__].log +################################################################################ +filter f_local_system { + not filter({{ all_filters|join(') and not filter(') }}) + and level(notice..emerg) +}; + +destination d_local_system { + file( + "/var/log/system/system_${YEAR}${MONTH}${DAY}.log" + create-dirs(yes) + ); +}; + +log { + source(s_all); + filter(f_local_system); + destination(d_local_system); +}; + +{% endif %} diff --git a/src/opnsense/service/templates/OPNsense/Syslog/syslog-ng-lockout.conf b/src/opnsense/service/templates/OPNsense/Syslog/syslog-ng-lockout.conf new file mode 100644 index 000000000..afaa36bc7 --- /dev/null +++ b/src/opnsense/service/templates/OPNsense/Syslog/syslog-ng-lockout.conf @@ -0,0 +1,13 @@ +filter f_local_lockout_auth { + facility(auth); +}; + +destination d_local_lockout_auth { + program("/usr/local/opnsense/scripts/syslog/lockout_handler"); +}; + +log { + source(s_all); + filter(f_local_lockout_auth); + destination(d_local_lockout_auth); +}; diff --git a/src/www/diag_logs_filter_summary.php b/src/www/diag_logs_filter_summary.php deleted file mode 100644 index 5a391ae89..000000000 --- a/src/www/diag_logs_filter_summary.php +++ /dev/null @@ -1,480 +0,0 @@ - - * Copyright (C) 2009 Jim Pingle - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -require_once("guiconfig.inc"); -require_once("system.inc"); -require_once("interfaces.inc"); - -function conv_log_interface_names() -{ - global $config; - - // collect interface names - $interface_names = array(); - $interface_names['enc0'] = gettext("IPsec"); - - if (!empty($config['interfaces'])) { - foreach (legacy_config_get_interfaces(array("virtual" => false)) as $intfkey => $interface) { - $interface_names[$interface['if']] = !empty($interface['descr']) ? $interface['descr'] : $intfkey; - } - } - return $interface_names; -} - -/* format filter logs */ -function conv_log_filter($logfile, $nentries, $tail = 50, $filtertext = '', $filterinterface = null) -{ - global $config; - - /* Make sure this is a number before using it in a system call */ - if (!(is_numeric($tail))) { - return; - } - - if ($filtertext!=""){ - $tail = 5000; - } - - /* Always do a reverse tail, to be sure we're grabbing the 'end' of the log. */ - $logarr = []; - - exec("/usr/local/sbin/clog " . escapeshellarg($logfile) . " | grep -v \"CLOG\" | grep -v \"\033\" | /usr/bin/grep 'filterlog.*:' | /usr/bin/tail -r -n {$tail}", $logarr); - - $filterlog = array(); - $counter = 0; - $interface_names = conv_log_interface_names(); - foreach ($logarr as $logent) { - if ($counter >= $nentries) { - break; - } - - $flent = parse_filter_line($logent, $interface_names); - if (isset($flent) && is_array($flent)) { - if ($filterinterface == null || strtoupper($filterinterface) == $flent['interface']) { - if ( (!is_array($filtertext) && match_filter_line ($flent, $filtertext)) || - ( is_array($filtertext) && match_filter_field($flent, $filtertext)) - ) { - $counter++; - $filterlog[] = $flent; - } - } - } - } - /* Since the lines are in reverse order, flip them around if needed based on the user's preference */ - return isset($config['syslog']['reverse']) ? $filterlog : array_reverse($filterlog); -} - -function escape_filter_regex($filtertext) -{ - /* If the caller (user) has not already put a backslash before a slash, to escape it in the regex, */ - /* then this will do it. Take out any "\/" already there, then turn all ordinary "/" into "\/". */ - return str_replace('/', '\/', str_replace('\/', '/', $filtertext)); -} - -function match_filter_line($flent, $filtertext = "") -{ - if (!$filtertext) { - return true; - } - $filtertext = escape_filter_regex(str_replace(' ', '\s+', $filtertext)); - return @preg_match("/{$filtertext}/i", implode(" ", array_values($flent))); -} - -function match_filter_field($flent, $fields) { - foreach ($fields as $key => $field) { - if ($field == "All") { - continue; - } - if ((strpos($field, '!') === 0)) { - $field = substr($field, 1); - if (strtolower($key) == 'act') { - if (in_arrayi($flent[$key], explode(" ", $field))) { - return false; - } - } else { - $field_regex = escape_filter_regex($field); - if (@preg_match("/{$field_regex}/i", $flent[$key])) { - return false; - } - } - } else { - if (strtolower($key) == 'act') { - if (!in_arrayi($flent[$key], explode(" ", $field))) { - return false; - } - } else { - $field_regex = escape_filter_regex($field); - if (!@preg_match("/{$field_regex}/i", $flent[$key])) { - return false; - } - } - } - } - return true; -} - -// Case Insensitive in_array function -function in_arrayi($needle, $haystack) -{ - return in_array(strtolower($needle), array_map('strtolower', $haystack)); -} - -function parse_filter_line($line, $interface_names = array()) -{ - $flent = array(); - $log_split = ''; - - if (!preg_match('/(.*)\s(.*)\sfilterlog.*:\s(.*)$/', $line, $log_split)) { - return ''; - } - - list($all, $flent['time'], $host, $rule) = $log_split; - - if (trim($flent['time']) == '') { - log_error(sprintf('There was an error parsing a rule: no time (%s)', $log_split)); - return ''; - } - - $rule_data = explode(',', $rule); - $field = 0; - - $flent['rulenum'] = $rule_data[$field++]; - $flent['subrulenum'] = $rule_data[$field++]; - $flent['anchor'] = $rule_data[$field++]; - $field++; // skip field - $flent['realint'] = $rule_data[$field++]; - $flent['interface'] = !empty($interface_names[$flent['realint']]) ? $interface_names[$flent['realint']] : $flent['realint'] ; - $flent['reason'] = $rule_data[$field++]; - $flent['act'] = $rule_data[$field++]; - $flent['direction'] = $rule_data[$field++]; - $flent['version'] = $rule_data[$field++]; - - if ($flent['version'] != '4' && $flent['version'] != '6') { - log_error(sprintf( - gettext('There was an error parsing rule number: %s -- not IPv4 or IPv6 (`%s\')'), - $flent['rulenum'], - $rule - )); - return ''; - } - - if ($flent['version'] == '4') { - $flent['tos'] = $rule_data[$field++]; - $flent['ecn'] = $rule_data[$field++]; - $flent['ttl'] = $rule_data[$field++]; - $flent['id'] = $rule_data[$field++]; - $flent['offset'] = $rule_data[$field++]; - $flent['flags'] = $rule_data[$field++]; - $flent['protoid'] = $rule_data[$field++]; - $flent['proto'] = strtoupper($rule_data[$field++]); - } else { - $flent['class'] = $rule_data[$field++]; - $flent['flowlabel'] = $rule_data[$field++]; - $flent['hlim'] = $rule_data[$field++]; - $flent['proto'] = strtoupper($rule_data[$field++]); - $flent['protoid'] = $rule_data[$field++]; - } - - $flent['length'] = $rule_data[$field++]; - $flent['srcip'] = $rule_data[$field++]; - $flent['dstip'] = $rule_data[$field++]; - - /* bootstrap src and dst for non-port protocols */ - $flent['src'] = $flent['srcip']; - $flent['dst'] = $flent['dstip']; - - if (trim($flent['src']) == '' || trim($flent['dst']) == '') { - log_error(sprintf( - gettext('There was an error parsing rule number: %s -- no src or dst (`%s\')'), - $flent['rulenum'], - $rule - )); - return ''; - } - - if ($flent['protoid'] == '6' || $flent['protoid'] == '17') { // TCP or UDP - $flent['srcport'] = $rule_data[$field++]; - $flent['dstport'] = $rule_data[$field++]; - - $flent['src'] = $flent['srcip'] . ':' . $flent['srcport']; - $flent['dst'] = $flent['dstip'] . ':' . $flent['dstport']; - - $flent['datalen'] = $rule_data[$field++]; - if ($flent['protoid'] == '6') { // TCP - $flent['tcpflags'] = $rule_data[$field++]; - $flent['seq'] = $rule_data[$field++]; - $flent['ack'] = $rule_data[$field++]; - $flent['window'] = $rule_data[$field++]; - $flent['urg'] = $rule_data[$field++]; - $flent['options'] = explode(";",$rule_data[$field++]); - } - } elseif ($flent['protoid'] == '1') { // ICMP - $flent['icmp_type'] = $rule_data[$field++]; - switch ($flent['icmp_type']) { - case 'request': - case 'reply': - $flent['icmp_id'] = $rule_data[$field++]; - $flent['icmp_seq'] = $rule_data[$field++]; - break; - case 'unreachproto': - $flent['icmp_dstip'] = $rule_data[$field++]; - $flent['icmp_protoid'] = $rule_data[$field++]; - break; - case 'unreachport': - $flent['icmp_dstip'] = $rule_data[$field++]; - $flent['icmp_protoid'] = $rule_data[$field++]; - $flent['icmp_port'] = $rule_data[$field++]; - break; - case 'unreach': - case 'timexceed': - case 'paramprob': - case 'redirect': - case 'maskreply': - $flent['icmp_descr'] = $rule_data[$field++]; - break; - case 'needfrag': - $flent['icmp_dstip'] = $rule_data[$field++]; - $flent['icmp_mtu'] = $rule_data[$field++]; - break; - case 'tstamp': - $flent['icmp_id'] = $rule_data[$field++]; - $flent['icmp_seq'] = $rule_data[$field++]; - break; - case 'tstampreply': - $flent['icmp_id'] = $rule_data[$field++]; - $flent['icmp_seq'] = $rule_data[$field++]; - $flent['icmp_otime'] = $rule_data[$field++]; - $flent['icmp_rtime'] = $rule_data[$field++]; - $flent['icmp_ttime'] = $rule_data[$field++]; - break; - default : - if (isset($rule_data[$field++])) { - $flent['icmp_descr'] = $rule_data[$field++]; - } - break; - } - } elseif ($flent['protoid'] == '2') { // IGMP - $flent['src'] = $flent['srcip']; - $flent['dst'] = $flent['dstip']; - } elseif ($flent['protoid'] == '112') { // CARP - $flent['type'] = $rule_data[$field++]; - $flent['ttl'] = $rule_data[$field++]; - $flent['vhid'] = $rule_data[$field++]; - $flent['version'] = $rule_data[$field++]; - $flent['advskew'] = $rule_data[$field++]; - $flent['advbase'] = $rule_data[$field++]; - } - - return $flent; -} - -$filter_logfile = '/var/log/filter.log'; -$lines = 5000; // Maximum number of log entries to fetch -$entriesperblock = 10; // Maximum elements to show individually - -// flush log file -if (!empty($_POST['clear'])) { - system_clear_clog($filter_logfile); -} - -// Retrieve filter log data -$filterlog = conv_log_filter($filter_logfile, $lines, $lines); -// Set total retrieved line counter -$gotlines = count($filterlog); -// Set readable fieldnames -$fields = array( - 'act' => gettext("Actions"), - 'interface' => gettext("Interfaces"), - 'proto' => gettext("Protocols"), - 'srcip' => gettext("Source IPs"), - 'dstip' => gettext("Destination IPs"), - 'srcport' => gettext("Source Ports"), - 'dstport' => gettext("Destination Ports")); - -$summary = array(); - -foreach (array_keys($fields) as $f) { - $summary[$f] = array(); -} - -// Fill summary array with filterlog data -foreach ($filterlog as $fe) { - foreach (array_keys($fields) as $field) { - if (isset($fe[$field])) { - if (!isset($summary[$field])) { - $summary[$field] = array(); - } - if (!isset($summary[$field][$fe[$field]])) { - $summary[$field][$fe[$field]] = 0; - } - $summary[$field][$fe[$field]]++; - } - } -} - -// Setup full data array for pie and table -function d3pie_data($summary, $num) { - $data=array(); - foreach (array_keys($summary) as $stat) { - uasort($summary[$stat], function ($a, $b) { - if ($a == $b) { - return 0; - } - return ($a < $b) ? 1 : -1; - }); - - $other=0; - foreach(array_keys($summary[$stat]) as $key) { - - if (!isset($data[$stat])) { - $data[$stat] = array(); - } - if ( count($data[$stat]) < $num ) { - $data[$stat][] = array('label' => $key, 'value' => $summary[$stat][$key]); - } else { - $other+=$summary[$stat][$key]; - } - } - if ($other > 0) { - $data[$stat][] = array('label' => gettext("other"), 'value' => $other); - } - } - - return $data; -} - -include("head.inc"); ?> - - - - -
-
-
- - 0) print_input_errors($input_errors); ?> -
-
-
- - - - - -
- - -
-
- -
-
-
-
-
-
- -
- - - - -
-

-
-
- -
- - - - - - - - - - - - - -
- - " title=""> - -
-
-
- -
-
-
-
- - - - - -isFile() && strpos($file->getFilename(), '.log') > -1) { + if (strpos($file->getFilename(), 'flowd') === false) { + @unlink((string)$file); + } + } } - - foreach ($log_files as $lfile) { - system_clear_log("/var/log/{$lfile}.log", false); - } - system_syslogd_start(); plugins_configure('dhcp'); } @@ -91,7 +59,9 @@ function is_valid_syslog_server($target) { if ($_SERVER['REQUEST_METHOD'] === 'GET') { $pconfig = array(); $pconfig['reverse'] = isset($config['syslog']['reverse']); + $pconfig['disable_clog'] = isset($config['syslog']['disable_clog']); $pconfig['logfilesize'] = !empty($config['syslog']['logfilesize']) ? $config['syslog']['logfilesize'] : null; + $pconfig['preservelogs'] = !empty($config['syslog']['preservelogs']) ? $config['syslog']['preservelogs'] : null; $pconfig['logdefaultblock'] = empty($config['syslog']['nologdefaultblock']); $pconfig['logdefaultpass'] = empty($config['syslog']['nologdefaultpass']); $pconfig['logbogons'] = empty($config['syslog']['nologbogons']); @@ -113,13 +83,26 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { $input_errors[] = gettext("Log file size must be a positive integer greater than 5120."); } } + if (!empty($pconfig['preservelogs']) && (strlen($pconfig['preservelogs']) > 0)) { + if (!is_numeric($pconfig['preservelogs'])) { + $input_errors[] = gettext("Preserve logs must be a positive integer value."); + } + } + if (count($input_errors) == 0) { - $config['syslog']['reverse'] = !empty($pconfig['reverse']) ? true : false; + $config['syslog']['reverse'] = !empty($pconfig['reverse']); + $config['syslog']['disable_clog'] = !empty($pconfig['disable_clog']); if (isset($_POST['logfilesize']) && (strlen($pconfig['logfilesize']) > 0)) { $config['syslog']['logfilesize'] = (int)$pconfig['logfilesize']; } elseif (isset($config['syslog']['logfilesize'])) { unset($config['syslog']['logfilesize']); } + if (isset($_POST['preservelogs']) && (strlen($pconfig['preservelogs']) > 0)) { + $config['syslog']['preservelogs'] = (int)$pconfig['preservelogs']; + } elseif (isset($config['syslog']['preservelogs'])) { + unset($config['syslog']['preservelogs']); + } + $config['syslog']['disablelocallogging'] = !empty($pconfig['disablelocallogging']); $oldnologdefaultblock = isset($config['syslog']['nologdefaultblock']); $oldnologdefaultpass = isset($config['syslog']['nologdefaultpass']); @@ -185,6 +168,17 @@ $(document).ready(function() { }] }); }); + + $("#disable_clog").change(function(){ + if ($(this).is(":checked")) { + $("#preservelogs").prop("disabled", false).closest("tr").removeClass("hidden"); + $("#logfilesize").prop("disabled", true).closest("tr").addClass("hidden"); + } else { + $("#preservelogs").prop("disabled", true).closest("tr").addClass("hidden"); + $("#logfilesize").prop("disabled", false).closest("tr").removeClass("hidden"); + } + }); + $("#disable_clog").change(); }); //]]> @@ -223,10 +217,28 @@ $(document).ready(function() { + + + + /> + + + + + + + + + + - +