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