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