banip: release 0.8.3-1
[feed/packages.git] / net / banip / files / banip-functions.sh
1 # banIP shared function library/include
2 # Copyright (c) 2018-2023 Dirk Brenken (dev@brenken.org)
3 # This is free software, licensed under the GNU General Public License v3.
4
5 # (s)hellcheck exceptions
6 # shellcheck disable=all
7
8 # set initial defaults
9 #
10 export LC_ALL=C
11 export PATH="/usr/sbin:/usr/bin:/sbin:/bin"
12
13 ban_basedir="/tmp"
14 ban_backupdir="/tmp/banIP-backup"
15 ban_reportdir="/tmp/banIP-report"
16 ban_feedfile="/etc/banip/banip.feeds"
17 ban_allowlist="/etc/banip/banip.allowlist"
18 ban_blocklist="/etc/banip/banip.blocklist"
19 ban_mailtemplate="/etc/banip/banip.tpl"
20 ban_pidfile="/var/run/banip.pid"
21 ban_rtfile="/var/run/banip_runtime.json"
22 ban_lock="/var/run/banip.lock"
23 ban_fetchcmd=""
24 ban_logreadcmd="$(command -v logread)"
25 ban_logcmd="$(command -v logger)"
26 ban_ubuscmd="$(command -v ubus)"
27 ban_nftcmd="$(command -v nft)"
28 ban_fw4cmd="$(command -v fw4)"
29 ban_awkcmd="$(command -v awk)"
30 ban_grepcmd="$(command -v grep)"
31 ban_lookupcmd="$(command -v nslookup)"
32 ban_mailcmd="$(command -v msmtp)"
33 ban_mailsender="no-reply@banIP"
34 ban_mailreceiver=""
35 ban_mailtopic="banIP notification"
36 ban_mailprofile="ban_notify"
37 ban_mailnotifcation="0"
38 ban_reportelements="1"
39 ban_nftloglevel="warn"
40 ban_nftpriority="-200"
41 ban_nftpolicy="memory"
42 ban_nftexpiry=""
43 ban_loglimit="100"
44 ban_logcount="1"
45 ban_logterm=""
46 ban_country=""
47 ban_asn=""
48 ban_loginput="1"
49 ban_logforwardwan="1"
50 ban_logforwardlan="0"
51 ban_allowlistonly="0"
52 ban_autoallowlist="1"
53 ban_autoblocklist="1"
54 ban_deduplicate="1"
55 ban_splitsize="0"
56 ban_autodetect="1"
57 ban_feed=""
58 ban_blockinput=""
59 ban_blockforwardwan=""
60 ban_blockforwardlan=""
61 ban_protov4="0"
62 ban_protov6="0"
63 ban_ifv4=""
64 ban_ifv6=""
65 ban_dev=""
66 ban_sub=""
67 ban_fetchinsecure=""
68 ban_cores=""
69 ban_memory=""
70 ban_trigger=""
71 ban_triggerdelay="10"
72 ban_resolver=""
73 ban_enabled="0"
74 ban_debug="0"
75
76 # gather system information
77 #
78 f_system() {
79 local cpu core
80
81 [ -z "${ban_dev}" ] && ban_cores="$(uci_get banip global ban_cores)"
82 ban_memory="$("${ban_awkcmd}" '/^MemAvailable/{printf "%s",int($2/1000)}' "/proc/meminfo" 2>/dev/null)"
83 ban_ver="$(${ban_ubuscmd} -S call rpc-sys packagelist '{ "all": true }' 2>/dev/null | jsonfilter -ql1 -e '@.packages.banip')"
84 ban_sysver="$(${ban_ubuscmd} -S call system board 2>/dev/null | jsonfilter -ql1 -e '@.model' -e '@.release.description' |
85 "${ban_awkcmd}" 'BEGIN{RS="";FS="\n"}{printf "%s, %s",$1,$2}')"
86 if [ -z "${ban_cores}" ]; then
87 cpu="$("${ban_grepcmd}" -c '^processor' /proc/cpuinfo 2>/dev/null)"
88 core="$("${ban_grepcmd}" -cm1 '^core id' /proc/cpuinfo 2>/dev/null)"
89 [ "${cpu}" = "0" ] && cpu="1"
90 [ "${core}" = "0" ] && core="1"
91 ban_cores="$((cpu * core))"
92 fi
93
94 f_log "debug" "f_system ::: system: ${ban_sysver:-"n/a"}, version: ${ban_ver:-"n/a"}, memory: ${ban_memory:-"0"}, cpu_cores: ${ban_cores}"
95 }
96
97 # create directories
98 #
99 f_mkdir() {
100 local dir="${1}"
101
102 if [ ! -d "${dir}" ]; then
103 rm -f "${dir}"
104 mkdir -p "${dir}"
105 f_log "debug" "f_mkdir ::: created directory: ${dir}"
106 fi
107 }
108
109 # create files
110 #
111 f_mkfile() {
112 local file="${1}"
113
114 if [ ! -f "${file}" ]; then
115 : >"${file}"
116 f_log "debug" "f_mkfile ::: created file: ${file}"
117 fi
118 }
119
120 # create temporary files and directories
121 #
122 f_tmp() {
123 f_mkdir "${ban_basedir}"
124 ban_tmpdir="$(mktemp -p "${ban_basedir}" -d)"
125 ban_tmpfile="$(mktemp -p "${ban_tmpdir}" -tu)"
126
127 f_log "debug" "f_tmp ::: base_dir: ${ban_basedir:-"-"}, tmp_dir: ${ban_tmpdir:-"-"}"
128 }
129
130 # remove directories
131 #
132 f_rmdir() {
133 local dir="${1}"
134
135 if [ -d "${dir}" ]; then
136 rm -rf "${dir}"
137 f_log "debug" "f_rmdir ::: deleted directory: ${dir}"
138 fi
139 }
140
141 # convert chars
142 #
143 f_char() {
144 local char="${1}"
145
146 [ "${char}" = "1" ] && printf "%s" "✔" || printf "%s" "✘"
147 }
148
149 # trim strings
150 #
151 f_trim() {
152 local string="${1}"
153
154 string="${string#"${string%%[![:space:]]*}"}"
155 string="${string%"${string##*[![:space:]]}"}"
156 printf "%s" "${string}"
157 }
158
159 # write log messages
160 #
161 f_log() {
162 local class="${1}" log_msg="${2}"
163
164 if [ -n "${log_msg}" ] && { [ "${class}" != "debug" ] || [ "${ban_debug}" = "1" ]; }; then
165 if [ -x "${ban_logcmd}" ]; then
166 "${ban_logcmd}" -p "${class}" -t "banIP-${ban_ver}[${$}]" "${log_msg}"
167 else
168 printf "%s %s %s\n" "${class}" "banIP-${ban_ver}[${$}]" "${log_msg}"
169 fi
170 fi
171 if [ "${class}" = "err" ]; then
172 "${ban_nftcmd}" delete table inet banIP >/dev/null 2>&1
173 if [ "${ban_enabled}" = "1" ]; then
174 f_genstatus "error"
175 [ "${ban_mailnotification}" = "1" ] && [ -n "${ban_mailreceiver}" ] && [ -x "${ban_mailcmd}" ] && f_mail
176 else
177 f_genstatus "disabled"
178 fi
179 f_rmdir "${ban_tmpdir}"
180 f_rmpid
181 rm -rf "${ban_lock}"
182 exit 1
183 fi
184 }
185
186 # load config
187 #
188 f_conf() {
189 unset ban_dev ban_ifv4 ban_ifv6 ban_feed ban_blockinput ban_blockforwardwan ban_blockforwardlan ban_logterm ban_country ban_asn
190 config_cb() {
191 option_cb() {
192 local option="${1}"
193 local value="${2}"
194 eval "${option}=\"${value}\""
195 }
196 list_cb() {
197 local option="${1}"
198 local value="${2}"
199 case "${option}" in
200 "ban_dev")
201 eval "${option}=\"$(printf "%s" "${ban_dev}")${value} \""
202 ;;
203 "ban_ifv4")
204 eval "${option}=\"$(printf "%s" "${ban_ifv4}")${value} \""
205 ;;
206 "ban_ifv6")
207 eval "${option}=\"$(printf "%s" "${ban_ifv6}")${value} \""
208 ;;
209 "ban_feed")
210 eval "${option}=\"$(printf "%s" "${ban_feed}")${value} \""
211 ;;
212 "ban_blockinput")
213 eval "${option}=\"$(printf "%s" "${ban_blockinput}")${value} \""
214 ;;
215 "ban_blockforwardwan")
216 eval "${option}=\"$(printf "%s" "${ban_blockforwardwan}")${value} \""
217 ;;
218 "ban_blockforwardlan")
219 eval "${option}=\"$(printf "%s" "${ban_blockforwardlan}")${value} \""
220 ;;
221 "ban_logterm")
222 eval "${option}=\"$(printf "%s" "${ban_logterm}")${value}\\|\""
223 ;;
224 "ban_country")
225 eval "${option}=\"$(printf "%s" "${ban_country}")${value} \""
226 ;;
227 "ban_asn")
228 eval "${option}=\"$(printf "%s" "${ban_asn}")${value} \""
229 ;;
230 esac
231 }
232 }
233 config_load banip
234
235 [ "${ban_action}" = "boot" ] && [ -z "${ban_trigger}" ] && sleep ${ban_triggerdelay}
236 }
237
238 # prepare fetch utility
239 #
240 f_fetch() {
241 local ut utils packages insecure
242
243 if [ -z "${ban_fetchcmd}" ] || [ ! -x "${ban_fetchcmd}" ]; then
244 packages="$(${ban_ubuscmd} -S call rpc-sys packagelist 2>/dev/null)"
245 [ -z "${packages}" ] && f_log "err" "local opkg package repository is not available, please set the download utility 'ban_fetchcmd' manually"
246 utils="aria2c curl wget uclient-fetch"
247 for ut in ${utils}; do
248 if { [ "${ut}" = "uclient-fetch" ] && printf "%s" "${packages}" | "${ban_grepcmd}" -q '"libustream-'; } ||
249 { [ "${ut}" = "wget" ] && printf "%s" "${packages}" | "${ban_grepcmd}" -q '"wget-ssl'; } ||
250 [ "${ut}" = "curl" ] || [ "${ut}" = "aria2c" ]; then
251 ban_fetchcmd="$(command -v "${ut}")"
252 if [ -x "${ban_fetchcmd}" ]; then
253 uci_set banip global ban_fetchcmd "${ban_fetchcmd##*/}"
254 uci_commit "banip"
255 break
256 fi
257 fi
258 done
259 fi
260 [ ! -x "${ban_fetchcmd}" ] && f_log "err" "download utility with SSL support not found"
261 case "${ban_fetchcmd##*/}" in
262 "aria2c")
263 [ "${ban_fetchinsecure}" = "1" ] && insecure="--check-certificate=false"
264 ban_fetchparm="${ban_fetchparm:-"${insecure} --timeout=20 --allow-overwrite=true --auto-file-renaming=false --log-level=warn --dir=/ -o"}"
265 ;;
266 "curl")
267 [ "${ban_fetchinsecure}" = "1" ] && insecure="--insecure"
268 ban_fetchparm="${ban_fetchparm:-"${insecure} --connect-timeout 20 --fail --silent --show-error --location -o"}"
269 ;;
270 "uclient-fetch")
271 [ "${ban_fetchinsecure}" = "1" ] && insecure="--no-check-certificate"
272 ban_fetchparm="${ban_fetchparm:-"${insecure} --timeout=20 -O"}"
273 ;;
274 "wget")
275 [ "${ban_fetchinsecure}" = "1" ] && insecure="--no-check-certificate"
276 ban_fetchparm="${ban_fetchparm:-"${insecure} --no-cache --no-cookies --max-redirect=0 --timeout=20 -O"}"
277 ;;
278 esac
279
280 f_log "debug" "f_fetch ::: fetch_cmd: ${ban_fetchcmd:-"-"}, fetch_parm: ${ban_fetchparm:-"-"}"
281 }
282
283 # remove logservice
284 #
285 f_rmpid() {
286 local ppid pid pids
287
288 ppid="$(cat "${ban_pidfile}" 2>/dev/null)"
289 [ -n "${ppid}" ] && pids="$(pgrep -P "${ppid}" 2>/dev/null)" || return 0
290 for pid in ${pids}; do
291 kill -INT "${pid}" >/dev/null 2>&1
292 done
293 : >"${ban_pidfile}"
294 }
295
296 # get nft/monitor actuals
297 #
298 f_actual() {
299 local nft monitor
300
301 if "${ban_nftcmd}" -t list set inet banIP allowlistvMAC >/dev/null 2>&1; then
302 nft="$(f_char "1")"
303 else
304 nft="$(f_char "0")"
305 fi
306 if pgrep -f "logread" -P "$(cat "${ban_pidfile}" 2>/dev/null)" >/dev/null 2>&1; then
307 monitor="$(f_char "1")"
308 else
309 monitor="$(f_char "0")"
310 fi
311 printf "%s" "nft: ${nft}, monitor: ${monitor}"
312 }
313
314 # get wan interfaces
315 #
316 f_getif() {
317 local iface update="0"
318
319 if [ "${ban_autodetect}" = "1" ]; then
320 if [ -z "${ban_ifv4}" ]; then
321 network_flush_cache
322 network_find_wan iface
323 if [ -n "${iface}" ] && "${ban_ubuscmd}" -t 10 wait_for network.interface."${iface}" >/dev/null 2>&1; then
324 ban_protov4="1"
325 ban_ifv4="${iface}"
326 uci_set banip global ban_protov4 "1"
327 uci_add_list banip global ban_ifv4 "${iface}"
328 f_log "info" "added IPv4 interface '${iface}' to config"
329 fi
330 fi
331 if [ -z "${ban_ifv6}" ]; then
332 network_flush_cache
333 network_find_wan6 iface
334 if [ -n "${iface}" ] && "${ban_ubuscmd}" -t 10 wait_for network.interface."${iface}" >/dev/null 2>&1; then
335 ban_protov6="1"
336 ban_ifv6="${iface}"
337 uci_set banip global ban_protov6 "1"
338 uci_add_list banip global ban_ifv6 "${iface}"
339 f_log "info" "added IPv6 interface '${iface}' to config"
340 fi
341 fi
342 fi
343 if [ -n "$(uci -q changes "banip")" ]; then
344 update="1"
345 uci_commit "banip"
346 else
347 ban_ifv4="${ban_ifv4%%?}"
348 ban_ifv6="${ban_ifv6%%?}"
349 for iface in ${ban_ifv4} ${ban_ifv6}; do
350 if ! "${ban_ubuscmd}" -t 10 wait_for network.interface."${iface}" >/dev/null 2>&1; then
351 f_log "err" "wan interface '${iface}' is not available, please check your configuration"
352 fi
353 done
354 fi
355 [ -z "${ban_ifv4}" ] && [ -z "${ban_ifv6}" ] && f_log "err" "wan interfaces not found, please check your configuration"
356
357 f_log "debug" "f_getif ::: auto/update: ${ban_autodetect}/${update}, interfaces (4/6): ${ban_ifv4}/${ban_ifv6}, protocols (4/6): ${ban_protov4}/${ban_protov6}"
358 }
359
360 # get wan devices
361 #
362 f_getdev() {
363 local dev iface update="0" cnt="0" cnt_max="30"
364
365 if [ "${ban_autodetect}" = "1" ]; then
366 while [ "${cnt}" -lt "${cnt_max}" ] && [ -z "${ban_dev}" ]; do
367 network_flush_cache
368 for iface in ${ban_ifv4} ${ban_ifv6}; do
369 network_get_device dev "${iface}"
370 if [ -n "${dev}" ]; then
371 if printf "%s" "${dev}" | "${ban_grepcmd}" -qE "pppoe|6in4"; then
372 dev="${iface}"
373 fi
374 if ! printf " %s " "${ban_dev}" | "${ban_grepcmd}" -q " ${dev} "; then
375 ban_dev="${ban_dev}${dev} "
376 uci_add_list banip global ban_dev "${dev}"
377 f_log "info" "added device '${dev}' to config"
378 fi
379 fi
380 done
381 cnt="$((cnt + 1))"
382 sleep 1
383 done
384 fi
385 if [ -n "$(uci -q changes "banip")" ]; then
386 update="1"
387 uci_commit "banip"
388 fi
389 ban_dev="${ban_dev%%?}"
390 [ -z "${ban_dev}" ] && f_log "err" "wan devices not found, please check your configuration"
391
392 f_log "debug" "f_getdev ::: auto/update: ${ban_autodetect}/${update}, devices: ${ban_dev}, cnt: ${cnt}"
393 }
394
395 # get local subnets
396 #
397 f_getsub() {
398 local sub iface ip update="0"
399
400 if [ "${ban_autoallowlist}" = "1" ]; then
401 for iface in ${ban_ifv4} ${ban_ifv6}; do
402 network_flush_cache
403 network_get_subnet sub "${iface}"
404 if [ -n "${sub}" ] && ! printf " %s " "${ban_sub}" | "${ban_grepcmd}" -q " ${sub} "; then
405 ban_sub="${ban_sub}${sub} "
406 fi
407 network_get_subnet6 sub "${iface}"
408 if [ -n "${sub}" ] && ! printf " %s " "${ban_sub}" | "${ban_grepcmd}" -q " ${sub} "; then
409 ban_sub="${ban_sub}${sub} "
410 fi
411 done
412 for ip in ${ban_sub}; do
413 if ! "${ban_grepcmd}" -q "${ip}" "${ban_allowlist}"; then
414 update="1"
415 printf "%-42s%s\n" "${ip}" "# subnet added on $(date "+%Y-%m-%d %H:%M:%S")" >>"${ban_allowlist}"
416 f_log "info" "added subnet '${ip}' to local allowlist"
417 fi
418 done
419 ban_sub="${ban_sub%%?}"
420 fi
421
422 f_log "debug" "f_getsub ::: auto/update: ${ban_autoallowlist}/${update}, subnet(s): ${ban_sub:-"-"}"
423 }
424
425 # get set elements
426 #
427 f_getelements() {
428 local file="${1}"
429
430 [ -s "${file}" ] && printf "%s" "elements={ $(cat "${file}" 2>/dev/null) };"
431 }
432
433 # build initial nft file with base table, chains and rules
434 #
435 f_nftinit() {
436 local feed_log feed_rc file="${1}"
437
438 {
439 # nft header (tables and chains)
440 #
441 printf "%s\n\n" "#!/usr/sbin/nft -f"
442 if "${ban_nftcmd}" -t list set inet banIP allowlistvMAC >/dev/null 2>&1; then
443 printf "%s\n" "delete table inet banIP"
444 fi
445 printf "%s\n" "add table inet banIP"
446 printf "%s\n" "add chain inet banIP wan-input { type filter hook input priority ${ban_nftpriority}; policy accept; }"
447 printf "%s\n" "add chain inet banIP wan-forward { type filter hook forward priority ${ban_nftpriority}; policy accept; }"
448 printf "%s\n" "add chain inet banIP lan-forward { type filter hook forward priority ${ban_nftpriority}; policy accept; }"
449
450 # default wan-input rules
451 #
452 printf "%s\n" "add rule inet banIP wan-input ct state established,related counter accept"
453 printf "%s\n" "add rule inet banIP wan-input iifname != { ${ban_dev// /, } } counter accept"
454 printf "%s\n" "add rule inet banIP wan-input meta nfproto ipv4 udp sport 67-68 udp dport 67-68 counter accept"
455 printf "%s\n" "add rule inet banIP wan-input meta nfproto ipv6 udp sport 547 udp dport 546 counter accept"
456 printf "%s\n" "add rule inet banIP wan-input meta nfproto ipv4 icmp type { echo-request } limit rate 1000/second counter accept"
457 printf "%s\n" "add rule inet banIP wan-input meta nfproto ipv6 icmpv6 type { echo-request } limit rate 1000/second counter accept"
458 printf "%s\n" "add rule inet banIP wan-input meta nfproto ipv6 icmpv6 type { nd-neighbor-advert, nd-neighbor-solicit, nd-router-advert} limit rate 1000/second ip6 hoplimit 1 counter accept"
459 printf "%s\n" "add rule inet banIP wan-input meta nfproto ipv6 icmpv6 type { nd-neighbor-advert, nd-neighbor-solicit, nd-router-advert} limit rate 1000/second ip6 hoplimit 255 counter accept"
460
461 # default wan-forward rules
462 #
463 printf "%s\n" "add rule inet banIP wan-forward ct state established,related counter accept"
464 printf "%s\n" "add rule inet banIP wan-forward iifname != { ${ban_dev// /, } } counter accept"
465
466 # default lan-forward rules
467 #
468 printf "%s\n" "add rule inet banIP lan-forward ct state established,related counter accept"
469 printf "%s\n" "add rule inet banIP lan-forward oifname != { ${ban_dev// /, } } counter accept"
470 } >"${file}"
471
472 # load initial banIP table within nft (atomic load)
473 #
474 feed_log="$("${ban_nftcmd}" -f "${file}" 2>&1)"
475 feed_rc="${?}"
476
477 f_log "debug" "f_nftinit ::: devices: ${ban_dev}, priority: ${ban_nftpriority}, policy: ${ban_nftpolicy}, loglevel: ${ban_nftloglevel}, rc: ${feed_rc:-"-"}, log: ${feed_log:-"-"}"
478 return ${feed_rc}
479 }
480
481 # handle downloads
482 #
483 f_down() {
484 local log_input log_forwardwan log_forwardlan start_ts end_ts tmp_raw tmp_load tmp_file split_file ruleset_raw handle
485 local cnt_set cnt_dl restore_rc feed_direction feed_rc feed_log feed="${1}" proto="${2}" feed_url="${3}" feed_rule="${4}" feed_flag="${5}"
486
487 start_ts="$(date +%s)"
488 feed="${feed}v${proto}"
489 tmp_load="${ban_tmpfile}.${feed}.load"
490 tmp_raw="${ban_tmpfile}.${feed}.raw"
491 tmp_split="${ban_tmpfile}.${feed}.split"
492 tmp_file="${ban_tmpfile}.${feed}.file"
493 tmp_flush="${ban_tmpfile}.${feed}.flush"
494 tmp_nft="${ban_tmpfile}.${feed}.nft"
495
496 [ "${ban_loginput}" = "1" ] && log_input="log level ${ban_nftloglevel} prefix \"banIP/inp-wan/drp/${feed}: \""
497 [ "${ban_logforwardwan}" = "1" ] && log_forwardwan="log level ${ban_nftloglevel} prefix \"banIP/fwd-wan/drp/${feed}: \""
498 [ "${ban_logforwardlan}" = "1" ] && log_forwardlan="log level ${ban_nftloglevel} prefix \"banIP/fwd-lan/rej/${feed}: \""
499
500 # set source block direction
501 #
502 if printf "%s" "${ban_blockinput}" | "${ban_grepcmd}" -q "${feed%v*}"; then
503 feed_direction="input"
504 fi
505 if printf "%s" "${ban_blockforwardwan}" | "${ban_grepcmd}" -q "${feed%v*}"; then
506 feed_direction="${feed_direction} forwardwan"
507 fi
508 if printf "%s" "${ban_blockforwardlan}" | "${ban_grepcmd}" -q "${feed%v*}"; then
509 feed_direction="${feed_direction} forwardlan"
510 fi
511
512 # chain/rule maintenance
513 #
514 if [ "${ban_action}" = "reload" ] && "${ban_nftcmd}" -t list set inet banIP "${feed}" >/dev/null 2>&1; then
515 ruleset_raw="$("${ban_nftcmd}" -tj list ruleset 2>/dev/null)"
516 {
517 printf "%s\n" "flush set inet banIP ${feed}"
518 handle="$(printf "%s\n" "${ruleset_raw}" | jsonfilter -l1 -qe "@.nftables[@.rule.table=\"banIP\"&&@.rule.chain=\"wan-input\"][@.expr[0].match.right=\"@${feed}\"].handle")"
519 [ -n "${handle}" ] && printf "%s\n" "delete rule inet banIP wan-input handle ${handle}"
520 handle="$(printf "%s\n" "${ruleset_raw}" | jsonfilter -l1 -qe "@.nftables[@.rule.table=\"banIP\"&&@.rule.chain=\"wan-forward\"][@.expr[0].match.right=\"@${feed}\"].handle")"
521 [ -n "${handle}" ] && printf "%s\n" "delete rule inet banIP wan-forward handle ${handle}"
522 handle="$(printf "%s\n" "${ruleset_raw}" | jsonfilter -l1 -qe "@.nftables[@.rule.table=\"banIP\"&&@.rule.chain=\"lan-forward\"][@.expr[0].match.right=\"@${feed}\"].handle")"
523 [ -n "${handle}" ] && printf "%s\n" "delete rule inet banIP lan-forward handle ${handle}"
524 } >"${tmp_flush}"
525 fi
526
527 # restore local backups during init
528 #
529 if { [ "${ban_action}" != "reload" ] || [ "${feed_url}" = "local" ]; } && [ "${feed%v*}" != "allowlist" ] && [ "${feed%v*}" != "blocklist" ]; then
530 f_restore "${feed}" "${feed_url}" "${tmp_load}"
531 restore_rc="${?}"
532 feed_rc="${restore_rc}"
533 fi
534
535 # handle local lists
536 #
537 if [ "${feed%v*}" = "allowlist" ]; then
538 {
539 printf "%s\n\n" "#!/usr/sbin/nft -f"
540 [ -s "${tmp_flush}" ] && cat "${tmp_flush}"
541 if [ "${proto}" = "MAC" ]; then
542 "${ban_awkcmd}" '/^([0-9A-f]{2}:){5}[0-9A-f]{2}([[:space:]]|$)/{printf "%s, ",tolower($1)}' "${ban_allowlist}" >"${tmp_file}"
543 printf "%s\n" "add set inet banIP ${feed} { type ether_addr; policy ${ban_nftpolicy}; $(f_getelements "${tmp_file}") }"
544 [ -z "${feed_direction##*forwardlan*}" ] && printf "%s\n" "add rule inet banIP lan-forward ether saddr @${feed} counter accept"
545 elif [ "${proto}" = "4" ]; then
546 "${ban_awkcmd}" '/^(([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:]]|$)/{printf "%s, ",$1}' "${ban_allowlist}" >"${tmp_file}"
547 printf "%s\n" "add set inet banIP ${feed} { type ipv4_addr; flags interval; auto-merge; policy ${ban_nftpolicy}; $(f_getelements "${tmp_file}") }"
548 if [ -z "${feed_direction##*input*}" ]; then
549 if [ "${ban_allowlistonly}" = "1" ]; then
550 printf "%s\n" "add rule inet banIP wan-input ip saddr != @${feed} ${log_input} counter drop"
551 else
552 printf "%s\n" "add rule inet banIP wan-input ip saddr @${feed} counter accept"
553 fi
554 fi
555 if [ -z "${feed_direction##*forwardwan*}" ]; then
556 if [ "${ban_allowlistonly}" = "1" ]; then
557 printf "%s\n" "add rule inet banIP wan-forward ip saddr != @${feed} ${log_forwardwan} counter drop"
558 else
559 printf "%s\n" "add rule inet banIP wan-forward ip saddr @${feed} counter accept"
560 fi
561 fi
562 if [ -z "${feed_direction##*forwardlan*}" ]; then
563 if [ "${ban_allowlistonly}" = "1" ]; then
564 printf "%s\n" "add rule inet banIP lan-forward ip daddr != @${feed} ${log_forwardlan} counter reject with icmp type admin-prohibited"
565 else
566 printf "%s\n" "add rule inet banIP lan-forward ip daddr @${feed} counter accept"
567 fi
568 fi
569 elif [ "${proto}" = "6" ]; then
570 "${ban_awkcmd}" '!/^([0-9A-f]{2}:){5}[0-9A-f]{2}([[:space:]]|$)/{printf "%s\n",$1}' "${ban_allowlist}" |
571 "${ban_awkcmd}" '/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\/(1?[0-2][0-8]|[0-9][0-9]))?)([[:space:]]|$)/{printf "%s, ",tolower($1)}' >"${tmp_file}"
572 printf "%s\n" "add set inet banIP ${feed} { type ipv6_addr; flags interval; auto-merge; policy ${ban_nftpolicy}; $(f_getelements "${tmp_file}") }"
573 if [ -z "${feed_direction##*input*}" ]; then
574 if [ "${ban_allowlistonly}" = "1" ]; then
575 printf "%s\n" "add rule inet banIP wan-input ip6 saddr != @${feed} ${log_input} counter drop"
576 else
577 printf "%s\n" "add rule inet banIP wan-input ip6 saddr @${feed} counter accept"
578 fi
579 fi
580 if [ -z "${feed_direction##*forwardwan*}" ]; then
581 if [ "${ban_allowlistonly}" = "1" ]; then
582 printf "%s\n" "add rule inet banIP wan-forward ip6 saddr != @${feed} ${log_forwardwan} counter drop"
583 else
584 printf "%s\n" "add rule inet banIP wan-forward ip6 saddr @${feed} counter accept"
585 fi
586 fi
587 if [ -z "${feed_direction##*forwardlan*}" ]; then
588 if [ "${ban_allowlistonly}" = "1" ]; then
589 printf "%s\n" "add rule inet banIP lan-forward ip6 daddr != @${feed} ${log_forwardlan} counter reject with icmpv6 type admin-prohibited"
590 else
591 printf "%s\n" "add rule inet banIP lan-forward ip6 daddr @${feed} counter accept"
592 fi
593 fi
594 fi
595 } >"${tmp_nft}"
596 feed_rc="${?}"
597 elif [ "${feed%v*}" = "blocklist" ]; then
598 {
599 printf "%s\n\n" "#!/usr/sbin/nft -f"
600 [ -s "${tmp_flush}" ] && cat "${tmp_flush}"
601 if [ "${proto}" = "MAC" ]; then
602 "${ban_awkcmd}" '/^([0-9A-f]{2}:){5}[0-9A-f]{2}([[:space:]]|$)/{printf "%s, ",tolower($1)}' "${ban_blocklist}" >"${tmp_file}"
603 printf "%s\n" "add set inet banIP ${feed} { type ether_addr; policy ${ban_nftpolicy}; $(f_getelements "${tmp_file}") }"
604 [ -z "${feed_direction##*forwardlan*}" ] && printf "%s\n" "add rule inet banIP lan-forward ether saddr @${feed} ${log_forwardlan} counter reject"
605 elif [ "${proto}" = "4" ]; then
606 if [ "${ban_deduplicate}" = "1" ]; then
607 "${ban_awkcmd}" '/^(([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:]]|$)/{printf "%s,\n",$1}' "${ban_blocklist}" >"${tmp_raw}"
608 "${ban_awkcmd}" 'NR==FNR{member[$0];next}!($0 in member)' "${ban_tmpfile}.deduplicate" "${tmp_raw}" 2>/dev/null >"${tmp_split}"
609 "${ban_awkcmd}" 'BEGIN{FS="[ ,]"}NR==FNR{member[$1];next}!($1 in member)' "${ban_tmpfile}.deduplicate" "${ban_blocklist}" 2>/dev/null >"${tmp_raw}"
610 cat "${tmp_raw}" 2>/dev/null >"${ban_blocklist}"
611 else
612 "${ban_awkcmd}" '/^(([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:]]|$)/{printf "%s,\n",$1}' "${ban_blocklist}" >"${tmp_split}"
613 fi
614 "${ban_awkcmd}" '{ORS=" ";print}' "${tmp_split}" 2>/dev/null >"${tmp_file}"
615 printf "%s\n" "add set inet banIP ${feed} { type ipv4_addr; flags interval, timeout; auto-merge; policy ${ban_nftpolicy}; $(f_getelements "${tmp_file}") }"
616 [ -z "${feed_direction##*input*}" ] && printf "%s\n" "add rule inet banIP wan-input ip saddr @${feed} ${log_input} counter drop"
617 [ -z "${feed_direction##*forwardwan*}" ] && printf "%s\n" "add rule inet banIP wan-forward ip saddr @${feed} ${log_forwardwan} counter drop"
618 [ -z "${feed_direction##*forwardlan*}" ] && printf "%s\n" "add rule inet banIP lan-forward ip daddr @${feed} ${log_forwardlan} counter reject with icmp type admin-prohibited"
619 elif [ "${proto}" = "6" ]; then
620 if [ "${ban_deduplicate}" = "1" ]; then
621 "${ban_awkcmd}" '!/^([0-9A-f]{2}:){5}[0-9A-f]{2}([[:space:]]|$)/{printf "%s\n",$1}' "${ban_blocklist}" |
622 "${ban_awkcmd}" '/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\/(1?[0-2][0-8]|[0-9][0-9]))?)([[:space:]]|$)/{printf "%s,\n",tolower($1)}' >"${tmp_raw}"
623 "${ban_awkcmd}" 'NR==FNR{member[$0];next}!($0 in member)' "${ban_tmpfile}.deduplicate" "${tmp_raw}" 2>/dev/null >"${tmp_split}"
624 "${ban_awkcmd}" 'BEGIN{FS="[ ,]"}NR==FNR{member[$1];next}!($1 in member)' "${ban_tmpfile}.deduplicate" "${ban_blocklist}" 2>/dev/null >"${tmp_raw}"
625 cat "${tmp_raw}" 2>/dev/null >"${ban_blocklist}"
626 else
627 "${ban_awkcmd}" '!/^([0-9A-f]{2}:){5}[0-9A-f]{2}([[:space:]]|$)/{printf "%s\n",$1}' "${ban_blocklist}" |
628 "${ban_awkcmd}" '/^(([0-9A-f]{0,4}:){1,7}[0-9A-f]{0,4}:?(\/(1?[0-2][0-8]|[0-9][0-9]))?)([[:space:]]|$)/{printf "%s,\n",tolower($1)}' >"${tmp_split}"
629 fi
630 "${ban_awkcmd}" '{ORS=" ";print}' "${tmp_split}" 2>/dev/null >"${tmp_file}"
631 printf "%s\n" "add set inet banIP ${feed} { type ipv6_addr; flags interval, timeout; auto-merge; policy ${ban_nftpolicy}; $(f_getelements "${tmp_file}") }"
632 [ -z "${feed_direction##*input*}" ] && printf "%s\n" "add rule inet banIP wan-input ip6 saddr @${feed} ${log_input} counter drop"
633 [ -z "${feed_direction##*forwardwan*}" ] && printf "%s\n" "add rule inet banIP wan-forward ip6 saddr @${feed} ${log_forwardwan} counter drop"
634 [ -z "${feed_direction##*forwardlan*}" ] && printf "%s\n" "add rule inet banIP lan-forward ip6 daddr @${feed} ${log_forwardlan} counter reject with icmpv6 type admin-prohibited"
635 fi
636 } >"${tmp_nft}"
637 feed_rc="${?}"
638 # handle external downloads
639 #
640 elif [ "${restore_rc}" != "0" ] && [ "${feed_url}" != "local" ]; then
641 # handle country downloads
642 #
643 if [ "${feed%v*}" = "country" ]; then
644 for country in ${ban_country}; do
645 feed_log="$("${ban_fetchcmd}" ${ban_fetchparm} "${tmp_raw}" "${feed_url}${country}-aggregated.zone" 2>&1)"
646 feed_rc="${?}"
647 [ "${feed_rc}" = "0" ] && cat "${tmp_raw}" 2>/dev/null >>"${tmp_load}"
648 done
649 rm -f "${tmp_raw}"
650
651 # handle asn downloads
652 #
653 elif [ "${feed%v*}" = "asn" ]; then
654 for asn in ${ban_asn}; do
655 feed_log="$("${ban_fetchcmd}" ${ban_fetchparm} "${tmp_raw}" "${feed_url}AS${asn}" 2>&1)"
656 feed_rc="${?}"
657 [ "${feed_rc}" = "0" ] && cat "${tmp_raw}" 2>/dev/null >>"${tmp_load}"
658 done
659 rm -f "${tmp_raw}"
660
661 # handle compressed downloads
662 #
663 elif [ -n "${feed_flag}" ]; then
664 case "${feed_flag}" in
665 "gz")
666 feed_log="$("${ban_fetchcmd}" ${ban_fetchparm} "${tmp_raw}" "${feed_url}" 2>&1)"
667 feed_rc="${?}"
668 if [ "${feed_rc}" = "0" ]; then
669 zcat "${tmp_raw}" 2>/dev/null >"${tmp_load}"
670 feed_rc="${?}"
671 fi
672 rm -f "${tmp_raw}"
673 ;;
674 esac
675
676 # handle normal downloads
677 #
678 else
679 feed_log="$("${ban_fetchcmd}" ${ban_fetchparm} "${tmp_load}" "${feed_url}" 2>&1)"
680 feed_rc="${?}"
681 fi
682 fi
683
684 # backup/restore
685 #
686 if [ "${restore_rc}" != "0" ] && [ "${feed_rc}" = "0" ] && [ "${feed_url}" != "local" ] && [ ! -s "${tmp_nft}" ]; then
687 f_backup "${feed}" "${tmp_load}"
688 feed_rc="${?}"
689 elif [ -z "${restore_rc}" ] && [ "${feed_rc}" != "0" ] && [ "${feed_url}" != "local" ] && [ ! -s "${tmp_nft}" ]; then
690 f_restore "${feed}" "${feed_url}" "${tmp_load}" "${feed_rc}"
691 feed_rc="${?}"
692 fi
693
694 # build nft file with set and rules for regular downloads
695 #
696 if [ "${feed_rc}" = "0" ] && [ ! -s "${tmp_nft}" ]; then
697 # deduplicate sets
698 #
699 if [ "${ban_deduplicate}" = "1" ] && [ "${feed_url}" != "local" ]; then
700 "${ban_awkcmd}" "${feed_rule}" "${tmp_load}" 2>/dev/null >"${tmp_raw}"
701 "${ban_awkcmd}" 'NR==FNR{member[$0];next}!($0 in member)' "${ban_tmpfile}.deduplicate" "${tmp_raw}" 2>/dev/null | tee -a "${ban_tmpfile}.deduplicate" >"${tmp_split}"
702 else
703 "${ban_awkcmd}" "${feed_rule}" "${tmp_load}" 2>/dev/null >"${tmp_split}"
704 fi
705 feed_rc="${?}"
706 # split sets
707 #
708 if [ "${feed_rc}" = "0" ]; then
709 if [ -n "${ban_splitsize//[![:digit]]/}" ] && [ "${ban_splitsize//[![:digit]]/}" -gt "0" ]; then
710 if ! "${ban_awkcmd}" "NR%${ban_splitsize//[![:digit]]/}==1{file=\"${tmp_file}.\"++i;}{ORS=\" \";print > file}" "${tmp_split}" 2>/dev/null; then
711 rm -f "${tmp_file}".*
712 f_log "info" "failed to split ${feed} set to size '${ban_splitsize//[![:digit]]/}'"
713 fi
714 else
715 "${ban_awkcmd}" '{ORS=" ";print}' "${tmp_split}" 2>/dev/null >"${tmp_file}.1"
716 fi
717 feed_rc="${?}"
718 fi
719 rm -f "${tmp_raw}" "${tmp_load}"
720 if [ "${feed_rc}" = "0" ] && [ "${proto}" = "4" ]; then
721 {
722 # nft header (IPv4 set)
723 #
724 printf "%s\n\n" "#!/usr/sbin/nft -f"
725 [ -s "${tmp_flush}" ] && cat "${tmp_flush}"
726 printf "%s\n" "add set inet banIP ${feed} { type ipv4_addr; flags interval; auto-merge; policy ${ban_nftpolicy}; $(f_getelements "${tmp_file}.1") }"
727
728 # input and forward rules
729 #
730 [ -z "${feed_direction##*input*}" ] && printf "%s\n" "add rule inet banIP wan-input ip saddr @${feed} ${log_input} counter drop"
731 [ -z "${feed_direction##*forwardwan*}" ] && printf "%s\n" "add rule inet banIP wan-forward ip saddr @${feed} ${log_forwardwan} counter drop"
732 [ -z "${feed_direction##*forwardlan*}" ] && printf "%s\n" "add rule inet banIP lan-forward ip daddr @${feed} ${log_forwardlan} counter reject with icmp type admin-prohibited"
733 } >"${tmp_nft}"
734 elif [ "${feed_rc}" = "0" ] && [ "${proto}" = "6" ]; then
735 {
736 # nft header (IPv6 set)
737 #
738 printf "%s\n\n" "#!/usr/sbin/nft -f"
739 [ -s "${tmp_flush}" ] && cat "${tmp_flush}"
740 printf "%s\n" "add set inet banIP ${feed} { type ipv6_addr; flags interval; auto-merge; policy ${ban_nftpolicy}; $(f_getelements "${tmp_file}.1") }"
741
742 # input and forward rules
743 #
744 [ -z "${feed_direction##*input*}" ] && printf "%s\n" "add rule inet banIP wan-input ip6 saddr @${feed} ${log_input} counter drop"
745 [ -z "${feed_direction##*forwardwan*}" ] && printf "%s\n" "add rule inet banIP wan-forward ip6 saddr @${feed} ${log_forwardwan} counter drop"
746 [ -z "${feed_direction##*forwardlan*}" ] && printf "%s\n" "add rule inet banIP lan-forward ip6 daddr @${feed} ${log_forwardlan} counter reject with icmpv6 type admin-prohibited"
747 } >"${tmp_nft}"
748 fi
749 fi
750
751 # load generated nft file in banIP table
752 #
753 if [ "${feed_rc}" = "0" ]; then
754 cnt_dl="$("${ban_awkcmd}" 'END{printf "%d",NR}' "${tmp_split}" 2>/dev/null)"
755 if [ "${cnt_dl:-"0"}" -gt "0" ] || [ "${feed_url}" = "local" ] || [ "${feed%v*}" = "allowlist" ] || [ "${feed%v*}" = "blocklist" ]; then
756 feed_log="$("${ban_nftcmd}" -f "${tmp_nft}" 2>&1)"
757 feed_rc="${?}"
758 # load additional split files
759 #
760 if [ "${feed_rc}" = "0" ]; then
761 for split_file in "${tmp_file}".*; do
762 [ ! -f "${split_file}" ] && break
763 if [ "${split_file##*.}" = "1" ]; then
764 rm -f "${split_file}"
765 continue
766 fi
767 if ! "${ban_nftcmd}" add element inet banIP "${feed}" "{ $(cat "${split_file}") }" >/dev/null 2>&1; then
768 f_log "info" "failed to add split file '${split_file##*.}' to ${feed} set"
769 fi
770 rm -f "${split_file}"
771 done
772 if [ "${ban_debug}" = "1" ] && [ "${ban_reportelements}" = "1" ]; then
773 cnt_set="$("${ban_nftcmd}" -j list set inet banIP "${feed}" 2>/dev/null | jsonfilter -qe '@.nftables[*].set.elem[*]' | wc -l 2>/dev/null)"
774 fi
775 fi
776 else
777 f_log "info" "empty feed ${feed} will be skipped"
778 fi
779 fi
780 rm -f "${tmp_split}" "${tmp_nft}"
781 end_ts="$(date +%s)"
782
783 f_log "debug" "f_down ::: name: ${feed}, cnt_dl: ${cnt_dl:-"-"}, cnt_set: ${cnt_set:-"-"}, split_size: ${ban_splitsize:-"-"}, time: $((end_ts - start_ts)), rc: ${feed_rc:-"-"}, log: ${feed_log:-"-"}"
784 }
785
786 # backup feeds
787 #
788 f_backup() {
789 local backup_rc feed="${1}" feed_file="${2}"
790
791 gzip -cf "${feed_file}" >"${ban_backupdir}/banIP.${feed}.gz"
792 backup_rc="${?}"
793
794 f_log "debug" "f_backup ::: name: ${feed}, source: ${feed_file##*/}, target: banIP.${feed}.gz, rc: ${backup_rc}"
795 return ${backup_rc}
796 }
797
798 # restore feeds
799 #
800 f_restore() {
801 local tmp_feed restore_rc="1" feed="${1}" feed_url="${2}" feed_file="${3}" feed_rc="${4:-"0"}"
802
803 [ "${feed_rc}" != "0" ] && restore_rc="${feed_rc}"
804 [ "${feed_url}" = "local" ] && tmp_feed="${feed%v*}v4" || tmp_feed="${feed}"
805 if [ -f "${ban_backupdir}/banIP.${tmp_feed}.gz" ]; then
806 zcat "${ban_backupdir}/banIP.${tmp_feed}.gz" 2>/dev/null >"${feed_file}"
807 restore_rc="${?}"
808 fi
809
810 f_log "debug" "f_restore ::: name: ${feed}, source: banIP.${tmp_feed}.gz, target: ${feed_file##*/}, in_rc: ${feed_rc}, rc: ${restore_rc}"
811 return ${restore_rc}
812 }
813
814 # remove disabled feeds
815 #
816 f_rmset() {
817 local tmp_del ruleset_raw table_sets handle set del_set feed_log feed_rc
818
819 tmp_del="${ban_tmpfile}.final.delete"
820 ruleset_raw="$("${ban_nftcmd}" -tj list ruleset 2>/dev/null)"
821 table_sets="$(printf "%s\n" "${ruleset_raw}" | jsonfilter -qe '@.nftables[@.set.table="banIP"].set.name')"
822 {
823 printf "%s\n\n" "#!/usr/sbin/nft -f"
824 for set in ${table_sets}; do
825 if ! printf "%s" "allowlist blocklist ${ban_feed}" | "${ban_grepcmd}" -q "${set%v*}"; then
826 del_set="${del_set}${set}, "
827 rm -f "${ban_backupdir}/banIP.${set}.gz"
828 printf "%s\n" "flush set inet banIP ${set}"
829 handle="$(printf "%s\n" "${ruleset_raw}" | jsonfilter -l1 -qe "@.nftables[@.rule.table=\"banIP\"&&@.rule.chain=\"wan-input\"][@.expr[0].match.right=\"@${set}\"].handle")"
830 [ -n "${handle}" ] && printf "%s\n" "delete rule inet banIP wan-input handle ${handle}"
831 handle="$(printf "%s\n" "${ruleset_raw}" | jsonfilter -l1 -qe "@.nftables[@.rule.table=\"banIP\"&&@.rule.chain=\"wan-forward\"][@.expr[0].match.right=\"@${set}\"].handle")"
832 [ -n "${handle}" ] && printf "%s\n" "delete rule inet banIP wan-forward handle ${handle}"
833 handle="$(printf "%s\n" "${ruleset_raw}" | jsonfilter -l1 -qe "@.nftables[@.rule.table=\"banIP\"&&@.rule.chain=\"lan-forward\"][@.expr[0].match.right=\"@${set}\"].handle")"
834 [ -n "${handle}" ] && printf "%s\n" "delete rule inet banIP lan-forward handle ${handle}"
835 printf "%s\n\n" "delete set inet banIP ${set}"
836 fi
837 done
838 } >"${tmp_del}"
839
840 if [ -n "${del_set}" ]; then
841 del_set="${del_set%%??}"
842 feed_log="$("${ban_nftcmd}" -f "${tmp_del}" 2>&1)"
843 feed_rc="${?}"
844 fi
845 rm -f "${tmp_del}"
846
847 f_log "debug" "f_rmset ::: sets: ${del_set:-"-"}, rc: ${feed_rc:-"-"}, log: ${feed_log:-"-"}"
848 }
849
850 # generate status information
851 #
852 f_genstatus() {
853 local object duration set table_sets cnt_elements="0" split="0" status="${1}"
854
855 [ -z "${ban_dev}" ] && f_conf
856 if [ "${status}" = "active" ]; then
857 if [ -n "${ban_starttime}" ]; then
858 ban_endtime="$(date "+%s")"
859 duration="$(((ban_endtime - ban_starttime) / 60))m $(((ban_endtime - ban_starttime) % 60))s"
860 fi
861 table_sets="$("${ban_nftcmd}" -tj list ruleset 2>/dev/null | jsonfilter -qe '@.nftables[@.set.table="banIP"].set.name')"
862 if [ "${ban_reportelements}" = "1" ]; then
863 for set in ${table_sets}; do
864 cnt_elements="$((cnt_elements + $("${ban_nftcmd}" -j list set inet banIP "${set}" 2>/dev/null | jsonfilter -qe '@.nftables[*].set.elem[*]' | wc -l 2>/dev/null)))"
865 done
866 fi
867 runtime="action: ${ban_action:-"-"}, duration: ${duration:-"-"}, date: $(date "+%Y-%m-%d %H:%M:%S")"
868 fi
869 [ ${ban_splitsize:-"0"} -gt "0" ] && split="1"
870
871 : >"${ban_rtfile}"
872 json_init
873 json_load_file "${ban_rtfile}" >/dev/null 2>&1
874 json_add_string "status" "${status}"
875 json_add_string "version" "${ban_ver}"
876 json_add_string "element_count" "${cnt_elements}"
877 json_add_array "active_feeds"
878 if [ "${status}" != "active" ]; then
879 json_add_object
880 json_add_string "feed" "-"
881 json_close_object
882 else
883 for object in ${table_sets}; do
884 json_add_object
885 json_add_string "feed" "${object}"
886 json_close_object
887 done
888 fi
889 json_close_array
890 json_add_array "active_devices"
891 if [ "${status}" != "active" ]; then
892 json_add_object
893 json_add_string "device" "-"
894 json_close_object
895 else
896 for object in ${ban_dev}; do
897 json_add_object
898 json_add_string "device" "${object}"
899 json_close_object
900 done
901 for object in ${ban_ifv4} ${ban_ifv6}; do
902 json_add_object
903 json_add_string "interface" "${object}"
904 json_close_object
905 done
906 fi
907 json_close_array
908 json_add_array "active_subnets"
909 if [ "${status}" != "active" ]; then
910 json_add_object
911 json_add_string "subnet" "-"
912 json_close_object
913 else
914 for object in ${ban_sub}; do
915 json_add_object
916 json_add_string "subnet" "${object}"
917 json_close_object
918 done
919 fi
920 json_close_array
921 json_add_string "nft_info" "priority: ${ban_nftpriority}, policy: ${ban_nftpolicy}, loglevel: ${ban_nftloglevel}, expiry: ${ban_nftexpiry:-"-"}"
922 json_add_string "run_info" "base: ${ban_basedir}, backup: ${ban_backupdir}, report: ${ban_reportdir}, feed: ${ban_feedfile}"
923 json_add_string "run_flags" "auto: $(f_char ${ban_autodetect}), proto (4/6): $(f_char ${ban_protov4})/$(f_char ${ban_protov6}), log (wan-inp/wan-fwd/lan-fwd): $(f_char ${ban_loginput})/$(f_char ${ban_logforwardwan})/$(f_char ${ban_logforwardlan}), dedup: $(f_char ${ban_deduplicate}), split: $(f_char ${split}), allowed only: $(f_char ${ban_allowlistonly})"
924 json_add_string "last_run" "${runtime:-"-"}"
925 json_add_string "system_info" "cores: ${ban_cores}, memory: ${ban_memory}, device: ${ban_sysver}"
926 json_dump >"${ban_rtfile}"
927 }
928
929 # get status information
930 #
931 f_getstatus() {
932 local key keylist type value index_key1 index_key2 index_value1 index_value2
933
934 [ -z "${ban_dev}" ] && f_conf
935 json_load_file "${ban_rtfile}" >/dev/null 2>&1
936 if json_get_keys keylist; then
937 printf "%s\n" "::: banIP runtime information"
938 for key in ${keylist}; do
939 json_get_var value "${key}" >/dev/null 2>&1
940 if [ "${key}" = "status" ]; then
941 value="${value} ($(f_actual))"
942 elif [ "${key}" = "active_devices" ]; then
943 json_select "${key}" >/dev/null 2>&1
944 index=1
945 while json_get_type type "${index}" && [ "${type}" = "object" ]; do
946 json_get_keys index_key1 "${index}" >/dev/null 2>&1
947 json_get_keys index_key2 "$((index + 1))" >/dev/null 2>&1
948 json_get_values index_value1 "${index}" >/dev/null 2>&1
949 if [ "${index}" = "1" ] && [ "${index_key1// /}" = "device" ] && [ "${index_key2// /}" = "interface" ]; then
950 json_get_values index_value2 "$((index + 1))" >/dev/null 2>&1
951 value="${index_value1} ::: ${index_value2}"
952 index="$((index + 1))"
953 elif [ "${index}" = "1" ]; then
954 value="${index_value1}"
955 elif [ "${index}" != "1" ] && [ "${index_key1// /}" = "device" ] && [ "${index_key2// /}" = "interface" ]; then
956 json_get_values index_value2 "$((index + 1))" >/dev/null 2>&1
957 value="${value}, ${index_value1} ::: ${index_value2}"
958 index="$((index + 1))"
959 elif [ "${index}" != "1" ]; then
960 value="${value}, ${index_value1}"
961 fi
962 index="$((index + 1))"
963 done
964 json_select ".."
965 elif [ "${key%_*}" = "active" ]; then
966 json_select "${key}" >/dev/null 2>&1
967 index=1
968 while json_get_type type "${index}" && [ "${type}" = "object" ]; do
969 json_get_values index_value1 "${index}" >/dev/null 2>&1
970 if [ "${index}" = "1" ]; then
971 value="${index_value1}"
972 else
973 value="${value}, ${index_value1}"
974 fi
975 index="$((index + 1))"
976 done
977 json_select ".."
978 fi
979 printf " + %-17s : %s\n" "${key}" "${value:-"-"}"
980 done
981 else
982 printf "%s\n" "::: no banIP runtime information available"
983 fi
984 }
985
986 # domain lookup
987 #
988 f_lookup() {
989 local cnt list domain lookup ip elementsv4 elementsv6 start_time end_time duration cnt_domain="0" cnt_ip="0" feed="${1}"
990
991 start_time="$(date "+%s")"
992 if [ "${feed}" = "allowlist" ]; then
993 list="$("${ban_awkcmd}" '/^([[:alnum:]_-]{1,63}\.)+[[:alpha:]]+([[:space:]]|$)/{printf "%s ",tolower($1)}' "${ban_allowlist}" 2>/dev/null)"
994 elif [ "${feed}" = "blocklist" ]; then
995 list="$("${ban_awkcmd}" '/^([[:alnum:]_-]{1,63}\.)+[[:alpha:]]+([[:space:]]|$)/{printf "%s ",tolower($1)}' "${ban_blocklist}" 2>/dev/null)"
996 fi
997
998 for domain in ${list}; do
999 lookup="$("${ban_lookupcmd}" "${domain}" ${ban_resolver} 2>/dev/null | "${ban_awkcmd}" '/^Address[ 0-9]*: /{if(!seen[$NF]++)printf "%s ",$NF}' 2>/dev/null)"
1000 for ip in ${lookup}; do
1001 if [ "${ip%%.*}" = "0" ] || [ -z "${ip%%::*}" ]; then
1002 continue
1003 else
1004 if { [ "${feed}" = "allowlist" ] && ! "${ban_grepcmd}" -q "^${ip}" "${ban_allowlist}"; } ||
1005 { [ "${feed}" = "blocklist" ] && ! "${ban_grepcmd}" -q "^${ip}" "${ban_blocklist}"; }; then
1006 if [ "${ip##*:}" = "${ip}" ]; then
1007 elementsv4="${elementsv4} ${ip},"
1008 else
1009 elementsv6="${elementsv6} ${ip},"
1010 fi
1011 if [ "${feed}" = "allowlist" ] && [ "${ban_autoallowlist}" = "1" ]; then
1012 printf "%-42s%s\n" "${ip}" "# '${domain}' added on $(date "+%Y-%m-%d %H:%M:%S")" >>"${ban_allowlist}"
1013 elif [ "${feed}" = "blocklist" ] && [ "${ban_autoblocklist}" = "1" ]; then
1014 printf "%-42s%s\n" "${ip}" "# '${domain}' added on $(date "+%Y-%m-%d %H:%M:%S")" >>"${ban_blocklist}"
1015 fi
1016 cnt_ip="$((cnt_ip + 1))"
1017 fi
1018 fi
1019 done
1020 cnt_domain="$((cnt_domain + 1))"
1021 done
1022 if [ -n "${elementsv4}" ]; then
1023 if ! "${ban_nftcmd}" add element inet banIP "${feed}v4" "{ ${elementsv4} }" >/dev/null 2>&1; then
1024 f_log "info" "failed to add lookup file to ${feed}v4 set"
1025 fi
1026 fi
1027 if [ -n "${elementsv6}" ]; then
1028 if ! "${ban_nftcmd}" add element inet banIP "${feed}v6" "{ ${elementsv6} }" >/dev/null 2>&1; then
1029 f_log "info" "failed to add lookup file to ${feed}v6 set"
1030 fi
1031 fi
1032 end_time="$(date "+%s")"
1033 duration="$(((end_time - start_time) / 60))m $(((end_time - start_time) % 60))s"
1034
1035 f_log "info" "Lookup summary for the local ${feed}: Domains processed: ${cnt_domain}, IPs added: ${cnt_ip}, Duration: ${duration}"
1036 }
1037
1038 # table statistics
1039 #
1040 f_report() {
1041 local report_jsn report_txt set tmp_val ruleset_raw table_sets set_cnt set_input set_forwardwan set_forwardlan set_cntinput set_cntforwardwan set_cntforwardlan output="${1}"
1042 local detail set_details jsnval timestamp autoadd_allow autoadd_block sum_sets sum_setinput sum_setforwardwan sum_setforwardlan sum_setelements sum_cntinput sum_cntforwardwan sum_cntforwardlan
1043
1044 [ -z "${ban_dev}" ] && f_conf
1045 f_mkdir "${ban_reportdir}"
1046 report_jsn="${ban_reportdir}/ban_report.jsn"
1047 report_txt="${ban_reportdir}/ban_report.txt"
1048
1049 # json output preparation
1050 #
1051 ruleset_raw="$("${ban_nftcmd}" -tj list ruleset 2>/dev/null)"
1052 table_sets="$(printf "%s" "${ruleset_raw}" | jsonfilter -qe '@.nftables[@.set.table="banIP"].set.name')"
1053 sum_sets="0"
1054 sum_setinput="0"
1055 sum_setforwardwan="0"
1056 sum_setforwardlan="0"
1057 sum_setelements="0"
1058 sum_cntinput="0"
1059 sum_cntforwardwan="0"
1060 sum_cntforwardlan="0"
1061 timestamp="$(date "+%Y-%m-%d %H:%M:%S")"
1062 : >"${report_jsn}"
1063 {
1064 printf "%s\n" "{"
1065 printf "\t%s\n" '"sets": {'
1066 for set in ${table_sets}; do
1067 set_cntinput="$(printf "%s" "${ruleset_raw}" | jsonfilter -l1 -qe "@.nftables[@.rule.table=\"banIP\"&&@.rule.chain=\"wan-input\"][@.expr[0].match.right=\"@${set}\"].expr[*].counter.packets")"
1068 set_cntforwardwan="$(printf "%s" "${ruleset_raw}" | jsonfilter -l1 -qe "@.nftables[@.rule.table=\"banIP\"&&@.rule.chain=\"wan-forward\"][@.expr[0].match.right=\"@${set}\"].expr[*].counter.packets")"
1069 set_cntforwardlan="$(printf "%s" "${ruleset_raw}" | jsonfilter -l1 -qe "@.nftables[@.rule.table=\"banIP\"&&@.rule.chain=\"lan-forward\"][@.expr[0].match.right=\"@${set}\"].expr[*].counter.packets")"
1070 if [ "${ban_reportelements}" = "1" ]; then
1071 set_cnt="$("${ban_nftcmd}" -j list set inet banIP "${set}" 2>/dev/null | jsonfilter -qe '@.nftables[*].set.elem[*]' | wc -l 2>/dev/null)"
1072 sum_setelements="$((sum_setelements + set_cnt))"
1073 else
1074 set_cnt=""
1075 sum_setelements="n/a"
1076 fi
1077 if [ -n "${set_cntinput}" ]; then
1078 set_input="OK"
1079 sum_setinput="$((sum_setinput + 1))"
1080 sum_cntinput="$((sum_cntinput + set_cntinput))"
1081 else
1082 set_input="-"
1083 set_cntinput=""
1084 fi
1085 if [ -n "${set_cntforwardwan}" ]; then
1086 set_forwardwan="OK"
1087 sum_setforwardwan="$((sum_setforwardwan + 1))"
1088 sum_cntforwardwan="$((sum_cntforwardwan + set_cntforwardwan))"
1089 else
1090 set_forwardwan="-"
1091 set_cntforwardwan=""
1092 fi
1093 if [ -n "${set_cntforwardlan}" ]; then
1094 set_forwardlan="OK"
1095 sum_setforwardlan="$((sum_setforwardlan + 1))"
1096 sum_cntforwardlan="$((sum_cntforwardlan + set_cntforwardlan))"
1097 else
1098 set_forwardlan="-"
1099 set_cntforwardlan=""
1100 fi
1101 [ "${sum_sets}" -gt "0" ] && printf "%s\n" ","
1102 printf "\t\t%s\n" "\"${set}\": {"
1103 printf "\t\t\t%s\n" "\"cnt_elements\": \"${set_cnt}\","
1104 printf "\t\t\t%s\n" "\"cnt_input\": \"${set_cntinput}\","
1105 printf "\t\t\t%s\n" "\"input\": \"${set_input}\","
1106 printf "\t\t\t%s\n" "\"cnt_forwardwan\": \"${set_cntforwardwan}\","
1107 printf "\t\t\t%s\n" "\"wan_forward\": \"${set_forwardwan}\","
1108 printf "\t\t\t%s\n" "\"cnt_forwardlan\": \"${set_cntforwardlan}\","
1109 printf "\t\t\t%s\n" "\"lan_forward\": \"${set_forwardlan}\""
1110 printf "\t\t%s" "}"
1111 sum_sets="$((sum_sets + 1))"
1112 done
1113 printf "\n\t%s\n" "},"
1114 printf "\t%s\n" "\"timestamp\": \"${timestamp}\","
1115 printf "\t%s\n" "\"autoadd_allow\": \"$("${ban_grepcmd}" -c "added on ${timestamp% *}" "${ban_allowlist}")\","
1116 printf "\t%s\n" "\"autoadd_block\": \"$("${ban_grepcmd}" -c "added on ${timestamp% *}" "${ban_blocklist}")\","
1117 printf "\t%s\n" "\"sum_sets\": \"${sum_sets}\","
1118 printf "\t%s\n" "\"sum_setinput\": \"${sum_setinput}\","
1119 printf "\t%s\n" "\"sum_setforwardwan\": \"${sum_setforwardwan}\","
1120 printf "\t%s\n" "\"sum_setforwardlan\": \"${sum_setforwardlan}\","
1121 printf "\t%s\n" "\"sum_setelements\": \"${sum_setelements}\","
1122 printf "\t%s\n" "\"sum_cntinput\": \"${sum_cntinput}\","
1123 printf "\t%s\n" "\"sum_cntforwardwan\": \"${sum_cntforwardwan}\","
1124 printf "\t%s\n" "\"sum_cntforwardlan\": \"${sum_cntforwardlan}\""
1125 printf "%s\n" "}"
1126 } >>"${report_jsn}"
1127
1128 # text output preparation
1129 #
1130 if [ "${output}" != "json" ] && [ -s "${report_jsn}" ]; then
1131 : >"${report_txt}"
1132 json_init
1133 if json_load_file "${report_jsn}" >/dev/null 2>&1; then
1134 json_get_var timestamp "timestamp" >/dev/null 2>&1
1135 json_get_var autoadd_allow "autoadd_allow" >/dev/null 2>&1
1136 json_get_var autoadd_block "autoadd_block" >/dev/null 2>&1
1137 json_get_var sum_sets "sum_sets" >/dev/null 2>&1
1138 json_get_var sum_setinput "sum_setinput" >/dev/null 2>&1
1139 json_get_var sum_setforwardwan "sum_setforwardwan" >/dev/null 2>&1
1140 json_get_var sum_setforwardlan "sum_setforwardlan" >/dev/null 2>&1
1141 json_get_var sum_setelements "sum_setelements" >/dev/null 2>&1
1142 json_get_var sum_cntinput "sum_cntinput" >/dev/null 2>&1
1143 json_get_var sum_cntforwardwan "sum_cntforwardwan" >/dev/null 2>&1
1144 json_get_var sum_cntforwardlan "sum_cntforwardlan" >/dev/null 2>&1
1145 {
1146 printf "%s\n%s\n%s\n" ":::" "::: banIP Set Statistics" ":::"
1147 printf "%s\n" " Timestamp: ${timestamp}"
1148 printf "%s\n" " ------------------------------"
1149 printf "%s\n" " auto-added to allowlist today: ${autoadd_allow}"
1150 printf "%s\n\n" " auto-added to blocklist today: ${autoadd_block}"
1151 json_select "sets" >/dev/null 2>&1
1152 json_get_keys table_sets >/dev/null 2>&1
1153 if [ -n "${table_sets}" ]; then
1154 printf "%-25s%-15s%-24s%-24s%s\n" " Set" "| Elements" "| WAN-Input (packets)" "| WAN-Forward (packets)" "| LAN-Forward (packets)"
1155 printf "%s\n" " ---------------------+--------------+-----------------------+-----------------------+------------------------"
1156 for set in ${table_sets}; do
1157 printf " %-21s" "${set}"
1158 json_select "${set}"
1159 json_get_keys set_details
1160 for detail in ${set_details}; do
1161 json_get_var jsnval "${detail}" >/dev/null 2>&1
1162 case "${detail}" in
1163 "cnt_elements")
1164 printf "%-15s" "| ${jsnval}"
1165 ;;
1166 "cnt_input" | "cnt_forwardwan" | "cnt_forwardlan")
1167 [ -n "${jsnval}" ] && tmp_val=": ${jsnval}"
1168 ;;
1169 *)
1170 printf "%-24s" "| ${jsnval}${tmp_val}"
1171 tmp_val=""
1172 ;;
1173 esac
1174 done
1175 printf "\n"
1176 json_select ".."
1177 done
1178 printf "%s\n" " ---------------------+--------------+-----------------------+-----------------------+------------------------"
1179 printf "%-25s%-15s%-24s%-24s%s\n" " ${sum_sets}" "| ${sum_setelements}" "| ${sum_setinput} (${sum_cntinput})" "| ${sum_setforwardwan} (${sum_cntforwardwan})" "| ${sum_setforwardlan} (${sum_cntforwardlan})"
1180 fi
1181 } >>"${report_txt}"
1182 fi
1183 fi
1184
1185 # output channel (text|json|mail)
1186 #
1187 case "${output}" in
1188 "text")
1189 [ -s "${report_txt}" ] && cat "${report_txt}"
1190 ;;
1191 "json")
1192 [ -s "${report_jsn}" ] && cat "${report_jsn}"
1193 ;;
1194 "mail")
1195 [ -n "${ban_mailreceiver}" ] && [ -x "${ban_mailcmd}" ] && f_mail
1196 ;;
1197 esac
1198 rm -f "${report_txt}"
1199 }
1200
1201 # set search
1202 #
1203 f_search() {
1204 local set table_sets ip proto run_search hold cnt search="${1}"
1205
1206 if [ -n "${search}" ]; then
1207 ip="$(printf "%s" "${search}" | "${ban_awkcmd}" 'BEGIN{RS="(([0-9]{1,3}\\.){3}[0-9]{1,3})+"}{printf "%s",RT}')"
1208 [ -n "${ip}" ] && proto="v4"
1209 if [ -z "${proto}" ]; then
1210 ip="$(printf "%s" "${search}" | "${ban_awkcmd}" 'BEGIN{RS="([A-Fa-f0-9]{1,4}::?){3,7}[A-Fa-f0-9]{1,4}"}{printf "%s",RT}')"
1211 [ -n "${ip}" ] && proto="v6"
1212 fi
1213 fi
1214 if [ -n "${proto}" ]; then
1215 table_sets="$("${ban_nftcmd}" -tj list ruleset 2>/dev/null | jsonfilter -qe "@.nftables[@.set.table=\"banIP\"&&@.set.type=\"ip${proto}_addr\"].set.name")"
1216 else
1217 printf "%s\n%s\n%s\n" ":::" "::: no valid search input" ":::"
1218 return
1219 fi
1220 printf "%s\n%s\n%s\n" ":::" "::: banIP Search" ":::"
1221 printf " %s\n" "Looking for IP '${ip}' on $(date "+%Y-%m-%d %H:%M:%S")"
1222 printf " %s\n" "---"
1223 cnt="1"
1224 run_search="/var/run/banIP.search"
1225 for set in ${table_sets}; do
1226 [ -f "${run_search}" ] && break
1227 (
1228 if "${ban_nftcmd}" get element inet banIP "${set}" "{ ${ip} }" >/dev/null 2>&1; then
1229 printf " %s\n" "IP found in Set '${set}'"
1230 : >"${run_search}"
1231 fi
1232 ) &
1233 hold="$((cnt % ban_cores))"
1234 [ "${hold}" = "0" ] && wait
1235 cnt="$((cnt + 1))"
1236 done
1237 wait
1238 [ ! -f "${run_search}" ] && printf " %s\n" "IP not found"
1239 rm -f "${run_search}"
1240 }
1241
1242 # set survey
1243 #
1244 f_survey() {
1245 local set_elements set="${1}"
1246
1247 if [ -z "${set}" ]; then
1248 printf "%s\n%s\n%s\n" ":::" "::: no valid survey input" ":::"
1249 return
1250 fi
1251 [ -n "${set}" ] && set_elements="$("${ban_nftcmd}" -j list set inet banIP "${set}" 2>/dev/null | jsonfilter -qe '@.nftables[*].set.elem[*]')"
1252 printf "%s\n%s\n%s\n" ":::" "::: banIP Survey" ":::"
1253 printf " %s\n" "List the elements of Set '${set}' on $(date "+%Y-%m-%d %H:%M:%S")"
1254 printf " %s\n" "---"
1255 [ -n "${set_elements}" ] && printf "%s\n" "${set_elements}" || printf " %s\n" "empty set"
1256 }
1257
1258 # send status mails
1259 #
1260 f_mail() {
1261 local msmtp_debug
1262
1263 # load mail template
1264 #
1265 if [ -r "${ban_mailtemplate}" ]; then
1266 . "${ban_mailtemplate}"
1267 else
1268 f_log "info" "the mail template is missing"
1269 fi
1270 [ -z "${mail_text}" ] && f_log "info" "the 'mail_text' template variable is empty"
1271 [ "${ban_debug}" = "1" ] && msmtp_debug="--debug"
1272
1273 # send mail
1274 #
1275 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"
1276 if printf "%b" "${ban_mailhead}${mail_text}" | "${ban_mailcmd}" --timeout=10 ${msmtp_debug} -a "${ban_mailprofile}" "${ban_mailreceiver}" >/dev/null 2>&1; then
1277 f_log "info" "status mail was sent successfully"
1278 else
1279 f_log "info" "failed to send status mail (${?})"
1280 fi
1281
1282 f_log "debug" "f_mail ::: notification: ${ban_mailnotification}, template: ${ban_mailtemplate}, profile: ${ban_mailprofile}, receiver: ${ban_mailreceiver}, rc: ${?}"
1283 }
1284
1285 # check banIP availability and initial sourcing
1286 #
1287 f_system
1288 if [ "${ban_action}" != "stop" ]; then
1289 if [ -r "/lib/functions.sh" ] && [ -r "/lib/functions/network.sh" ] && [ -r "/usr/share/libubox/jshn.sh" ]; then
1290 . "/lib/functions.sh"
1291 . "/lib/functions/network.sh"
1292 . "/usr/share/libubox/jshn.sh"
1293 else
1294 f_log "err" "system libraries not found"
1295 fi
1296 [ ! -d "/etc/banip" ] && f_log "err" "banIP config directory not found, please re-install the package"
1297 [ ! -r "/etc/banip/banip.feeds" ] && f_log "err" "banIP feed file not found, please re-install the package"
1298 [ ! -r "/etc/config/banip" ] && f_log "err" "banIP config not found, please re-install the package"
1299 [ "$(uci_get banip global ban_enabled)" = "0" ] && f_log "err" "banIP is currently disabled, please set the config option 'ban_enabled' to '1' to use this service"
1300 fi