#!/bin/sh # banIP - ban incoming and outgoing ip adresses/subnets via ipset # written by Dirk Brenken (dev@brenken.org) # This is free software, licensed under the GNU General Public License v3. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # (s)hellcheck exceptions # shellcheck disable=1091,2039,2086,2140,2143,2181,2188 # set initial defaults # LC_ALL=C PATH="/usr/sbin:/usr/bin:/sbin:/bin" ban_ver="0.3.13" ban_basever="" ban_enabled=0 ban_automatic="1" ban_sources="" ban_iface="" ban_debug=0 ban_backupdir="/mnt" ban_maxqueue=4 ban_autoblacklist=1 ban_autowhitelist=1 ban_realtime="false" ban_fetchutil="" ban_ipt="$(command -v iptables)" ban_ipt_save="$(command -v iptables-save)" ban_ipt_restore="$(command -v iptables-restore)" ban_ipt6="$(command -v ip6tables)" ban_ipt6_save="$(command -v ip6tables-save)" ban_ipt6_restore="$(command -v ip6tables-restore)" ban_ipset="$(command -v ipset)" ban_logger="$(command -v logger)" ban_chain="banIP" ban_action="${1:-"start"}" ban_pidfile="/var/run/banip.pid" ban_rtfile="/tmp/ban_runtime.json" ban_logservice="/etc/banip/banip.service" ban_sshdaemon="" ban_setcnt=0 ban_cnt=0 ban_log_src=0 ban_log_dst=0 # load environment # f_envload() { # get system information # ban_sysver="$(ubus -S call system board 2>/dev/null | jsonfilter -e '@.model' -e '@.release.description' | \ awk 'BEGIN{ORS=", "}{print $0}' | awk '{print substr($0,1,length($0)-2)}')" # parse 'global' and 'extra' section by callback # config_cb() { local type="${1}" if [ "${type}" = "banip" ] then option_cb() { local option="${1}" local value="${2}" eval "${option}=\"${value}\"" } else reset_cb fi } # parse 'source' typed sections # parse_config() { local value opt section="${1}" options="ban_src ban_src_6 ban_src_rset ban_src_rset_6 ban_src_settype ban_src_ruletype ban_src_on ban_src_on_6 ban_src_cat" for opt in ${options} do config_get value "${section}" "${opt}" if [ -n "${value}" ] then eval "${opt}_${section}=\"${value}\"" if [ "${opt}" = "ban_src" ] then eval "ban_sources=\"${ban_sources} ${section}\"" elif [ "${opt}" = "ban_src_6" ] then eval "ban_sources=\"${ban_sources} ${section}_6\"" fi fi done } # load config # config_load banip config_foreach parse_config source # setup logging # ban_log_chain_src="${ban_log_chain_src:-"${ban_chain}_log_src"}" if [ "${ban_log_src}" -eq 1 ] then log_target_src="${ban_target_src:-"DROP"}" ban_target_src="${ban_log_chain_src}" log_target_src_6="${ban_target_src_6:-"DROP"}" ban_target_src_6="${ban_log_chain_src}" fi ban_log_chain_dst="${ban_log_chain_dst:-"${ban_chain}_log_dst"}" if [ "${ban_log_dst}" -eq 1 ] then log_target_dst="${ban_target_dst:-"REJECT"}" ban_target_dst="${ban_log_chain_dst}" log_target_dst_6="${ban_target_dst_6:-"REJECT"}" ban_target_dst_6="${ban_log_chain_dst}" fi # version check # if [ -z "${ban_basever}" ] || [ "${ban_ver%.*}" != "${ban_basever}" ] then f_log "info" "your banIP config seems to be too old, please update your config with the '--force-maintainer' opkg option" f_rmtemp exit 0 fi # create temp directory & files # f_temp # check status # if [ "${ban_enabled}" -eq 0 ] then f_bgserv "stop" f_jsnup disabled f_ipset destroy f_rmbackup f_rmtemp f_log "info" "banIP is currently disabled, please set ban_enabled to '1' to use this service" exit 0 fi } # check environment # f_envcheck() { local util utils packages iface tmp cnt=0 cnt_max=0 f_jsnup "running" f_log "info" "start banIP processing (${ban_action})" # check backup directory # if [ ! -d "${ban_backupdir}" ] then f_log "err" "the backup directory '${ban_backupdir}' does not exist or has not been mounted yet, please create the directory or raise the 'ban_triggerdelay' to defer the banIP start" fi # get wan devices and wan subnets # if [ "${ban_automatic}" = "1" ] then while [ "${cnt}" -le 30 ] do network_find_wan iface if [ -n "${iface}" ] && [ -z "$(printf "%s\\n" "${ban_iface}" | grep -F "${iface}")" ] then ban_iface="${ban_iface} ${iface}" if [ "${cnt_max}" -eq 0 ] then cnt_max=$((cnt+5)) fi fi network_find_wan6 iface if [ -n "${iface}" ] && [ -z "$(printf "%s\\n" "${ban_iface}" | grep -F "${iface}")" ] then ban_iface="${ban_iface} ${iface}" if [ "${cnt_max}" -eq 0 ] then cnt_max=$((cnt+5)) fi fi if [ -z "${ban_iface}" ] || [ "${cnt}" -le "${cnt_max}" ] then network_flush_cache cnt=$((cnt+1)) sleep 1 else break fi done fi for iface in ${ban_iface} do network_get_device tmp "${iface}" if [ -n "${tmp}" ] && [ -z "$(printf "%s\\n" "${ban_dev}" | grep -F "${tmp}")" ] then ban_dev="${ban_dev} ${tmp}" else network_get_physdev tmp "${iface}" if [ -n "${tmp}" ] && [ -z "$(printf "%s\\n" "${ban_dev}" | grep -F "${tmp}")" ] then ban_dev="${ban_dev} ${tmp}" fi fi network_get_subnets tmp "${iface}" if [ -n "${tmp}" ] && [ -z "$(printf "%s\\n" "${ban_subnets}" | grep -F "${tmp}")" ] then ban_subnets="${ban_subnets} ${tmp}" fi network_get_subnets6 tmp "${iface}" if [ -n "${tmp}" ] && [ -z "$(printf "%s\\n" "${ban_subnets6}" | grep -F "${tmp}")" ] then ban_subnets6="${ban_subnets6} ${tmp}" fi done ban_dev_all="$(ip link show 2>/dev/null | awk 'BEGIN{FS="[@: ]"}/^[0-9:]/{if($3!="lo"){print $3}}')" if [ -z "${ban_iface}" ] || [ -z "${ban_dev}" ] || [ -z "${ban_dev_all}" ] then f_log "err" "wan interface(s)/device(s) (${ban_iface:-"-"}/${ban_dev:-"-"}) not found, please please check your configuration" fi # check fetch utility # if [ -z "${ban_fetchutil}" ] then cnt_max=$((cnt+5)) while [ -z "${packages}" ] do packages="$(opkg list-installed 2>/dev/null)" if [ "${cnt}" -gt "${cnt_max}" ] then break fi cnt=$((cnt+1)) sleep 1 done if [ -n "${packages}" ] then utils="aria2c curl wget uclient-fetch" for util in ${utils} do if { [ "${util}" = "uclient-fetch" ] && [ -n "$(printf "%s\\n" "${packages}" | grep "^libustream-")" ]; } || \ { [ "${util}" = "wget" ] && [ -n "$(printf "%s\\n" "${packages}" | grep "^wget -")" ]; } || \ { [ "${util}" != "uclient-fetch" ] && [ "${util}" != "wget" ]; } then ban_fetchutil="$(command -v "${util}")" if [ -x "${ban_fetchutil}" ] then break fi fi unset ban_fetchutil util done fi else util="${ban_fetchutil}" ban_fetchutil="$(command -v "${util}")" if [ ! -x "${ban_fetchutil}" ] then unset ban_fetchutil util fi fi case "${util}" in "aria2c") ban_fetchparm="${ban_fetchparm:-"--timeout=20 --allow-overwrite=true --auto-file-renaming=false --check-certificate=true --dir=/ -o"}" ;; "curl") ban_fetchparm="${ban_fetchparm:-"--connect-timeout 20 -o"}" ;; "uclient-fetch") ban_fetchparm="${ban_fetchparm:-"--timeout=20 -O"}" ;; "wget") ban_fetchparm="${ban_fetchparm:-"--no-cache --no-cookies --max-redirect=0 --timeout=20 -O"}" ;; esac if [ -z "${ban_fetchutil}" ] || [ -z "${ban_fetchparm}" ] then f_log "err" "download utility with SSL support not found, please install 'uclient-fetch' with a 'libustream-*' variant or another download utility like 'wget', 'curl' or 'aria2'" fi # check ssh daemon # if [ -z "${ban_sshdaemon}" ] then utils="sshd dropbear" for util in ${utils} do if [ -x "$(command -v "${util}")" ] then if [ "$("/etc/init.d/${util}" enabled; printf "%u" ${?})" -eq 0 ] then ban_sshdaemon="${util}" break fi fi done fi if [ -z "${ban_sshdaemon}" ] then f_log "err" "ssh daemon not found, please install 'dropbear' or 'sshd'" fi } # create temporary files and directories # f_temp() { if [ -d "/tmp" ] && [ -z "${ban_tmpdir}" ] then ban_tmpdir="$(mktemp -p /tmp -d)" ban_tmpfile="$(mktemp -p "${ban_tmpdir}" -tu)" elif [ ! -d "/tmp" ] then f_log "err" "the temp directory '/tmp' does not exist or has not been mounted yet, please create the directory or raise the 'ban_triggerdelay' to defer the banIP start" fi if [ ! -f "${ban_pidfile}" ] || [ ! -s "${ban_pidfile}" ] then printf "%s" "${$}" > "${ban_pidfile}" fi } # remove temporary files and directories # f_rmtemp() { if [ -d "${ban_tmpdir}" ] then rm -rf "${ban_tmpdir}" fi > "${ban_pidfile}" } # remove backup files # f_rmbackup() { if [ -d "${ban_backupdir}" ] then rm -f "${ban_backupdir}"/banIP.*.gz fi } # iptables rules engine # f_iptrule() { local rc timeout="-w 5" action="${1}" rule="${2}" if [ "${src_name##*_}" = "6" ] then if [ -x "${ban_ipt6}" ] then rc="$("${ban_ipt6}" "${timeout}" -C ${rule} 2>/dev/null; printf "%u" ${?})" if { [ "${rc}" -ne 0 ] && { [ "${action}" = "-A" ] || [ "${action}" = "-I" ]; } } || \ { [ "${rc}" -eq 0 ] && [ "${action}" = "-D" ]; } then "${ban_ipt6}" "${timeout}" "${action}" ${rule} 2>/dev/null fi fi else if [ -x "${ban_ipt}" ] then rc="$("${ban_ipt}" "${timeout}" -C ${rule} 2>/dev/null; printf "%u" ${?})" if { [ "${rc}" -ne 0 ] && { [ "${action}" = "-A" ] || [ "${action}" = "-I" ]; } } || \ { [ "${rc}" -eq 0 ] && [ "${action}" = "-D" ]; } then "${ban_ipt}" "${timeout}" "${action}" ${rule} 2>/dev/null fi fi fi if [ "${?}" -ne 0 ] then > "${tmp_err}" f_log "info" "can't create iptables rule: action: '${action:-"-"}', rule: '${rule:-"-"}'" fi } # remove/add iptables rules # f_iptadd() { local rm="${1}" dev for dev in ${ban_dev_all} do f_iptrule "-D" "${ban_chain} -i ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} src -j ${target_src}" f_iptrule "-D" "${ban_chain} -o ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} dst -j ${target_dst}" done if [ -z "${rm}" ] && [ "${cnt}" -gt 0 ] then if [ "${src_ruletype}" != "dst" ] then f_iptrule "-I" "${wan_input} -j ${ban_chain}" f_iptrule "-I" "${wan_forward} -j ${ban_chain}" if [ "${src_name##*_}" != "6" ] then f_iptrule "-A" "${ban_chain} -p udp --dport 67:68 --sport 67:68 -j RETURN" else f_iptrule "-A" "${ban_chain} -p udp -s fc00::/6 --sport 547 -d fc00::/6 --dport 546 -j RETURN" f_iptrule "-A" "${ban_chain} -p ipv6-icmp -s fe80::/10 -d fe80::/10 -j RETURN" fi for dev in ${ban_dev} do f_iptrule "${action:-"-A"}" "${ban_chain} -i ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} src -j ${target_src}" done fi if [ "${src_ruletype}" != "src" ] then f_iptrule "-I" "${lan_input} -j ${ban_chain}" f_iptrule "-I" "${lan_forward} -j ${ban_chain}" if [ "${src_name##*_}" != "6" ] then f_iptrule "-A" "${ban_chain} -p udp --dport 67:68 --sport 67:68 -j RETURN" else f_iptrule "-A" "${ban_chain} -p udp -s fc00::/6 --sport 547 -d fc00::/6 --dport 546 -j RETURN" f_iptrule "-A" "${ban_chain} -p ipv6-icmp -s fe80::/10 -d fe80::/10 -j RETURN" fi for dev in ${ban_dev} do f_iptrule "${action:-"-A"}" "${ban_chain} -o ${dev} -m conntrack --ctstate NEW -m set --match-set ${src_name} dst -j ${target_dst}" done fi else if [ -x "${ban_ipset}" ] && [ -n "$("${ban_ipset}" -q -n list "${src_name}")" ] then "${ban_ipset}" -q destroy "${src_name}" fi fi } # ipset/iptables actions # f_ipset() { local out_rc source action ruleset rule cnt=0 cnt_ip=0 cnt_cidr=0 timeout="-w 5" mode="${1}" in_rc="${src_rc:-0}" if [ "${src_name%_6*}" = "whitelist" ] then target_src="RETURN" target_dst="RETURN" action="-I" fi case "${mode}" in "backup") gzip -cf "${tmp_load}" 2>/dev/null > "${ban_backupdir}/banIP.${src_name}.gz" out_rc="${?:-"${in_rc}"}" f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, out_rc: ${out_rc}" return "${out_rc}" ;; "restore") if [ -f "${ban_backupdir}/banIP.${src_name}.gz" ] then zcat "${ban_backupdir}/banIP.${src_name}.gz" 2>/dev/null > "${tmp_load}" out_rc="${?}" fi out_rc="${out_rc:-"${in_rc}"}" f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, out_rc: ${out_rc}" return "${out_rc}" ;; "remove") if [ -f "${ban_backupdir}/banIP.${src_name}.gz" ] then rm -f "${ban_backupdir}/banIP.${src_name}.gz" out_rc="${?}" fi out_rc="${out_rc:-"${in_rc}"}" f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, out_rc: ${out_rc}" return "${out_rc}" ;; "initial") local ipt log_src_target log_src_opts log_src_prefix log_dst_target log_dst_opts log_dst_prefix for src_name in "ruleset" "ruleset_6" do if [ "${src_name##*_}" = "6" ] then ipt="${ban_ipt6}" ruleset="${ban_wan_input_chain_6:-"input_wan_rule"} ${ban_wan_forward_chain_6:-"forwarding_wan_rule"} ${ban_lan_input_chain_6:-"input_lan_rule"} ${ban_lan_forward_chain_6:-"forwarding_lan_rule"}" log_src_target="${log_target_src_6}" log_src_opts="${ban_log_src_opts_6:-"-m limit --limit 10/sec"}" log_src_prefix="${ban_log_src_prefix_6:-"${log_target_src_6}(src banIP) "}" log_dst_target="${log_target_dst_6}" log_dst_opts="${ban_log_dst_opts_6:-"-m limit --limit 10/sec"}" log_dst_prefix="${ban_log_dst_prefix_6:-"${log_target_dst_6}(dst banIP) "}" else ipt="${ban_ipt}" ruleset="${ban_wan_input_chain:-"input_wan_rule"} ${ban_wan_forward_chain:-"forwarding_wan_rule"} ${ban_lan_input_chain:-"input_lan_rule"} ${ban_lan_forward_chain:-"forwarding_lan_rule"}" log_src_target="${log_target_src}" log_src_opts="${ban_log_src_opts:-"-m limit --limit 10/sec"}" log_src_prefix="${ban_log_src_prefix:-"${log_target_src}(src banIP) "}" log_dst_target="${log_target_dst}" log_dst_opts="${ban_log_dst_opts:-"-m limit --limit 10/sec"}" log_dst_prefix="${ban_log_dst_prefix:-"${log_target_dst}(dst banIP) "}" fi if [ -x "${ipt}" ] then if [ -z "$("${ipt}" "${timeout}" -nL "${ban_chain}" 2>/dev/null)" ] then "${ipt}" "${timeout}" -N "${ban_chain}" 2>/dev/null out_rc="${?}" else out_rc=0 for rule in ${ruleset} do f_iptrule "-D" "${rule} -j ${ban_chain}" done fi f_log "debug" "f_ipset ::: name: -, mode: ${mode:-"-"}, chain: ${ban_chain:-"-"}, $src_name: ${ruleset:-"-"}, out_rc: ${out_rc}" if [ "${ban_log_src}" -eq 1 ] && [ "${out_rc}" -eq 0 ] then if [ -z "$("${ipt}" "${timeout}" -nL "${ban_log_chain_src}" 2>/dev/null)" ] then "${ipt}" "${timeout}" -N "${ban_log_chain_src}" 2>/dev/null out_rc="${?}" if [ "${out_rc}" -eq 0 ] then "${ipt}" "${timeout}" -A "${ban_log_chain_src}" -j LOG ${log_src_opts} --log-prefix "${log_src_prefix}" && \ "${ipt}" "${timeout}" -A "${ban_log_chain_src}" -j "${log_src_target}" out_rc="${?}" fi fi f_log "debug" "f_ipset ::: name: -, mode: ${mode:-"-"}, chain: ${ban_log_chain_src:-"-"}, out_rc: ${out_rc}" fi if [ "${ban_log_dst}" -eq 1 ] && [ "${out_rc}" -eq 0 ] then if [ -z "$("${ipt}" "${timeout}" -nL "${ban_log_chain_dst}" 2>/dev/null)" ] then "${ipt}" "${timeout}" -N "${ban_log_chain_dst}" 2>/dev/null out_rc="${?}" if [ "${out_rc}" -eq 0 ] then "${ipt}" "${timeout}" -A "${ban_log_chain_dst}" -j LOG ${log_dst_opts} --log-prefix "${log_dst_prefix}" && \ "${ipt}" "${timeout}" -A "${ban_log_chain_dst}" -j "${log_dst_target}" out_rc="${?}" fi fi f_log "debug" "f_ipset ::: name: -, mode: ${mode:-"-"}, chain: ${ban_log_chain_dst:-"-"}, out_rc: ${out_rc}" fi fi done out_rc="${out_rc:-"${in_rc}"}" f_log "debug" "f_ipset ::: name: -, mode: ${mode:-"-"}, out_rc: ${out_rc}" return "${out_rc}" ;; "create") if [ -x "${ban_ipset}" ] then if [ -s "${tmp_file}" ] && [ -z "$("${ban_ipset}" -q -n list "${src_name}")" ] then "${ban_ipset}" -q create "${src_name}" hash:"${src_settype}" hashsize 64 maxelem 262144 family "${src_setipv}" counters out_rc="${?}" else "${ban_ipset}" -q flush "${src_name}" out_rc="${?}" fi if [ -s "${tmp_file}" ] && [ "${out_rc}" -eq 0 ] then "${ban_ipset}" -q -! restore < "${tmp_file}" out_rc="${?}" if [ "${out_rc}" -eq 0 ] then "${ban_ipset}" -q save "${src_name}" > "${tmp_file}" cnt="$(($(wc -l 2>/dev/null < "${tmp_file}")-1))" cnt_cidr="$(grep -cF "/" "${tmp_file}")" cnt_ip="$((cnt-cnt_cidr))" printf "%s\\n" "${cnt}" > "${tmp_cnt}" fi fi f_iptadd fi end_ts="$(date +%s)" out_rc="${out_rc:-"${in_rc}"}" f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, settype: ${src_settype:-"-"}, setipv: ${src_setipv:-"-"}, ruletype: ${src_ruletype:-"-"}, count(sum/ip/cidr): ${cnt}/${cnt_ip}/${cnt_cidr}, time: $((end_ts-start_ts)), out_rc: ${out_rc}" return "${out_rc}" ;; "refresh") if [ -x "${ban_ipset}" ] && [ -n "$("${ban_ipset}" -q -n list "${src_name}")" ] then "${ban_ipset}" -q save "${src_name}" > "${tmp_file}" out_rc="${?}" if [ -s "${tmp_file}" ] && [ "${out_rc}" -eq 0 ] then cnt="$(($(wc -l 2>/dev/null < "${tmp_file}")-1))" cnt_cidr="$(grep -cF "/" "${tmp_file}")" cnt_ip="$((cnt-cnt_cidr))" printf "%s\\n" "${cnt}" > "${tmp_cnt}" fi f_iptadd fi end_ts="$(date +%s)" out_rc="${out_rc:-"${in_rc}"}" f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}, count: ${cnt}/${cnt_ip}/${cnt_cidr}, time: $((end_ts-start_ts)), out_rc: ${out_rc}" return "${out_rc}" ;; "flush") f_iptadd "remove" if [ -x "${ban_ipset}" ] && [ -n "$("${ban_ipset}" -q -n list "${src_name}")" ] then "${ban_ipset}" -q flush "${src_name}" "${ban_ipset}" -q destroy "${src_name}" fi f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}" ;; "destroy") for chain in ${ban_log_chain_src} ${ban_log_chain_dst} ${ban_chain} do if [ -x "${ban_ipt}" ] && [ -x "${ban_ipt_save}" ] && [ -x "${ban_ipt_restore}" ] && \ [ -n "$("${ban_ipt}" "${timeout}" -nL "${chain}" 2>/dev/null)" ] then "${ban_ipt_save}" | grep -v -- "-j ${chain}" | "${ban_ipt_restore}" "${ban_ipt}" "${timeout}" -F "${chain}" 2>/dev/null "${ban_ipt}" "${timeout}" -X "${chain}" 2>/dev/null fi if [ -x "${ban_ipt6}" ] && [ -x "${ban_ipt6_save}" ] && [ -x "${ban_ipt6_restore}" ] && \ [ -n "$("${ban_ipt6}" "${timeout}" -nL "${chain}" 2>/dev/null)" ] then "${ban_ipt6_save}" | grep -v -- "-j ${chain}" | "${ban_ipt6_restore}" "${ban_ipt6}" "${timeout}" -F "${chain}" 2>/dev/null "${ban_ipt6}" "${timeout}" -X "${chain}" 2>/dev/null fi done for source in ${ban_sources} do if [ -x "${ban_ipset}" ] && [ -n "$("${ban_ipset}" -q -n list "${source}")" ] then "${ban_ipset}" -q destroy "${source}" fi done f_log "debug" "f_ipset ::: name: ${src_name:-"-"}, mode: ${mode:-"-"}" ;; esac } # write to syslog # f_log() { local class="${1}" log_msg="${2}" if [ -n "${log_msg}" ] && { [ "${class}" != "debug" ] || [ "${ban_debug}" -eq 1 ]; } then if [ -x "${ban_logger}" ] then "${ban_logger}" -p "${class}" -t "banIP-${ban_ver}[${$}]" "${log_msg}" else printf "%s %s %s\\n" "${class}" "banIP-${ban_ver}[${$}]" "${log_msg}" fi if [ "${class}" = "err" ] then f_jsnup error f_ipset destroy f_rmbackup f_rmtemp exit 1 fi fi } # start log service to trace failed ssh/luci logins # f_bgserv() { local bg_pid status="${1}" bg_pid="$(pgrep -f "^/bin/sh ${ban_logservice}.*|^logread -f -e ${ban_sshdaemon}\|luci: failed login|^grep -qE Exit before auth|luci: failed login|[0-9]+ \[preauth\]$" | awk '{ORS=" "; print $1}')" if [ -z "${bg_pid}" ] && [ "${status}" = "start" ] \ && [ -x "${ban_logservice}" ] && [ "${ban_realtime}" = "true" ] then ( "${ban_logservice}" "${ban_ver}" "${ban_sshdaemon}" & ) elif [ -n "${bg_pid}" ] && [ "${status}" = "stop" ] then kill -HUP "${bg_pid}" 2>/dev/null fi f_log "debug" "f_bgserv ::: status: ${status:-"-"}, bg_pid: ${bg_pid:-"-"}, ban_realtime: ${ban_realtime:-"-"}, log_service: ${ban_logservice:-"-"}" } # main function for banIP processing # f_main() { local pid pid_list start_ts end_ts ip tmp_raw tmp_cnt tmp_load tmp_file mem_total mem_free cnt=1 local src_name src_on src_url src_rset src_setipv src_settype src_ruletype src_cat src_log src_addon src_ts src_rc local wan_input wan_forward lan_input lan_forward target_src target_dst ssh_log luci_log if [ "${ban_sshdaemon}" = "dropbear" ] then ssh_log="$(logread -e "${ban_sshdaemon}" | grep -o "${ban_sshdaemon}.*" | sed 's/:[0-9]*$//g')" elif [ "${ban_sshdaemon}" = "sshd" ] then ssh_log="$(logread -e "${ban_sshdaemon}" | grep -o "${ban_sshdaemon}.*" | sed 's/ port.*$//g')" fi luci_log="$(logread -e "luci: failed login" | grep -o "luci:.*")" mem_total="$(awk '/^MemTotal/ {print int($2/1000)}' "/proc/meminfo" 2>/dev/null)" mem_free="$(awk '/^MemFree/ {print int($2/1000)}' "/proc/meminfo" 2>/dev/null)" f_log "debug" "f_main ::: fetch_util: ${ban_fetchutil:-"-"}, fetch_parm: ${ban_fetchparm:-"-"}, ssh_daemon: ${ban_sshdaemon}, interface(s): ${ban_iface:-"-"}, device(s): ${ban_dev:-"-"}, all_devices: ${ban_dev_all:-"-"}, backup_dir: ${ban_backupdir:-"-"}, mem_total: ${mem_total:-0}, mem_free: ${mem_free:-0}, max_queue: ${ban_maxqueue}" # chain creation # f_ipset initial if [ "${?}" -ne 0 ] then f_log "err" "banIP processing failed, fatal error during iptables chain creation (${ban_sysver})" fi # main loop # for src_name in ${ban_sources} do unset src_on if [ "${src_name##*_}" = "6" ] then if [ -x "${ban_ipt6}" ] then src_on="$(eval printf "%s" \"\$\{ban_src_on_6_${src_name%_6*}\}\")" src_url="$(eval printf "%s" \"\$\{ban_src_6_${src_name%_6*}\}\")" src_rset="$(eval printf "%s" \"\$\{ban_src_rset_6_${src_name%_6*}\}\")" src_setipv="inet6" wan_input="${ban_wan_input_chain_6:-"input_wan_rule"}" wan_forward="${ban_wan_forward_chain_6:-"forwarding_wan_rule"}" lan_input="${ban_lan_input_chain_6:-"input_lan_rule"}" lan_forward="${ban_lan_forward_chain_6:-"forwarding_lan_rule"}" target_src="${ban_target_src_6:-"DROP"}" target_dst="${ban_target_dst_6:-"REJECT"}" fi else if [ -x "${ban_ipt}" ] then src_on="$(eval printf "%s" \"\$\{ban_src_on_${src_name}\}\")" src_url="$(eval printf "%s" \"\$\{ban_src_${src_name}\}\")" src_rset="$(eval printf "%s" \"\$\{ban_src_rset_${src_name}\}\")" src_setipv="inet" wan_input="${ban_wan_input_chain:-"input_wan_rule"}" wan_forward="${ban_wan_forward_chain:-"forwarding_wan_rule"}" lan_input="${ban_lan_input_chain:-"input_lan_rule"}" lan_forward="${ban_lan_forward_chain:-"forwarding_lan_rule"}" target_src="${ban_target_src:-"DROP"}" target_dst="${ban_target_dst:-"REJECT"}" fi fi src_settype="$(eval printf "%s" \"\$\{ban_src_settype_${src_name%_6*}\}\")" src_ruletype="$(eval printf "%s" \"\$\{ban_src_ruletype_${src_name%_6*}\}\")" src_cat="$(eval printf "%s" \"\$\{ban_src_cat_${src_name%_6*}\}\")" src_addon="" src_rc=4 tmp_load="${ban_tmpfile}.${src_name}.load" tmp_file="${ban_tmpfile}.${src_name}.file" tmp_raw="${tmp_file}.raw" tmp_cnt="${tmp_file}.cnt" tmp_err="${tmp_file}.err" # basic pre-checks # f_log "debug" "f_main ::: name: ${src_name}, src_on: ${src_on:-"-"}" if [ -z "${src_on}" ] || [ "${src_on}" != "1" ] || [ -z "${src_url}" ] || \ [ -z "${src_rset}" ] || [ -z "${src_settype}" ] || [ -z "${src_ruletype}" ] then f_ipset flush f_ipset remove continue elif [ "${ban_action}" = "refresh" ] && [ ! -f "${src_url}" ] then start_ts="$(date +%s)" f_ipset refresh if [ "${?}" -eq 0 ] then continue fi fi # download queue processing # ( start_ts="$(date +%s)" if [ "${ban_action}" = "start" ] && [ ! -f "${src_url}" ] then f_ipset restore fi src_rc="${?}" if [ "${src_rc}" -ne 0 ] || [ ! -s "${tmp_load}" ] then if [ -f "${src_url}" ] then src_log="$(cat "${src_url}" 2>/dev/null > "${tmp_load}")" src_rc="${?}" case "${src_name}" in "whitelist") src_addon="${ban_subnets}" ;; "whitelist_6") src_addon="${ban_subnets6}" ;; "blacklist") if [ "${ban_sshdaemon}" = "dropbear" ] then pid_list="$(printf "%s\\n" "${ssh_log}" | grep -F "Exit before auth" | awk 'match($0,/(\[[0-9]+\])/){ORS=" ";print substr($0,RSTART,RLENGTH)}')" for pid in ${pid_list} do src_addon="${src_addon} $(printf "%s\\n" "${ssh_log}" | grep -F "${pid}" | awk 'match($0,/([0-9]{1,3}\.){3}[0-9]{1,3}$/){ORS=" ";print substr($0,RSTART,RLENGTH);exit}')" done elif [ "${ban_sshdaemon}" = "sshd" ] then src_addon="$(printf "%s\\n" "${ssh_log}" | grep -F "error: maximum authentication attempts exceeded" | awk 'match($0,/([0-9]{1,3}\.){3}[0-9]{1,3}$/){ORS=" ";print substr($0,RSTART,RLENGTH)}')" fi src_addon="${src_addon} $(printf "%s\\n" "${luci_log}" | awk 'match($0,/([0-9]{1,3}\.){3}[0-9]{1,3}$/){ORS=" ";print substr($0,RSTART,RLENGTH)}')" ;; "blacklist_6") if [ "${ban_sshdaemon}" = "dropbear" ] then pid_list="$(printf "%s\\n" "${ssh_log}" | grep -F "Exit before auth" | awk 'match($0,/(\[[0-9]+\])/){ORS=" ";print substr($0,RSTART,RLENGTH)}')" for pid in ${pid_list} do src_addon="${src_addon} $(printf "%s\\n" "${ssh_log}" | grep -F "${pid}" | awk 'match($0,/(([0-9A-f]{0,4}::?){1,7}[0-9A-f]{0,4}$)/){ORS=" ";print substr($0,RSTART,RLENGTH);exit}')" done elif [ "${ban_sshdaemon}" = "sshd" ] then src_addon="$(printf "%s\\n" "${ssh_log}" | grep -F "error: maximum authentication attempts exceeded" | awk 'match($0,/(([0-9A-f]{0,4}::?){1,7}[0-9A-f]{0,4}$)/){ORS=" ";print substr($0,RSTART,RLENGTH)}')" fi src_addon="${src_addon} $(printf "%s\\n" "${luci_log}" | awk 'match($0,/(([0-9A-f]{0,4}::?){1,7}[0-9A-f]{0,4}$)/){ORS=" ";print substr($0,RSTART,RLENGTH)}')" ;; esac for ip in ${src_addon} do if [ -z "$(grep -F "${ip}" "${src_url}")" ] then printf "%s\\n" "${ip}" >> "${tmp_load}" if { [ "${src_name//_*/}" = "blacklist" ] && [ "${ban_autoblacklist}" -eq 1 ]; } || \ { [ "${src_name//_*/}" = "whitelist" ] && [ "${ban_autowhitelist}" -eq 1 ]; } then src_ts="# auto-added $(date "+%d.%m.%Y %H:%M:%S")" printf "%s %s\\n" "${ip}" "${src_ts}" >> "${src_url}" fi fi done elif [ -n "${src_cat}" ] then if [ "${src_cat//[0-9]/}" != "${src_cat}" ] then for as in ${src_cat} do src_log="$("${ban_fetchutil}" ${ban_fetchparm} "${tmp_raw}" "${src_url}AS${as}" 2>&1)" src_rc="${?}" if [ "${src_rc}" -eq 0 ] then jsonfilter -i "${tmp_raw}" -e '@.data.prefixes.*.prefix' 2>/dev/null >> "${tmp_load}" else break fi done if [ "${src_rc}" -eq 0 ] then f_ipset backup elif [ "${ban_action}" != "start" ] then f_ipset restore fi else for co in ${src_cat} do src_log="$("${ban_fetchutil}" ${ban_fetchparm} "${tmp_raw}" "${src_url}${co}&v4_format=prefix" 2>&1)" src_rc="${?}" if [ "${src_rc}" -eq 0 ] then if [ "${src_name##*_}" = "6" ] then jsonfilter -i "${tmp_raw}" -e '@.data.resources.ipv6.*' 2>/dev/null >> "${tmp_load}" else jsonfilter -i "${tmp_raw}" -e '@.data.resources.ipv4.*' 2>/dev/null >> "${tmp_load}" fi else break fi done if [ "${src_rc}" -eq 0 ] then f_ipset backup elif [ "${ban_action}" != "start" ] then f_ipset restore fi fi else src_log="$("${ban_fetchutil}" ${ban_fetchparm} "${tmp_raw}" "${src_url}" 2>&1)" src_rc="${?}" if [ "${src_rc}" -eq 0 ] then zcat "${tmp_raw}" 2>/dev/null > "${tmp_load}" src_rc="${?}" if [ "${src_rc}" -ne 0 ] then mv -f "${tmp_raw}" "${tmp_load}" src_rc="${?}" fi if [ "${src_rc}" -eq 0 ] then f_ipset backup src_rc="${?}" fi elif [ "${ban_action}" != "start" ] then f_ipset restore src_rc="${?}" fi fi fi if [ "${src_rc}" -eq 0 ] then awk "${src_rset}" "${tmp_load}" 2>/dev/null > "${tmp_file}" src_rc="${?}" if [ "${src_rc}" -eq 0 ] then f_ipset create src_rc="${?}" elif [ "${ban_action}" != "refresh" ] then f_ipset refresh src_rc="${?}" fi else src_log="$(printf "%s" "${src_log}" | awk '{ORS=" ";print $0}')" if [ "${ban_action}" != "refresh" ] then f_ipset refresh src_rc="${?}" fi f_log "debug" "f_main ::: name: ${src_name}, url: ${src_url}, rc: ${src_rc}, log: ${src_log:-"-"}" fi )& hold="$((cnt%ban_maxqueue))" if [ "${hold}" -eq 0 ] then wait fi cnt="$((cnt+1))" done wait if [ -z "$(ls "${ban_tmpfile}".*.err 2>/dev/null)" ] then for cnt_file in "${ban_tmpfile}".*.cnt do if [ -f "$cnt_file" ] then read -r cnt < "$cnt_file" ban_cnt="$((ban_cnt+cnt))" ban_setcnt="$((ban_setcnt+1))" fi done f_log "info" "${ban_setcnt} IPSets with overall ${ban_cnt} IPs/Prefixes loaded successfully (${ban_sysver})" f_bgserv "start" f_jsnup f_rmtemp else f_log "err" "banIP processing failed, fatal iptables error(s) during subshell processing (${ban_sysver})" fi } # update runtime information # f_jsnup() { local rundate status="${1:-"enabled"}" rundate="$(date "+%d.%m.%Y %H:%M:%S")" ban_cntinfo="${ban_setcnt} IPSets with overall ${ban_cnt} IPs/Prefixes" > "${ban_rtfile}" json_load_file "${ban_rtfile}" >/dev/null 2>&1 json_init json_add_object "data" json_add_string "status" "${status}" json_add_string "version" "${ban_ver}" json_add_string "util_info" "${ban_fetchutil:-"-"}, ${ban_realtime:-"-"}" json_add_string "ipset_info" "${ban_cntinfo:-"-"}" json_add_string "backup_dir" "${ban_backupdir}" json_add_string "last_run" "${rundate:-"-"}" json_add_string "system" "${ban_sysver}" json_close_object json_dump > "${ban_rtfile}" f_log "debug" "f_jsnup ::: status: ${status}, setcnt: ${ban_setcnt}, cnt: ${ban_cnt}" } # source required system libraries # if [ -r "/lib/functions.sh" ] && [ -r "/lib/functions/network.sh" ] && [ -r "/usr/share/libubox/jshn.sh" ] then . "/lib/functions.sh" . "/lib/functions/network.sh" . "/usr/share/libubox/jshn.sh" else f_log "err" "system libraries not found" fi # handle different banIP actions # f_envload case "${ban_action}" in "stop") f_bgserv "stop" f_jsnup stopped f_ipset destroy f_rmbackup f_rmtemp ;; "start"|"restart"|"reload"|"refresh") f_bgserv "stop" f_envcheck f_main ;; esac