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