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