banip: release 0.7.0
authorDirk Brenken <dev@brenken.org>
Thu, 4 Feb 2021 14:35:21 +0000 (15:35 +0100)
committerDirk Brenken <dev@brenken.org>
Thu, 4 Feb 2021 20:18:02 +0000 (21:18 +0100)
* major rewrite
* add support for multiple chains
* add mac whitelisting
* add support for multiple ssh daemons in parallel
* add an ipset report engine
* add mail notifications
* add suspend/resume functions
* add a cron wrapper to set an ipset related auto-timer for
  automatic blocklist updates
* add a list wrapper to add/remove blocklist sources
* add 19.x and Turris OS 5.x compatibility code
* sources stored in an external compressed json file
  (/etc/banip/banip.sources.gz)
* change Country/ASN download sources (faster/more reliable)
* fix DHCPv6/icmpv6 issues

Signed-off-by: Dirk Brenken <dev@brenken.org>
net/banip/Makefile
net/banip/files/README.md
net/banip/files/banip.conf
net/banip/files/banip.countries [new file with mode: 0644]
net/banip/files/banip.hotplug
net/banip/files/banip.init
net/banip/files/banip.maclist [new file with mode: 0644]
net/banip/files/banip.mail [new file with mode: 0755]
net/banip/files/banip.service
net/banip/files/banip.sh
net/banip/files/banip.sources [new file with mode: 0644]

index 5ebd6456f1ddb8bb05093ef1dab49ead17bff331..614487147d55c900d3d9e5021d310b7989139324 100644 (file)
@@ -1,12 +1,12 @@
 #
-# Copyright (c) 2018-2019 Dirk Brenken (dev@brenken.org)
+# Copyright (c) 2018-2021 Dirk Brenken (dev@brenken.org)
 # This is free software, licensed under the GNU General Public License v3.
 #
 
 include $(TOPDIR)/rules.mk
 
 PKG_NAME:=banip
-PKG_VERSION:=0.3.13
+PKG_VERSION:=0.7.0
 PKG_RELEASE:=1
 PKG_LICENSE:=GPL-3.0-or-later
 PKG_MAINTAINER:=Dirk Brenken <dev@brenken.org>
@@ -16,7 +16,7 @@ include $(INCLUDE_DIR)/package.mk
 define Package/banip
        SECTION:=net
        CATEGORY:=Network
-       TITLE:=Ban incoming and/or outgoing ip adresses via ipsets
+       TITLE:=Ban incoming and outgoing ip adresses via ipsets
        DEPENDS:=+jshn +jsonfilter +ip +ipset +iptables +ca-bundle
        PKGARCH:=all
 endef
@@ -30,8 +30,9 @@ endef
 
 define Package/banip/conffiles
 /etc/config/banip
-/etc/banip/banip.whitelist
+/etc/banip/banip.maclist
 /etc/banip/banip.blacklist
+/etc/banip/banip.whitelist
 endef
 
 define Build/Prepare
@@ -54,9 +55,14 @@ define Package/banip/install
        $(INSTALL_CONF) ./files/banip.conf $(1)/etc/config/banip
 
        $(INSTALL_DIR) $(1)/etc/banip
+       $(INSTALL_BIN) ./files/banip.mail $(1)/etc/banip
        $(INSTALL_BIN) ./files/banip.service $(1)/etc/banip
+       $(INSTALL_CONF) ./files/banip.maclist $(1)/etc/banip
        $(INSTALL_CONF) ./files/banip.blacklist $(1)/etc/banip
        $(INSTALL_CONF) ./files/banip.whitelist $(1)/etc/banip
+       $(INSTALL_CONF) ./files/banip.countries $(1)/etc/banip
+       $(INSTALL_CONF) ./files/banip.sources $(1)/etc/banip
+       gzip -9 $(1)/etc/banip/banip.sources
 
        $(INSTALL_DIR) $(1)/etc/hotplug.d/firewall
        $(INSTALL_DATA) ./files/banip.hotplug $(1)/etc/hotplug.d/firewall/30-banip
index 7cb716147cefd34ef991481a24f047b1fec81949..d8add1726e5a7ee9c45068ffefe22ef8528c170b 100644 (file)
@@ -1,10 +1,42 @@
+<!-- markdownlint-disable -->
+
 # banIP - ban incoming and/or outgoing ip adresses via ipsets
 
 ## Description
 IP address blocking is commonly used to protect against brute force attacks, prevent disruptive or unauthorized address(es) from access or it can be used to restrict access to or from a particular geographic area — for example.  
 
 ## Main Features
-* support many IP blocklist sources (free for private usage, for commercial use please check their individual licenses):
+* Support of the following fully pre-configured domain blocklist sources (free for private usage, for commercial use please check their individual licenses)
+
+| Source              | Focus                        | Information                                                                       |
+| :------------------ | :--------------------------: | :-------------------------------------------------------------------------------- |
+| asn                 | ASN block                    | [Link](https://asn.ipinfo.app)                                                    |
+| bogon               | Bogon prefixes               | [Link](https://team-cymru.com)                                                    |
+| country             | Country blocks               | [Link](https://www.ipdeny.com/ipblocks)                                           |
+| darklist            | Attacker IP blacklist        | [Link](https://darklist.de)                                                       |
+| debl                | Fail2ban IP blacklist        | [Link](https://www.blocklist.de)                                                  |
+| doh                 | Public DoH-Provider          | [Link](https://github.com/dibdot/DoH-IP-blocklists)                               |
+| drop                | Spamhaus drop compilation    | [Link](https://www.spamhaus.org)                                                  |
+| dshield             | Dshield IP blocklist         | [Link](https://www.dshield.org)                                                   |
+| edrop               | Spamhaus edrop compilation   | [Link](https://www.spamhaus.org)                                                  |
+| feodo               | Feodo Tracker                | [Link](https://feodotracker.abuse.ch)                                             |
+| firehol1            | Firehol Level 1 compilation  | [Link](https://iplists.firehol.org/?ipset=firehol_level1)                         |
+| firehol2            | Firehol Level 2 compilation  | [Link](https://iplists.firehol.org/?ipset=firehol_level2)                         |
+| firehol3            | Firehol Level 3 compilation  | [Link](https://iplists.firehol.org/?ipset=firehol_level3)                         |
+| firehol4            | Firehol Level 4 compilation  | [Link](https://iplists.firehol.org/?ipset=firehol_level4)                         |
+| iblockads           | Advertising blocklist        | [Link](https://www.iblocklist.com)                                                |
+| iblockspy           | Malicious spyware blocklist  | [Link](https://www.iblocklist.com)                                                |
+| myip                | Myip Live IP blacklist       | [Link](https://myip.ms)                                                           |
+| nixspam             | iX spam protection           | [Link](http://www.nixspam.org)                                                    |
+| proxy               | Firehol list of open proxies | [Link](https://iplists.firehol.org/?ipset=proxylists)                             |
+| ssbl                | SSL botnet IP blacklist      | [Link](https://sslbl.abuse.ch)                                                    |
+| threat              | Emerging Threats             | [Link](https://rules.emergingthreats.net)                                         |
+| tor                 | Tor exit nodes               | [Link](https://fissionrelays.net/lists)                                           |
+| uceprotect1         | Spam protection level 1      | [Link](http://www.uceprotect.net/en/index.php)                                    |
+| uceprotect2         | Spam protection level 2      | [Link](http://www.uceprotect.net/en/index.php)                                    |
+| voip                | VoIP fraud blocklist         | [Link](http://www.voipbl.org)                                                     |
+| yoyo                | Ad protection blacklist      | [Link](https://pgl.yoyo.org/adservers/)                                           |
+
 * zero-conf like automatic installation & setup, usually no manual changes needed
 * automatically selects one of the following download utilities: aria2c, curl, uclient-fetch, wget
 * Really fast downloads & list processing as they are handled in parallel as background jobs in a configurable 'Download Queue'
@@ -24,88 +56,305 @@ IP address blocking is commonly used to protect against brute force attacks, pre
 * procd based init system support (start/stop/restart/reload/refresh/status)
 * procd network interface trigger support
 * automatic blocklist backup & restore, they will be used in case of download errors or during startup
-* output comprehensive runtime information via LuCI or via 'status' init command
+* Provides comprehensive runtime information
+* Provides a detailed IPSet Report
+* Provides a powerful query function to quickly find blocked IPs/CIDR in banIP related IPSets
+* Provides an easily configurable blocklist update scheduler called 'Refresh Timer'
 * strong LuCI support
 * optional: add new banIP sources on your own
-* optional: log banned inbound and/or outbound IP to syslog.
 
 ## Prerequisites
-* [OpenWrt](https://openwrt.org), tested with the stable release series (19.07) and with the latest snapshot
-* download utility: 'uclient-fetch' with one of the 'libustream-*' ssl libraries, 'wget',  'aria2c' or 'curl' is required
+* [OpenWrt](https://openwrt.org), tested with the stable release series (19.07.x) and with the latest rolling snapshot releases. On turris devices it has been successfully tested with TurrisOS 5.2.x  
+  <b>Please note:</b> Older OpenWrt releases like 18.06.x or 17.01.x are _not_ supported!  
+  <b>Please note:</b> Devices with less than 128 MByte RAM are _not_ supported!  
+* A download utility with SSL support: 'wget', 'uclient-fetch' with one of the 'libustream-*' ssl libraries, 'aria2c' or 'curl' is required
+* A certificate store like 'ca-bundle', as banIP checks the validity of the SSL certificates of all download sites by default
+* Optional E-Mail notification support: for E-Mail notifications you need to install the additional 'msmtp' package
 
 ## Installation & Usage
-* install 'banip' (_opkg install banip_)
-* at minimum configure the needed IP blocklist sources, the download utility and enable the banIP service in _/etc/config/banip_
-* control the banip service manually with _/etc/init.d/banip_ start/stop/restart/reload/refresh/status or use the LuCI frontend
+* Update your local opkg repository (_opkg update_)
+* Install 'banip' (_opkg install banip_). The banIP service is disabled by default
+* Install the LuCI companion package 'luci-app-banip' (_opkg install luci-app-banip_)
+* It's strongly recommended to use the LuCI frontend to easily configure all aspects of banIP, the application is located in LuCI under the 'Services' menu
 
-## LuCI banIP companion package
-* it's recommended to use the provided LuCI frontend to control all aspects of banIP
-* install 'luci-app-banip' (_opkg install luci-app-banip_)
-* the application is located in LuCI under 'Services' menu
-
-## banIP config options
-* usually the pre-configured banIP setup works quite well and no manual overrides are needed
-* the following options apply to the 'global' config section:
-  * ban\_enabled => main switch to enable/disable banIP service (bool/default: '0', disabled)
-  * ban\_automatic => determine the L2/L3 WAN network device automatically (bool/default: '1', enabled)
-  * ban\_iface => space separated list of WAN network interface(s)/device(s) used by banIP (default: not set, automatically detected)
-  * ban\_realtime => a small log/banIP background monitor to block SSH/LuCI brute force attacks in realtime (bool/default: 'false', disabled)
-  * ban\_target\_src => action to perform when banning inbound IPv4 packets ('DROP'/'REJECT', default: 'DROP')
-  * ban\_target\_src\_6 => action to perform when banning inbound IPv6 packets ('DROP'/'REJECT', default: 'DROP')
-  * ban\_target\_dst => action to perform when banning outbound IPv4 packets ('DROP'/'REJECT', default: 'REJECT')
-  * ban\_target\_dst\_6 => action to perform when banning outbound IPv6 packets ('DROP'/'REJECT', default: 'REJECT')
-  * ban\_log\_src => switch to enable/disable logging of banned inbound IPv4 packets (bool/default: '0', disabled)
-  * ban\_log\_dst => switch to enable/disable logging of banned outbound IPv4 packets (bool/default: '0', disabled)
-
-* the following options apply to the 'extra' config section:
-  * ban\_debug => enable/disable banIP debug output (bool/default: '0', disabled)
-  * ban\_nice => set the nice level of the banIP process and all sub-processes (int/default: '0', standard priority)
-  * ban\_triggerdelay => additional trigger delay in seconds before banIP processing begins (int/default: '2')
-  * ban\_backupdir => target directory for banIP backups (default: '/tmp')
-  * ban\_sshdaemon => select the SSH daemon for logfile parsing, 'dropbear' or 'sshd' (default: 'dropbear')
-  * ban\_starttype => select the used start type during boot, 'start', 'refresh' or 'reload' (default: 'start')
-  * ban\_maxqueue => size of the download queue to handle downloads & IPSet processing in parallel (int/default: '4')
-  * ban\_fetchutil => name of the used download utility: 'uclient-fetch', 'wget', 'curl', 'aria2c' (default: not set, automatically detected)
-  * ban\_fetchparm => special config options for the download utility (default: not set)
-  * ban\_autoblacklist => store auto-addons temporary in ipset and permanently in local blacklist as well (bool/default: '1', enabled)
-  * ban\_autowhitelist => store auto-addons temporary in ipset and permanently in local whitelist as well (bool/default: '1', enabled)
-
-## Logging of banned packets
-* by setting ban\_log\_src=1 / ban\_log\_dst=1 in the config options, banIP will log banned inbound / outbound packets to syslog.
-* example of a logged inbound (dst) and outbound (src) packet:
+## banIP CLI
+* All important banIP functions are accessible via CLI as well.  
 <pre><code>
-Oct  2 12:49:14 gateway kernel: [434134.855130] REJECT(dst banIP) IN=br-lan OUT=br-wan MAC=xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx SRC=x.x.x.x DST=x.x.x.x LEN=100 TOS=0x00 PREC=0x00 TTL=63 ID=7938 PROTO=UDP SPT=16393 DPT=16393 LEN=80
+~# /etc/init.d/banip 
+Syntax: /etc/init.d/banip [command]
 
-Oct  3 14:11:13 gateway kernel: [11290.429712] DROP(src banIP) IN=br-wan OUT= MAC=xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx SRC=x.x.x.x DST=x.x.x.x LEN=40 TOS=0x00 PREC=0x00 TTL=235 ID=63275 PROTO=TCP SPT=48246 DPT=37860 WINDOW=1024 RES=0x00 SYN URGP=0
+Available commands:
+       start           Start the service
+       stop            Stop the service
+       restart         Restart the service
+       reload          Reload configuration files (or restart if service does not implement reload)
+       enable          Enable service autostart
+       disable         Disable service autostart
+       enabled         Check if service is started on boot
+       refresh         Refresh ipsets without new list downloads
+       suspend         Suspend banIP processing
+       resume          Resume banIP processing
+       query           &lt;IP&gt; Query active banIP IPSets for a specific IP address
+       report          [&lt;cli&gt;|&lt;mail&gt;|&lt;gen&gt;|&lt;json&gt;] Print banIP related IPset statistics
+       list            [&lt;add&gt;|&lt;add_asn&gt;|&lt;add_country&gt;|&lt;remove>|&lt;remove_asn&gt;|&lt;remove_country&gt;] &lt;source(s)&gt; List/Edit available sources
+       timer           [&lt;add&gt; &lt;tasks&gt; &lt;hour&gt; [&lt;minute&gt;] [&lt;weekday&gt;]]|[&lt;remove&gt; &lt;line no.&gt;] List/Edit cron update intervals
+       version         Print version information
+       running         Check if service is running
+       status          Service status
+       trace           Start with syscall trace
 </code></pre>
-* to change the default logging behavior, the following options can be added to the 'global' config section:
-  * ban\_log\_src\_opts => IPv4 iptables LOG options for banned inbound packets (default: '-m limit --limit 10/sec')
-  * ban\_log\_src\_opts\_6 => IPv6 iptables LOG options for banned inbound packets (default: '-m limit --limit 10/sec')
-  * ban\_log\_src\_prefix (default: '<ban\_target\_src>(src banIP) ', typically 'DROP(src banIP) ')
-  * ban\_log\_src\_prefix\_6 (default: '<ban\_target\_src\_6>(src banIP) ', typically 'DROP('src banIP)' )
-  * ban\_log\_dst\_opts => IPv4 iptables LOG options for banned outbound packets (default: '-m limit --limit 10/sec')
-  * ban\_log\_dst\_opts\_6 => IPv6 iptables LOG options for banned outbound packets (default: '-m limit --limit 10/sec')
-  * ban\_log\_dst\_prefix (default: '<ban\_target\_dst>(dst banIP) ', typically 'REJECT(dst banIP) ')
-  * ban\_log\_dst\_prefix\_6 (default: '<ban\_target\_dst\_6>(dst banIP) ', typically 'REJECT('dst banIP)' )
 
+## banIP config options
+* Usually the auto pre-configured banIP setup works quite well and no manual overrides are needed
+
+| Option                  | Type   | Default             | Description                                                                                          |
+| :---------------------- | :----- | :------------------ | :--------------------------------------------------------------------------------------------------- |
+| ban_enabled             | option | 0                   | enable the banIP service                                                                             |
+| ban_autodetect          | option | 1                   | auto-detect wan interfaces, devices and subnets                                                      |
+| ban_debug               | option | 0                   | enable banIP related debug logging                                                                   |
+| ban_mail_enabled        | option | 0                   | enable the mail service                                                                              |
+| ban_monitor_enabled     | option | 0                   | enable the log monitor, e.g. to catch failed ssh/luci logins                                         |
+| ban_logsrc_enabled      | option | 0                   | enable the src-related logchain                                                                      |
+| ban_logdst_enabled      | option | 0                   | enable the dst-related logchain                                                                      |
+| ban_autoblacklist       | option | 1                   | add suspicious IPs automatically to the local blacklist                                              |
+| ban_autowhitelist       | option | 1                   | add wan IPs/subnets automatically to the local whitelist                                             |
+| ban_maxqueue            | option | 4                           | size of the download queue to handle downloads and processing in parallel             |
+| ban_reportdir           | option | /tmp/banIP-Report           | directory where banIP stores the report files                                         |
+| ban_backupdir           | option | /tmp/banIP-Backup           | directory where banIP stores the compressed backup files                              |
+| ban_ifaces              | list   | -                           | list option to add logical wan interfaces manually                                    |
+| ban_sources             | list   | -                           | list option to add banIP sources                                                      |
+| ban_countries           | list   | -                           | list option to add certain countries as an alpha-2 ISO code, e.g. 'de' for germany    |
+| ban_asns                | list   | -                           | list option to add certain ASNs (autonomous system number), e.g. '32934' for facebook |
+| ban_chain               | option | banIP                       | name of the root chain used by banIP                                                  |
+| ban_global_settype      | option | src+dst                     | global settype as default for all sources                                             |
+| ban_settype_src         | list   | -                           | special SRC settype for a certain sources                                             |
+| ban_settype_dst         | list   | -                           | special DST settype for a certain sources                                             |
+| ban_settype_all         | list   | -                           | special SRC+DST settype for a certain sources                                         |
+| ban_target_src          | option | DROP                        | default src action (used by log chains as well)                                       |
+| ban_target_dst          | option | REJECT                      | default dst action (used by log chains as well)                                       |
+| ban_lan_inputchains_4   | list   | input_lan_rule              | list option to add IPv4 lan input chains                                              |
+| ban_lan_inputchains_6   | list   | input_lan_rule              | list option to add IPv6 lan input chains                                              |
+| ban_lan_forwardchains_4 | list   | forwarding_lan_rule         | list option to add IPv4 lan forward chains                                            |
+| ban_lan_forwardchains_6 | list   | forwarding_lan_rule         | list option to add IPv6 lan forward chains                                            |
+| ban_wan_inputchains_4   | list   | input_wan_rule              | list option to add IPv4 wan input chains                                              |
+| ban_wan_inputchains_6   | list   | input_wan_rule              | list option to add IPv6 wan input chains                                              |
+| ban_wan_forwardchains_4 | list   | forwarding_wan_rule         | list option to add IPv4 wan forward chains                                            |
+| ban_wan_forwardchains_6 | list   | forwarding_wan_rule         | list option to add IPv6 wan forward chains                                            |
+| ban_mailreceiver        | option | -                           | receiver address for banIP related notification E-Mails                               |
+| ban_mailsender          | option | no-reply@banIP              | sender address for banIP related notification E-Mails                                 |
+| ban_mailtopic           | option | banIP notification          | topic for banIP related notification E-Mails                                          |
+| ban_mailprofile         | option | ban_notify                  | mail profile used in 'msmtp' for banIP related notification E-Mails                   |
+| ban_srcarc              | option | /etc/banip/banip.sources.gz | full path to the compressed source archive file used by banIP                         |
+| ban_maclist             | option | /etc/banip/banip.maclist    | full path to the maclist file used by banIP                                           |
+| ban_blacklist           | option | /etc/banip/banip.blacklist  | full path to the blacklist file used by banIP                                         |
+| ban_whitelist           | option | /etc/banip/banip.whitelist  | full path to the whitelist file used by banIP                                         |
+  
 ## Examples
-**receive banIP runtime information:**
+**list/edit banIP sources:**
 
-    # /etc/init.d/banip status
-    ::: banIP runtime information
-      + status     : enabled
-      + version    : 0.3.0
-      + util_info  : /usr/bin/aria2c, true
-      + ipset_info : 10 IPSets with overall 106729 IPs/Prefixes
-      + backup_dir : /tmp
-      + last_run   : 03.10.2019 19:15:25
-      + system     : UBNT-ERX, OpenWrt SNAPSHOT r11102-ced4c0e635
+<pre><code>
+~# /etc/init.d/banip list
+::: Available banIP sources
+:::
+    Name                 Enabled   Focus                               Info URL
+    ---------------------------------------------------------------------------
+  + asn                            ASN blocks                          https://asn.ipinfo.app
+  + bogon                          Bogon prefixes                      https://team-cymru.com
+  + country              x         Country blocks                      https://www.ipdeny.com/ipblocks
+  + debl                 x         Fail2ban IP blacklist               https://www.blocklist.de
+  + doh                  x         Public DoH-Provider                 https://github.com/dibdot/DoH-IP-blocklists
+  + drop                 x         Spamhaus drop compilation           https://www.spamhaus.org
+  + dshield              x         Dshield IP blocklist                https://www.dshield.org
+  + edrop                          Spamhaus edrop compilation          https://www.spamhaus.org
+  + feodo                x         Feodo Tracker                       https://feodotracker.abuse.ch
+  + firehol1             x         Firehol Level 1 compilation         https://iplists.firehol.org/?ipset=firehol_level1
+  + firehol2                       Firehol Level 2 compilation         https://iplists.firehol.org/?ipset=firehol_level2
+  + firehol3                       Firehol Level 3 compilation         https://iplists.firehol.org/?ipset=firehol_level3
+  + firehol4                       Firehol Level 4 compilation         https://iplists.firehol.org/?ipset=firehol_level4
+  + iblockads                      Advertising blocklist               https://www.iblocklist.com
+  + iblockspy            x         Malicious spyware blocklist         https://www.iblocklist.com
+  + myip                           Myip Live IP blacklist              https://myip.ms
+  + nixspam              x         iX spam protection                  http://www.nixspam.org
+  + proxy                          Firehol list of open proxies        https://iplists.firehol.org/?ipset=proxylists
+  + sslbl                x         SSL botnet IP blacklist             https://sslbl.abuse.ch
+  + threat               x         Emerging Threats                    https://rules.emergingthreats.net
+  + tor                  x         Tor exit nodes                      https://fissionrelays.net/lists
+  + uceprotect1          x         Spam protection level 1             http://www.uceprotect.net/en/index.php
+  + uceprotect2                    Spam protection level 2             http://www.uceprotect.net/en/index.php
+  + voip                 x         VoIP fraud blocklist                http://www.voipbl.org
+  + yoyo                 x         Ad protection blacklist             https://pgl.yoyo.org/adservers/
+    ---------------------------------------------------------------------------
+  * Configured ASNs: -
+  * Configured Countries: af, bd, br, cn, hk, hu, id, il, in, iq, ir, kp, kr, no, pk, pl, ro, ru, sa, th, tr, ua, gb
+</code></pre>
+  
+**receive banIP runtime information:**
 
-**cronjob for a regular IPSet blocklist update (/etc/crontabs/root):**
+<pre><code>
+~# /etc/init.d/banip status
+::: banIP runtime information
+  + status          : enabled
+  + version         : 0.7.0
+  + ipset_info      : 23 IPSets with 302008 IPs/Prefixes
+  + active_sources  : blacklist, country, debl, doh, drop, dshield, feodo, firehol1, iblockspy, nixspam, sslbl, threat, 
+                      tor, uceprotect1, voip, whitelist, yoyo
+  + active_devs     : eth3
+  + active_ifaces   : wan, wan6
+  + active_logterms : dropbear, sshd, luci
+  + active_subnets  : xxx.xxx.x.xxx/24, xxxx:xxxx:xxxx:x:xxxx:xxxx:xxxx:xxxx/64
+  + run_infos       : settype: src+dst, backup_dir: /mnt/data/banip, report_dir: /tmp/banIP-Report
+  + run_flags       : protocols (4/6): ✔/✔, log (src/dst): ✔/✘, monitor: ✔, mail: ✔
+  + last_run        : refresh, 0m 16s, 4019/3527/3680, 03.02.2021 19:57:46
+  + system          : PC Engines apu4, OpenWrt SNAPSHOT r15556-20a0d435d8
+</code></pre>
+  
+**generate an IPSet report:**
 
-    # Every day at 06:00, update the IPSets of banIP
-    00 06 * * *    /etc/init.d/banip reload
+<pre><code>
+~# /etc/init.d/banip report
+:::
+::: report on all banIP related IPSets
+:::
+  + Report timestamp           ::: 04.02.2021 06:24:41
+  + Number of all IPSets       ::: 24
+  + Number of all entries      ::: 302448
+  + Number of IP entries       ::: 224748
+  + Number of CIDR entries     ::: 77700
+  + Number of MAC entries      ::: 0
+  + Number of accessed entries ::: 36
+:::
+::: IPSet details
+:::
+    Name                 Type        Count      Cnt_IP    Cnt_CIDR  Cnt_MAC   Cnt_ACC   Entry details (Entry/Count)
+    --------------------------------------------------------------------------------------------------------------------
+    whitelist_4          src+dst     1          0         1         0         1
+                                                                                        xxx.xxxx.xxx.xxxx/24     85
+    --------------------------------------------------------------------------------------------------------------------
+    whitelist_6          src+dst     2          0         2         0         1
+                                                                                        xxxx:xxxx:xxxx::/64      29
+    --------------------------------------------------------------------------------------------------------------------
+    blacklist_4          src+dst     513        513       0         0         2
+                                                                                        192.35.168.16            3
+                                                                                        80.82.65.74              1
+    --------------------------------------------------------------------------------------------------------------------
+    blacklist_6          src+dst     1          1         0         0         0
+    --------------------------------------------------------------------------------------------------------------------
+    country_4            src         52150      0         52150     0         23
+                                                                                        124.5.0.0/16             1
+                                                                                        95.188.0.0/14            1
+                                                                                        121.16.0.0/12            1
+                                                                                        46.161.0.0/18            1
+                                                                                        42.56.0.0/14             1
+                                                                                        113.64.0.0/10            1
+                                                                                        113.252.0.0/14           1
+                                                                                        5.201.128.0/17           1
+                                                                                        125.64.0.0/11            1
+                                                                                        90.188.0.0/15            1
+                                                                                        60.0.0.0/11              1
+                                                                                        78.160.0.0/11            1
+                                                                                        1.80.0.0/12              1
+                                                                                        183.184.0.0/13           1
+                                                                                        175.24.0.0/14            1
+                                                                                        119.176.0.0/12           1
+                                                                                        59.88.0.0/13             1
+                                                                                        103.78.12.0/22           1
+                                                                                        123.128.0.0/13           1
+                                                                                        116.224.0.0/12           1
+                                                                                        42.224.0.0/12            1
+                                                                                        82.80.0.0/15             1
+                                                                                        14.32.0.0/11             1
+    --------------------------------------------------------------------------------------------------------------------
+    country_6            src         20099      0         20099     0         0
+    --------------------------------------------------------------------------------------------------------------------
+    debl_4               src+dst     29389      29389     0         0         1
+                                                                                        5.182.210.16             4
+    --------------------------------------------------------------------------------------------------------------------
+    debl_6               src+dst     64         64        0         0         0
+    --------------------------------------------------------------------------------------------------------------------
+    doh_4                src+dst     168        168       0         0         0
+    --------------------------------------------------------------------------------------------------------------------
+    doh_6                src+dst     122        122       0         0         0
+    --------------------------------------------------------------------------------------------------------------------
+    drop_4               src+dst     965        0         965       0         0
+    --------------------------------------------------------------------------------------------------------------------
+    drop_6               src+dst     36         0         36        0         0
+    --------------------------------------------------------------------------------------------------------------------
+    dshield_4            src+dst     20         0         20        0         1
+                                                                                        89.248.165.0/24          1
+    --------------------------------------------------------------------------------------------------------------------
+    feodo_4              src+dst     325        325       0         0         0
+    --------------------------------------------------------------------------------------------------------------------
+    firehol1_4           src+dst     2763       403       2360      0         0
+    --------------------------------------------------------------------------------------------------------------------
+    iblockspy_4          src+dst     3650       2832      818       0         0
+    --------------------------------------------------------------------------------------------------------------------
+    nixspam_4            src+dst     9577       9577      0         0         0
+    --------------------------------------------------------------------------------------------------------------------
+    sslbl_4              src+dst     104        104       0         0         0
+    --------------------------------------------------------------------------------------------------------------------
+    threat_4             src+dst     1300       315       985       0         0
+    --------------------------------------------------------------------------------------------------------------------
+    tor_4                src+dst     1437       1437      0         0         0
+    --------------------------------------------------------------------------------------------------------------------
+    tor_6                src+dst     478        478       0         0         0
+    --------------------------------------------------------------------------------------------------------------------
+    uceprotect1_4        src+dst     156249     156249    0         0         6
+                                                                                        192.241.220.137          1
+                                                                                        128.14.137.178           1
+                                                                                        61.219.11.153            1
+                                                                                        138.34.32.33             1
+                                                                                        107.174.133.130          2
+                                                                                        180.232.99.46            1
+    --------------------------------------------------------------------------------------------------------------------
+    voip_4               src+dst     12563      12299     264       0         0
+    --------------------------------------------------------------------------------------------------------------------
+    yoyo_4               src+dst     10472      10472     0         0         1
+                                                                                        204.79.197.200           2
+    --------------------------------------------------------------------------------------------------------------------
+</code></pre>
+  
+**Enable E-Mail notification via 'msmtp':**  
+To use the email notification you have to install & configure the package 'msmtp'.  
+Modify the file '/etc/msmtprc', e.g.:
+<pre><code>
+[...]
+defaults
+auth            on
+tls             on
+tls_certcheck   off
+timeout         5
+syslog          LOG_MAIL
+[...]
+account         ban_notify
+host            smtp.gmail.com
+port            587
+from            <address>k@gmail.com
+user            <gmail-user>
+password        <password>
+</code></pre>
+Finally enable E-Mail support and add a valid E-Mail receiver address in LuCI.
+  
+**Edit, add new adblock sources:**  
+The banIP blocklist sources are stored in an external, compressed JSON file '/etc/banip/banip.sources.gz'. 
+This file is directly parsed in LuCI and accessible via CLI, just call _/etc/init.d/banip list_.
 
+To add new or edit existing sources extract the compressed JSON file _gunzip /etc/banip/banip.sources.gz_.  
+A valid JSON source object contains the following required information, e.g.:
+<pre><code>
+       [...]
+       "tor": {
+               "url_4": "https://lists.fissionrelays.net/tor/exits-ipv4.txt",
+               "url_6": "https://lists.fissionrelays.net/tor/exits-ipv6.txt",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add tor_4 \"$1}",
+               "rule_6": "/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\\/(1?[0-2][0-8]|[0-9][0-9]))?)([[:space:]]|$)/{print \"add tor_6 \"$1}",
+               "focus": "Tor exit nodes",
+               "descurl": "https://fissionrelays.net/lists"
+       },
+       [...]
+</code></pre>
+Add an unique object name, make the required changes to 'url_4', 'rule_4' (and/or 'url_6', 'rule_6'), 'focus' and 'descurl' and finally compress the changed JSON file _gzip /etc/banip/banip.sources.gz_ to use the new source object in banIP.  
+<b>Please note:</b> if you're going to add new sources on your own, please make a copy of the default file and work with that copy further on, cause the default will be overwritten with every banIP update. To reference your copy set the option 'ban\_srcarc' which points by default to '/etc/banip/banip.sources.gz'  
+  
 ## Support
 Please join the banIP discussion in this [forum thread](https://forum.openwrt.org/t/banip-support-thread/16985) or contact me by mail <dev@brenken.org>  
 
index d3d4fc14844a6f06b105159f08302d2063713776..faae505380f00277e9b7940eac2ead67f8084352 100644 (file)
-
 config banip 'global'
        option ban_enabled '0'
-       option ban_basever '0.3'
-       option ban_automatic '1'
-       option ban_realtime 'false'
-       option ban_log_src '0'
-       option ban_log_dst '0'
-
-config banip 'extra'
        option ban_debug '0'
+       option ban_mail_enabled '0'
+       option ban_monitor_enabled '0'
+       option ban_logsrc_enabled '0'
+       option ban_logdst_enabled '0'
+       option ban_autodetect '1'
+       option ban_autoblacklist '1'
+       option ban_autowhitelist '1'
+       option ban_nice '0'
        option ban_maxqueue '4'
-
-config source 'whitelist'
-       option ban_src '/etc/banip/banip.whitelist'
-       option ban_src_6 '/etc/banip/banip.whitelist'
-       option ban_src_desc 'Always allow these IPs (IPv4/IPv6)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]{1,2})?)([[:space:]]|$)/{print \"add whitelist \"\$1}'
-       option ban_src_rset_6 '/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}(:\/[0-9]{1,2})?([[:space:]]|$)/{print \"add whitelist_6 \"\$1}'
-       option ban_src_settype 'net'
-       option ban_src_ruletype 'src+dst'
-       option ban_src_on '1'
-       option ban_src_on_6 '0'
-
-config source 'blacklist'
-       option ban_src '/etc/banip/banip.blacklist'
-       option ban_src_6 '/etc/banip/banip.blacklist'
-       option ban_src_desc 'Always deny these IPs (IPv4/IPv6)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]{1,2})?)([[:space:]]|$)/{print \"add blacklist \"\$1}'
-       option ban_src_rset_6 '/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}(:\/[0-9]{1,2})?([[:space:]]|$)/{print \"add blacklist_6 \"\$1}'
-       option ban_src_settype 'net'
-       option ban_src_ruletype 'src+dst'
-       option ban_src_on '0'
-       option ban_src_on_6 '0'
-
-config source 'bogon'
-       option ban_src 'https://www.team-cymru.org/Services/Bogons/fullbogons-ipv4.txt'
-       option ban_src_6 'https://www.team-cymru.org/Services/Bogons/fullbogons-ipv6.txt'
-       option ban_src_desc 'Bogon prefixes, plus prefixes that have been allocated to RIRs but not yet assigned to ISPs (IPv4/IPv6)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]{1,2})?)([[:space:]]|$)/{print \"add bogon \"\$1}'
-       option ban_src_rset_6 '/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}(:\/[0-9]{1,2})?([[:space:]]|$)/{print \"add bogon_6 \"\$1}'
-       option ban_src_settype 'net'
-       option ban_src_ruletype 'src+dst'
-       option ban_src_on '0'
-       option ban_src_on_6 '0'
-
-config source 'DoH'
-       option ban_src 'https://raw.githubusercontent.com/dibdot/DoH-IP-blocklists/master/doh-ipv4.txt'
-       option ban_src_6 'https://raw.githubusercontent.com/dibdot/DoH-IP-blocklists/master/doh-ipv6.txt'
-       option ban_src_desc 'List of public DoH providers (DNS over HTTPS) (IPv4/IPv6)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]{1,2})?)([[:space:]]|$)/{print \"add DoH \"\$1}'
-       option ban_src_rset_6 '/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}(:\/[0-9]{1,2})?([[:space:]]|$)/{print \"add DoH_6 \"\$1}'
-       option ban_src_settype 'net'
-       option ban_src_ruletype 'src+dst'
-       option ban_src_on '0'
-       option ban_src_on_6 '0'
-
-config source 'tor'
-       option ban_src 'https://check.torproject.org/exit-addresses'
-       option ban_src_desc 'List of Tor Exit Nodes (IPv4)'
-       option ban_src_rset '/^(ExitAddress ([0-9]{1,3}\.){3}[0-9]{1,3})([[:space:]]|$)/{print \"add tor \"\$2}'
-       option ban_src_settype 'ip'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-       option ban_src_on_6 '0'
-
-config source 'threat'
-       option ban_src 'https://rules.emergingthreats.net/fwrules/emerging-Block-IPs.txt'
-       option ban_src_desc 'Emerging Threats (IPv4)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]{1,2})?)([[:space:]]|$)/{print \"add threat \"\$1}'
-       option ban_src_settype 'net'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-
-config source 'debl'
-       option ban_src 'https://www.blocklist.de/downloads/export-ips_all.txt'
-       option ban_src_6 'https://www.blocklist.de/downloads/export-ips_all.txt'
-       option ban_src_desc 'Fail2ban reporting service (IPv4/IPv6)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3})([[:space:]]|$)/{print \"add debl \"\$1}'
-       option ban_src_rset_6 '/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}(:\/[0-9]{1,2})?([[:space:]]|$)/{print \"add debl_6 \"\$1}'
-       option ban_src_settype 'ip'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-       option ban_src_on_6 '0'
-
-config source 'myip'
-       option ban_src 'https://www.myip.ms/files/blacklist/general/latest_blacklist.txt'
-       option ban_src_6 'https://www.myip.ms/files/blacklist/general/latest_blacklist.txt'
-       option ban_src_desc 'IP blacklist provided by myip.ms (IPv4/IPv6)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3})([[:space:]]|$)/{print \"add myip \"\$1}'
-       option ban_src_rset_6 '/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}(:\/[0-9]{1,2})?([[:space:]]|$)/{print \"add myip_6 \"\$1}'
-       option ban_src_settype 'ip'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-       option ban_src_on_6 '0'
-
-config source 'yoyo'
-       option ban_src 'https://pgl.yoyo.org/adservers/iplist.php?ipformat=plain&showintro=0&mimetype=plaintext'
-       option ban_src_desc 'IP blocklist provided by Peter Lowe (IPv4)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3})([[:space:]]|$)/{print \"add yoyo \"\$1}'
-       option ban_src_settype 'ip'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-
-config source 'sslbl'
-       option ban_src 'https://sslbl.abuse.ch/blacklist/sslipblacklist.csv'
-       option ban_src_desc 'SSL Blacklist by abuse.ch (IPv4)'
-       option ban_src_rset 'BEGIN{FS=\",\"}/(([0-9]{1,3}\.){3}[0-9]{1,3},).*/{print \"add sslbl \"\$2}'
-       option ban_src_settype 'ip'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-
-config source 'ransomware'
-       option ban_src 'https://ransomwaretracker.abuse.ch/downloads/RW_IPBL.txt'
-       option ban_src_desc 'Ransomware Tracker by abuse.ch (IPv4)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3})([[:space:]]|$)/{print \"add ransomware \"\$1}'
-       option ban_src_settype 'ip'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-
-config source 'feodo'
-       option ban_src 'https://feodotracker.abuse.ch/downloads/ipblocklist.txt'
-       option ban_src_desc 'Feodo Tracker by abuse.ch (IPv4)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3})([[:space:]]|$)/{print \"add feodo \"\$1}'
-       option ban_src_settype 'ip'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-
-config source 'dshield'
-       option ban_src 'https://feeds.dshield.org/block.txt'
-       option ban_src_desc 'Dshield recommended IP blocklist. Contains top 20 attacking class C subnets (IPv4)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3})([[:space:]]|$)/{print \"add dshield \"\$1 \"/\"\$3}'
-       option ban_src_settype 'net'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-
-config source 'proxy'
-       option ban_src 'https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/proxylists.ipset'
-       option ban_src_desc 'List of Open Proxies (IPv4)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3})([[:space:]]|$)/{print \"add proxy \"\$1}'
-       option ban_src_settype 'ip'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-
-config source 'iblocklist'
-       option ban_src 'https://list.iblocklist.com/?list=dgxtneitpuvgqqcpfulq&fileformat=cidr&archiveformat=gz'
-       option ban_src_desc 'Contains advertising trackers and a short list of bad/intrusive porn sites (IPv4)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]{1,2})?)([[:space:]]|$)/{print \"add iblocklist \"\$1}'
-       option ban_src_settype 'net'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-
-config source 'drop'
-       option ban_src 'https://www.spamhaus.org/drop/drop.txt'
-       option ban_src_6 'https://www.spamhaus.org/drop/dropv6.txt'
-       option ban_src_desc 'Spamhaus drop compilation (IPv4/IPv6)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]{1,2})?)([[:space:]]|$)/{print \"add drop \"\$1}'
-       option ban_src_rset_6 '/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}(:\/[0-9]{1,2})?([[:space:]]|$)/{print \"add drop_6 \"\$1}'
-       option ban_src_settype 'net'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-       option ban_src_on_6 '0'
-
-config source 'edrop'
-       option ban_src 'https://www.spamhaus.org/drop/edrop.txt'
-       option ban_src_desc 'Spamhaus edrop compilation (IPv4)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]{1,2})?)([[:space:]]|$)/{print \"add edrop \"\$1}'
-       option ban_src_settype 'net'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-
-config source 'firehol1'
-       option ban_src 'https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level1.netset'
-       option ban_src_desc 'Firehol Level 1 compilation. Contains bogons, spamhaus drop and edrop, dshield and malware lists (IPv4)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]{1,2})?)([[:space:]]|$)/{print \"add firehol1 \"\$1}'
-       option ban_src_settype 'net'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-
-config source 'firehol2'
-       option ban_src 'https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level2.netset'
-       option ban_src_desc 'Firehol Level 2 compilation. Contains blocklists that track attacks, during the last 48 hours (IPv4)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]{1,2})?)([[:space:]]|$)/{print \"add firehol2 \"\$1}'
-       option ban_src_settype 'net'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-
-config source 'firehol3'
-       option ban_src 'https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level3.netset'
-       option ban_src_desc 'Firehol Level 3 compilation. Contains blocklists that track attacks, spyware and viruses (IPv4)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]{1,2})?)([[:space:]]|$)/{print \"add firehol3 \"\$1}'
-       option ban_src_settype 'net'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-
-config source 'firehol4'
-       option ban_src 'https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level4.netset'
-       option ban_src_desc 'Firehol Level 4 compilation. May include a large number of false positives (IPv4)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]{1,2})?)([[:space:]]|$)/{print \"add firehol4 \"\$1}'
-       option ban_src_settype 'net'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-
-config source 'country'
-       option ban_src 'https://stat.ripe.net/data/country-resource-list/data.json?resource='
-       option ban_src_6 'https://stat.ripe.net/data/country-resource-list/data.json?resource='
-       option ban_src_desc 'Build a dynamic IPSet by country iso codes based on RIPE data (IPv4/IPv6)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]{1,2})?)([[:space:]]|$)/{print \"add country \"\$1}'
-       option ban_src_rset_6 '/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}(:\/[0-9]{1,2})?([[:space:]]|$)/{print \"add country_6 \"\$1}'
-       list ban_src_cat 'de'
-       option ban_src_settype 'net'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-       option ban_src_on_6 '0'
-
-config source 'asn'
-       option ban_src 'https://stat.ripe.net/data/announced-prefixes/data.json?resource='
-       option ban_src_6 'https://stat.ripe.net/data/announced-prefixes/data.json?resource='
-       option ban_src_desc 'Build a dynamic IPSet by ASN numbers based on RIPE data (IPv4/IPv6)'
-       option ban_src_rset '/^(([0-9]{1,3}\.){3}[0-9]{1,3}(\/[0-9]{1,2})?)([[:space:]]|$)/{print \"add asn \"\$1}'
-       option ban_src_rset_6 '/^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}(:\/[0-9]{1,2})?([[:space:]]|$)/{print \"add asn_6 \"\$1}'
-       list ban_src_cat '32934'
-       option ban_src_settype 'net'
-       option ban_src_ruletype 'src'
-       option ban_src_on '0'
-       option ban_src_on_6 '0'
+       option ban_global_settype 'src+dst'
+       option ban_target_src 'DROP'
+       option ban_target_dst 'REJECT'
+       option ban_loglimit '100'
diff --git a/net/banip/files/banip.countries b/net/banip/files/banip.countries
new file mode 100644 (file)
index 0000000..92aa6c1
--- /dev/null
@@ -0,0 +1,249 @@
+tw;Taiwan
+af;Afghanistan
+al;Albania
+dz;Algeria
+as;American Samoa
+ad;Andorra
+ao;Angola
+ai;Anguilla
+aq;Antarctica
+ag;Antigua & Barbuda
+ar;Argentina
+am;Armenia
+aw;Aruba
+au;Australia
+at;Austria
+az;Azerbaijan
+bs;Bahamas
+bh;Bahrain
+bd;Bangladesh
+bb;Barbados
+by;Belarus
+be;Belgium
+bz;Belize
+bj;Benin
+bm;Bermuda
+bt;Bhutan
+bo;Bolivia
+bq;Caribbean Netherlands
+ba;Bosnia
+bw;Botswana
+bv;Bouvet Island
+br;Brazil
+io;British Indian Ocean Territory
+vg;British Virgin Islands
+bn;Brunei
+bg;Bulgaria
+bf;Burkina Faso
+bi;Burundi
+cv;Cape Verde
+kh;Cambodia
+cm;Cameroon
+ca;Canada
+ky;Cayman Islands
+cf;Central African Republic
+td;Chad
+cl;Chile
+cn;China
+hk;Hong Kong
+mo;Macau
+cx;Christmas Island
+cc;Cocos (Keeling) Islands
+co;Colombia
+km;Comoros
+cg;Congo - Brazzaville
+ck;Cook Islands
+cr;Costa Rica
+hr;Croatia
+cu;Cuba
+cw;Curaçao
+cy;Cyprus
+cz;Czechia
+ci;Côte d’Ivoire
+kp;North Korea
+cd;Congo - Kinshasa
+dk;Denmark
+dj;Djibouti
+dm;Dominica
+do;Dominican Republic
+ec;Ecuador
+eg;Egypt
+sv;El Salvador
+gq;Equatorial Guinea
+er;Eritrea
+ee;Estonia
+sz;Eswatini
+et;Ethiopia
+fk;Falkland Islands
+fo;Faroe Islands
+fj;Fiji
+fi;Finland
+fr;France
+gf;French Guiana
+pf;French Polynesia
+tf;French Southern Territories
+ga;Gabon
+gm;Gambia
+ge;Georgia
+de;Germany
+gh;Ghana
+gi;Gibraltar
+gr;Greece
+gl;Greenland
+gd;Grenada
+gp;Guadeloupe
+gu;Guam
+gt;Guatemala
+gg;Guernsey
+gn;Guinea
+gw;Guinea-Bissau
+gy;Guyana
+ht;Haiti
+hm;Heard & McDonald Islands
+va;Vatican City
+hn;Honduras
+hu;Hungary
+is;Iceland
+in;India
+id;Indonesia
+ir;Iran
+iq;Iraq
+ie;Ireland
+im;Isle of Man
+il;Israel
+it;Italy
+jm;Jamaica
+jp;Japan
+je;Jersey
+jo;Jordan
+kz;Kazakhstan
+ke;Kenya
+ki;Kiribati
+kw;Kuwait
+kg;Kyrgyzstan
+la;Laos
+lv;Latvia
+lb;Lebanon
+ls;Lesotho
+lr;Liberia
+ly;Libya
+li;Liechtenstein
+lt;Lithuania
+lu;Luxembourg
+mg;Madagascar
+mw;Malawi
+my;Malaysia
+mv;Maldives
+ml;Mali
+mt;Malta
+mh;Marshall Islands
+mq;Martinique
+mr;Mauritania
+mu;Mauritius
+yt;Mayotte
+mx;Mexico
+fm;Micronesia
+mc;Monaco
+mn;Mongolia
+me;Montenegro
+ms;Montserrat
+ma;Morocco
+mz;Mozambique
+mm;Myanmar
+na;Namibia
+nr;Nauru
+np;Nepal
+nl;Netherlands
+nc;New Caledonia
+nz;New Zealand
+ni;Nicaragua
+ne;Niger
+ng;Nigeria
+nu;Niue
+nf;Norfolk Island
+mp;Northern Mariana Islands
+no;Norway
+om;Oman
+pk;Pakistan
+pw;Palau
+pa;Panama
+pg;Papua New Guinea
+py;Paraguay
+pe;Peru
+ph;Philippines
+pn;Pitcairn Islands
+pl;Poland
+pt;Portugal
+pr;Puerto Rico
+qa;Qatar
+kr;South Korea
+md;Moldova
+ro;Romania
+ru;Russia
+rw;Rwanda
+re;Réunion
+bl;St. Barthélemy
+sh;St. Helena
+kn;St. Kitts & Nevis
+lc;St. Lucia
+mf;St. Martin
+pm;St. Pierre & Miquelon
+vc;St. Vincent & Grenadines
+ws;Samoa
+sm;San Marino
+st;São Tomé & Príncipe
+sa;Saudi Arabia
+sn;Senegal
+rs;Serbia
+sc;Seychelles
+sl;Sierra Leone
+sg;Singapore
+sx;Sint Maarten
+sk;Slovakia
+si;Slovenia
+sb;Solomon Islands
+so;Somalia
+za;South Africa
+gs;South Georgia & South Sandwich Islands
+ss;South Sudan
+es;Spain
+lk;Sri Lanka
+ps;Palestine
+sd;Sudan
+sr;Suriname
+sj;Svalbard & Jan Mayen
+se;Sweden
+ch;Switzerland
+sy;Syria
+tj;Tajikistan
+th;Thailand
+mk;North Macedonia
+tl;Timor-Leste
+tg;Togo
+tk;Tokelau
+to;Tonga
+tt;Trinidad & Tobago
+tn;Tunisia
+tr;Turkey
+tm;Turkmenistan
+tc;Turks & Caicos Islands
+tv;Tuvalu
+ug;Uganda
+ua;Ukraine
+ae;United Arab Emirates
+gb;United Kingdom
+tz;Tanzania
+um;U.S. Outlying Islands
+vi;U.S. Virgin Islands
+us;United States
+uy;Uruguay
+uz;Uzbekistan
+vu;Vanuatu
+ve;Venezuela
+vn;Vietnam
+wf;Wallis & Futuna
+eh;Western Sahara
+ye;Yemen
+zm;Zambia
+zw;Zimbabwe
+ax;Åland Islands
index 7807e7c46cb50f70278a832a14f7bf92f22c7fbe..9b9469ebfe1f8ab5e3a131f1e469c4afe6b8b21e 100644 (file)
@@ -1,15 +1,14 @@
 #!/bin/sh
 #
-[ "${ACTION}" != "add" ] && exit 0
-
-ban_iface="wan"
-[ -r "/lib/functions/network.sh" ] && { . "/lib/functions/network.sh"; network_find_wan ban_iface; }
-[ "${INTERFACE}" != "${ban_iface}" ] && exit 0
-
 ban_pidfile="/var/run/banip.pid"
 ban_enabled="$(/etc/init.d/banip enabled; printf "%u" "${?}")"
-if [ "${ban_enabled}" = "0" ] && [ ! -s "${ban_pidfile}" ]
+
+if [ "${ban_enabled}" = "0" ] && [ "${ACTION}" = "add" ] && [ -n "${INTERFACE}" ]
 then
-       /etc/init.d/banip refresh
+       ban_ifaces="$(uci_get banip global ban_ifaces)"
+       if [ ! -s "${ban_pidfile}" ] && [ -n "$(printf "%s\n" "${ban_ifaces}" | grep -F "${INTERFACE}")" ]
+       then
+               /etc/init.d/banip refresh
+       fi
 fi
 exit 0
index 034050d5891400b420bd51126b0a705366860a23..47f274daa858a01cfcc1b17630f2659f0abb6f33 100755 (executable)
@@ -1,30 +1,58 @@
 #!/bin/sh /etc/rc.common
+# written by Dirk Brenken (dev@brenken.org)
 #
+# This is free software, licensed under the GNU General Public License v3.
+#
+# (s)hellcheck exceptions
+# shellcheck disable=1091,2030,2031,2034,2039,2086,2129,2140,2143,2154,2181,2183,2188
 
 START=30
 USE_PROCD=1
 
-extra_command "refresh" "Refresh ipsets without new list downloads"
+if [ -n "$(type -t extra_command)" ]
+then
+       extra_command "refresh" "Refresh ipsets without new list downloads"
+       extra_command "suspend" "Suspend banIP processing"
+       extra_command "resume" "Resume banIP processing"
+       extra_command "query" "<IP> Query active banIP IPSets for a specific IP address"
+       extra_command "report" "[<cli>|<mail>|<gen>|<json>] Print banIP related IPset statistics"
+       extra_command "list" "[<add>|<add_asn>|<add_country>|<remove>|<remove_asn>|<remove_country>] <source(s)> List/Edit available sources"
+       extra_command "timer" "[<add> <tasks> <hour> [<minute>] [<weekday>]]|[<remove> <line no.>] List/Edit cron update intervals"
+       extra_command "version" "Print version information"
+else
+       EXTRA_COMMANDS="status refresh suspend resume query report list timer version"
+       EXTRA_HELP="    status  Service status
+       refresh Refresh ipsets without new list downloads
+       suspend Suspend banIP processing
+       resume  Resume banIP processing
+       query   <IP> Query active banIP IPSets for a specific IP address
+       report  [<cli>|<mail>|<gen>|<json>] Print banIP related IPset statistics
+       list    [<add>|<add_asn>|<add_country>|<remove>|<remove_asn>|<remove_country>] <source(s)> List/Edit available sources
+       timer   [<add> <tasks> <hour> [<minute>] [<weekday>]]|[<remove> <line no.>] List/Edit cron update intervals
+       version Print version information"
+fi
 
 ban_init="/etc/init.d/banip"
 ban_script="/usr/bin/banip.sh"
 ban_pidfile="/var/run/banip.pid"
 
 if [ -s "${ban_pidfile}" ] && { [ "${action}" = "start" ] || [ "${action}" = "stop" ] || \
-       [ "${action}" = "restart" ] || [ "${action}" = "reload" ] || [ "${action}" = "refresh" ]; }
+       [ "${action}" = "restart" ] || [ "${action}" = "reload" ] || [ "${action}" = "refresh" ] || \
+       [ "${action}" = "suspend" ] || [ "${action}" = "resume" ] || [ "${action}" = "query" ] || \
+       { [ "${action}" = "list" ] && [ -n "${1}" ]; } || { [ "${action}" = "report" ] && [ "${1}" != "json" ]; }; }
 then
        exit 0
 fi
 
 boot()
 {
-       [ -s "${ban_pidfile}" ] && > "${ban_pidfile}"
+       > "${ban_pidfile}"
        rc_procd start_service
 }
 
 start_service()
 {
-       if [ "$("${ban_init}" enabled; printf "%u" ${?})" -eq 0 ]
+       if [ "$("${ban_init}" enabled; printf "%u" ${?})" = "0" ]
        then
                if [ "${action}" = "boot" ]
                then
@@ -33,13 +61,18 @@ start_service()
                procd_open_instance "banip"
                procd_set_param command "${ban_script}" "${@}"
                procd_set_param pidfile "${ban_pidfile}"
-               procd_set_param nice "$(uci_get banip extra ban_nice "0")"
+               procd_set_param nice "$(uci_get banip global ban_nice "0")"
                procd_set_param stdout 1
                procd_set_param stderr 1
                procd_close_instance
        fi
 }
 
+version()
+{
+       rc_procd "${ban_script}" version
+}
+
 refresh()
 {
        rc_procd start_service refresh
@@ -60,49 +93,254 @@ restart()
        rc_procd start_service restart
 }
 
+suspend()
+{
+       rc_procd start_service suspend
+}
+
+resume()
+{
+       rc_procd start_service resume
+}
+
+query()
+{
+       rc_procd "${ban_script}" query "${1}"
+}
+
+list()
+{
+       local src_archive src_file src_enabled key name enabled focus url_4 rule_4 url_6 rule_6 action="${1}"
+
+       if [ "${action%_*}" = "add" ] || [ "${action%_*}" = "remove" ]
+       then
+               shift
+               for name in "${@}"
+               do
+                       case "${action}" in
+                               "add")
+                                       if [ -z "$(uci_get banip global ban_sources | grep -Fo "${name}")" ]
+                                       then
+                                               uci_add_list banip global ban_sources "${name}"
+                                               printf "%s\n" "::: banIP source '${name}' added to config"
+                                       fi
+                               ;;
+                               "remove")
+                                       if [ -n "$(uci_get banip global ban_sources | grep -Fo "${name}")" ]
+                                       then
+                                               uci_remove_list banip global ban_sources "${name}"
+                                               printf "%s\n" "::: banIP source '${name}' removed from config"
+                                       fi
+                               ;;
+                               "add_asn")
+                                       if [ -z "$(uci_get banip global ban_asns | grep -Fo "${name}")" ]
+                                       then
+                                               uci_add_list banip global ban_asns "${name}"
+                                               printf "%s\n" "::: banIP asn '${name}' added to config"
+                                       fi
+                               ;;
+                               "remove_asn")
+                                       if [ -n "$(uci_get banip global ban_asns | grep -Fo "${name}")" ]
+                                       then
+                                               uci_remove_list banip global ban_asns "${name}"
+                                               printf "%s\n" "::: banIP asn '${name}' removed from config"
+                                       fi
+                               ;;
+                               "add_country")
+                                       if [ -z "$(uci_get banip global ban_countries | grep -Fo "${name}")" ]
+                                       then
+                                               uci_add_list banip global ban_countries "${name}"
+                                               printf "%s\n" "::: banIP country '${name}' added to config"
+                                       fi
+                               ;;
+                               "remove_country")
+                                       if [ -n "$(uci_get banip global ban_countries | grep -Fo "${name}")" ]
+                                       then
+                                               uci_remove_list banip global ban_countries "${name}"
+                                               printf "%s\n" "::: banIP country '${name}' removed from config"
+                                       fi
+                               ;;
+                       esac
+               done
+               if [ -n "$(uci -q changes banip)" ]
+               then
+                       uci_commit banip
+                       "${ban_init}" start
+               fi
+       else
+               src_archive="$(uci_get banip global ban_srcarc "/etc/banip/banip.sources.gz")"
+               src_file="$(uci_get banip global ban_srcfile "/tmp/ban_sources.json")"
+               src_enabled="$(uci -q show banip.global.ban_sources)"
+               if [ -r "${src_archive}" ]
+               then
+                       zcat "${src_archive}" > "${src_file}"
+               else
+                       printf "%s\n" "::: banIP source archive '${src_archive}' not found"
+               fi
+               if [ -r "${src_file}" ]
+               then
+                       src_enabled="${src_enabled#*=}"
+                       src_enabled="${src_enabled//\'}"
+                       printf "%s\n" "::: Available banIP sources"
+                       printf "%s\n" ":::"
+                       printf "%-25s%-10s%-36s%s\n" "    Name" "Enabled" "Focus" "Info URL"
+                       printf "%s\n" "    ---------------------------------------------------------------------------"
+                       json_load_file "${src_file}"
+                       json_get_keys keylist
+                       for key in ${keylist}
+                       do
+                               json_select "${key}"
+                               json_get_var focus "focus"
+                               json_get_var descurl "descurl"
+                               json_get_var url_4 "url_4"
+                               json_get_var rule_4 "rule_4"
+                               json_get_var url_6 "url_6"
+                               json_get_var rule_6 "rule_6"
+                               if { [ -n "${url_4}" ] && [ -n "${rule_4}" ]; } || { [ -n "${url_6}" ] && [ -n "${rule_6}" ]; }
+                               then
+                                       if [ -n "$(printf "%s" "${src_enabled}" | grep -Fo "${key}")" ]
+                                       then
+                                               enabled="x"
+                                       else
+                                               enabled=" "
+                                       fi
+                                       src_enabled="${src_enabled/${key}}"
+                                       printf "  + %-21s%-10s%-36s%s\n" "${key:0:20}" "${enabled}" "${focus:0:35}" "${descurl:0:50}"
+                               else
+                                       src_enabled="${src_enabled} ${key}"
+                               fi
+                               json_select ..
+                       done
+                       asn_list="$(uci_get banip global ban_asns "-")"
+                       country_list="$(uci_get banip global ban_countries "-")"
+                       printf "%s\n" "    ---------------------------------------------------------------------------"
+                       printf "  * %s\n" "Configured ASNs: ${asn_list// /, }"
+                       printf "  * %s\n" "Configured Countries: ${country_list// /, }"
+
+                       if [ -n "${src_enabled// }" ]
+                       then
+                               printf "%s\n" "    ---------------------------------------------------------------------------"
+                               printf "%s\n" "    Sources without valid configuration"
+                               printf "%s\n" "    ---------------------------------------------------------------------------"
+                               for key in ${src_enabled}
+                               do
+                                       printf "  - %s\n" "${key:0:20}"
+                               done
+                       fi
+               else
+                       printf "%s\n" "::: banIP source file '${src_file}' not found"
+               fi
+       fi
+}
+
+status()
+{
+       status_service
+}
+
 status_service()
 {
-       local key keylist value
-       local rtfile="$(uci_get banip global ban_rtfile "/tmp/ban_runtime.json")"
+       local key keylist value index_value values rtfile
+
+       rtfile="$(uci_get banip global ban_rtfile "/tmp/ban_runtime.json")"
 
        json_load_file "${rtfile}" >/dev/null 2>&1
-       json_select data >/dev/null 2>&1
-       if [ "${?}" -eq 0 ]
+       json_get_keys keylist
+       if [ -n "${keylist}" ]
        then
-               printf "%s\\n" "::: banIP runtime information"
-               json_get_keys keylist
+               printf "%s\n" "::: banIP runtime information"
                for key in ${keylist}
                do
-                       json_get_var value "${key}"
-                       printf "  + %-10s : %s\\n" "${key}" "${value}"
+                       json_get_var value "${key}" >/dev/null 2>&1
+                       if [ "${key%_*}" = "active" ]
+                       then
+                               printf "  + %-15s : " "${key}"
+                               json_select "${key}" >/dev/null 2>&1
+                               values=""
+                               index=1
+                               while json_get_type type "${index}" && [ "${type}" = "object" ]
+                               do
+                                       json_get_values index_value "${index}" >/dev/null 2>&1
+                                       if [ "${index}" = "1" ]
+                                       then
+                                               values="${index_value}"
+                                       else
+                                               values="${values}, ${index_value}"
+                                       fi
+                                       index=$((index+1))
+                               done
+                               values="$(printf "%s" "${values}" | awk '{NR=1;max=98;if(length($0)>max+1)while($0){if(NR==1){print substr($0,1,max)}else{printf"%-22s%s\n","",substr($0,1,max)}{$0=substr($0,max+1);NR=NR+1}}else print}')"
+                               printf "%s\n" "${values:-"-"}"
+                               json_select ".."
+                       else
+                               printf "  + %-15s : %s\n" "${key}" "${value:-"-"}"
+                       fi
                done
        else
-               printf "%s\\n" "::: no banIP runtime information available"
+               printf "%s\n" "::: no banIP runtime information available"
+       fi
+}
+
+report()
+{
+       rc_procd "${ban_script}" report "${1:-"cli"}"
+}
+
+timer()
+{
+       local cron_file cron_content cron_lineno action="${1:-"list"}" cron_tasks="${2}" hour="${3}" minute="${4:-0}" weekday="${5:-"*"}"
+
+       cron_file="/etc/crontabs/root"
+
+       if [ -s "${cron_file}" ] && [ "${action}" = "list" ]
+       then
+               awk '{print NR ">  " $0}' "${cron_file}"
+       elif [ "${action}" = "add" ]
+       then
+               hour="${hour//[[:alpha:]]/}"
+               minute="${minute//[[:alpha:]]/}"
+               if [ -n "${cron_tasks}" ] && [ -n "${hour}" ] && [ -n "${minute}" ] && [ -n "${weekday}" ] && \
+                       [ "${hour}" -ge 0 ] && [ "${hour}" -le 23 ] && \
+                       [ "${minute}" -ge 0 ] && [ "${minute}" -le 59 ]
+               then
+                       printf "%02d %02d %s\n" "${minute}" "${hour}" "* * ${weekday} ${ban_init} ${cron_tasks}" >> "${cron_file}"
+                       /etc/init.d/cron restart
+               fi
+       elif [ -s "${cron_file}" ] && [ "${action}" = "remove" ]
+       then
+               cron_tasks="${cron_tasks//[[:alpha:]]/}"
+               cron_lineno="$(awk 'END{print NR}' "${cron_file}")"
+               cron_content="$(awk '{print $0}' "${cron_file}")"
+               if [ "${cron_tasks:-"0"}" -le "${cron_lineno:-"1"}" ] && [ -n "${cron_content}" ]
+               then
+                       printf "%s\n" "${cron_content}" | awk "NR!~/^${cron_tasks}$/" > "${cron_file}"
+                       /etc/init.d/cron restart
+               fi
        fi
 }
 
 service_triggers()
 {
-       local trigger trigger_list="$(uci_get banip global ban_trigger)"
-       local delay="$(uci_get banip extra ban_triggerdelay "2")"
-       local type="$(uci_get banip extra ban_starttype "start")"
+       local iface delay
 
-       PROCD_RELOAD_DELAY=$((${delay}*1000))
+       iface="$(uci_get banip global ban_trigger)"
+       delay="$(uci_get banip global ban_triggerdelay "5")"
+       PROCD_RELOAD_DELAY=$((delay*1000))
 
-       if [ -z "${trigger_list}" ] && [ -r "/lib/functions/network.sh" ]
+       if [ -z "${iface}" ]
        then
                . "/lib/functions/network.sh"
-               network_find_wan trigger_list
+               network_find_wan iface
+               if [ -n "${iface}" ]
+               then
+                       uci_set banip global ban_trigger "${iface}"
+                       uci_commit "banip"
+               fi
        fi
-
-       if [ -n "${trigger_list}" ]
+       if [ -n "${iface}" ]
        then
-               for trigger in ${trigger_list}
-               do
-                       procd_add_interface_trigger "interface.*.up" "${trigger}" "${ban_init}" "${type}"
-               done
-       else
-               procd_add_raw_trigger "interface.*.up" ${PROCD_RELOAD_DELAY} "${ban_init}" "${type}"
+               
+               procd_add_interface_trigger "interface.*.up" "${iface}" "${ban_init}" "start"
        fi
        procd_add_reload_trigger "banip"
 }
diff --git a/net/banip/files/banip.maclist b/net/banip/files/banip.maclist
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/net/banip/files/banip.mail b/net/banip/files/banip.mail
new file mode 100755 (executable)
index 0000000..0b63975
--- /dev/null
@@ -0,0 +1,84 @@
+#!/bin/sh
+# send mail script for banIP notifications
+# written by Dirk Brenken (dev@brenken.org)
+#
+# This is free software, licensed under the GNU General Public License v3.
+#
+# (s)hellcheck exceptions
+# shellcheck disable=1091,2030,2031,2034,2039,2086,2129,2140,2143,2154,2181,2183,2188
+
+export LC_ALL=C
+export PATH="/usr/sbin:/usr/bin:/sbin:/bin"
+set -o pipefail
+
+if [ -r "/lib/functions.sh" ]
+then
+       . "/lib/functions.sh"
+       ban_debug="$(uci_get banip global ban_debug "0")"
+       ban_loglimit="$(uci_get banip global ban_loglimit "100")"
+       ban_mailsender="$(uci_get banip global ban_mailsender "no-reply@banIP")"
+       ban_mailreceiver="$(uci_get banip global ban_mailreceiver)"
+       ban_mailtopic="$(uci_get banip global ban_mailtopic "banIP notification")"
+       ban_mailprofile="$(uci_get banip global ban_mailprofile "ban_notify")"
+fi
+ban_ver="${1}"
+ban_mail="$(command -v msmtp)"
+ban_logger="$(command -v logger)"
+ban_logread="$(command -v logread)"
+ban_rc=1
+
+f_log()
+{
+       local class="${1}" log_msg="${2}"
+
+       if [ -x "${ban_logger}" ]
+       then
+               "${ban_logger}" -p "${class}" -t "banIP-${ban_ver%-*}[${$}]" "${log_msg}"
+       else
+               printf "%s %s %s\n" "${class}" "banIP-${ban_ver%-*}[${$}]" "${log_msg}"
+       fi
+}
+
+if [ -z "${ban_mailreceiver}" ]
+then
+       f_log "err" "please set the mail receiver with the 'ban_mailreceiver' option"
+       exit ${ban_rc}
+fi
+
+if [ "${ban_debug}" = "1" ]
+then
+       msmtp_debug="--debug"
+fi
+
+ban_mailhead="From: ${ban_mailsender}\nTo: ${ban_mailreceiver}\nSubject: ${ban_mailtopic}\nReply-to: ${ban_mailsender}\nMime-Version: 1.0\nContent-Type: text/html;charset=utf-8\nContent-Disposition: inline\n\n"
+
+# info preparation
+#
+sys_info="$(strings /etc/banner 2>/dev/null)"
+ban_info="$(/etc/init.d/banip "status" 2>/dev/null)"
+rep_info="${2}"
+log_info="$("${ban_logread}" -l "${ban_loglimit}" -e "banIP-" 2>/dev/null | awk '{NR=1;max=120;if(length($0)>max+1)while($0){if(NR==1){print substr($0,1,max)}else{print substr($0,1,max)}{$0=substr($0,max+1);NR=NR+1}}else print}')"
+
+# mail body
+#
+ban_mailtext="<html><body><pre style='display:block;font-family:monospace;font-size:1rem;padding:20;background-color:#f3eee5;white-space:pre'>"
+ban_mailtext="${ban_mailtext}\n<strong>++\n++ System Information ++\n++</strong>\n${sys_info}"
+ban_mailtext="${ban_mailtext}\n\n<strong>++\n++ banIP Status ++\n++</strong>\n${ban_info}"
+if [ -n "${rep_info}" ]
+then
+       ban_mailtext="${ban_mailtext}\n\n<strong>++\n++ banIP Report ++\n++</strong>\n${rep_info}"
+fi
+ban_mailtext="${ban_mailtext}\n\n<strong>++\n++ Logfile Information ++\n++</strong>\n${log_info}"
+ban_mailtext="${ban_mailtext}</pre></body></html>"
+
+# send mail
+#
+if [ -x "${ban_mail}" ]
+then
+       printf "%b" "${ban_mailhead}${ban_mailtext}" 2>/dev/null | "${ban_mail}" ${msmtp_debug} -a "${ban_mailprofile}" "${ban_mailreceiver}" >/dev/null 2>&1
+       ban_rc=${?}
+       f_log "info" "mail sent to '${ban_mailreceiver}' with rc '${ban_rc}'"
+else
+       f_log "err" "msmtp mail daemon not found"
+fi
+exit ${ban_rc}
index eab59cc3397a950d989968eeef97bd1c1cde9dc6..d41c8a28da22216bb609803404d1b29f765cae21 100755 (executable)
@@ -1,15 +1,17 @@
 #!/bin/sh
 # log service to trace failed ssh/luci logins and conditionally refresh banIP
 # written by Dirk Brenken (dev@brenken.org)
-
+#
 # This is free software, licensed under the GNU General Public License v3.
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# (s)hellcheck exceptions
+# shellcheck disable=1091,2030,2031,2034,2039,2086,2129,2140,2143,2154,2181,2183,2188
 
-LC_ALL=C
-PATH="/usr/sbin:/usr/bin:/sbin:/bin"
+export LC_ALL=C
+export PATH="/usr/sbin:/usr/bin:/sbin:/bin"
+set -o pipefail
 ban_ver="${1}"
-ban_sshdaemon="${2}"
+ban_search="${2}"
 ban_logger="$(command -v logger)"
 ban_logread="$(command -v logread)"
 
@@ -19,17 +21,16 @@ f_log()
 
        if [ -x "${ban_logger}" ]
        then
-               "${ban_logger}" -p "${class}" -t "banIP-${ban_ver}[${$}]" "${log_msg}"
+               "${ban_logger}" -p "${class}" -t "banIP-${ban_ver%-*}[${$}]" "${log_msg}"
        else
-               printf "%s %s %s\\n" "${class}" "banIP-${ban_ver}[${$}]" "${log_msg}"
+               printf "%s %s %s\n" "${class}" "banIP-${ban_ver%-*}[${$}]" "${log_msg}"
        fi
 }
 
 if [ -x "${ban_logread}" ]
 then
        f_log "info" "log/banIP service started"
-       "${ban_logread}" -f -e "${ban_sshdaemon}\|luci: failed login" | \
-               { grep -q "Exit before auth\|luci: failed login\|error: maximum authentication attempts exceeded"; [ $? -eq 0 ] && /etc/init.d/banip refresh; }
+       "${ban_logread}" -f | { grep -q "${ban_search}"; [ "${?}" = "0" ] && /etc/init.d/banip refresh; }
 else
        f_log "err" "can't start log/banIP service"
 fi
index 14cc1f5f223780ad23e08c3eaf00bd746f0076cd..34cd90eae90f76515d65fc7db8ab38b81e631f00 100755 (executable)
 #!/bin/sh
 # banIP - ban incoming and outgoing ip adresses/subnets via ipset
 # written by Dirk Brenken (dev@brenken.org)
-
+#
 # This is free software, licensed under the GNU General Public License v3.
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
+#
 # (s)hellcheck exceptions
-# shellcheck disable=1091,2039,2086,2140,2143,2181,2188
+# shellcheck disable=1091,2030,2031,2034,2039,2086,2129,2140,2143,2154,2181,2183,2188
 
 # set initial defaults
 #
-LC_ALL=C
-PATH="/usr/sbin:/usr/bin:/sbin:/bin"
-ban_ver="0.3.13"
-ban_basever=""
-ban_enabled=0
-ban_automatic="1"
-ban_sources=""
-ban_iface=""
-ban_debug=0
-ban_backupdir="/mnt"
-ban_maxqueue=4
-ban_autoblacklist=1
-ban_autowhitelist=1
-ban_realtime="false"
+export LC_ALL=C
+export PATH="/usr/sbin:/usr/bin:/sbin:/bin"
+set -o pipefail
+ban_ver="0.7.0"
+ban_enabled="0"
+ban_mail_enabled="0"
+ban_proto4_enabled="0"
+ban_proto6_enabled="0"
+ban_logsrc_enabled="0"
+ban_logdst_enabled="0"
+ban_monitor_enabled="0"
+ban_autodetect="1"
+ban_autoblacklist="1"
+ban_autowhitelist="1"
+ban_logterms=""
+ban_loglimit="100"
+ban_mailactions=""
+ban_search=""
+ban_devs=""
+ban_ifaces=""
+ban_debug="0"
+ban_maxqueue="4"
 ban_fetchutil=""
-ban_ipt="$(command -v iptables)"
-ban_ipt_save="$(command -v iptables-save)"
-ban_ipt_restore="$(command -v iptables-restore)"
-ban_ipt6="$(command -v ip6tables)"
-ban_ipt6_save="$(command -v ip6tables-save)"
-ban_ipt6_restore="$(command -v ip6tables-restore)"
-ban_ipset="$(command -v ipset)"
-ban_logger="$(command -v logger)"
-ban_chain="banIP"
+ban_ip_cmd="$(command -v ip)"
+ban_ipt4_cmd="$(command -v iptables)"
+ban_ipt4_savecmd="$(command -v iptables-save)"
+ban_ipt4_restorecmd="$(command -v iptables-restore)"
+ban_ipt6_cmd="$(command -v ip6tables)"
+ban_ipt6_savecmd="$(command -v ip6tables-save)"
+ban_ipt6_restorecmd="$(command -v ip6tables-restore)"
+ban_ipset_cmd="$(command -v ipset)"
+ban_logger_cmd="$(command -v logger)"
+ban_allsources=""
+ban_sources=""
+ban_asns=""
+ban_countries=""
+ban_settype_src=""
+ban_settype_dst=""
+ban_settype_all=""
+ban_lan_inputchains_4=""
+ban_lan_inputchains_6=""
+ban_lan_forwardchains_4=""
+ban_lan_forwardchains_6=""
+ban_wan_inputchains_4=""
+ban_wan_inputchains_6=""
+ban_wan_forwardchains_4=""
+ban_wan_forwardchains_6=""
 ban_action="${1:-"start"}"
 ban_pidfile="/var/run/banip.pid"
-ban_rtfile="/tmp/ban_runtime.json"
+ban_tmpbase="/tmp"
+ban_rtfile="${ban_tmpbase}/ban_runtime.json"
+ban_srcfile="${ban_tmpbase}/ban_sources.json"
+ban_reportdir="${ban_tmpbase}/banIP-Report"
+ban_backupdir="${ban_tmpbase}/banIP-Backup"
+ban_srcarc="/etc/banip/banip.sources.gz"
+ban_mailservice="/etc/banip/banip.mail"
 ban_logservice="/etc/banip/banip.service"
-ban_sshdaemon=""
-ban_setcnt=0
-ban_cnt=0
-ban_log_src=0
-ban_log_dst=0
+ban_maclist="/etc/banip/banip.maclist"
+ban_blacklist="/etc/banip/banip.blacklist"
+ban_whitelist="/etc/banip/banip.whitelist"
+ban_setcnt="0"
+ban_cnt="0"
 
 # load environment
 #
-f_envload()
+f_load()
 {
        # get system information
        #
        ban_sysver="$(ubus -S call system board 2>/dev/null | jsonfilter -e '@.model' -e '@.release.description' | \
                awk 'BEGIN{ORS=", "}{print $0}' | awk '{print substr($0,1,length($0)-2)}')"
 
-       # parse 'global' and 'extra' section by callback
-       #
-       config_cb()
-       {
-               local type="${1}"
-               if [ "${type}" = "banip" ]
-               then
-                       option_cb()
-                       {
-                               local option="${1}"
-                               local value="${2}"
-                               eval "${option}=\"${value}\""
-                       }
-               else
-                       reset_cb
-               fi
-       }
-
-       # parse 'source' typed sections
-       #
-       parse_config()
-       {
-               local value opt section="${1}" options="ban_src ban_src_6 ban_src_rset ban_src_rset_6 ban_src_settype ban_src_ruletype ban_src_on ban_src_on_6 ban_src_cat"
-               for opt in ${options}
-               do
-                       config_get value "${section}" "${opt}"
-                       if [ -n "${value}" ]
-                       then
-                               eval "${opt}_${section}=\"${value}\""
-                               if [ "${opt}" = "ban_src" ]
-                               then
-                                       eval "ban_sources=\"${ban_sources} ${section}\""
-                               elif [ "${opt}" = "ban_src_6" ]
-                               then
-                                       eval "ban_sources=\"${ban_sources} ${section}_6\""
-                               fi
-                       fi
-               done
-       }
-
        # load config
        #
-       config_load banip
-       config_foreach parse_config source
+       f_conf
 
-       # setup logging
+       # check status
        #
-       ban_log_chain_src="${ban_log_chain_src:-"${ban_chain}_log_src"}"
-       if [ "${ban_log_src}" -eq 1 ]
+       if [ "${ban_enabled}" = "0" ]
        then
-               log_target_src="${ban_target_src:-"DROP"}"
-               ban_target_src="${ban_log_chain_src}"
-
-               log_target_src_6="${ban_target_src_6:-"DROP"}"
-               ban_target_src_6="${ban_log_chain_src}"
+               f_bgsrv "stop"
+               f_ipset "destroy"
+               f_jsnup "disabled"
+               f_rmbckp
+               f_log "info" "banIP is currently disabled, please set the config option 'ban_enabled' to '1' to use this service"
+               exit 0
        fi
 
-       ban_log_chain_dst="${ban_log_chain_dst:-"${ban_chain}_log_dst"}"
-       if [ "${ban_log_dst}" -eq 1 ]
-       then
-               log_target_dst="${ban_target_dst:-"REJECT"}"
-               ban_target_dst="${ban_log_chain_dst}"
+       f_dir "${ban_backupdir}"
+       f_dir "${ban_reportdir}"
+}
+
+# check/create directories
+#
+f_dir()
+{
+       local dir="${1}"
 
-               log_target_dst_6="${ban_target_dst_6:-"REJECT"}"
-               ban_target_dst_6="${ban_log_chain_dst}"
+       if [ ! -d "${dir}" ]
+       then
+               mkdir -p "${dir}"
+               if [ "${?}" = "0" ]
+               then
+                       f_log "info" "directory '${dir}' created"
+               else
+                       f_log "err" "directory '${dir}' could not be created"
+               fi
+       else
+               f_log "info" "directory '${dir}' is used"
        fi
+}
 
-       # version check
-       #
-       if [ -z "${ban_basever}" ] || [ "${ban_ver%.*}" != "${ban_basever}" ]
+# load banIP config
+#
+f_conf()
+{
+       if [ ! -r "/etc/config/banip" ] || [ -z "$(uci -q show banip.global.ban_autodetect)" ]
        then
-               f_log "info" "your banIP config seems to be too old, please update your config with the '--force-maintainer' opkg option"
-               f_rmtemp
-               exit 0
+               f_log "err" "no valid banIP config found, please re-install the package via opkg with the '--force-reinstall --force-maintainer' options"
        fi
 
-       # create temp directory & files
-       #
-       f_temp
+       config_cb()
+       {
+               option_cb()
+               {
+                       local option="${1}"
+                       local value="${2}"
+                       eval "${option}=\"${value}\""
+               }
+               list_cb()
+               {
+                       local option="${1}"
+                       local value="${2}"
+                       if [ "${option}" = "ban_ifaces" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_ifaces}")${value} \""
+                       elif [ "${option}" = "ban_sources" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_sources}")${value} \""
+                       elif [ "${option}" = "ban_localsources" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_localsources}")${value} \""
+                       elif [ "${option}" = "ban_settype_src" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_settype_src}")${value} \""
+                       elif [ "${option}" = "ban_settype_dst" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_settype_dst}")${value} \""
+                       elif [ "${option}" = "ban_settype_all" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_settype_all}")${value} \""
+                       elif [ "${option}" = "ban_mailactions" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_mailactions}")${value} \""
+                       elif [ "${option}" = "ban_logterms" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_logterms}")${value} \""
+                       elif [ "${option}" = "ban_countries" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_countries}")${value} \""
+                       elif [ "${option}" = "ban_asns" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_asns}")${value} \""
+                       elif [ "${option}" = "ban_lan_inputchains_4" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_lan_inputchains_4}")${value} \""
+                       elif [ "${option}" = "ban_lan_inputchains_6" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_lan_inputchains_6}")${value} \""
+                       elif [ "${option}" = "ban_lan_forwardchains_4" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_lan_forwardchains_4}")${value} \""
+                       elif [ "${option}" = "ban_lan_forwardchains_6" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_lan_forwardchains_6}")${value} \""
+                       elif [ "${option}" = "ban_wan_inputchains_4" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_wan_inputchains_4}")${value} \""
+                       elif [ "${option}" = "ban_wan_inputchains_6" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_wan_inputchains_6}")${value} \""
+                       elif [ "${option}" = "ban_wan_forwardchains_4" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_wan_forwardchains_4}")${value} \""
+                       elif [ "${option}" = "ban_wan_forwardchains_6" ]
+                       then
+                               eval "${option}=\"$(printf "%s" "${ban_wan_forwardchains_6}")${value} \""
+                       fi
+               }
+       }
+       config_load banip
 
-       # check status
-       #
-       if [ "${ban_enabled}" -eq 0 ]
+       ban_chain="${ban_chain:-"banIP"}"
+       ban_global_settype="${ban_global_settype:-"src+dst"}"
+       ban_target_src="${ban_target_src:-"DROP"}"
+       ban_target_dst="${ban_target_dst:-"REJECT"}"
+       ban_lan_inputchains_4="${ban_lan_inputchains_4:-"input_lan_rule"}"
+       ban_lan_inputchains_6="${ban_lan_inputchains_6:-"input_lan_rule"}"
+       ban_lan_forwardchains_4="${ban_lan_forwardchains_4:-"forwarding_lan_rule"}"
+       ban_lan_forwardchains_6="${ban_lan_forwardchains_6:-"forwarding_lan_rule"}"
+       ban_wan_inputchains_4="${ban_wan_inputchains_4:-"input_wan_rule"}"
+       ban_wan_inputchains_6="${ban_wan_inputchains_6:-"input_wan_rule"}"
+       ban_wan_forwardchains_4="${ban_wan_forwardchains_4:-"forwarding_wan_rule"}"
+       ban_wan_forwardchains_6="${ban_wan_forwardchains_6:-"forwarding_wan_rule"}"
+       ban_logchain_src="${ban_logchain_src:-"${ban_chain}_log_src"}"
+       ban_logchain_dst="${ban_logchain_dst:-"${ban_chain}_log_dst"}"
+       ban_logtarget_src="${ban_target_src}"
+       ban_logtarget_dst="${ban_target_dst}"
+       if [ "${ban_logsrc_enabled}" = "1" ]
        then
-               f_bgserv "stop"
-               f_jsnup disabled
-               f_ipset destroy
-               f_rmbackup
-               f_rmtemp
-               f_log "info" "banIP is currently disabled, please set ban_enabled to '1' to use this service"
-               exit 0
+               ban_logprefix_src="${ban_logprefix_src:-"[banIP-${ban_ver%-*}, src/${ban_target_src}] "}"
+               ban_logopts_src="${ban_logopts_src:-"-m limit --limit 2/sec"}"
+               ban_target_src="${ban_logchain_src}"
        fi
+       if [ "${ban_logdst_enabled}" = "1" ]
+       then
+               ban_logprefix_dst="${ban_logprefix_dst:-"[banIP-${ban_ver%-*}, dst/${ban_target_dst}] "}"
+               ban_logopts_dst="${ban_logopts_dst:-"-m limit --limit 2/sec"}"
+               ban_target_dst="${ban_logchain_dst}"
+       fi
+       ban_localsources="${ban_localsources:-"maclist whitelist blacklist"}"
+       ban_logterms="${ban_logterms:-"dropbear sshd luci"}"
+       f_log "debug" "f_conf  ::: ifaces: ${ban_ifaces:-"-"}, chain: ${ban_chain}, set_type: ${ban_global_settype}, log_chains (src/dst): ${ban_logchain_src}/${ban_logchain_dst}, targets (src/dst): ${ban_target_src}/${ban_target_dst}"
+       f_log "debug" "f_conf  ::: lan_inputs (4/6): ${ban_lan_inputchains_4}/${ban_lan_inputchains_6}, lan_forwards (4/6): ${ban_lan_forwardchains_4}/${ban_lan_forwardchains_6}, wan_inputs (4/6): ${ban_wan_inputchains_4}/${ban_wan_inputchains_6}, wan_forwards (4/6): ${ban_wan_forwardchains_4}/${ban_wan_forwardchains_6}"
+       f_log "debug" "f_conf  ::: local_sources: ${ban_localsources:-"-"}, log_terms: ${ban_logterms:-"-"}, log_prefixes (src/dst): ${ban_logprefix_src}/${ban_logprefix_dst}, log_options (src/dst): ${ban_logopts_src}/${ban_logopts_dst}"
 }
 
 # check environment
 #
-f_envcheck()
+f_env()
 {
-       local util utils packages iface tmp cnt=0 cnt_max=0
+       local util utils packages iface tmp cnt="0" cnt_max="10"
 
+       ban_starttime="$(date "+%s")"
        f_jsnup "running"
        f_log "info" "start banIP processing (${ban_action})"
 
-       # check backup directory
+       # create temp directory & files
        #
-       if [ ! -d "${ban_backupdir}" ]
-       then
-               f_log "err" "the backup directory '${ban_backupdir}' does not exist or has not been mounted yet, please create the directory or raise the 'ban_triggerdelay' to defer the banIP start"
-       fi
+       f_tmp
 
        # get wan devices and wan subnets
        #
-       if [ "${ban_automatic}" = "1" ]
+       if [ "${ban_autodetect}" = "1" ] && [ -z "${ban_ifaces}" ]
        then
-               while [ "${cnt}" -le 30 ]
+               while [ "${cnt}" -le "${cnt_max}" ]
                do
                        network_find_wan iface
-                       if [ -n "${iface}" ] && [ -z "$(printf "%s\\n" "${ban_iface}" | grep -F "${iface}")" ]
+                       if [ -n "${iface}" ] && [ -z "$(printf "%s\n" "${ban_ifaces}" | grep -F "${iface}")" ]
                        then
-                               ban_iface="${ban_iface} ${iface}"
-                               if [ "${cnt_max}" -eq 0 ]
-                               then
-                                       cnt_max=$((cnt+5))
-                               fi
+                               ban_proto4_enabled="1"
+                               ban_ifaces="${ban_ifaces}${iface} "
+                               uci_set banip global ban_proto4_enabled "1"
+                               uci_add_list banip global ban_ifaces "${iface}"
                        fi
                        network_find_wan6 iface
-                       if [ -n "${iface}" ] && [ -z "$(printf "%s\\n" "${ban_iface}" | grep -F "${iface}")" ]
+                       if [ -n "${iface}" ] && [ -z "$(printf "%s\n" "${ban_ifaces}" | grep -F "${iface}")" ]
                        then
-                               ban_iface="${ban_iface} ${iface}"
-                               if [ "${cnt_max}" -eq 0 ]
-                               then
-                                       cnt_max=$((cnt+5))
-                               fi
+                               ban_proto6_enabled="1"
+                               ban_ifaces="${ban_ifaces}${iface} "
+                               uci_set banip global ban_proto6_enabled "1"
+                               uci_add_list banip global ban_ifaces "${iface}"
                        fi
-                       if [ -z "${ban_iface}" ] || [ "${cnt}" -le "${cnt_max}" ]
+                       if [ -z "${ban_ifaces}" ]
                        then
-                               network_flush_cache
-                               cnt=$((cnt+1))
-                               sleep 1
+                               if [ "${cnt}" -le "${cnt_max}" ]
+                               then
+                                       network_flush_cache
+                                       cnt=$((cnt+1))
+                                       sleep 1
+                               else
+                                       break
+                               fi
                        else
+                               if [ -n "$(uci -q changes "banip")" ]
+                               then
+                                       uci_commit "banip"
+                               fi
                                break
                        fi
                done
        fi
 
-       for iface in ${ban_iface}
+       while [ "${cnt}" -le "${cnt_max}" ]
        do
-               network_get_device tmp "${iface}"
-               if [ -n "${tmp}" ] && [ -z "$(printf "%s\\n" "${ban_dev}" | grep -F "${tmp}")" ]
-               then
-                       ban_dev="${ban_dev} ${tmp}"
-               else
-                       network_get_physdev tmp "${iface}"
-                       if [ -n "${tmp}" ] && [ -z "$(printf "%s\\n" "${ban_dev}" | grep -F "${tmp}")" ]
+               for iface in ${ban_ifaces}
+               do
+                       network_get_device tmp "${iface}"
+                       if [ -n "${tmp}" ] && [ -z "$(printf "%s\n" "${ban_devs}" | grep -F "${tmp}")" ]
+                       then
+                               ban_devs="${ban_devs} ${tmp}"
+                       else
+                               network_get_physdev tmp "${iface}"
+                               if [ -n "${tmp}" ] && [ -z "$(printf "%s\n" "${ban_devs}" | grep -F "${tmp}")" ]
+                               then
+                                       ban_devs="${ban_devs} ${tmp}"
+                               fi
+                       fi
+                       network_get_subnet tmp "${iface}"
+                       if [ -n "${tmp}" ] && [ -z "$(printf "%s\n" "${ban_subnets}" | grep -F "${tmp}")" ]
                        then
-                               ban_dev="${ban_dev} ${tmp}"
+                               ban_subnets="${ban_subnets} ${tmp}"
                        fi
-               fi
-               network_get_subnets tmp "${iface}"
-               if [ -n "${tmp}" ] && [ -z "$(printf "%s\\n" "${ban_subnets}" | grep -F "${tmp}")" ]
-               then
-                       ban_subnets="${ban_subnets} ${tmp}"
-               fi
-               network_get_subnets6 tmp "${iface}"
-               if [ -n "${tmp}" ] && [ -z "$(printf "%s\\n" "${ban_subnets6}" | grep -F "${tmp}")" ]
+                       network_get_subnet6 tmp "${iface}"
+                       if [ -n "${tmp}" ] && [ -z "$(printf "%s\n" "${ban_subnets}" | grep -F "${tmp}")" ]
+                       then
+                               ban_subnets="${ban_subnets} ${tmp}"
+                       fi
+               done
+               if [ -z "${ban_devs}" ] || [ -z "${ban_subnets}" ]
                then
-                       ban_subnets6="${ban_subnets6} ${tmp}"
+                       if [ "${cnt}" -le "${cnt_max}" ]
+                       then
+                               network_flush_cache
+                               cnt=$((cnt+1))
+                               sleep 1
+                       else
+                               break
+                       fi
+               else
+                       break
                fi
        done
-       ban_dev_all="$(ip link show 2>/dev/null | awk 'BEGIN{FS="[@: ]"}/^[0-9:]/{if($3!="lo"){print $3}}')"
+       ban_ipdevs="$("${ban_ip_cmd}" link show 2>/dev/null | awk 'BEGIN{FS="[@: ]"}/^[0-9:]/{if($3!="lo"){ORS=" ";print $3}}')"
 
-       if [ -z "${ban_iface}" ] || [ -z "${ban_dev}" ] || [ -z "${ban_dev_all}" ]
+       if [ -z "${ban_ifaces}" ] || [ -z "${ban_devs}" ] || [ -z "${ban_ipdevs}" ]
        then
-               f_log "err" "wan interface(s)/device(s) (${ban_iface:-"-"}/${ban_dev:-"-"}) not found, please please check your configuration"
+               f_log "err" "logical wan interface(s)/device(s) '${ban_ifaces:-"-"}/${ban_devs:-"-"}' not found, please please check your configuration"
+       elif [ -z "${ban_ipdevs}" ]
+       then
+               f_log "err" "ip device(s) '${ban_ipdevs:-"-"}' not found, please please check your configuration"
+       fi
+
+       # check ipset/iptables utility
+       #
+       if [ ! -x "${ban_ipset_cmd}" ]
+       then
+               f_log "err" "ipset utility '${ban_ipset_cmd:-"-"}' not executable, please install package 'ipset'"
+       fi
+       if { [ "${ban_proto4_enabled}" = "1" ] && { [ ! -x "${ban_ipt4_cmd}" ] || [ ! -x "${ban_ipt4_savecmd}" ] || [ ! -x "${ban_ipt4_restorecmd}" ]; }; } || \
+               { [ "${ban_proto6_enabled}" = "1" ] && { [ ! -x "${ban_ipt6_cmd}" ] || [ ! -x "${ban_ipt6_savecmd}" ] || [ ! -x "${ban_ipt6_restorecmd}" ]; }; }
+       then
+               f_log "err" "iptables utilities '${ban_ipt4_cmd:-"-"}, ${ban_ipt4_savecmd:-"-"}, ${ban_ipt4_restorecmd:-"-"}/${ban_ipt6_cmd:-"-"}', ${ban_ipt6_savecmd:-"-"}, ${ban_ipt6_restorecmd:-"-"} not executable, please install the relevant iptables packages"
        fi
 
-       # check fetch utility
+       # check download utility
        #
        if [ -z "${ban_fetchutil}" ]
        then
-               cnt_max=$((cnt+5))
-               while [ -z "${packages}" ]
+               while [ -z "${packages}" ] && [ "${cnt}" -le "${cnt_max}" ]
                do
                        packages="$(opkg list-installed 2>/dev/null)"
-                       if [ "${cnt}" -gt "${cnt_max}" ]
-                       then
-                               break
-                       fi
                        cnt=$((cnt+1))
                        sleep 1
                done
-               if [ -n "${packages}" ]
+               if [ -z "${packages}" ]
                then
-                       utils="aria2c curl wget uclient-fetch"
-                       for util in ${utils}
-                       do
-                               if { [ "${util}" = "uclient-fetch" ] && [ -n "$(printf "%s\\n" "${packages}" | grep "^libustream-")" ]; } || \
-                                       { [ "${util}" = "wget" ] && [ -n "$(printf "%s\\n" "${packages}" | grep "^wget -")" ]; } || \
-                                       { [ "${util}" != "uclient-fetch" ] && [ "${util}" != "wget" ]; }
+                       f_log "err" "local opkg package repository is not available, please set 'ban_fetchutil' manually"
+               fi
+
+               utils="aria2c curl wget uclient-fetch"
+               for util in ${utils}
+               do
+                       if { [ "${util}" = "uclient-fetch" ] && [ -n "$(printf "%s" "${packages}" | grep "^libustream-")" ]; } || \
+                               { [ "${util}" = "wget" ] && [ -n "$(printf "%s" "${packages}" | grep "^wget -")" ]; } || \
+                               [ "${util}" = "curl" ] || [ "${util}" = "aria2c" ]
+                       then
+                               if [ -x "$(command -v "${util}")" ]
                                then
-                                       ban_fetchutil="$(command -v "${util}")"
-                                       if [ -x "${ban_fetchutil}" ]
-                                       then
-                                               break
-                                       fi
+                                       ban_fetchutil="${util}"
+                                       uci_set banip global ban_fetchutil "${util}"
+                                       uci_commit "banip"
+                                       break
                                fi
-                               unset ban_fetchutil util
-                       done
-               fi
-       else
-               util="${ban_fetchutil}"
-               ban_fetchutil="$(command -v "${util}")"
-               if [ ! -x "${ban_fetchutil}" ]
-               then
-                       unset ban_fetchutil util
-               fi
+                       fi
+               done
+       elif [ ! -x "$(command -v "${ban_fetchutil}")" ]
+       then
+               unset ban_fetchutil
        fi
-       case "${util}" in
+       case "${ban_fetchutil}" in
                "aria2c")
-                       ban_fetchparm="${ban_fetchparm:-"--timeout=20 --allow-overwrite=true --auto-file-renaming=false --check-certificate=true --dir=/ -o"}"
+                       ban_fetchparm="${ban_fetchparm:-"--timeout=20 --allow-overwrite=true --auto-file-renaming=false --check-certificate=true --log-level=warn --dir=/ -o"}"
                ;;
                "curl")
-                       ban_fetchparm="${ban_fetchparm:-"--connect-timeout 20 -o"}"
+                       ban_fetchparm="${ban_fetchparm:-"--connect-timeout 20 --silent --show-error --location -o"}"
                ;;
                "uclient-fetch")
                        ban_fetchparm="${ban_fetchparm:-"--timeout=20 -O"}"
@@ -285,182 +403,244 @@ f_envcheck()
                        ban_fetchparm="${ban_fetchparm:-"--no-cache --no-cookies --max-redirect=0 --timeout=20 -O"}"
                ;;
        esac
-       if [ -z "${ban_fetchutil}" ] || [ -z "${ban_fetchparm}" ]
+       if [ -n "${ban_fetchutil}" ] && [ -n "${ban_fetchparm}" ]
        then
+               ban_fetchutil="$(command -v "${ban_fetchutil}")"
+       else
                f_log "err" "download utility with SSL support not found, please install 'uclient-fetch' with a 'libustream-*' variant or another download utility like 'wget', 'curl' or 'aria2'"
        fi
 
-       # check ssh daemon
+       # load JSON source file
        #
-       if [ -z "${ban_sshdaemon}" ]
+       if [ ! -r "${ban_srcfile}" ]
        then
-               utils="sshd dropbear"
-               for util in ${utils}
-               do
-                       if [ -x "$(command -v "${util}")" ]
-                       then
-                               if [ "$("/etc/init.d/${util}" enabled; printf "%u" ${?})" -eq 0 ]
-                               then
-                                       ban_sshdaemon="${util}"
-                                       break
-                               fi
-                       fi
-               done
+               if [ -r "${ban_srcarc}" ]
+               then
+                       zcat "${ban_srcarc}" > "${ban_srcfile}"
+               else
+                       f_log "err" "banIP source archive not found"
+               fi
        fi
-       if [ -z "${ban_sshdaemon}" ]
+       if [ -r "${ban_srcfile}" ]
        then
-               f_log "err" "ssh daemon not found, please install 'dropbear' or 'sshd'"
+               json_load_file "${ban_srcfile}"
+               json_get_keys ban_allsources
+               ban_allsources="${ban_allsources} ${ban_localsources}"
+       else
+               f_log "err" "banIP source file not found"
        fi
+       f_log "debug" "f_env   ::: auto_detect: ${ban_autodetect}, fetch_util: ${ban_fetchutil:-"-"}, fetch_parm: ${ban_fetchparm:-"-"}, src_file: ${ban_srcfile:-"-"}, log_terms: ${ban_logterms}, interfaces: ${ban_ifaces:-"-"}, devices: ${ban_devs:-"-"}, subnets: ${ban_subnets:-"-"}, ip_devices: ${ban_ipdevs:-"-"}, protocols (4/6): ${ban_proto4_enabled}/${ban_proto6_enabled}"
 }
 
 # create temporary files and directories
 #
-f_temp()
+f_tmp()
 {
-       if [ -d "/tmp" ] && [ -z "${ban_tmpdir}" ]
-       then
-               ban_tmpdir="$(mktemp -p /tmp -d)"
-               ban_tmpfile="$(mktemp -p "${ban_tmpdir}" -tu)"
-       elif [ ! -d "/tmp" ]
-       then
-               f_log "err" "the temp directory '/tmp' does not exist or has not been mounted yet, please create the directory or raise the 'ban_triggerdelay' to defer the banIP start"
-       fi
+       f_dir "${ban_tmpbase}"
+
+       ban_tmpdir="$(mktemp -p "${ban_tmpbase}" -d)"
+       ban_tmpfile="$(mktemp -p "${ban_tmpdir}" -tu)"
 
        if [ ! -f "${ban_pidfile}" ] || [ ! -s "${ban_pidfile}" ]
        then
                printf "%s" "${$}" > "${ban_pidfile}"
        fi
+       f_log "debug" "f_tmp   ::: tmp_base: ${ban_tmpbase:-"-"}, tmp_dir: ${ban_tmpdir:-"-"}, pid_file: ${ban_pidfile:-"-"}"
 }
 
 # remove temporary files and directories
 #
-f_rmtemp()
+f_rmtmp()
 {
        if [ -d "${ban_tmpdir}" ]
        then
                rm -rf "${ban_tmpdir}"
        fi
+       rm -f "${ban_srcfile}"
        > "${ban_pidfile}"
+       f_log "debug" "f_rmtmp ::: tmp_base: ${ban_tmpbase:-"-"}, tmp_dir: ${ban_tmpdir:-"-"}, pid_file: ${ban_pidfile:-"-"}"
 }
 
 # remove backup files
 #
-f_rmbackup()
+f_rmbckp()
 {
        if [ -d "${ban_backupdir}" ]
        then
-               rm -f "${ban_backupdir}"/banIP.*.gz
+               rm -f "${ban_backupdir}/banIP."*".gz"
+       fi
+}
+
+# status helper function
+#
+f_char()
+{
+       local result input="${1}"
+
+       if [ "${input}" = "1" ]
+       then
+               result="✔"
+       else
+               result="✘"
        fi
+       printf "%s" "${result}"
 }
 
-# iptables rules engine
+# apply iptables rules
 #
 f_iptrule()
 {
-       local rc timeout="-w 5" action="${1}" rule="${2}"
+       local rc timeout="-w 5" action="${1}" chain="${2}" rule="${3}" pos="${4}"
 
-       if [ "${src_name##*_}" = "6" ]
+       if [ "${src_name}" = "maclist" ] || [ "${src_name##*_}" = "4" ]
        then
-               if [ -x "${ban_ipt6}" ]
+               rc="$("${ban_ipt4_cmd}" "${timeout}" -C ${chain} ${rule} 2>/dev/null; printf "%u" ${?})"
+               if { [ "${rc}" != "0" ] && { [ "${action}" = "-A" ] || [ "${action}" = "-I" ]; }; } || \
+                       { [ "${rc}" = "0" ] && [ "${action}" = "-D" ]; }
                then
-                       rc="$("${ban_ipt6}" "${timeout}" -C ${rule} 2>/dev/null; printf "%u" ${?})"
-
-                       if { [ "${rc}" -ne 0 ] && { [ "${action}" = "-A" ] || [ "${action}" = "-I" ]; } } || \
-                               { [ "${rc}" -eq 0 ] && [ "${action}" = "-D" ]; }
-                       then
-                               "${ban_ipt6}" "${timeout}" "${action}" ${rule} 2>/dev/null
-                       fi
+                       "${ban_ipt4_cmd}" "${timeout}" "${action}" ${chain} ${pos} ${rule} 2>/dev/null
+                       rc="${?}"
+               else
+                       rc=0
                fi
-       else
-               if [ -x "${ban_ipt}" ]
+       fi
+       if [ "${src_name}" = "maclist" ] || [ "${src_name##*_}" = "6" ]
+       then
+               rc="$("${ban_ipt6_cmd}" "${timeout}" -C ${chain} ${rule} 2>/dev/null; printf "%u" ${?})"
+               if { [ "${rc}" != "0" ] && { [ "${action}" = "-A" ] || [ "${action}" = "-I" ]; }; } || \
+                       { [ "${rc}" = "0" ] && [ "${action}" = "-D" ]; }
                then
-                       rc="$("${ban_ipt}" "${timeout}" -C ${rule} 2>/dev/null; printf "%u" ${?})"
-
-                       if { [ "${rc}" -ne 0 ] && { [ "${action}" = "-A" ] || [ "${action}" = "-I" ]; } } || \
-                               { [ "${rc}" -eq 0 ] && [ "${action}" = "-D" ]; }
-                       then
-                               "${ban_ipt}" "${timeout}" "${action}" ${rule} 2>/dev/null
-                       fi
+                       "${ban_ipt6_cmd}" "${timeout}" "${action}" ${chain} ${pos} ${rule} 2>/dev/null
+                       rc="${?}"
+               else
+                       rc=0
                fi
        fi
-       if [ "${?}" -ne 0 ]
+       if [ -n "${rc}" ] && [ "${rc}" != "0" ]
        then
                > "${tmp_err}"
-               f_log "info" "can't create iptables rule: action: '${action:-"-"}', rule: '${rule:-"-"}'"
+               f_log "info" "iptables action '${action:-"-"}' failed with '${chain}, ${pos:-"-"}, ${rule:-"-"}'"
        fi
 }
 
-# remove/add iptables rules
+# iptables controller
 #
-f_iptadd()
+f_iptables()
 {
-       local rm="${1}" dev
-
-       for dev in ${ban_dev_all}
-       do
-               f_iptrule "-D" "${ban_chain} -i ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} src -j ${target_src}"
-               f_iptrule "-D" "${ban_chain} -o ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} dst -j ${target_dst}"
-       done
+       local destroy="${1}" dev
 
-       if [ -z "${rm}" ] && [ "${cnt}" -gt 0 ]
+       if [ "${ban_action}" != "refresh" ] && [ "${ban_action}" != "resume" ]
        then
-               if [ "${src_ruletype}" != "dst" ]
-               then
-                       f_iptrule "-I" "${wan_input} -j ${ban_chain}"
-                       f_iptrule "-I" "${wan_forward} -j ${ban_chain}"
-                       if [ "${src_name##*_}" != "6" ]
+               for dev in ${ban_ipdevs}
+               do
+                       if [ "${src_name}" = "maclist" ]
                        then
-                               f_iptrule "-A" "${ban_chain} -p udp --dport 67:68 --sport 67:68 -j RETURN"
+                               f_iptrule "-D" "${ban_chain}" "-o ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} src -j RETURN"
+                       elif [ "${src_name%_*}" = "whitelist" ]
+                       then
+                               f_iptrule "-D" "${ban_chain}" "-i ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} src -j RETURN"
+                               f_iptrule "-D" "${ban_chain}" "-o ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} dst -j RETURN"
                        else
-                               f_iptrule "-A" "${ban_chain} -p udp -s fc00::/6 --sport 547 -d fc00::/6 --dport 546 -j RETURN"
-                               f_iptrule "-A" "${ban_chain} -p ipv6-icmp -s fe80::/10 -d fe80::/10 -j RETURN"
+                               f_iptrule "-D" "${ban_chain}" "-i ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} src -j ${ban_logtarget_src}"
+                               f_iptrule "-D" "${ban_chain}" "-o ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} dst -j ${ban_logtarget_dst}"
+                               f_iptrule "-D" "${ban_chain}" "-i ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} src -j ${ban_logchain_src}"
+                               f_iptrule "-D" "${ban_chain}" "-o ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} dst -j ${ban_logchain_dst}"
+                       fi
+               done
+       fi
+       if [ -z "${destroy}" ] && [ "${cnt}" -gt "0" ]
+       then
+               if [ "${src_settype}" != "dst" ]
+               then
+                       if [ "${src_name##*_}" = "4" ]
+                       then
+                               for chain in ${ban_wan_inputchains_4}
+                               do
+                                       f_iptrule "-I" "${chain}" "-j ${ban_chain}"
+                               done
+                               for chain in ${ban_wan_forwardchains_4}
+                               do
+                                       f_iptrule "-I" "${chain}" "-j ${ban_chain}"
+                               done
+                               f_iptrule "-A" "${ban_chain}" "-p udp --dport 67:68 --sport 67:68 -j RETURN"
+                       elif [ "${src_name##*_}" = "6" ]
+                       then
+                               for chain in ${ban_wan_inputchains_6}
+                               do
+                                       f_iptrule "-I" "${chain}" "-j ${ban_chain}"
+                               done
+                               for chain in ${ban_wan_forwardchains_6}
+                               do
+                                       f_iptrule "-I" "${chain}" "-j ${ban_chain}"
+                               done
+                               f_iptrule "-A" "${ban_chain}" "-p ipv6-icmp -s fe80::/10 -d fe80::/10 -j RETURN"
+                               f_iptrule "-A" "${ban_chain}" "-p udp -s fc00::/6 --sport 547 -d fc00::/6 --dport 546 -j RETURN"
                        fi
-                       for dev in ${ban_dev}
+                       for dev in ${ban_devs}
                        do
-                               f_iptrule "${action:-"-A"}" "${ban_chain} -i ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} src -j ${target_src}"
+                               if [ "${src_name}" = "maclist" ]
+                               then
+                                       f_iptrule "-I" "${ban_chain}" "-o ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} src -j RETURN" "1"
+                               elif [ "${src_name%_*}" = "whitelist" ]
+                               then
+                                       f_iptrule "-I" "${ban_chain}" "-i ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} src -j RETURN" "2"
+                               else
+                                       f_iptrule "${action:-"-A"}" "${ban_chain}" "-i ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} src -j ${ban_target_src}"
+                               fi
                        done
                fi
-               if [ "${src_ruletype}" != "src" ]
+               if [ "${src_settype}" != "src" ]
                then
-                       f_iptrule "-I" "${lan_input} -j ${ban_chain}"
-                       f_iptrule "-I" "${lan_forward} -j ${ban_chain}"
-                       if [ "${src_name##*_}" != "6" ]
+                       if [ "${src_name##*_}" = "4" ]
                        then
-                               f_iptrule "-A" "${ban_chain} -p udp --dport 67:68 --sport 67:68 -j RETURN"
-                       else
-                               f_iptrule "-A" "${ban_chain} -p udp -s fc00::/6 --sport 547 -d fc00::/6 --dport 546 -j RETURN"
-                               f_iptrule "-A" "${ban_chain} -p ipv6-icmp -s fe80::/10 -d fe80::/10 -j RETURN"
+                               for chain in ${ban_lan_inputchains_4}
+                               do
+                                       f_iptrule "-I" "${chain}" "-j ${ban_chain}"
+                               done
+                               for chain in ${ban_lan_forwardchains_4}
+                               do
+                                       f_iptrule "-I" "${chain}" "-j ${ban_chain}"
+                               done
+                               f_iptrule "-A" "${ban_chain}" "-p udp --dport 67:68 --sport 67:68 -j RETURN"
+                       elif [ "${src_name##*_}" = "6" ]
+                       then
+                               for chain in ${ban_lan_inputchains_6}
+                               do
+                                       f_iptrule "-I" "${chain}" "-j ${ban_chain}"
+                               done
+                               for chain in ${ban_lan_forwardchains_6}
+                               do
+                                       f_iptrule "-I" "${chain}" "-j ${ban_chain}"
+                               done
+                               f_iptrule "-A" "${ban_chain}" "-p ipv6-icmp -s fe80::/10 -d fe80::/10 -j RETURN"
+                               f_iptrule "-A" "${ban_chain}" "-p udp -s fc00::/6 --sport 547 -d fc00::/6 --dport 546 -j RETURN"
                        fi
-                       for dev in ${ban_dev}
+                       for dev in ${ban_devs}
                        do
-                               f_iptrule "${action:-"-A"}" "${ban_chain} -o ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} dst -j ${target_dst}"
+                               if [ "${src_name%_*}" = "whitelist" ]
+                               then
+                                       f_iptrule "-I" "${ban_chain}" "-o ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} dst -j RETURN" "3"
+                               elif [ "${src_name}" != "maclist" ]
+                               then
+                                       f_iptrule "${action:-"-A"}" "${ban_chain}" "-o ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} dst -j ${ban_target_dst}"
+                               fi
                        done
                fi
        else
-               if [ -x "${ban_ipset}" ] && [ -n "$("${ban_ipset}" -q -n list "${src_name}")" ]
-               then
-                       "${ban_ipset}" -q destroy "${src_name}"
-               fi
+               "${ban_ipset_cmd}" -q destroy "${src_name}"
        fi
 }
 
-# ipset/iptables actions
+# ipset controller
 #
 f_ipset()
 {
-       local out_rc source action ruleset rule cnt=0 cnt_ip=0 cnt_cidr=0 timeout="-w 5" mode="${1}" in_rc="${src_rc:-0}"
-
-       if [ "${src_name%_6*}" = "whitelist" ]
-       then
-               target_src="RETURN"
-               target_dst="RETURN"
-               action="-I"
-       fi
+       local src src_list action rule ipt_cmd out_rc cnt="0" cnt_ip="0" cnt_cidr="0" cnt_mac="0" timeout="-w 5" mode="${1}" in_rc="4"
 
        case "${mode}" in
                "backup")
                        gzip -cf "${tmp_load}" 2>/dev/null > "${ban_backupdir}/banIP.${src_name}.gz"
-                       out_rc="${?:-"${in_rc}"}"
+                       out_rc="${?}"
                        f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, out_rc: ${out_rc}"
                        return "${out_rc}"
                ;;
@@ -469,8 +649,9 @@ f_ipset()
                        then
                                zcat "${ban_backupdir}/banIP.${src_name}.gz" 2>/dev/null > "${tmp_load}"
                                out_rc="${?}"
+                       else
+                               out_rc="${in_rc}"
                        fi
-                       out_rc="${out_rc:-"${in_rc}"}"
                        f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, out_rc: ${out_rc}"
                        return "${out_rc}"
                ;;
@@ -479,172 +660,206 @@ f_ipset()
                        then
                                rm -f "${ban_backupdir}/banIP.${src_name}.gz"
                                out_rc="${?}"
+                       else
+                               out_rc="${in_rc}"
                        fi
-                       out_rc="${out_rc:-"${in_rc}"}"
                        f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, out_rc: ${out_rc}"
                        return "${out_rc}"
                ;;
                "initial")
-                       local ipt log_src_target log_src_opts log_src_prefix log_dst_target log_dst_opts log_dst_prefix
-                       for src_name in "ruleset" "ruleset_6"
+                       for proto in "4" "6"
                        do
-                               if [ "${src_name##*_}" = "6" ]
+                               if [ "${proto}" = "4" ]
+                               then
+                                       ipt_cmd="${ban_ipt4_cmd}"
+                                       chainsets="${ban_lan_inputchains_4} ${ban_lan_forwardchains_4} ${ban_wan_inputchains_4} ${ban_wan_forwardchains_4}"
+                               elif [ "${proto}" = "6" ]
+                               then
+                                       ipt_cmd="${ban_ipt6_cmd}"
+                                       chainsets="${ban_lan_inputchains_6} ${ban_lan_forwardchains_6} ${ban_wan_inputchains_6} ${ban_wan_forwardchains_6}"
+                               fi
+
+                               if [ -z "$("${ipt_cmd}" "${timeout}" -nL "${ban_chain}" 2>/dev/null)" ]
                                then
-                                       ipt="${ban_ipt6}"
-                                       ruleset="${ban_wan_input_chain_6:-"input_wan_rule"} ${ban_wan_forward_chain_6:-"forwarding_wan_rule"} ${ban_lan_input_chain_6:-"input_lan_rule"} ${ban_lan_forward_chain_6:-"forwarding_lan_rule"}"
-                                       log_src_target="${log_target_src_6}"
-                                       log_src_opts="${ban_log_src_opts_6:-"-m limit --limit 10/sec"}"
-                                       log_src_prefix="${ban_log_src_prefix_6:-"${log_target_src_6}(src banIP) "}"
-                                       log_dst_target="${log_target_dst_6}"
-                                       log_dst_opts="${ban_log_dst_opts_6:-"-m limit --limit 10/sec"}"
-                                       log_dst_prefix="${ban_log_dst_prefix_6:-"${log_target_dst_6}(dst banIP) "}"
+                                       "${ipt_cmd}" "${timeout}" -N "${ban_chain}" 2>/dev/null
+                                       out_rc="${?}"
+                                       f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, chain: ${ban_chain:-"-"}, out_rc: ${out_rc}"
                                else
-                                       ipt="${ban_ipt}"
-                                       ruleset="${ban_wan_input_chain:-"input_wan_rule"} ${ban_wan_forward_chain:-"forwarding_wan_rule"} ${ban_lan_input_chain:-"input_lan_rule"} ${ban_lan_forward_chain:-"forwarding_lan_rule"}"
-                                       log_src_target="${log_target_src}"
-                                       log_src_opts="${ban_log_src_opts:-"-m limit --limit 10/sec"}"
-                                       log_src_prefix="${ban_log_src_prefix:-"${log_target_src}(src banIP) "}"
-                                       log_dst_target="${log_target_dst}"
-                                       log_dst_opts="${ban_log_dst_opts:-"-m limit --limit 10/sec"}"
-                                       log_dst_prefix="${ban_log_dst_prefix:-"${log_target_dst}(dst banIP) "}"
+                                       out_rc=0
+                                       for chain in ${chainsets}
+                                       do
+                                               f_iptrule "-D" "${chain}" "-j ${ban_chain}"
+                                       done
                                fi
 
-                               if [ -x "${ipt}" ]
+                               if [ "${ban_logsrc_enabled}" = "1" ] && [ "${out_rc}" = "0" ] && [ -z "$("${ipt_cmd}" "${timeout}" -nL "${ban_logchain_src}" 2>/dev/null)" ]
                                then
-                                       if [ -z "$("${ipt}" "${timeout}" -nL "${ban_chain}" 2>/dev/null)" ]
+                                       "${ipt_cmd}" "${timeout}" -N "${ban_logchain_src}" 2>/dev/null
+                                       out_rc="${?}"
+                                       if [ "${out_rc}" = "0" ]
                                        then
-                                               "${ipt}" "${timeout}" -N "${ban_chain}" 2>/dev/null
+                                               "${ipt_cmd}" "${timeout}" -A "${ban_logchain_src}" -j LOG ${ban_logopts_src} --log-prefix "${ban_logprefix_src}"
                                                out_rc="${?}"
-                                       else
-                                               out_rc=0
-                                               for rule in ${ruleset}
-                                               do
-                                                       f_iptrule "-D" "${rule} -j ${ban_chain}"
-                                               done
-                                       fi
-                                       f_log "debug" "f_ipset ::: name: -, mode: ${mode:-"-"}, chain: ${ban_chain:-"-"}, $src_name: ${ruleset:-"-"}, out_rc: ${out_rc}"
-
-                                       if [ "${ban_log_src}" -eq 1 ] && [ "${out_rc}" -eq 0 ]
-                                       then
-                                               if [ -z "$("${ipt}" "${timeout}" -nL "${ban_log_chain_src}" 2>/dev/null)" ]
+                                               if [ "${out_rc}" = "0" ]
                                                then
-                                                       "${ipt}" "${timeout}" -N "${ban_log_chain_src}" 2>/dev/null
+                                                       "${ipt_cmd}" "${timeout}" -A "${ban_logchain_src}" -j "${ban_logtarget_src}"
                                                        out_rc="${?}"
-                                                       if [ "${out_rc}" -eq 0 ]
-                                                       then
-                                                               "${ipt}" "${timeout}" -A "${ban_log_chain_src}" -j LOG ${log_src_opts} --log-prefix "${log_src_prefix}" && \
-                                                                       "${ipt}" "${timeout}" -A "${ban_log_chain_src}" -j "${log_src_target}"
-                                                               out_rc="${?}"
-                                                       fi
                                                fi
-                                               f_log "debug" "f_ipset ::: name: -, mode: ${mode:-"-"}, chain: ${ban_log_chain_src:-"-"}, out_rc: ${out_rc}"
                                        fi
+                                       f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, logchain_src: ${ban_logchain_src:-"-"}, out_rc: ${out_rc}"
+                               fi
 
-                                       if [ "${ban_log_dst}" -eq 1 ] && [ "${out_rc}" -eq 0 ]
+                               if [ "${ban_logdst_enabled}" = "1" ] && [ "${out_rc}" = "0" ] && [ -z "$("${ipt_cmd}" "${timeout}" -nL "${ban_logchain_dst}" 2>/dev/null)" ]
+                               then
+                                       "${ipt_cmd}" "${timeout}" -N "${ban_logchain_dst}" 2>/dev/null
+                                       out_rc="${?}"
+                                       if [ "${out_rc}" = "0" ]
                                        then
-                                               if [ -z "$("${ipt}" "${timeout}" -nL "${ban_log_chain_dst}" 2>/dev/null)" ]
+                                               "${ipt_cmd}" "${timeout}" -A "${ban_logchain_dst}" -j LOG ${ban_logopts_dst} --log-prefix "${ban_logprefix_dst}"
+                                               out_rc="${?}"
+                                               if [ "${out_rc}" = "0" ]
                                                then
-                                                       "${ipt}" "${timeout}" -N "${ban_log_chain_dst}" 2>/dev/null
+                                                       "${ipt_cmd}" "${timeout}" -A "${ban_logchain_dst}" -j "${ban_logtarget_dst}"
                                                        out_rc="${?}"
-                                                       if [ "${out_rc}" -eq 0 ]
-                                                       then
-                                                               "${ipt}" "${timeout}" -A "${ban_log_chain_dst}" -j LOG ${log_dst_opts} --log-prefix "${log_dst_prefix}" && \
-                                                                       "${ipt}" "${timeout}" -A "${ban_log_chain_dst}" -j "${log_dst_target}"
-                                                               out_rc="${?}"
-                                                       fi
                                                fi
-                                               f_log "debug" "f_ipset ::: name: -, mode: ${mode:-"-"}, chain: ${ban_log_chain_dst:-"-"}, out_rc: ${out_rc}"
                                        fi
+                                       f_log "debug" "f_ipset ::: name: initial, mode: ${mode:-"-"}, logchain_dst: ${ban_logchain_dst:-"-"}, out_rc: ${out_rc}"
                                fi
                        done
-
                        out_rc="${out_rc:-"${in_rc}"}"
-                       f_log "debug" "f_ipset ::: name: -, mode: ${mode:-"-"}, out_rc: ${out_rc}"
+                       f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, out_rc: ${out_rc}"
                        return "${out_rc}"
                ;;
                "create")
-                       if [ -x "${ban_ipset}" ]
+                       if [ "${src_name}" = "maclist" ] && [ -s "${tmp_file}" ] && [ -z "$("${ban_ipset_cmd}" -q -n list "${src_name}")" ]
                        then
-                               if [ -s "${tmp_file}" ] && [ -z "$("${ban_ipset}" -q -n list "${src_name}")" ]
-                               then
-                                       "${ban_ipset}" -q create "${src_name}" hash:"${src_settype}" hashsize 64 maxelem 262144 family "${src_setipv}" counters
-                                       out_rc="${?}"
-                               else
-                                       "${ban_ipset}" -q flush "${src_name}"
-                                       out_rc="${?}"
-                               fi
-                               if [ -s "${tmp_file}" ] && [ "${out_rc}" -eq 0 ]
+                               "${ban_ipset_cmd}" create "${src_name}" hash:mac maxelem 262144 counters
+                               out_rc="${?}"
+                       elif [ -s "${tmp_file}" ] && [ -z "$("${ban_ipset_cmd}" -q -n list "${src_name}")" ]
+                       then
+                               "${ban_ipset_cmd}" create "${src_name}" hash:net hashsize 64 maxelem 262144 family "${src_ipver}" counters
+                               out_rc="${?}"
+                       else
+                               "${ban_ipset_cmd}" -q flush "${src_name}"
+                               out_rc="${?}"
+                       fi
+                       if [ -s "${tmp_file}" ] && [ "${out_rc}" = "0" ]
+                       then
+                               "${ban_ipset_cmd}" -q -! restore < "${tmp_file}"
+                               out_rc="${?}"
+                               if [ "${out_rc}" = "0" ]
                                then
-                                       "${ban_ipset}" -q -! restore < "${tmp_file}"
-                                       out_rc="${?}"
-                                       if [ "${out_rc}" -eq 0 ]
-                                       then
-                                               "${ban_ipset}" -q save "${src_name}" > "${tmp_file}"
-                                               cnt="$(($(wc -l 2>/dev/null < "${tmp_file}")-1))"
-                                               cnt_cidr="$(grep -cF "/" "${tmp_file}")"
-                                               cnt_ip="$((cnt-cnt_cidr))"
-                                               printf "%s\\n" "${cnt}" > "${tmp_cnt}"
-                                       fi
+                                       src_list="$("${ban_ipset_cmd}" -q list "${src_name}")"
+                                       cnt="$(printf "%s\n" "${src_list}" | awk '/^Number of entries:/{print $4}')"
+                                       cnt_mac="$(printf "%s\n" "${src_list}" | grep -cE "^(([0-9A-Z][0-9A-Z]:){5}[0-9A-Z]{2} packets)")"
+                                       cnt_cidr="$(printf "%s\n" "${src_list}" | grep -cE "(/[0-9]{1,3} packets)")"
+                                       cnt_ip=$((cnt-cnt_cidr-cnt_mac))
+                                       printf "%s\n" "${cnt}" > "${tmp_cnt}"
                                fi
-                               f_iptadd
                        fi
+                       f_iptables
                        end_ts="$(date +%s)"
                        out_rc="${out_rc:-"${in_rc}"}"
-                       f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, settype: ${src_settype:-"-"}, setipv: ${src_setipv:-"-"}, ruletype: ${src_ruletype:-"-"}, count(sum/ip/cidr): ${cnt}/${cnt_ip}/${cnt_cidr}, time: $((end_ts-start_ts)), out_rc: ${out_rc}"
+                       f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, ipver: ${src_ipver:-"-"}, settype: ${src_settype:-"-"}, count(sum/ip/cidr/mac): ${cnt}/${cnt_ip}/${cnt_cidr}/${cnt_mac}, time: $((end_ts-start_ts)), out_rc: ${out_rc}"
                        return "${out_rc}"
                ;;
                "refresh")
-                       if [ -x "${ban_ipset}" ] && [ -n "$("${ban_ipset}" -q -n list "${src_name}")" ]
+                       if [ -n "$("${ban_ipset_cmd}" -q -n list "${src_name}")" ]
                        then
-                               "${ban_ipset}" -q save "${src_name}" > "${tmp_file}"
-                               out_rc="${?}"
-                               if [ -s "${tmp_file}" ] && [ "${out_rc}" -eq 0 ]
+                               out_rc=0
+                               src_list="$("${ban_ipset_cmd}" -q list "${src_name}")"
+                               cnt="$(printf "%s\n" "${src_list}" | awk '/^Number of entries:/{print $4}')"
+                               cnt_mac="$(printf "%s\n" "${src_list}" | grep -cE "^(([0-9A-Z][0-9A-Z]:){5}[0-9A-Z]{2} packets)")"
+                               cnt_cidr="$(printf "%s\n" "${src_list}" | grep -cE "(/[0-9]{1,3} packets)")"
+                               cnt_ip=$((cnt-cnt_cidr-cnt_mac))
+                               printf "%s\n" "${cnt}" > "${tmp_cnt}"
+                               f_iptables
+                       fi
+                       end_ts="$(date +%s)"
+                       out_rc="${out_rc:-"${in_rc}"}"
+                       f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, count(sum/ip/cidr/mac): ${cnt}/${cnt_ip}/${cnt_cidr}/${cnt_mac}, time: $((end_ts-start_ts)), out_rc: ${out_rc}"
+                       return "${out_rc}"
+               ;;
+               "suspend")
+                       for src in ${ban_sources} ${ban_localsources}
+                       do
+                               if [ "${src}" = "maclist" ] && [ -n "$("${ban_ipset_cmd}" -q -n list "${src}")" ]
                                then
-                                       cnt="$(($(wc -l 2>/dev/null < "${tmp_file}")-1))"
-                                       cnt_cidr="$(grep -cF "/" "${tmp_file}")"
-                                       cnt_ip="$((cnt-cnt_cidr))"
-                                       printf "%s\\n" "${cnt}" > "${tmp_cnt}"
+                                       tmp_file="${ban_backupdir}/${src}.file"
+                                       "${ban_ipset_cmd}" -q save "${src}" | tail -n +2 > "${tmp_file}"
+                                       "${ban_ipset_cmd}" -q flush "${src}"
+                               else
+                                       for proto in "4" "6"
+                                       do
+                                               if [ -n "$("${ban_ipset_cmd}" -q -n list "${src}_${proto}")" ]
+                                               then
+                                                       tmp_file="${ban_backupdir}/${src}_${proto}.file"
+                                                       "${ban_ipset_cmd}" -q save "${src}_${proto}" | tail -n +2 > "${tmp_file}"
+                                                       "${ban_ipset_cmd}" -q flush "${src}_${proto}"
+                                               fi
+                                       done
                                fi
-                               f_iptadd
+                       done
+                       f_log "debug" "f_ipset ::: name: ${src:-"-"}, mode: ${mode:-"-"}"
+               ;;
+               "resume")
+                       "${ban_ipset_cmd}" -q -! restore < "${ban_backupdir}/${src_name}.file"
+                       out_rc="${?}"
+                       if [ "${out_rc}" = "0" ]
+                       then
+                               rm -f "${ban_backupdir}/${src_name}.file"
+                               src_list="$("${ban_ipset_cmd}" -q list "${src_name}")"
+                               cnt="$(printf "%s\n" "${src_list}" | awk '/^Number of entries:/{print $4}')"
+                               cnt_mac="$(printf "%s\n" "${src_list}" | grep -cE "^(([0-9A-Z][0-9A-Z]:){5}[0-9A-Z]{2} packets)")"
+                               cnt_cidr="$(printf "%s\n" "${src_list}" | grep -cE "(/[0-9]{1,3} packets)")"
+                               cnt_ip=$((cnt-cnt_cidr-cnt_mac))
+                               printf "%s\n" "${cnt}" > "${tmp_cnt}"
                        fi
+                       f_iptables
                        end_ts="$(date +%s)"
                        out_rc="${out_rc:-"${in_rc}"}"
-                       f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, count: ${cnt}/${cnt_ip}/${cnt_cidr}, time: $((end_ts-start_ts)), out_rc: ${out_rc}"
+                       f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, ipver: ${src_ipver:-"-"}, settype: ${src_settype:-"-"}, count(sum/ip/cidr/mac): ${cnt}/${cnt_ip}/${cnt_cidr}/${cnt_mac}, time: $((end_ts-start_ts)), out_rc: ${out_rc}"
                        return "${out_rc}"
                ;;
                "flush")
-                       f_iptadd "remove"
-
-                       if [ -x "${ban_ipset}" ] && [ -n "$("${ban_ipset}" -q -n list "${src_name}")" ]
+                       if [ -n "$("${ban_ipset_cmd}" -q -n list "${src_name}")" ]
                        then
-                               "${ban_ipset}" -q flush "${src_name}"
-                               "${ban_ipset}" -q destroy "${src_name}"
+                               f_iptables "destroy"
+                               out_rc=0
                        fi
-                       f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}"
+                       out_rc="${out_rc:-"${in_rc}"}"
+                       f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, out_rc: ${out_rc}"
+                       return "${out_rc}"
                ;;
                "destroy")
-                       for chain in ${ban_log_chain_src} ${ban_log_chain_dst} ${ban_chain}
+                       for chain in ${ban_chain} ${ban_logchain_src} ${ban_logchain_dst}
                        do
-                               if [ -x "${ban_ipt}" ] && [ -x "${ban_ipt_save}" ] && [ -x "${ban_ipt_restore}" ] && \
-                                       [ -n "$("${ban_ipt}" "${timeout}" -nL "${chain}" 2>/dev/null)" ]
+                               if [ -n "$("${ban_ipt4_cmd}" "${timeout}" -nL "${chain}" 2>/dev/null)" ]
                                then
-                                       "${ban_ipt_save}" | grep -v -- "-j ${chain}" | "${ban_ipt_restore}"
-                                       "${ban_ipt}" "${timeout}" -F "${chain}" 2>/dev/null
-                                       "${ban_ipt}" "${timeout}" -X "${chain}" 2>/dev/null
+                                       "${ban_ipt4_savecmd}" | grep -v -- "-j ${chain}" | "${ban_ipt4_restorecmd}"
+                                       "${ban_ipt4_cmd}" "${timeout}" -F "${chain}" 2>/dev/null
+                                       "${ban_ipt4_cmd}" "${timeout}" -X "${chain}" 2>/dev/null
                                fi
-                               if [ -x "${ban_ipt6}" ] && [ -x "${ban_ipt6_save}" ] && [ -x "${ban_ipt6_restore}" ] && \
-                                       [ -n "$("${ban_ipt6}" "${timeout}" -nL "${chain}" 2>/dev/null)" ]
+                               if [ -n "$("${ban_ipt6_cmd}" "${timeout}" -nL "${chain}" 2>/dev/null)" ]
                                then
-                                       "${ban_ipt6_save}" | grep -v -- "-j ${chain}" | "${ban_ipt6_restore}"
-                                       "${ban_ipt6}" "${timeout}" -F "${chain}" 2>/dev/null
-                                       "${ban_ipt6}" "${timeout}" -X "${chain}" 2>/dev/null
+                                       "${ban_ipt6_savecmd}" | grep -v -- "-j ${chain}" | "${ban_ipt6_restorecmd}"
+                                       "${ban_ipt6_cmd}" "${timeout}" -F "${chain}" 2>/dev/null
+                                       "${ban_ipt6_cmd}" "${timeout}" -X "${chain}" 2>/dev/null
                                fi
                        done
-                       for source in ${ban_sources}
+                       for src in ${ban_sources} ${ban_localsources}
                        do
-                               if [ -x "${ban_ipset}" ] && [ -n "$("${ban_ipset}" -q -n list "${source}")" ]
+                               if [ "${src}" = "maclist" ] && [ -n "$("${ban_ipset_cmd}" -q -n list "${src}")" ]
                                then
-                                       "${ban_ipset}" -q destroy "${source}"
+                                       "${ban_ipset_cmd}" -q destroy "${src}"
+                               else
+                                       for proto in "4" "6"
+                                       do
+                                               if [ -n "$("${ban_ipset_cmd}" -q -n list "${src}_${proto}")" ]
+                                               then
+                                                       "${ban_ipset_cmd}" -q destroy "${src}_${proto}"
+                                               fi
+                                       done
                                fi
                        done
                        f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}"
@@ -658,20 +873,20 @@ f_log()
 {
        local class="${1}" log_msg="${2}"
 
-       if [ -n "${log_msg}" ] && { [ "${class}" != "debug" ] || [ "${ban_debug}" -eq 1 ]; }
+       if [ -n "${log_msg}" ] && { [ "${class}" != "debug" ] || [ "${ban_debug}" = "1" ]; }
        then
-               if [ -x "${ban_logger}" ]
+               if [ -x "${ban_logger_cmd}" ]
                then
-                       "${ban_logger}" -p "${class}" -t "banIP-${ban_ver}[${$}]" "${log_msg}"
+                       "${ban_logger_cmd}" -p "${class}" -t "banIP-${ban_ver%-*}[${$}]" "${log_msg}"
                else
-                       printf "%s %s %s\\n" "${class}" "banIP-${ban_ver}[${$}]" "${log_msg}"
+                       printf "%s %s %s\n" "${class}" "banIP-${ban_ver%-*}[${$}]" "${log_msg}"
                fi
                if [ "${class}" = "err" ]
                then
-                       f_jsnup error
-                       f_ipset destroy
-                       f_rmbackup
-                       f_rmtemp
+                       f_jsnup "error"
+                       f_ipset "destroy"
+                       f_rmbckp
+                       f_rmtmp
                        exit 1
                fi
        fi
@@ -679,327 +894,759 @@ f_log()
 
 # start log service to trace failed ssh/luci logins
 #
-f_bgserv()
+f_bgsrv()
 {
-       local bg_pid status="${1}"
+       local bg_pid action="${1}"
 
-       bg_pid="$(pgrep -f "^/bin/sh ${ban_logservice}.*|^logread -f -e ${ban_sshdaemon}\|luci: failed login|^grep -qE Exit before auth|luci: failed login|[0-9]+ \[preauth\]$" | awk '{ORS=" "; print $1}')"
-       if [ -z "${bg_pid}" ] && [ "${status}" = "start" ] \
-               && [ -x "${ban_logservice}" ] && [ "${ban_realtime}" = "true" ]
+       bg_pid="$(pgrep -f "^/bin/sh ${ban_logservice}|logread -f|^grep -q Exit|^grep -q error|^grep -q luci" | awk '{ORS=" "; print $1}')"
+       if [ -z "${bg_pid}" ] && [ "${action}" = "start" ] && [ -x "${ban_logservice}" ] && [ "${ban_monitor_enabled}" = "1" ]
        then
-               ( "${ban_logservice}" "${ban_ver}" "${ban_sshdaemon}" & )
-       elif [ -n "${bg_pid}" ] && [ "${status}" = "stop" ]
+               if [ -n "$(printf "%s\n" "${ban_logterms}" | grep -F "dropbear")" ]
+               then
+                       ban_search="Exit before auth from\|"
+               fi
+               if [ -n "$(printf "%s\n" "${ban_logterms}" | grep -F "sshd")" ]
+               then
+                       ban_search="${ban_search}error: maximum authentication attempts exceeded\|sshd.*Connection closed by.*\[preauth\]\|"
+               fi
+               if [ -n "$(printf "%s\n" "${ban_logterms}" | grep -F "luci")" ]
+               then
+                       ban_search="${ban_search}luci: failed login"
+               fi
+               ( "${ban_logservice}" "${ban_ver}" "${ban_search}" & )
+       elif [ -n "${bg_pid}" ] && [ "${action}" = "stop" ]
        then
                kill -HUP "${bg_pid}" 2>/dev/null
        fi
-       f_log "debug" "f_bgserv ::: status: ${status:-"-"}, bg_pid: ${bg_pid:-"-"}, ban_realtime: ${ban_realtime:-"-"}, log_service: ${ban_logservice:-"-"}"
+       f_log "debug" "f_bgsrv ::: action: ${action:-"-"}, bg_pid: ${bg_pid:-"-"}, monitor_enabled: ${ban_monitor_enabled:-"-"}, log_service: ${ban_logservice:-"-"}"
+}
+
+# download controller
+#
+f_down()
+{
+       local src_name="${1}" proto="${2}" src_ipver="${3}" src_url="${4}" src_rule="${5}" src_comp="${6}"
+       local ip start_ts end_ts src_settype src_log src_rc tmp_load tmp_file tmp_raw tmp_cnt tmp_err
+
+       start_ts="$(date +%s)"
+       if [ -n "$(printf "%s\n" "${ban_settype_src}" | grep -F "${src_name}")" ]
+       then
+               src_settype="src"
+       elif [ -n "$(printf "%s\n" "${ban_settype_dst}" | grep -F "${src_name}")" ]
+       then
+               src_settype="dst"
+       elif [ -n "$(printf "%s\n" "${ban_settype_all}" | grep -F "${src_name}")" ]
+       then
+               src_settype="src+dst"
+       else
+               src_settype="${ban_global_settype}"
+       fi
+       src_name="${src_name}_${proto}"
+       tmp_load="${ban_tmpfile}.${src_name}.load"
+       tmp_file="${ban_tmpfile}.${src_name}.file"
+       tmp_raw="${tmp_file}.raw"
+       tmp_cnt="${tmp_file}.cnt"
+       tmp_err="${tmp_file}.err"
+
+       # 'resume' mode
+       #
+       if [ "${ban_action}" = "resume" ]
+       then
+               if [ "${src_name%_*}" = "maclist" ]
+               then
+                       src_name="maclist"
+               fi
+               f_ipset "resume"
+               src_rc="${?}"
+               if [ "${src_rc}" = "0" ]
+               then
+                       return
+               fi
+       fi
+
+       # handle local downloads
+       #
+       case "${src_name%_*}" in
+               "blacklist"|"whitelist")
+                       awk "${src_rule}" "${src_url}" > "${tmp_file}"
+                       src_rc="${?}"
+                       if [ "${src_rc}" = "0" ]
+                       then
+                               f_ipset "create"
+                       else
+                               f_log "debug" "f_down  ::: name: ${src_name}, url: ${src_url}, rule: ${src_rule}, rc: ${src_rc}"
+                       fi
+                       return
+               ;;
+               "maclist")
+                       src_name="${src_name%_*}"
+                       tmp_file="${ban_tmpfile}.${src_name}.file"
+                       tmp_cnt="${tmp_file}.cnt"
+                       tmp_err="${tmp_file}.err"
+                       awk "${src_rule}" "${src_url}" > "${tmp_file}"
+                       src_rc="${?}"
+                       if [ "${src_rc}" = "0" ]
+                       then
+                               f_ipset "create"
+                       else
+                               f_log "debug" "f_down  ::: name: ${src_name}, url: ${src_url}, rule: ${src_rule}, rc: ${src_rc}"
+                       fi
+                       return
+               ;;
+       esac
+
+       # 'refresh' mode
+       #
+       if [ "${ban_action}" = "refresh" ]
+       then
+               f_ipset "refresh"
+               src_rc="${?}"
+               if [ "${src_rc}" = "0" ]
+               then
+                       return
+               fi
+       fi
+
+       # 'start' mode
+       #
+       if [ "${ban_action}" = "start" ]
+       then
+               f_ipset "restore"
+       fi
+       src_rc="${?}"
+       if [ "${src_rc}" = "0" ]
+       then
+               awk "${src_rule}" "${tmp_load}" 2>/dev/null > "${tmp_file}"
+               src_rc="${?}"
+               if [ "${src_rc}" = "0" ]
+               then
+                       f_ipset "create"
+                       src_rc="${?}"
+                       if [ "${src_rc}" = "0" ]
+                       then
+                               return
+                       fi
+               fi
+       fi
+
+       # handle country related downloads
+       #
+       if [ "${src_name%_*}" = "country" ]
+       then
+               for country in ${ban_countries}
+               do
+                       src_log="$("${ban_fetchutil}" ${ban_fetchparm} "${tmp_raw}" "${src_url}${country}-aggregated.zone" 2>&1)"
+                       src_rc="${?}"
+                       if [ "${src_rc}" = "0" ]
+                       then
+                               cat "${tmp_raw}" 2>/dev/null >> "${tmp_load}"
+                       else
+                               continue
+                       fi
+               done
+
+       # handle asn related downloads
+       #
+       elif [ "${src_name%_*}" = "asn" ]
+       then
+               for asn in ${ban_asns}
+               do
+                       src_log="$("${ban_fetchutil}" ${ban_fetchparm} "${tmp_raw}" "${src_url}AS${asn}" 2>&1)"
+                       src_rc="${?}"
+                       if [ "${src_rc}" = "0" ]
+                       then
+                               cat "${tmp_raw}" 2>/dev/null >> "${tmp_load}"
+                       else
+                               continue
+                       fi
+               done
+
+       # handle compressed downloads
+       #
+       elif [ -n "${src_comp}" ]
+       then
+               case "${src_comp}" in
+                       "gz")
+                               src_log="$("${ban_fetchutil}" ${ban_fetchparm} "${tmp_raw}" "${src_url}" 2>&1)"
+                               src_rc="${?}"
+                               if [ "${src_rc}" -eq 0 ]
+                               then
+                                       zcat "${tmp_raw}" 2>/dev/null > "${tmp_load}"
+                                       src_rc="${?}"
+                               fi
+                       ;;
+               esac
+
+       # handle normal downloads
+       #
+       else
+               src_log="$("${ban_fetchutil}" ${ban_fetchparm} "${tmp_load}" "${src_url}" 2>&1)"
+               src_rc="${?}"
+       fi
+
+       # download post-processing (backup, restore, regex)
+       #
+       if [ "${src_rc}" = "0" ]
+       then
+               f_ipset "backup"
+               src_rc="${?}"
+       elif [ "${ban_action}" != "start" ] && [ "${ban_action}" != "refresh" ]
+       then
+               f_ipset "restore"
+               src_rc="${?}"
+       fi
+       if [ "${src_rc}" = "0" ]
+       then
+               awk "${src_rule}" "${tmp_load}" 2>/dev/null > "${tmp_file}"
+               src_rc="${?}"
+               if [ "${src_rc}" = "0" ]
+               then
+                       f_ipset "create"
+                       src_rc="${?}"
+               elif [ "${ban_action}" != "refresh" ]
+               then
+                       f_ipset "refresh"
+                       src_rc="${?}"
+               fi
+       else
+               src_log="$(printf "%s" "${src_log}" | awk '{ORS=" ";print $0}')"
+               if [ "${ban_action}" != "refresh" ]
+               then
+                       f_ipset "refresh"
+                       src_rc="${?}"
+               fi
+               f_log "debug" "f_down  ::: name: ${src_name}, url: ${src_url}, rule: ${src_rule}, rc: ${src_rc}, log: ${src_log:-"-"}"
+       fi
 }
 
-# main function for banIP processing
+# main controller
 #
 f_main()
 {
-       local pid pid_list start_ts end_ts ip tmp_raw tmp_cnt tmp_load tmp_file mem_total mem_free cnt=1
-       local src_name src_on src_url src_rset src_setipv src_settype src_ruletype src_cat src_log src_addon src_ts src_rc
-       local wan_input wan_forward lan_input lan_forward target_src target_dst ssh_log luci_log
+       local src_name src_url_4 src_rule_4 src_url_6 src_rule_6 src_comp src_rc src_ts log_raw log_merge hold err_file cnt_file cnt=0
 
-       if [ "${ban_sshdaemon}" = "dropbear" ]
+       # prepare logfile excerpts (dropbear, sshd, luci)
+       #
+       if [ "${ban_autoblacklist}" = "1" ] || [ "${ban_monitor_enabled}" = "1" ]
        then
-               ssh_log="$(logread -e "${ban_sshdaemon}" | grep -o "${ban_sshdaemon}.*" | sed 's/:[0-9]*$//g')"
-       elif [ "${ban_sshdaemon}" = "sshd" ]
+               log_raw="$(logread -l "${ban_loglimit}")"
+               if [ -n "$(printf "%s\n" "${ban_logterms}" | grep -F "dropbear")" ]
+               then
+                       log_merge="$(printf "%s\n" "${log_raw}" | grep "Exit before auth from" | awk 'match($0,/<[0-9A-f:\.]+:/){printf "%s\n",substr($0,RSTART+1,RLENGTH-2)}')"
+               fi
+               if [ -n "$(printf "%s\n" "${ban_logterms}" | grep -F "sshd")" ]
+               then
+                       log_merge="${log_merge} $(printf "%s\n" "${log_raw}" | grep "error: maximum authentication attempts exceeded\|sshd.*Connection closed by.*\[preauth\]" | awk 'match($0,/[0-9A-f:\.]+ port/){printf "%s\n",substr($0,RSTART,RLENGTH-5)}')"
+               fi
+               if [ -n "$(printf "%s\n" "${ban_logterms}" | grep -F "luci")" ]
+               then
+                       log_merge="${log_merge} $(printf "%s\n" "${log_raw}" | grep "luci: failed login on " | awk 'match($0,/[0-9A-f:\.]+$/){printf "%s\n",substr($0,RSTART,RLENGTH)}')"
+               fi
+               log_merge="$(printf "%s" "${log_merge}" | awk '{ORS=" ";print $0}')"
+       fi
+
+       # prepare new black- and whitelist entries
+       #
+       if [ "${ban_autowhitelist}" = "1" ] && [ -f "${ban_whitelist}" ]
+       then
+               for ip in ${ban_subnets}
+               do
+                       if [ -z "$(grep -F "${ip}" "${ban_whitelist}")" ]
+                       then
+                               src_ts="# added on $(date "+%d.%m.%Y %H:%M:%S")"
+                               printf "%-42s%s\n" "${ip}" "${src_ts}" >> "${ban_whitelist}"
+                               f_log "info" "IP address '${ip}' added to whitelist"
+                       fi
+               done
+       fi
+       if [ "${ban_autoblacklist}" = "1" ] && [ -f "${ban_blacklist}" ]
        then
-               ssh_log="$(logread -e "${ban_sshdaemon}" | grep -o "${ban_sshdaemon}.*" | sed 's/ port.*$//g')"
+               for ip in ${log_merge}
+               do
+                       if [ -z "$(grep -F "${ip}" "${ban_blacklist}")" ]
+                       then
+                               src_ts="# added on $(date "+%d.%m.%Y %H:%M:%S")"
+                               printf "%-42s%s\n" "${ip}" "${src_ts}" >> "${ban_blacklist}"
+                               f_log "info" "IP address '${ip}' added to blacklist"
+                       fi
+               done
        fi
-       luci_log="$(logread -e "luci: failed login" | grep -o "luci:.*")"
-       mem_total="$(awk '/^MemTotal/ {print int($2/1000)}' "/proc/meminfo" 2>/dev/null)"
-       mem_free="$(awk '/^MemFree/ {print int($2/1000)}' "/proc/meminfo" 2>/dev/null)"
-       f_log "debug" "f_main  ::: fetch_util: ${ban_fetchutil:-"-"}, fetch_parm: ${ban_fetchparm:-"-"}, ssh_daemon: ${ban_sshdaemon}, interface(s): ${ban_iface:-"-"}, device(s): ${ban_dev:-"-"}, all_devices: ${ban_dev_all:-"-"}, backup_dir: ${ban_backupdir:-"-"}, mem_total: ${mem_total:-0}, mem_free: ${mem_free:-0}, max_queue: ${ban_maxqueue}"
 
-       # chain creation
+       # initial ipset/iptables creation
        #
-       f_ipset initial
-       if [ "${?}" -ne 0 ]
+       f_ipset "initial"
+       if [ "${?}" != "0" ]
        then
-               f_log "err" "banIP processing failed, fatal error during iptables chain creation (${ban_sysver})"
+               f_log "err" "banIP processing failed, fatal error during ipset/iptables creation (${ban_sysver})"
        fi
 
-       # main loop
+       # load local source files (maclist, blacklist, whitelist)
        #
-       for src_name in ${ban_sources}
+       for src_name in ${ban_localsources}
        do
-               unset src_on
-               if [ "${src_name##*_}" = "6" ]
+               if [ "${src_name}" = "maclist" ] && [ -s "${ban_maclist}" ]
                then
-                       if [ -x "${ban_ipt6}" ]
+                       (
+                               src_rule_4="/^([0-9A-z][0-9A-z]:){5}[0-9A-z]{2}([[:space:]]|$)/{print \"add ${src_name} \"toupper(\$1)}"
+                               f_down "${src_name}" "mac" "mac" "${ban_maclist}" "${src_rule_4}"
+                       )&
+               fi
+               if [ "${ban_proto4_enabled}" = "1" ]
+               then
+                       if [ "${src_name}" = "blacklist" ] && [ -s "${ban_blacklist}" ]
+                       then
+                               (
+                                       src_rule_4="/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add ${src_name}_4 \"\$1}"
+                                       f_down "${src_name}" "4" "inet" "${ban_blacklist}" "${src_rule_4}"
+                               )&
+                       elif [ "${src_name}" = "whitelist" ] && [ -s "${ban_whitelist}" ]
                        then
-                               src_on="$(eval printf "%s" \"\$\{ban_src_on_6_${src_name%_6*}\}\")"
-                               src_url="$(eval printf "%s" \"\$\{ban_src_6_${src_name%_6*}\}\")"
-                               src_rset="$(eval printf "%s" \"\$\{ban_src_rset_6_${src_name%_6*}\}\")"
-                               src_setipv="inet6"
-                               wan_input="${ban_wan_input_chain_6:-"input_wan_rule"}"
-                               wan_forward="${ban_wan_forward_chain_6:-"forwarding_wan_rule"}"
-                               lan_input="${ban_lan_input_chain_6:-"input_lan_rule"}"
-                               lan_forward="${ban_lan_forward_chain_6:-"forwarding_lan_rule"}"
-                               target_src="${ban_target_src_6:-"DROP"}"
-                               target_dst="${ban_target_dst_6:-"REJECT"}"
+                               (
+                                       src_rule_4="/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add ${src_name}_4 \"\$1}"
+                                       f_down "${src_name}" "4" "inet" "${ban_whitelist}" "${src_rule_4}"
+                               )&
                        fi
-               else
-                       if [ -x "${ban_ipt}" ]
+               fi
+               if [ "${ban_proto6_enabled}" = "1" ]
+               then
+                       if [ "${src_name}" = "blacklist" ] && [ -s "${ban_blacklist}" ]
                        then
-                               src_on="$(eval printf "%s" \"\$\{ban_src_on_${src_name}\}\")"
-                               src_url="$(eval printf "%s" \"\$\{ban_src_${src_name}\}\")"
-                               src_rset="$(eval printf "%s" \"\$\{ban_src_rset_${src_name}\}\")"
-                               src_setipv="inet"
-                               wan_input="${ban_wan_input_chain:-"input_wan_rule"}"
-                               wan_forward="${ban_wan_forward_chain:-"forwarding_wan_rule"}"
-                               lan_input="${ban_lan_input_chain:-"input_lan_rule"}"
-                               lan_forward="${ban_lan_forward_chain:-"forwarding_lan_rule"}"
-                               target_src="${ban_target_src:-"DROP"}"
-                               target_dst="${ban_target_dst:-"REJECT"}"
+                               (
+                                       src_rule_6="/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\\/(1?[0-2][0-8]|[0-9][0-9]))?)([[:space:]]|$)/{print \"add ${src_name}_6 \"\$1}"
+                                       f_down "${src_name}" "6" "inet6" "${ban_blacklist}" "${src_rule_6}"
+                               )&
+                       elif [ "${src_name}" = "whitelist" ] && [ -s "${ban_whitelist}" ]
+                       then
+                               (
+                                       src_rule_6="/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\\/(1?[0-2][0-8]|[0-9][0-9]))?)([[:space:]]|$)/{print \"add ${src_name}_6 \"\$1}"
+                                       f_down "${src_name}" "6" "inet6" "${ban_whitelist}" "${src_rule_6}"
+                               )&
                        fi
                fi
-               src_settype="$(eval printf "%s" \"\$\{ban_src_settype_${src_name%_6*}\}\")"
-               src_ruletype="$(eval printf "%s" \"\$\{ban_src_ruletype_${src_name%_6*}\}\")"
-               src_cat="$(eval printf "%s" \"\$\{ban_src_cat_${src_name%_6*}\}\")"
-               src_addon=""
-               src_rc=4
-               tmp_load="${ban_tmpfile}.${src_name}.load"
-               tmp_file="${ban_tmpfile}.${src_name}.file"
-               tmp_raw="${tmp_file}.raw"
-               tmp_cnt="${tmp_file}.cnt"
-               tmp_err="${tmp_file}.err"
-
-               # basic pre-checks
-               #
-               f_log "debug" "f_main  ::: name: ${src_name}, src_on: ${src_on:-"-"}"
+       done
+       wait
 
-               if [ -z "${src_on}" ] || [ "${src_on}" != "1" ] || [ -z "${src_url}" ] || \
-                       [ -z "${src_rset}" ] || [ -z "${src_settype}" ] || [ -z "${src_ruletype}" ]
+       # loop over all external sources
+       #
+       for src_name in ${ban_sources}
+       do
+               # get source data from JSON file
+               #
+               json_select "${src_name}" >/dev/null 2>&1
+               if [ "${?}" != "0" ]
                then
-                       f_ipset flush
-                       f_ipset remove
                        continue
-               elif [ "${ban_action}" = "refresh" ] && [ ! -f "${src_url}" ]
+               fi
+               json_objects="url_4 rule_4 url_6 rule_6 comp"
+               for object in ${json_objects}
+               do
+                       eval json_get_var src_${object} "\${object}" >/dev/null 2>&1
+               done
+               json_select ..
+
+               # handle external IPv4 source downloads in a subshell
+               #
+               if [ "${ban_proto4_enabled}" = "1" ] && [ -n "${src_url_4}" ] && [ -n "${src_rule_4}" ]
                then
-                       start_ts="$(date +%s)"
-                       f_ipset refresh
-                       if [ "${?}" -eq 0 ]
-                       then
-                               continue
-                       fi
+                       (
+                               f_down "${src_name}" "4" "inet" "${src_url_4}" "${src_rule_4}" "${src_comp}"
+                       )&
                fi
 
-               # download queue processing
+               # handle external IPv6 source downloads in a subshell
                #
-               (
-                       start_ts="$(date +%s)"
-                       if [ "${ban_action}" = "start" ] && [ ! -f "${src_url}" ]
+               if [ "${ban_proto6_enabled}" = "1" ] && [ -n "${src_url_6}" ] && [ -n "${src_rule_6}" ]
+               then
+                       (
+                               f_down "${src_name}" "6" "inet6" "${src_url_6}" "${src_rule_6}" "${src_comp}"
+                       )&
+               fi
+
+               # control/limit download queues
+               #
+               hold=$((cnt%ban_maxqueue))
+               if [ "${hold}" = "0" ]
+               then
+                       wait
+               fi
+               cnt=$((cnt+1))
+       done
+       wait
+
+       # error out
+       #
+       for err_file in "${ban_tmpfile}."*".err"
+       do
+               if [ -f "${err_file}" ]
+               then
+                       f_log "err" "banIP processing failed, fatal iptables errors during subshell processing (${ban_sysver})"
+               fi
+       done
+
+       # finish processing
+       #
+       ban_sources=""
+       for cnt_file in "${ban_tmpfile}."*".cnt"
+       do
+               if [ -f "${cnt_file}" ]
+               then
+                       read -r cnt < "${cnt_file}"
+                       ban_cnt=$((ban_cnt+cnt))
+                       ban_setcnt=$((ban_setcnt+1))
+                       src_name="$(printf "%s" "${cnt_file}" | grep -Eo "[a-z0-9_]+.file.cnt")"
+                       src_name="${src_name%%.*}"
+                       if [ -z "$(printf "%s" "${ban_sources}" | grep -F "${src_name%_*}")" ]
                        then
-                               f_ipset restore
+                               ban_sources="${ban_sources} ${src_name%_*}"
+                               ban_allsources="${ban_allsources/${src_name%_*}/}"
                        fi
-                       src_rc="${?}"
-                       if [ "${src_rc}" -ne 0 ] || [ ! -s "${tmp_load}" ]
+               fi
+       done
+       for src_name in ${ban_allsources}
+       do
+               if [ "${src_name}" = "maclist" ]
+               then
+                       f_ipset "flush"
+               else
+                       for proto in "4" "6"
+                       do
+                               src_name="${src_name%_*}_${proto}"
+                               f_ipset "flush"
+                               if [ "${src_name%_*}" != "blacklist" ] && [ "${src_name%_*}" != "whitelist" ]
+                               then
+                                       f_ipset "remove"
+                               fi
+                       done
+               fi
+       done
+       f_log "info" "${ban_setcnt} IPSets with overall ${ban_cnt} IPs/Prefixes loaded successfully (${ban_sysver})"
+       f_bgsrv "start"
+       f_jsnup
+       f_rmtmp
+}
+
+# query ipsets for certain IP
+#
+f_query()
+{
+       local src proto result query_start query_end query_timeout="30" match="0" search="${1}"
+
+       if [ -z "${search}" ]
+       then
+               printf "%s\n" "::: missing search term, please submit a single ip or mac address :::"
+       else
+               query_start="$(date "+%s")"
+               printf "%s\n%s\n%s\n" ":::" "::: search '${search}' in banIP related IPSets" ":::"
+
+               for src in ${ban_localsources} ${ban_sources}
+               do
+                       if [ "${src}" = "maclist" ] && [ -n "$("${ban_ipset_cmd}" -q -n list "${src}")" ]
                        then
-                               if [ -f "${src_url}" ]
+                               result="$(ipset -q test ${src} ${search} >/dev/null 2>&1; printf "%u" "${?}")"
+                               if [ "${result}" = "0" ]
                                then
-                                       src_log="$(cat "${src_url}" 2>/dev/null > "${tmp_load}")"
-                                       src_rc="${?}"
-                                       case "${src_name}" in
-                                               "whitelist")
-                                                       src_addon="${ban_subnets}"
-                                               ;;
-                                               "whitelist_6")
-                                                       src_addon="${ban_subnets6}"
-                                               ;;
-                                               "blacklist")
-                                                       if [ "${ban_sshdaemon}" = "dropbear" ]
-                                                       then
-                                                               pid_list="$(printf "%s\\n" "${ssh_log}" | grep -F "Exit before auth" | awk 'match($0,/(\[[0-9]+\])/){ORS=" ";print substr($0,RSTART,RLENGTH)}')"
-                                                               for pid in ${pid_list}
-                                                               do
-                                                                       src_addon="${src_addon} $(printf "%s\\n" "${ssh_log}" | grep -F "${pid}" | awk 'match($0,/([0-9]{1,3}\.){3}[0-9]{1,3}$/){ORS=" ";print substr($0,RSTART,RLENGTH);exit}')"
-                                                               done
-                                                       elif [ "${ban_sshdaemon}" = "sshd" ]
-                                                       then
-                                                               src_addon="$(printf "%s\\n" "${ssh_log}" | grep -F "error: maximum authentication attempts exceeded" | awk 'match($0,/([0-9]{1,3}\.){3}[0-9]{1,3}$/){ORS=" ";print substr($0,RSTART,RLENGTH)}')"
-                                                       fi
-                                                       src_addon="${src_addon} $(printf "%s\\n" "${luci_log}" | awk 'match($0,/([0-9]{1,3}\.){3}[0-9]{1,3}$/){ORS=" ";print substr($0,RSTART,RLENGTH)}')"
-                                               ;;
-                                               "blacklist_6")
-                                                       if [ "${ban_sshdaemon}" = "dropbear" ]
-                                                       then
-                                                               pid_list="$(printf "%s\\n" "${ssh_log}" | grep -F "Exit before auth" | awk 'match($0,/(\[[0-9]+\])/){ORS=" ";print substr($0,RSTART,RLENGTH)}')"
-                                                               for pid in ${pid_list}
-                                                               do
-                                                                       src_addon="${src_addon} $(printf "%s\\n" "${ssh_log}" | grep -F "${pid}" | awk 'match($0,/(([0-9A-f]{0,4}::?){1,7}[0-9A-f]{0,4}$)/){ORS=" ";print substr($0,RSTART,RLENGTH);exit}')"
-                                                               done
-                                                       elif [ "${ban_sshdaemon}" = "sshd" ]
-                                                       then
-                                                               src_addon="$(printf "%s\\n" "${ssh_log}" | grep -F "error: maximum authentication attempts exceeded" | awk 'match($0,/(([0-9A-f]{0,4}::?){1,7}[0-9A-f]{0,4}$)/){ORS=" ";print substr($0,RSTART,RLENGTH)}')"
-                                                       fi
-                                                       src_addon="${src_addon} $(printf "%s\\n" "${luci_log}" | awk 'match($0,/(([0-9A-f]{0,4}::?){1,7}[0-9A-f]{0,4}$)/){ORS=" ";print substr($0,RSTART,RLENGTH)}')"
-                                               ;;
-                                       esac
-                                       for ip in ${src_addon}
-                                       do
-                                               if [ -z "$(grep -F "${ip}" "${src_url}")" ]
+                                       match="1"
+                                       printf "%s\n" "  - found in IPSet '${src}'"
+                                       break
+                               fi
+                       else
+                               for proto in "4" "6"
+                               do
+                                       if [ -n "$("${ban_ipset_cmd}" -q -n list "${src}_${proto}")" ]
+                                       then
+                                               result="$(ipset -q test ${src}_${proto} ${search} >/dev/null 2>&1; printf "%u" "${?}")"
+                                               if [ "${result}" = "0" ]
                                                then
-                                                       printf "%s\\n" "${ip}" >> "${tmp_load}"
-                                                       if { [ "${src_name//_*/}" = "blacklist" ] && [ "${ban_autoblacklist}" -eq 1 ]; } || \
-                                                               { [ "${src_name//_*/}" = "whitelist" ] && [ "${ban_autowhitelist}" -eq 1 ]; }
-                                                       then
-                                                               src_ts="# auto-added $(date "+%d.%m.%Y %H:%M:%S")"
-                                                               printf "%s %s\\n" "${ip}" "${src_ts}" >> "${src_url}"
-                                                       fi
+                                                       match="1"
+                                                       printf "%s\n" "  - found in IPSet '${src}_${proto}'"
                                                fi
-                                       done
-                               elif [ -n "${src_cat}" ]
+                                       fi
+                               done
+                       fi
+                       query_end="$(date "+%s")"
+                       if [ "$((query_end-query_start))" -gt "${query_timeout}" ]
+                       then
+                               printf "%s\n\n" "  - [...]"
+                               break
+                       fi
+               done
+               if [ "${match}" = "0" ]
+               then
+                       printf "%s\n\n" "  - no match"
+               fi
+       fi
+}
+
+# generate statistics
+#
+f_report()
+{
+       local report_json report_txt bg_pid content proto src src_list cnt cnt_mac cnt_cidr cnt_ip cnt_acc cnt_sum="0" cnt_set_sum="1" cnt_acc_sum="0" cnt_mac_sum="0" cnt_ip_sum="0" cnt_cidr_sum="0" cnt_set_sum="0" action="${1}"
+
+       report_json="${ban_reportdir}/ban_report.json"
+       report_txt="${ban_reportdir}/ban_mailreport.txt"
+
+       # build json file
+       #
+       if [ "${action}" != "json" ] && { [ -n "$("${ban_ipt4_savecmd}" | grep " ${ban_chain} ")" ] || [ -n "$("${ban_ipt6_savecmd}" | grep " ${ban_chain} ")" ]; }
+       then
+               > "${report_json}"
+               > "${report_txt}"
+               printf "%s\n" "{" >> "${report_json}"
+               printf "\t%s\n" "\"ipsets\": {" >> "${report_json}"
+               for src in ${ban_localsources} ${ban_sources}
+               do
+                       if [ -n "$(printf "%s\n" "${ban_settype_src}" | grep -F "${src}")" ]
+                       then
+                               set_type="src"
+                       elif [ -n "$(printf "%s\n" "${ban_settype_dst}" | grep -F "${src}")" ]
+                       then
+                               set_type="dst"
+                       elif [ -n "$(printf "%s\n" "${ban_settype_all}" | grep -F "${src}")" ]
+                       then
+                               set_type="src+dst"
+                       else
+                               set_type="${ban_global_settype}"
+                       fi
+                       if [ "${src}" = "maclist" ]
+                       then
+                               src_list="$("${ban_ipset_cmd}" -q list "${src}")"
+                               if [ -n "${src_list}" ]
                                then
-                                       if [ "${src_cat//[0-9]/}" != "${src_cat}" ]
+                                       cnt="$(printf "%s" "${src_list}" | awk '/^Number of entries:/{print $4}')"
+                                       cnt_acc="$(printf "%s" "${src_list}" | grep -cE "^(([0-9A-Z][0-9A-Z]:){5}[0-9A-Z]{2} packets [1-9]+)")"
+                                       cnt_acc_sum=$((cnt_acc_sum+cnt_acc))
+                                       cnt_mac_sum="${cnt}"
+                                       cnt_sum=$((cnt_sum+cnt))
+                                       if [ "${cnt_set_sum}" != "0" ]
                                        then
-                                               for as in ${src_cat}
-                                               do
-                                                       src_log="$("${ban_fetchutil}" ${ban_fetchparm} "${tmp_raw}" "${src_url}AS${as}" 2>&1)"
-                                                       src_rc="${?}"
-                                                       if [ "${src_rc}" -eq 0 ]
-                                                       then
-                                                               jsonfilter -i "${tmp_raw}" -e '@.data.prefixes.*.prefix' 2>/dev/null >> "${tmp_load}"
-                                                       else
-                                                               break
-                                                       fi
-                                               done
-                                               if [ "${src_rc}" -eq 0 ]
-                                               then
-                                                       f_ipset backup
-                                               elif [ "${ban_action}" != "start" ]
-                                               then
-                                                       f_ipset restore
-                                               fi
-                                       else
-                                               for co in ${src_cat}
-                                               do
-                                                       src_log="$("${ban_fetchutil}" ${ban_fetchparm} "${tmp_raw}" "${src_url}${co}&v4_format=prefix" 2>&1)"
-                                                       src_rc="${?}"
-                                                       if [ "${src_rc}" -eq 0 ]
-                                                       then
-                                                               if [ "${src_name##*_}" = "6" ]
-                                                               then
-                                                                       jsonfilter -i "${tmp_raw}" -e '@.data.resources.ipv6.*' 2>/dev/null >> "${tmp_load}"
-                                                               else
-                                                                       jsonfilter -i "${tmp_raw}" -e '@.data.resources.ipv4.*' 2>/dev/null >> "${tmp_load}"
-                                                               fi
-                                                       else
-                                                               break
-                                                       fi
-                                               done
-                                               if [ "${src_rc}" -eq 0 ]
-                                               then
-                                                       f_ipset backup
-                                               elif [ "${ban_action}" != "start" ]
-                                               then
-                                                       f_ipset restore
-                                               fi
+                                               printf "%s\n" "," >> "${report_json}"
                                        fi
-                               else
-                                       src_log="$("${ban_fetchutil}" ${ban_fetchparm} "${tmp_raw}" "${src_url}" 2>&1)"
-                                       src_rc="${?}"
-                                       if [ "${src_rc}" -eq 0 ]
+                                       printf "\t\t%s\n" "\"${src}\": {" >> "${report_json}"
+                                       printf "\t\t\t%s\n" "\"type\": \"${set_type}\"," >> "${report_json}"
+                                       printf "\t\t\t%s\n" "\"count\": \"${cnt}\"," >> "${report_json}"
+                                       printf "\t\t\t%s\n" "\"count_ip\": \"0\"," >> "${report_json}"
+                                       printf "\t\t\t%s\n" "\"count_cidr\": \"0\"," >> "${report_json}"
+                                       printf "\t\t\t%s\n" "\"count_mac\": \"${cnt}\"," >> "${report_json}"
+                                       printf "\t\t\t%s" "\"count_acc\": \"${cnt_acc}\"" >> "${report_json}"
+                                       printf ",\n\t\t\t%s" "\"member_acc\": [" >> "${report_json}"
+                                       printf "%s" "${src_list}" | awk '/^(([0-9A-Z][0-9A-Z]:){5}[0-9A-Z]{2} packets [1-9]+)/{print $1,$3}' | \
+                                               awk 'BEGIN{i=0};{i=i+1;if(i==1){printf "\n\t\t\t\t\t{\n\t\t\t\t\t\t\"member\": \"%s\",\n\t\t\t\t\t\t\"packets\": \"%s\"\n\t\t\t\t\t}",$1,$2}else{printf ",\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"member\": \"%s\",\n\t\t\t\t\t\t\t\"packets\": \"%s\"\n\t\t\t\t\t\t}",$1,$2}}' >> "${report_json}"
+                                       printf "\n\t\t\t%s\n" "]" >> "${report_json}"
+                                       printf "\t\t%s" "}" >> "${report_json}"
+                                       cnt_set_sum=$((cnt_set_sum+1))
+                               fi
+                       else
+                               for proto in "4" "6"
+                               do
+                                       src_list="$("${ban_ipset_cmd}" -q list "${src}_${proto}")"
+                                       if [ -n "${src_list}" ]
                                        then
-                                               zcat "${tmp_raw}" 2>/dev/null > "${tmp_load}"
-                                               src_rc="${?}"
-                                               if [ "${src_rc}" -ne 0 ]
-                                               then
-                                                       mv -f "${tmp_raw}" "${tmp_load}"
-                                                       src_rc="${?}"
-                                               fi
-                                               if [ "${src_rc}" -eq 0 ]
+                                               cnt="$(printf "%s\n" "${src_list}" | awk '/^Number of entries:/{print $4}')"
+                                               cnt_cidr="$(printf "%s\n" "${src_list}" | grep -cE "(/[0-9]{1,3} packets)")"
+                                               cnt_ip=$((cnt-cnt_cidr-cnt_mac))
+                                               cnt_acc="$(printf "%s\n" "${src_list}" | grep -cE "( packets [1-9]+)")"
+                                               cnt_cidr_sum=$((cnt_cidr_sum+cnt_cidr))
+                                               cnt_ip_sum=$((cnt_ip_sum+cnt_ip))
+                                               cnt_acc_sum=$((cnt_acc_sum+cnt_acc))
+                                               cnt_sum=$((cnt_sum+cnt))
+                                               if [ "${cnt_set_sum}" != "0" ]
                                                then
-                                                       f_ipset backup
-                                                       src_rc="${?}"
+                                                       printf "%s\n" "," >> "${report_json}"
                                                fi
-                                       elif [ "${ban_action}" != "start" ]
-                                       then
-                                               f_ipset restore
-                                               src_rc="${?}"
+                                               printf "\t\t%s\n" "\"${src}_${proto}\": {" >> "${report_json}"
+                                               printf "\t\t\t%s\n" "\"type\": \"${set_type}\"," >> "${report_json}"
+                                               printf "\t\t\t%s\n" "\"count\": \"${cnt}\"," >> "${report_json}"
+                                               printf "\t\t\t%s\n" "\"count_ip\": \"${cnt_ip}\"," >> "${report_json}"
+                                               printf "\t\t\t%s\n" "\"count_cidr\": \"${cnt_cidr}\"," >> "${report_json}"
+                                               printf "\t\t\t%s\n" "\"count_mac\": \"0\"," >> "${report_json}"
+                                               printf "\t\t\t%s" "\"count_acc\": \"${cnt_acc}\"" >> "${report_json}"
+                                               printf ",\n\t\t\t%s" "\"member_acc\": [" >> "${report_json}"
+                                               printf "%s" "${src_list}" | awk '/( packets [1-9]+)/{print $1,$3}' | \
+                                                       awk 'BEGIN{i=0};{i=i+1;if(i==1){printf "\n\t\t\t\t\t{\n\t\t\t\t\t\t\"member\": \"%s\",\n\t\t\t\t\t\t\"packets\": \"%s\"\n\t\t\t\t\t}",$1,$2}else{printf ",\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"member\": \"%s\",\n\t\t\t\t\t\t\t\"packets\": \"%s\"\n\t\t\t\t\t\t}",$1,$2}}' >> "${report_json}"
+                                               printf "\n\t\t\t%s\n" "]" >> "${report_json}"
+                                               printf "\t\t%s" "}" >> "${report_json}"
+                                               cnt_set_sum=$((cnt_set_sum+1))
                                        fi
-                               fi
+                               done
                        fi
+               done
+               printf "\n\t%s" "}" >> "${report_json}"
+               printf ",\n\t%s\n" "\"timestamp\": \"$(date "+%d.%m.%Y %H:%M:%S")\"," >> "${report_json}"
+               printf "\t%s\n" "\"cnt_set_sum\": \"${cnt_set_sum}\"," >> "${report_json}"
+               printf "\t%s\n" "\"cnt_ip_sum\": \"${cnt_ip_sum}\"," >> "${report_json}"
+               printf "\t%s\n" "\"cnt_cidr_sum\": \"${cnt_cidr_sum}\"," >> "${report_json}"
+               printf "\t%s\n" "\"cnt_mac_sum\": \"${cnt_mac_sum}\"," >> "${report_json}"
+               printf "\t%s\n" "\"cnt_sum\": \"${cnt_sum}\"," >> "${report_json}"
+               printf "\t%s\n" "\"cnt_acc_sum\": \"${cnt_acc_sum}\"" >> "${report_json}"
+               printf "%s\n" "}" >> "${report_json}"
+       fi
 
-                       if [ "${src_rc}" -eq 0 ]
-                       then
-                               awk "${src_rset}" "${tmp_load}" 2>/dev/null > "${tmp_file}"
-                               src_rc="${?}"
-                               if [ "${src_rc}" -eq 0 ]
-                               then
-                                       f_ipset create
-                                       src_rc="${?}"
-                               elif [ "${ban_action}" != "refresh" ]
+       # output preparation
+       #
+       if [ -s "${report_json}" ] && { [ "${action}" = "cli" ] || [ "${action}" = "mail" ]; }
+       then
+               printf "%s\n%s\n%s\n" ":::" "::: report on all banIP related IPSets" ":::" >> "${report_txt}"
+               json_load_file "${report_json}" >/dev/null 2>&1
+               json_get_var value "timestamp" >/dev/null 2>&1
+               printf "  + %s\n" "Report timestamp           ::: ${value}" >> "${report_txt}"
+               json_get_var value "cnt_set_sum" >/dev/null 2>&1
+               printf "  + %s\n" "Number of all IPSets       ::: ${value:-"0"}" >> "${report_txt}"
+               json_get_var value "cnt_sum" >/dev/null 2>&1
+               printf "  + %s\n" "Number of all entries      ::: ${value:-"0"}" >> "${report_txt}"
+               json_get_var value "cnt_ip_sum" >/dev/null 2>&1
+               printf "  + %s\n" "Number of IP entries       ::: ${value:-"0"}" >> "${report_txt}"
+               json_get_var value "cnt_cidr_sum" >/dev/null 2>&1
+               printf "  + %s\n" "Number of CIDR entries     ::: ${value:-"0"}" >> "${report_txt}"
+               json_get_var value "cnt_mac_sum" >/dev/null 2>&1
+               printf "  + %s\n" "Number of MAC entries      ::: ${value:-"0"}" >> "${report_txt}"
+               json_get_var value "cnt_acc_sum" >/dev/null 2>&1
+               printf "  + %s\n" "Number of accessed entries ::: ${value:-"0"}" >> "${report_txt}"
+               json_select "ipsets"
+               json_get_keys ipsetlist
+               if [ -n "${ipsetlist}" ]
+               then
+                       printf "%s\n%s\n%s\n" ":::" "::: IPSet details" ":::" >> "${report_txt}"
+                       printf "%-25s%-12s%-11s%-10s%-10s%-10s%-10s%s\n" "    Name" "Type" "Count" "Cnt_IP" "Cnt_CIDR" "Cnt_MAC" "Cnt_ACC" "Entry details (Entry/Count)" >> "${report_txt}"
+                       printf "%s\n" "    --------------------------------------------------------------------------------------------------------------------" >> "${report_txt}"
+               fi
+               for ipset in ${ipsetlist}
+               do
+                       set_info="${ipset}"
+                       acc_info=""
+                       json_select "${ipset}"
+                       json_get_keys detaillist
+                       for detail in ${detaillist}
+                       do
+                               if [ "${detail}" != "member_acc" ]
                                then
-                                       f_ipset refresh
-                                       src_rc="${?}"
-                               fi
-                       else
-                               src_log="$(printf "%s" "${src_log}" | awk '{ORS=" ";print $0}')"
-                               if [ "${ban_action}" != "refresh" ]
+                                       json_get_var value "${detail}" >/dev/null 2>&1
+                                       set_info="${set_info} ${value}"
+                               elif [ "${detail}" = "member_acc" ]
                                then
-                                       f_ipset refresh
-                                       src_rc="${?}"
+                                       index=1
+                                       json_select "${detail}"
+                                       while json_get_type type "${index}" && [ "${type}" = "object" ]
+                                       do
+                                               json_get_values values "${index}" >/dev/null 2>&1
+                                               acc_info="${acc_info} ${values}"
+                                               index=$((index+1))
+                                       done
+                                       json_select ".."
                                fi
-                               f_log "debug" "f_main  ::: name: ${src_name}, url: ${src_url}, rc: ${src_rc}, log: ${src_log:-"-"}"
+                       done
+                       printf "    %-21s%-12s%-11s%-10s%-10s%-10s%s\n" ${set_info} >> "${report_txt}"
+                       if [ -n "${acc_info}" ]
+                       then
+                               printf "                                                                                        %-25s%s\n" ${acc_info} >> "${report_txt}"
                        fi
-               )&
-               hold="$((cnt%ban_maxqueue))"
-               if [ "${hold}" -eq 0 ]
-               then
-                       wait
-               fi
-               cnt="$((cnt+1))"
-       done
-       wait
+                       printf "%s\n" "    --------------------------------------------------------------------------------------------------------------------" >> "${report_txt}"
+                       json_select ".."
+               done
+               content="$(cat "${report_txt}" 2>/dev/null)"
+       fi
 
-       if [ -z "$(ls "${ban_tmpfile}".*.err 2>/dev/null)" ]
+       # report output
+       #
+       if [ "${action}" = "cli" ]
        then
-                for cnt_file in "${ban_tmpfile}".*.cnt
-                do
-                       if [ -f "$cnt_file" ]
-                       then
-                               read -r cnt < "$cnt_file"
-                               ban_cnt="$((ban_cnt+cnt))"
-                               ban_setcnt="$((ban_setcnt+1))"
-                       fi
-                done
-               f_log "info" "${ban_setcnt} IPSets with overall ${ban_cnt} IPs/Prefixes loaded successfully (${ban_sysver})"
-               f_bgserv "start"
-               f_jsnup
-               f_rmtemp
-       else
-               f_log "err" "banIP processing failed, fatal iptables error(s) during subshell processing (${ban_sysver})"
+               printf "%s\n" "${content}"
+       elif [ "${action}" = "json" ]
+       then
+               cat "${ban_reportdir}/ban_report.json"
+       elif [ "${action}" = "mail" ] && [ "${ban_mail_enabled}" = "1" ] && [ -x "${ban_mailservice}" ]
+       then
+               ( "${ban_mailservice}" "${ban_ver}" "${content}" >/dev/null 2>&1 )&
+               bg_pid="${!}"
        fi
+       f_log "debug" "f_report ::: action: ${action}, report_json: ${report_json}, report_txt: ${report_txt}, bg_pid: ${bg_pid:-"-"}"
 }
 
 # update runtime information
 #
 f_jsnup()
 {
-       local rundate status="${1:-"enabled"}"
+       local memory entry runtime cnt_info status="${1:-"enabled"}"
 
-       rundate="$(date "+%d.%m.%Y %H:%M:%S")"
-       ban_cntinfo="${ban_setcnt} IPSets with overall ${ban_cnt} IPs/Prefixes"
+       if [ "${status}" = "enabled" ] || [ "${status}" = "error" ]
+       then
+               ban_endtime="$(date "+%s")"
+               cnt_info="${ban_setcnt} IPSets with ${ban_cnt} IPs/Prefixes"
+               memory="$(awk '/^MemTotal|^MemFree|^MemAvailable/{ORS="/"; print int($2/1000)}' "/proc/meminfo" 2>/dev/null | awk '{print substr($0,1,length($0)-1)}')"
+               if [ "$(( (ban_endtime-ban_starttime)/60 ))" -lt "60" ]
+               then
+                       runtime="${ban_action}, $(( (ban_endtime-ban_starttime)/60 ))m $(( (ban_endtime-ban_starttime)%60 ))s, ${memory:-0}, $(date "+%d.%m.%Y %H:%M:%S")"
+               else
+                       runtime="${ban_action}, n/a, ${memory:-0}, $(date "+%d.%m.%Y %H:%M:%S")"
+               fi
+       fi
 
        > "${ban_rtfile}"
        json_load_file "${ban_rtfile}" >/dev/null 2>&1
        json_init
-       json_add_object "data"
        json_add_string "status" "${status}"
        json_add_string "version" "${ban_ver}"
-       json_add_string "util_info" "${ban_fetchutil:-"-"}, ${ban_realtime:-"-"}"
-       json_add_string "ipset_info" "${ban_cntinfo:-"-"}"
-       json_add_string "backup_dir" "${ban_backupdir}"
-       json_add_string "last_run" "${rundate:-"-"}"
+       json_add_string "ipset_info" "${cnt_info:-"-"}"
+       json_add_array "active_sources"
+       if [ "${status}" = "running" ] || [ "${status}" = "error" ]
+       then
+               json_add_object
+               json_add_string "source" "-"
+               json_close_object
+       else
+               for entry in ${ban_sources}
+               do
+                       json_add_object
+                       json_add_string "source" "${entry}"
+                       json_close_object
+               done
+       fi
+       json_close_array
+       json_add_array "active_devs"
+       for entry in ${ban_devs}
+       do
+               json_add_object
+               json_add_string "dev" "${entry}"
+               json_close_object
+       done
+       json_close_array
+       json_add_array "active_ifaces"
+       for entry in ${ban_ifaces}
+       do
+               json_add_object
+               json_add_string "iface" "${entry}"
+               json_close_object
+       done
+       json_close_array
+       json_add_array "active_logterms"
+       for entry in ${ban_logterms}
+       do
+               json_add_object
+               json_add_string "term" "${entry}"
+               json_close_object
+       done
+       json_close_array
+       json_add_array "active_subnets"
+       for entry in ${ban_subnets}
+       do
+               json_add_object
+               json_add_string "subnet" "${entry}"
+               json_close_object
+       done
+       json_close_array
+       json_add_string "run_infos" "settype: ${ban_global_settype}, backup_dir: ${ban_backupdir}, report_dir: ${ban_reportdir}"
+       json_add_string "run_flags" "protocols (4/6): $(f_char ${ban_proto4_enabled})/$(f_char ${ban_proto6_enabled}), log (src/dst): $(f_char ${ban_logsrc_enabled})/$(f_char ${ban_logdst_enabled}), monitor: $(f_char ${ban_monitor_enabled}), mail: $(f_char ${ban_mail_enabled})"
+       json_add_string "last_run" "${runtime:-"-"}"
        json_add_string "system" "${ban_sysver}"
-       json_close_object
        json_dump > "${ban_rtfile}"
-       f_log "debug" "f_jsnup ::: status: ${status}, setcnt: ${ban_setcnt}, cnt: ${ban_cnt}"
+
+       if [ "${ban_mail_enabled}" = "1" ] && [ -x "${ban_mailservice}" ] && { [ "${status}" = "error" ] || \
+               { [ "${status}" = "enabled" ] && { [ -z "${ban_mailactions}" ] || [ -n "$(printf "%s\n" "${ban_mailactions}" | grep -F "${ban_action}")" ]; }; }; }
+       then
+               ( "${ban_mailservice}" "${ban_ver}" >/dev/null 2>&1 )&
+               bg_pid="${!}"
+       fi
+       f_log "debug" "f_jsnup ::: status: ${status:-"-"}, action: ${ban_action}, mail_enabled: ${ban_mail_enabled}, mail_actions: ${ban_mailactions}, mail_service: ${ban_mailservice}, mail_pid: ${bg_pid:-"-"}"
 }
 
 # source required system libraries
@@ -1013,20 +1660,73 @@ else
        f_log "err" "system libraries not found"
 fi
 
+if [ "${ban_action}" = "suspend" ] || [ "${ban_action}" = "resume" ] || \
+       [ "${ban_action}" = "report" ] || [ "${ban_action}" = "query" ]
+then
+       json_load_file "${ban_rtfile}" >/dev/null 2>&1
+       json_get_var ban_status "status"
+fi
+
+# version information
+#
+if [ "${ban_action}" = "version" ]
+then
+       printf "%s\n" "${ban_ver}"
+       exit 0
+fi
+
 # handle different banIP actions
 #
-f_envload
+f_load
 case "${ban_action}" in
        "stop")
-               f_bgserv "stop"
-               f_jsnup stopped
-               f_ipset destroy
-               f_rmbackup
-               f_rmtemp
+               f_bgsrv "stop"
+               f_ipset "destroy"
+               f_jsnup "stopped"
+               f_rmbckp
+       ;;
+       "restart")
+               f_bgsrv "stop"
+               f_ipset "destroy"
+               f_rmbckp
+               f_env
+               f_main
+       ;;
+       "suspend")
+               if [ "${ban_status}" = "enabled" ]
+               then
+                       f_bgsrv "stop"
+                       f_jsnup "running"
+                       f_ipset "suspend"
+                       f_jsnup "paused"
+               fi
+               f_rmtmp
+       ;;
+       "resume")
+               if [ "${ban_status}" = "paused" ]
+               then
+                       f_bgsrv "stop"
+                       f_env
+                       f_main
+               else
+                       f_rmtmp
+               fi
+       ;;
+       "query")
+               if [ "${ban_status}" = "enabled" ]
+               then
+                       f_query "${2}"
+               fi
+       ;;
+       "report")
+               if [ "${ban_status}" = "enabled" ] || [ "${2}" = "json" ]
+               then
+                       f_report "${2}"
+               fi
        ;;
-       "start"|"restart"|"reload"|"refresh")
-               f_bgserv "stop"
-               f_envcheck
+       "start"|"reload"|"refresh")
+               f_bgsrv "stop"
+               f_env
                f_main
        ;;
 esac
diff --git a/net/banip/files/banip.sources b/net/banip/files/banip.sources
new file mode 100644 (file)
index 0000000..c08609c
--- /dev/null
@@ -0,0 +1,179 @@
+{
+       "asn": {
+               "url_4": "https://asn.ipinfo.app/api/text/list/",
+               "url_6": "https://asn.ipinfo.app/api/text/list/",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add asn_4 \"$1}",
+               "rule_6": "/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\\/(1?[0-2][0-8]|[0-9][0-9]))?)([[:space:]]|$)/{print \"add asn_6 \"$1}",
+               "focus": "ASN blocks",
+               "descurl": "https://asn.ipinfo.app"
+       },
+       "bogon": {
+               "url_4": "https://www.team-cymru.org/Services/Bogons/fullbogons-ipv4.txt",
+               "url_6": "https://www.team-cymru.org/Services/Bogons/fullbogons-ipv6.txt",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add bogon_4 \"$1}",
+               "rule_6": "/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\\/(1?[0-2][0-8]|[0-9][0-9]))?)([[:space:]]|$)/{print \"add bogon_6 \"$1}",
+               "focus": "Bogon prefixes",
+               "descurl": "https://team-cymru.com"
+       },
+       "country": {
+               "url_4": "https://www.ipdeny.com/ipblocks/data/aggregated/",
+               "url_6": "https://www.ipdeny.com/ipv6/ipaddresses/aggregated/",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add country_4 \"$1}",
+               "rule_6": "/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\\/(1?[0-2][0-8]|[0-9][0-9]))?)([[:space:]]|$)/{print \"add country_6 \"$1}",
+               "focus": "Country blocks",
+               "descurl": "https://www.ipdeny.com/ipblocks"
+       },
+       "darklist": {
+               "url_4": "https://darklist.de/raw.php",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add darklist_4 \"$1}",
+               "focus": "Attacker IP blacklist",
+               "descurl": "https://darklist.de"
+       },
+       "debl": {
+               "url_4": "https://www.blocklist.de/downloads/export-ips_all.txt",
+               "url_6": "https://www.blocklist.de/downloads/export-ips_all.txt",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add debl_4 \"$1}",
+               "rule_6": "/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\\/(1?[0-2][0-8]|[0-9][0-9]))?)([[:space:]]|$)/{print \"add debl_6 \"$1}",
+               "focus": "Fail2ban IP blacklist",
+               "descurl": "https://www.blocklist.de"
+       },
+       "doh": {
+               "url_4": "https://raw.githubusercontent.com/dibdot/DoH-IP-blocklists/master/doh-ipv4.txt",
+               "url_6": "https://raw.githubusercontent.com/dibdot/DoH-IP-blocklists/master/doh-ipv6.txt",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add doh_4 \"$1}",
+               "rule_6": "/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\\/(1?[0-2][0-8]|[0-9][0-9]))?)([[:space:]]|$)/{print \"add doh_6 \"$1}",
+               "focus": "Public DoH-Provider",
+               "descurl": "https://github.com/dibdot/DoH-IP-blocklists"
+       },
+       "drop": {
+               "url_4": "https://www.spamhaus.org/drop/drop.txt",
+               "url_6": "https://www.spamhaus.org/drop/dropv6.txt",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add drop_4 \"$1}",
+               "rule_6": "/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\\/(1?[0-2][0-8]|[0-9][0-9]))?)([[:space:]]|$)/{print \"add drop_6 \"$1}",
+               "focus": "Spamhaus drop compilation",
+               "descurl": "https://www.spamhaus.org"
+       },
+       "dshield": {
+               "url_4": "https://feeds.dshield.org/block.txt",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add dshield_4 \"$1 \"/\"$3}",
+               "focus": "Dshield IP blocklist",
+               "descurl": "https://www.dshield.org"
+       },
+       "edrop": {
+               "url_4": "https://www.spamhaus.org/drop/edrop.txt",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add edrop_4 \"$1}",
+               "focus": "Spamhaus edrop compilation",
+               "descurl": "https://www.spamhaus.org"
+       },
+       "feodo": {
+               "url_4": "https://feodotracker.abuse.ch/downloads/ipblocklist.txt",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add feodo_4 \"$1}",
+               "focus": "Feodo Tracker",
+               "descurl": "https://feodotracker.abuse.ch"
+       },
+       "firehol1": {
+               "url_4": "https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level1.netset",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add firehol1_4 \"$1}",
+               "focus": "Firehol Level 1 compilation",
+               "descurl": "https://iplists.firehol.org/?ipset=firehol_level1"
+       },
+       "firehol2": {
+               "url_4": "https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level2.netset",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add firehol2_4 \"$1}",
+               "focus": "Firehol Level 2 compilation",
+               "descurl": "https://iplists.firehol.org/?ipset=firehol_level2"
+       },
+       "firehol3": {
+               "url_4": "https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level3.netset",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add firehol3_4 \"$1}",
+               "focus": "Firehol Level 3 compilation",
+               "descurl": "https://iplists.firehol.org/?ipset=firehol_level3"
+       },
+       "firehol4": {
+               "url_4": "https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level4.netset",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add firehol4_4 \"$1}",
+               "focus": "Firehol Level 4 compilation",
+               "descurl": "https://iplists.firehol.org/?ipset=firehol_level4"
+       },
+       "iblockads": {
+               "url_4": "https://list.iblocklist.com/?list=dgxtneitpuvgqqcpfulq&fileformat=cidr&archiveformat=gz",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add iblockads_4 \"$1}",
+               "focus": "Advertising blocklist",
+               "descurl": "https://www.iblocklist.com",
+               "comp": "gz"
+       },
+       "iblockspy": {
+               "url_4": "https://list.iblocklist.com/?list=llvtlsjyoyiczbkjsxpf&fileformat=cidr&archiveformat=gz",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add iblockspy_4 \"$1}",
+               "focus": "Malicious spyware blocklist",
+               "descurl": "https://www.iblocklist.com",
+               "comp": "gz"
+       },
+       "myip": {
+               "url_4": "https://myip.ms/files/blacklist/general/latest_blacklist.txt",
+               "url_6": "https://myip.ms/files/blacklist/general/latest_blacklist.txt",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add myip_4 \"$1}",
+               "rule_6": "/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\\/(1?[0-2][0-8]|[0-9][0-9]))?)([[:space:]]|$)/{print \"add myip_6 \"$1}",
+               "focus": "Myip Live IP blacklist",
+               "descurl": "https://myip.ms"
+       },
+       "nixspam": {
+               "url_4": "http://www.dnsbl.manitu.net/download/nixspam-ip.dump.gz",
+               "rule_4": "/(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add nixspam_4 \"$2}",
+               "focus": "iX spam protection",
+               "descurl": "http://www.nixspam.org",
+               "comp": "gz"
+       },
+       "proxy": {
+               "url_4": "https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/proxylists.ipset",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add proxy_4 \"$1}",
+               "focus": "Firehol list of open proxies",
+               "descurl": "https://iplists.firehol.org/?ipset=proxylists"
+       },
+       "sslbl": {
+               "url_4": "https://sslbl.abuse.ch/blacklist/sslipblacklist.csv",
+               "rule_4": "BEGIN{FS=\",\"}/(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)/{print \"add sslbl_4 \"$2}",
+               "focus": "SSL botnet IP blacklist",
+               "descurl": "https://sslbl.abuse.ch"
+       },
+       "threat": {
+               "url_4": "https://rules.emergingthreats.net/fwrules/emerging-Block-IPs.txt",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add threat_4 \"$1}",
+               "focus": "Emerging Threats",
+               "descurl": "https://rules.emergingthreats.net"
+       },
+       "tor": {
+               "url_4": "https://lists.fissionrelays.net/tor/exits-ipv4.txt",
+               "url_6": "https://lists.fissionrelays.net/tor/exits-ipv6.txt",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add tor_4 \"$1}",
+               "rule_6": "/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\\/(1?[0-2][0-8]|[0-9][0-9]))?)([[:space:]]|$)/{print \"add tor_6 \"$1}",
+               "focus": "Tor exit nodes",
+               "descurl": "https://fissionrelays.net/lists"
+       },
+       "uceprotect1": {
+               "url_4": "http://wget-mirrors.uceprotect.net/rbldnsd-all/dnsbl-1.uceprotect.net.gz",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)$/{print \"add uceprotect1_4 \"$1}",
+               "focus": "Spam protection level 1",
+               "descurl": "http://www.uceprotect.net/en/index.php",
+               "comp": "gz"
+       },
+       "uceprotect2": {
+               "url_4": "http://wget-mirrors.uceprotect.net/rbldnsd-all/dnsbl-2.uceprotect.net.gz",
+               "rule_4": "BEGIN{IGNORECASE=1}/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]+NET[[:space:]]+)/{print \"add uceprotect2_4 \"$1}",
+               "focus": "Spam protection level 2",
+               "descurl": "http://www.uceprotect.net/en/index.php",
+               "comp": "gz"
+       },
+       "voip": {
+               "url_4": "http://www.voipbl.org/update/",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add voip_4 \"$1}",
+               "focus": "VoIP fraud blocklist",
+               "descurl": "http://www.voipbl.org"
+       },
+       "yoyo": {
+               "url_4": "https://pgl.yoyo.org/adservers/iplist.php?ipformat=plain&showintro=0&mimetype=plaintext",
+               "rule_4": "/^(([0-9]{1,3}\\.){3}(1?[0-9][0-9]?|2[0-4][0-9]|25[0-5])(\\/(1?[0-9]|2?[0-9]|3?[0-2]))?)([[:space:]]|$)/{print \"add yoyo_4 \"$1}",
+               "focus": "Ad protection blacklist",
+               "descurl": "https://pgl.yoyo.org/adservers/"
+       }
+}