diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..84b8cb2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +MIT License + +Portions Copyright (c) 2018 Intellex +Portions Copyright (c) 2020 Oliver Mueller +Portions Copyright (c) 2020 Andrej Walilko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 05c7f72..abb47f1 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ # blcheck -A powerful script for testing a domain or an IP against mailing black lists. +A powerful script for testing a domain or an IP against mailing block lists and allow lists. Script will use dig if it is found. If dig is not found script will use host. Features -------------------- -* More then __100 black lists__ already included! +* More then __280 block lists__ already included! * Automatic distinction between __domain or IP__ * Performs __PTR validation__ (only if domain is supplied, does not work for IP) * 3 verbose (-v) levels and a quiet (-q) mode -* The result of script is the number of servers which blacklisted the domain, so it can be used for any kind of __automated scripts or cronjobs__ +* The exit code of script is the number of servers which blocklisted the domain, so it can be used for any kind of __automated scripts or cronjobs__ * Informative and pleasant output @@ -32,50 +32,29 @@ If the IP is supplied, the PTR check cannot be executed and will be skipped.
 -d dnshost  Use host as DNS server to make lookups
--l file     Load blacklists from file, separated by space or new line
--c          Warn if the top level domain of the blacklist has expired
+-l file     Load blocklists from file, separated by space or new line
+-c          Warn if the top level domain of the blocklist has expired
 -v          Verbose mode, can be used multiple times (up to -vvv)
 -q          Quiet modem with absolutely no output (useful for scripts)
--p          Plain text output (no coloring, no interactive status)
+-p          Plain text output (no coloring, no interactive status, useful
+            for tee'd output to a text file)
+-j num	    The number of parallel processes to use (default is 200%, or
+	    2x system core count. See `man parallel` for details.)
 -h          The help you are just reading
 
-Result of the script is the number of blacklisted entries. So if the supplied -IP is not blacklisted on any of the servers the result is 0. +Exit code of the script is the number of blocklisted entries. So if the supplied +IP is not blocklisted on any of the servers the exit code is 0. TODO -------------------- 1. Handle domains with multiple DNS entries. -Licence --------------------- -MIT License - -Copyright (c) 2018 Intellex - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - Credits -------------------- Script has been written by the [Intellex](http://intellex.rs/en) team. -Contributors: +Additional contributors: [Darko Poljak](https://github.com/darko-poljak) - - + [Oliver Mueller](https://github.com/ogmueller) + [Andrej Walilko](https://github.com/ch604) diff --git a/blcheck b/blcheck old mode 100755 new mode 100644 index 02bdb59..f3210cd --- a/blcheck +++ b/blcheck @@ -1,165 +1,352 @@ #!/bin/bash -# -# MIT License -# -# Copyright (c) Intellex 2015 -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# +# shellcheck disable=SC2317 + # ============================================================================= # # title : blcheck -# description : Test any domain against more then 100 black lists. +# description : Test any domain against more than 100 blocklists. # author : Intellex -# contributors : Darko Poljak -# date : 2016-03-18 -# version : 0.6.0 +# contributors : Darko Poljak, Oliver Mueller, Andrej Walilko +# date : May 28 2025 +# version : 0.8.0 # usage : blcheck [options] +# reference : http://multirbl.valli.org/list/ +# code style : https://google.github.io/styleguide/shell.xml +# url : github.com/ch604/blcheck # # ============================================================================= # Config { + # where to log blocklist hits permanently if there are any + # this file will be appended, not clobbered. + # format is ${TARGET}\n$blocklist1\n$blocklist2, etc + output=~/blcheck_blocklists.txt + # how many dig/host processes to run in parallel + # this can be a number of threads (6) + # or a percentage of cpu cores (75%) + j="200%" # How many tries and for how long to wait for DNS queries CONF_DNS_TRIES=2 CONF_DNS_DURATION=3 - # Blacklists to check - CONF_BLACKLISTS=" - 0spam-killlist.fusionzero.com + # blocklists to check + # Example formats: + # - General format + # {ip_or_domain}={filter}#{type} + # - IP based blocklist (default DNSBL, any valid return code will match) + # block.list.com + # - IP based blocklist + # block.list.com#DNSBL + # - IP based allowlist + # allow.list.com#DNSWL + # - URI based blocklist + # uri.list.com#URIBL + # - Regexp defined return code, only a match will qualify for list. Here it is 127.0.0.2 and 127.0.0.3 + # block.list.com=127\.0\.0\.[23] + # - Regexp defined return code for a URI based blocklist + # block.list.com=127\.0\.[0-9]+\.0#URIBL + CONF_BLOCKLISTS=" + whitelist.rbl.ispa.at#DNSWL + t3direct.dnsbl.net.au + ucepn.dnsbl.net.au + wpbl.dnsbl.net.au + bl.reynolds.net.au + bl.mav.com.br + openproxy.bls.digibase.ca + proxyabuse.bls.digibase.ca + spambot.bls.digibase.ca + probes.dnsbl.net.auproxy.bl.gweep.ca + rbl2.triumf.ca + wbl.triumf.ca#DNSWL + ipbl.zeustracker.abuse.ch + uribl.zeustracker.abuse.ch + spamrbl.imp.ch + wormrbl.imp.ch + rbl.lugh.ch + dnsrbl.swinog.ch + uribl.swinog.ch + blacklist.woody.ch + ipv6.blacklist.woody.ch + uri.blacklist.woody.ch + block.ascams.com + superblock.ascams.com + rbl.blockedservers.com + netscan.rbl.blockedservers.com + spam.rbl.blockedservers.com + list.blogspambl.com + dnsbl1.dnsbl.borderware.com + dnsbl2.dnsbl.borderware.com + dnsbl3.dnsbl.borderware.com + dul.dnsbl.borderware.com + csi.cloudmark.com + dnsbl.cobion.com + bogons.cymru.com + v4.fullbogons.cymru.com + v6.fullbogons.cymru.com + rbl.dns-servicios.com + 0outspam.fusionzero.com 0spam.fusionzero.com - access.redhawk.org - all.rbl.jp - all.spam-rbl.fr + 0spam-n.fusionzero.com + 0spamtrust.fusionzero.com + 0spamurl.fusionzero.com + accredit.habeas.com + hil.habeas.com + hul.habeas.com + sa-accredit.habeas.com + sohul.habeas.com + blacklist.hostkarma.com + iadb.isipp.com + iadb2.isipp.com + iddb.isipp.com + wadb.isipp.com + black.junkemailfilter.com + hostkarma.junkemailfilter.com + nobl.junkemailfilter.com + krn.korumail.com + q.mail-abuse.com + r.mail-abuse.com + cidr.bl.mcafee.com + dnsbl.forefront.microsoft.com + bl.mipspace.com + bl.nordspam.com + dbl.nordspam.com#URIBL + safe.dnsbl.prs.proofpoint.com + rbl.realtimeblacklist.com + mailsl.dnsbl.rjek.com + urlsl.dnsbl.rjek.com + dynip.rothen.com + blackholes.scconsult.com + reputation-domain.rbl.scrolloutf1.com + reputation-ip.rbl.scrolloutf1.com + reputation-ns.rbl.scrolloutf1.com + bl.score.senderscore.com + feb.spamlab.com + rbl.spamlab.com all.spamrats.com - aspews.ext.sorbs.net - b.barracudacentral.org + auth.spamrats.com + dyna.spamrats.com + noptr.spamrats.com + spam.spamrats.com + rbl.suresupport.com + psbl.surriel.com + whitelist.surriel.com#DNSWL + bl.tiopan.com + dbl.tiopan.com#URIBL + ubl.unsubscore.com + spam.dnsbl.anonmails.de + bl.blocklist.de + dnsbl.darklist.de + dnsbl.inps.de + dnswl.inps.de#DNSWL + admin.bl.kundenserver.de + relays.bl.kundenserver.de + schizo-bl.kundenserver.de + spamblock.kundenserver.de + worms-bl.kundenserver.de + dnsbl.madavi.de + torserver.tor.dnsbl.sectoor.de + dunk.dnsbl.tuxad.de + hartkore.dnsbl.tuxad.de + spamsources.fabel.dk + st.technovision.dk + dnsbl.abyan.es + eswlrev.dnsbl.rediris.es#DNSWL + mtawlrev.dnsbl.rediris.es#DNSWL + dnsbl.isx.fr + all.spam-rbl.fr + pofon.foobar.hu + ispmx.pofon.foobar.hu + uribl.pofon.foobar.hu + singular.ttk.pte.hu + blacklist.netcore.co.in + dnsbl.rv-soft.info + db.wpbl.info + rbl.jp + spamlist.or.kr + bl.fmb.la + communicado.fmb.la + nsbl.fmb.la + sa.fmb.la + short.fmb.la + dnsbl.anticaptcha.net + dnsbl6.anticaptcha.net + blacklist.mail.ops.asp.att.net + blacklist.sequoia.ops.asp.att.net + blacklist.mailrelay.att.net + rbl.blakjak.net + dul.blackhole.cantv.net + hog.blackhole.cantv.net + rhsbl.blackhole.cantv.net + rot.blackhole.cantv.net + spam.blackhole.cantv.net + rbl.choon.net + ipv6.rbl.choon.net + rwl.choon.net + ipv6.rwl.choon.net + dnsbl.cyberlogic.net + fnrbl.fast.net + truncate.gbudb.net + rbl.interserver.net + rbl.iprange.net + dnsbl.kempt.net + spamguard.leadmon.net + niprbl.mailcleaner.net + uribl.mailcleaner.net + dnsbl.mailshell.net + bl.mailspike.net + rep.mailspike.net + wl.mailspike.net#DNSWL + z.mailspike.net + combined.rbl.msrbl.net + images.rbl.msrbl.net + phishing.rbl.msrbl.net + spam.rbl.msrbl.net + virus.rbl.msrbl.net + web.rbl.msrbl.net + relays.nether.net + trusted.nether.net + unsure.nether.net + dul.pacifier.net + all.s5h.net + bl.scientificspam.net + rhsbl.scientificspam.net + korea.services.net + bl.spamcop.net backscatter.spameatingmonkey.net - badnets.spameatingmonkey.net - bb.barracudacentral.org - bl.drmx.org - bl.konstant.no - bl.nszones.com - bl.spamcannibal.org bl.spameatingmonkey.net - bl.spamstinks.com - black.junkemailfilter.com - blackholes.five-ten-sg.com - blacklist.sci.kun.nl - blacklist.woody.ch - bogons.cymru.com - bsb.empty.us + fresh.spameatingmonkey.net + fresh10.spameatingmonkey.net + fresh15.spameatingmonkey.net + fresh30.spameatingmonkey.net + freshzero.spameatingmonkey.net + bl.ipv6.spameatingmonkey.net + netbl.spameatingmonkey.net + uribl.spameatingmonkey.net + urired.spameatingmonkey.net bsb.spamlookup.net - cart00ney.surriel.com - cbl.abuseat.org - cbl.anti-spam.org.cn - cblless.anti-spam.org.cn - cblplus.anti-spam.org.cn - cdl.anti-spam.org.cn - cidr.bl.mcafee.com - combined.rbl.msrbl.net - db.wpbl.info - dev.null.dk - dialups.visi.com + dnsbl.spfbl.net + score.spfbl.net + dnswl.spfbl.net#DNSWL + bl.suomispam.net + dbl.suomispam.net#URIBL + gl.suomispam.net + dob.sibl.support-intelligence.net + srn.surgate.net + srnblack.surgate.net + rbl.tdk.net dnsbl-0.uceprotect.net dnsbl-1.uceprotect.net dnsbl-2.uceprotect.net dnsbl-3.uceprotect.net - dnsbl.anticaptcha.net - dnsbl.aspnet.hu - dnsbl.inps.de - dnsbl.justspam.org - dnsbl.kempt.net - dnsbl.madavi.de - dnsbl.rizon.net - dnsbl.rv-soft.info - dnsbl.rymsho.ru - dnsbl.sorbs.net dnsbl.zapbl.net - dnsrbl.swinog.ch - dul.pacifier.net - dyn.nszones.com - dyna.spamrats.com - fnrbl.fast.net - fresh.spameatingmonkey.net - hostkarma.junkemailfilter.com - images.rbl.msrbl.net + rhsbl.zapbl.net + rbl.zenon.net + dnsbl.beetjevreemd.nl + bitonly.dnsbl.bit.nl + virbl.bit.nl + blacklist.sci.kun.nl + whitelist.sci.kun.nl#DNSWL + blackholes.tepucom.nl + bl.konstant.no + bl.0spam.org + nbl.0spam.org + url.0spam.org + orvedb.aupads.org + rsbl.aupads.org ips.backscatterer.org - ix.dnsbl.manitu.net - korea.services.net - l2.bbfh.ext.sorbs.net - l3.bbfh.ext.sorbs.net - l4.bbfh.ext.sorbs.net + b.barracudacentral.org + bb.barracudacentral.org list.bbfh.org - list.blogspambl.com - mail-abuse.blacklist.jippg.org - netbl.spameatingmonkey.net - netscan.rbl.blockedservers.com - no-more-funn.moensted.dk - noptr.spamrats.com - orvedb.aupads.org - pbl.spamhaus.org - phishing.rbl.msrbl.net - pofon.foobar.hu - psbl.surriel.com - rbl.abuse.ro - rbl.blockedservers.com - rbl.dns-servicios.com + plus.bondedsender.org + query.bondedsender.org + dnsblchile.org + dnsrbl.org + dwl.dnswl.org#DNSWL + list.dnswl.org#DNSWL + bl.drmx.org + dnsbl.dronebl.org rbl.efnet.org + tor.efnet.org rbl.efnetrbl.org - rbl.iprange.net + dnsbl.rangers.eu.org + dnsbl.httpbl.org + mail-abuse.blacklist.jippg.org + dnsbl.justspam.org + bl.mailspike.org + bl.nosolicitado.org + bl.worst.nosolicitado.org + dnsbl.openresolvers.org + spam.pedantic.org + access.redhawk.org + abuse.rfc-clueless.org + bogusmx.rfc-clueless.org + dsn.rfc-clueless.org + elitist.rfc-clueless.org + fulldom.rfc-clueless.org + postmaster.rfc-clueless.org + whois.rfc-clueless.org + public.sarbl.org rbl.schulte.org - rbl.talkactive.net - rbl2.triumf.ca - rsbl.aupads.org - sbl-xbl.spamhaus.org - sbl.nszones.com + sbl.spamdown.org + dbl.spamhaus.org#URIBL + vouch.dwl.spamhaus.org#DNSWL + pbl.spamhaus.org sbl.spamhaus.org - short.rbl.jp - spam.dnsbl.anonmails.de - spam.pedantic.org - spam.rbl.blockedservers.com - spam.rbl.msrbl.net - spam.spamrats.com - spamrbl.imp.ch - spamsources.fabel.dk - st.technovision.dk - tor.dan.me.uk - tor.dnsbl.sectoor.de - tor.efnet.org - torexit.dan.me.uk - truncate.gbudb.net - ubl.unsubscore.com - uribl.spameatingmonkey.net - urired.spameatingmonkey.net - virbl.dnsbl.bit.nl - virus.rbl.jp - virus.rbl.msrbl.net + sbl-xbl.spamhaus.org + swl.spamhaus.org#DNSWL + xbl.spamhaus.org + zen.spamhaus.org + abuse.surbl.org + cr.surbl.org + multi.surbl.org + mw.surbl.org + dnsbl.tornevall.org + opm.tornevall.org + free.v4bl.org + ip.v4bl.org + ips.whitelisted.org#DNSWL + dnsbl.calivent.com.pe + forbidden.icm.edu.pl + dyn.rbl.polspam.pl + lblip4.rbl.polspam.pl + lblip6.rbl.polspam.pl + rblip4.rbl.polspam.pl + rblip6.rbl.polspam.pl + rhsbl.rbl.polspam.pl + rbl.abuse.ro + uribl.abuse.ro vote.drbl.caravan.ru - vote.drbl.gremlin.ru - web.rbl.msrbl.net work.drbl.caravan.ru + vote.drbl.gremlin.ru work.drbl.gremlin.ru - wormrbl.imp.ch - xbl.spamhaus.org - zen.spamhaus.org" + dul.dnsbl.sorbs.netdul.ru + bl.shlink.orgdul.ru + dnsbl.rymsho.ru + rhsbl.rymsho.ru + netblockbl.spamgrouper.to + dnsbl.mcu.edu.tw + dnsbl.net.ua + rbl.fasthosts.co.uk + torexit.dan.me.uk + bsb.empty.us + 88.blocklist.zap" + + + # reputation/scoring pages + # score.senderscore.com + + # shellcheck disable=SC2034 + CONF_BLOCKLISTS_TEST=" + rbl.rbldns.ru + rbl.rbldns.ru=127\.0\.0\.1 + rbl.rbldns.ru#DNSWL + black.list.com=127.0.[0-9]+.0#URIBL + dbl.spamhaus.org#URIBL + t3direct.dnsbl.net.au=127\.0\.0\.1#DNSWL + t3direct.dnsbl.net.au#BLA + t3direct.dnsbl.net.au%127\.0\.0\.1#DNSWL" #~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ } @@ -171,6 +358,7 @@ REGEX_IP='\([0-9]\{1,3\}\)\.\([0-9]\{1,3\}\)\.\([0-9]\{1,3\}\)\.\([0-9]\{1,3\}\)' REGEX_DOMAIN='\([a-zA-Z0-9]\+\(-[a-zA-Z0-9]\+\)*\.\)\+[a-zA-Z]\{2,\}' REGEX_TDL='\([a-zA-Z0-9]\+\(-[a-zA-Z0-9]\+\)*\.\)[a-zA-Z]\{2,\}$' + REGEX_LIST='^[ \\t]*([^=#]+)(=(([^#])+))?(#(DNSBL|DNSWL|URIBL))?[ \\t]*$' # Colors if [[ $- == *i* ]]; then @@ -187,17 +375,8 @@ # Define spinner SPINNER="-\|/" - #SPINNER=".oO@*" - #SPINNER="▉▊▋▌▍▎▏▎▍▌▋▊▉" - #SPINNER="←↖↑↗→↘↓↙" - #SPINNER="▁▂▃▄▅▆▇█▇▆▅▄▃▁" - #SPINNER="▖▘▝▗" - #SPINNER="┤┘┴└├┌┬┐" - #SPINNER="◢◣◤◥" - #SPINNER="◰◳◲◱" - #SPINNER="◴◷◶◵" - #SPINNER="◐◓◑◒" + VERBOSE=0 #~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ } @@ -205,9 +384,8 @@ # Macros { # Verbose printing - VERBOSE=0 info() { - if [ $VERBOSE -ge "$1" ]; then + if [[ ${VERBOSE} -ge "$1" ]]; then echo "$2" fi } @@ -215,303 +393,442 @@ # Error handling error() { echo "ERROR: $1" >&2 - exit 2 + exit 254 } # Show progress + # shellcheck disable=SC2329 progress() { # Bar + local x BAR x=$(($1 % ${#SPINNER} + 1)) - BAR=$(echo $SPINNER | awk "{ print substr(\$0, ${x}, 1) }") - if test -z "$PLAIN"; then + BAR=$(awk "{ print substr(\$0, ${x}, 1) }" <<< "${SPINNER}") + if [[ -z "${PLAIN}" ]]; then printf "\r "; fi # BAR as printf arg so that backslash will be litteraly interpreted - printf "[ %s %3s%% ] checking... %4s / $2 " "$BAR" $(($1 * 100 / $2)) "$1"; + printf "[ %s %3s%% ] checking... %4s / $2 " "${BAR}" $(($1 * 100 / $2)) "$1"; } # Resolve the IP resolve() { # IP already? - IP=$(echo "$1" | grep "^$REGEX_IP$") - if [ "$IP" ]; then - echo "$IP" + local IP + IP=$(grep "^${REGEX_IP}$" <<< "$1") + if [[ "${IP}" ]]; then + echo "${IP}" # Resolve domain else # Handle special resolve types case "$2" in - "ns" ) TYPE="ns"; REGEX="$REGEX_DOMAIN\.$";; - * ) TYPE="a"; REGEX="$REGEX_IP$";; + "ns" ) TYPE="ns"; REGEX="${REGEX_DOMAIN}\.$";; + * ) TYPE="a"; REGEX="${REGEX_IP}$";; esac - case "$CMD" in - $CMD_DIG ) "$CMD" $DNSSERVER +short -t "$TYPE" +time=$CONF_DNS_DURATION +tries=$CONF_DNS_TRIES "$1" | grep -om 1 "$REGEX";; - $CMD_HOST ) "$CMD" -t "$TYPE" -W $CONF_DNS_DURATION -R $CONF_DNS_TRIES "$1" $DNSSERVER | tail -n1 | grep -om 1 "$REGEX";; + # shellcheck disable=SC2086 + case "${CMD}" in + "${CMD_DIG}" ) "${CMD}" ${DNSSERVER} +short -t "${TYPE}" +time=${CONF_DNS_DURATION} +tries=${CONF_DNS_TRIES} "$1" | grep -om 1 "${REGEX}";; + "${CMD_HOST}" ) "${CMD}" -t "${TYPE}" -W ${CONF_DNS_DURATION} -R ${CONF_DNS_TRIES} "$1" ${DNSSERVER} | tail -n1 | grep -om 1 "${REGEX}";; esac fi } - # Load the blacklist from file - loadBlacklists() { + # Result info for IP + # shellcheck disable=SC2329 + text() { + # IP already? + local IP + IP=$(grep "^${REGEX_IP}$" <<< "$1") + if [[ -n "${IP}" ]]; then + echo "${IP}" + + # Resolve domain + else + + local TYPE="txt" + case "${CMD}" in + "${CMD_DIG}" ) "${CMD}" "${DNSSERVER}" +short -t "${TYPE}" +time="${CONF_DNS_DURATION}" +tries="${CONF_DNS_TRIES}" "$1" ;; + "${CMD_HOST}" ) "${CMD}" -t "${TYPE}" -W "${CONF_DNS_DURATION}" -R "${CONF_DNS_TRIES}" "$1" "${DNSSERVER}" | tail -n1 ;; + esac + fi + } + + # Load the blocklist from file + loadBlocklists() { # Make sure the file is readable - if [ ! "$1" ]; then + if [[ -z "$1" ]]; then error "Option -l requires an additional parameter"; - elif [ ! -r $1 ]; then + elif [[ ! -r $1 ]]; then error "File $1 cannot be opened for reading, make sure it exists and that you have appropriate privileges" fi - CONF_BLACKLISTS=$(cat "$1") + CONF_BLOCKLISTS=$(cat "$1") } # Show help showHelp() { - cat < - -Supplied domain must be full qualified domain name. -If the IP is supplied, the PTR check cannot be executed and will be skipped. - --d dnshost Use host as DNS server to make lookups --l file Load blacklists from file, separated by space or new line --c Warn if the top level domain of the blacklist has expired --v Verbose mode, can be used multiple times (up to -vvv) --q Quiet modem with absolutely no output (useful for scripts) --p Plain text output (no coloring, no interactive status) --h The help you are just reading - -Result of the script is the number of blacklisted entries. So if the supplied -IP is not blacklisted on any of the servers the result is 0. - -HELP + cat <<-HELP + blcheck [options] + + Supplied domain must be full qualified domain name. + If the IP is supplied, the PTR check cannot be executed and will be skipped. + + -d dnshost Use host as DNS server to make lookups + -l file Load blocklists from file, separated by space or new line + -c Warn if the top level domain of the blocklist has expired + -v Verbose mode, can be used multiple times (up to -vvv) + -q Quiet mode with absolutely no output (useful for scripts) + -p Plain text output (no coloring, no interactive status, useful + for tee'd output to a text file) + -j num The number of parallel processes to use (default is 200%, or + 2x system core count. See \`man parallel\` for details.) + -h The help you are just reading + + Exit code of the script is the number of blocklisted entries. So if the supplied + IP is not blocklisted on any of the servers the exit code is 0. If there are five + blocklist hits, then the exit code is 5. + + HELP exit; } #~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ } # Parse the params -while getopts :vqphcl:d: arg; do - case "$arg" in - d) DNSSERVER=$OPTARG;; - l) loadBlacklists $OPTARG;; +while getopts :vqphcj:l:d: arg; do + case "${arg}" in + d) DNSSERVER="${OPTARG}";; + l) loadBlocklists "${OPTARG}";; c) VERIFY_BL=TRUE;; - v) VERBOSE=$(( (VERBOSE + 1) % 4));; + v) VERBOSE=$((VERBOSE + 1));; q) VERBOSE=-1;; p) PLAIN=1 RED="" GREEN="" YELLOW="" CLEAR="" ;; h) showHelp;; - ?) error "Unknown option $OPTARG";; + j) j="${OPTARG}";; + ?) error "Unknown option ${OPTARG}";; esac done shift $((OPTIND - 1)) # Get the domain -if [ $# -eq 0 ]; then +if [[ $# -eq 0 ]]; then echo "Missing target domain or IP." showHelp fi TARGET=$1 +if ! which parallel &> /dev/null; then + echo "Parallel missing, trying to install..." + if which yum &> /dev/null; then + [ ! "$(yum -q provides parallel)" ] && yum -y -q --skip-broken install epel-release + yum -y -q --skip-broken install parallel + elif which dnf &> /dev/null; then + [ ! "$(dnf -q provides parallel)" ] && dnf -y -q --skip-broken install epel-release + dnf -y -q --skip-broken install parallel + else #assumed debian-like + apt-get -qq update + apt-get -y -q install parallel + fi + if which parallel &> /dev/null; then + mkdir ~/.parallel + touch ~/.parallel/will-cite + else + error "Failed to install parallel, exiting." + fi +fi + # Some shells disable parsing backslash in echo statements by default # Set the flag to enable echo to behave consistently across platforms shopt -s xpg_echo # Get the command we will use: dig or host CMD_DIG=$(which dig) -CMD_HOST=$(which host) -if [ "$CMD_DIG" ]; then - if [[ $DNSSERVER ]]; then - DNSSERVER="@$DNSSERVER" +if [[ "${CMD_DIG}" ]]; then + if [[ -n "${DNSSERVER}" ]]; then + DNSSERVER="@${DNSSERVER}" + fi + CMD=${CMD_DIG} +else + CMD_HOST=$(which host) + if [[ "${CMD_HOST}" ]]; then + CMD=${CMD_HOST} fi - CMD=$CMD_DIG -elif [ "$CMD_HOST" ]; then - CMD=$CMD_HOST fi -if [ ! "$CMD" ]; then +if [[ -z "${CMD}" ]]; then error "Either dig or host command is required." fi -info 3 "Using $CMD to reslove DNS queries" +info 3 "Using ${CMD} to reslove DNS queries" # Parse IP -IP=$(resolve "$TARGET") -if [ ! "$IP" ]; then - error "No DNS record found for $TARGET" -elif [ "$IP" != "$TARGET" ]; then - DOMAIN=$TARGET - info 2 "Using $TARGET for target, resolved to $IP" +IP=$(resolve "${TARGET}") +if [[ -z "${IP}" ]]; then + error "No DNS record found for ${TARGET}" +elif [[ "${IP}" != "${TARGET}" ]]; then + DOMAIN=${TARGET} + info 2 "Using ${TARGET} for target, resolved to ${IP}" + TARGET_TYPE="domain" else - info 3 "Using $TARGET for target" + info 3 "Using ${TARGET} for target" + TARGET_TYPE="ip" fi # Reverse the IP -REVERSED=$(echo "$IP" | sed -ne "s~^$REGEX_IP$~\4.\3.\2.\1~p") -info 3 "Using $REVERSED for reversed IP" +REVERSED=$(echo "${IP}" | sed -ne "s~^${REGEX_IP}$~\4.\3.\2.\1~p") +info 3 "Using ${REVERSED} for reversed IP" # Get the PTR info 3 "Checking the PTR record" -case "$CMD" in - $CMD_DIG ) PTR=$("$CMD" $DNSSERVER +short -x "$IP" | sed s/\.$//);; - $CMD_HOST ) PTR=$("$CMD" "$IP" $DNSSERVER | tail -n1 | grep -o '[^ ]\+$' | sed s/\.$//) +# shellcheck disable=SC2086 +case "${CMD}" in + "${CMD_DIG}" ) PTR=$("${CMD}" ${DNSSERVER} +short -x "${IP}" | sed s/\.$//);; + "${CMD_HOST}" ) PTR=$("${CMD}" "${IP}" ${DNSSERVER} | tail -n1 | grep -o '[^ ]\+$' | sed s/\.$//) esac # Validate PTR -if [ ! "$PTR" ]; then +if [[ -z "${PTR}" ]]; then info 0 "${YELLOW}Warning: PTR lookup failed${CLEAR}" else # Match against supplied domain - info 1 "PTR resolves to $PTR" - if [ "$DOMAIN" ]; then - if [ "$DOMAIN" != "$PTR" ]; then - info 0 "${YELLOW}Warning: PTR record does not match supplied domain: $TARGET != $PTR${CLEAR}" + info 1 "PTR resolves to ${PTR}" + if [[ -n "${DOMAIN}" ]]; then + if [[ "${DOMAIN}" != "${PTR}" ]]; then + info 0 "${YELLOW}Warning: PTR record does not match supplied domain: ${TARGET} != ${PTR}${CLEAR}" else - info 1 "${GREEN}PTR record matches supplied domain $PTR${CLEAR}" + info 1 "${GREEN}PTR record matches supplied domain ${PTR}${CLEAR}" fi fi fi -# Filter out the blacklists -BLACKLISTS="" -for BL in $CONF_BLACKLISTS; do - if [ "$BL" ]; then +# Filter out the blocklists +BLOCKLISTS="" +COUNT_DNSBL=0 +COUNT_DNSWL=0 +COUNT_URIBL=0 +COUNT=0 +for BL in ${CONF_BLOCKLISTS}; do + if [[ "${BL}" ]]; then + if [[ ${BL} =~ ${REGEX_LIST} ]]; then + DOMAIN="${BASH_REMATCH[1]}" + if [[ -n "${BASH_REMATCH[3]}" ]]; then + FILTER="${BASH_REMATCH[3]}" + else + FILTER='127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})' + fi + if [[ -n "${BASH_REMATCH[6]}" ]]; then + TYPE="${BASH_REMATCH[6]}" + else + TYPE='DNSBL' + fi - # Make sure the domain is a proper one - DOMAIN=$(echo "$BL" | sed -e 's/^[ \t]*//' | grep ^"$REGEX_DOMAIN"$) - if [ ! "$DOMAIN" ]; then - info 0 "${YELLOW}Warning: blacklist '$BL' is not valid and will be ignored${CLEAR}" + # Make sure the domain is a proper one + # shellcheck disable=SC2001 + TMP=$(sed -e 's/^[ \t]*//' <<< "${DOMAIN}" | grep ^"${REGEX_DOMAIN}"$) + if [[ -z "${TMP}" ]]; then + info 0 "${YELLOW}Warning: domain '${DOMAIN}' is not valid and will be ignored${CLEAR}" - else - - # It is a proper blacklist - if [ "$BLACKLISTS" ]; then - BLACKLISTS=$(echo "$BLACKLISTS\n$DOMAIN") else - BLACKLISTS="$BL" + if [[ "${TARGET_TYPE}" == 'ip' && "${TYPE}" == 'URIBL' ]]; then + info 0 "${GREEN}Notice: URIBL entry '${DOMAIN}' will be ignore, because ${TARGET} is an IP address${CLEAR}" + else + case "${TYPE}" in + "DNSBL") COUNT_DNSBL=$((COUNT_DNSBL + 1)) ;; + "DNSWL") COUNT_DNSWL=$((COUNT_DNSWL + 1)) ;; + "URIBL") COUNT_URIBL=$((COUNT_URIBL + 1)) ;; + esac + COUNT=$((COUNT + 1)) + + # It is a proper blocklist + if [[ "${BLOCKLISTS}" ]]; then + BLOCKLISTS=$(echo -e "${BLOCKLISTS}\n${DOMAIN}=${FILTER}#${TYPE}") + else + BLOCKLISTS="${DOMAIN}=${FILTER}#${TYPE}" + fi + fi fi + else + info 0 "${YELLOW}Warning: list entry '${BL}' is not valid and will be ignored${CLEAR}" fi fi done -# Make sure we have at least one blacklist -COUNT=$(($(echo "$BLACKLISTS" | wc -l))) -if [ ! "$BLACKLISTS" ] || [ "$COUNT" -eq 0 ]; then - error "No blacklists have been specified" +# Make sure we have at least one blocklist +if [[ "${COUNT}" -eq 0 ]]; then + error "No blocklists have been specified" fi -info 1 "Matching against $COUNT blacklists" +info 1 "Matching against ${COUNT} entries:" +info 1 " - ${COUNT_DNSBL} DNS blocklists" +info 1 " - ${COUNT_DNSWL} DNS allowlists" +info 1 " - ${COUNT_URIBL} URI blocklists" + +# Iterate over all blocklists + +# shellcheck disable=SC2329 +parallel_check() { + local PREFIX= + local I=$1 + local BL=$2 + + # Parse list entry + if [[ ! ${BL} =~ ${REGEX_LIST} ]]; then + error "List entry ${BL} broken" + else + local DOMAIN="${BASH_REMATCH[1]}" + local FILTER="${BASH_REMATCH[3]}" + local TYPE="${BASH_REMATCH[6]}" + fi -# Initialize the counters -INVALID=0 -PASSED=0 -FAILED=0 -# Interate over all blacklists -I=0; -for BL in $BLACKLISTS; do - PREFIX= - I=$((I + 1)) # What should we test - TEST="$REVERSED.$BL." + if [[ "${TYPE}" != "URIBL" ]]; then + # IP based test + local TEST="${REVERSED}.${DOMAIN}." + else + # Domain name based test (URI) + local TEST="${TARGET}.${DOMAIN}." + fi + - # Make sure the info is shown if we are cheking the servers - if [ "$VERIFY_BL" ] && [ $VERBOSE -lt 1 ]; then - VERBOSE=1 + # Make sure the info is shown if we are checking the servers + if [[ "${VERIFY_BL}" && "${VERBOSE}" -lt 1 ]]; then + local VERBOSE=1 fi # For verbose output - if [ $VERBOSE -ge 1 ]; then + if [[ "${VERBOSE}" -ge 1 ]]; then # Show percentage - STATUS=$(printf " %3s" $((I * 100 / COUNT))) - STATUS="$STATUS%% " + local STATUS + STATUS="$(printf " %3s" $((I * 100 / COUNT)))% " # Show additional info - if [ $VERBOSE -ge 3 ]; then - PREFIX=$(printf "%-60s" "$TEST") + if [[ "${VERBOSE}" -ge 3 ]]; then + PREFIX=$(printf "%-60s" "${TEST}") else - PREFIX=$(printf "%-50s" "$BL") + PREFIX=$(printf "%-50s" "${DOMAIN}") fi - PREFIX="$STATUS $PREFIX" - if test -z "$PLAIN"; then - printf "%s \b" "$PREFIX" + PREFIX="${STATUS} ${PREFIX}" + if [[ -z "${PLAIN}" ]]; then + printf "%s \b" "${PREFIX}" fi - elif [ $VERBOSE -ge 0 ]; then - if test -z "$PLAIN"; then - progress "$I" "$COUNT" + elif [[ "${VERBOSE}" -ge 0 ]]; then + if [[ -z "${PLAIN}" ]]; then + progress "${I}" "${COUNT}" fi fi # Get the status - RESPONSE=$(resolve "$TEST") - START=$(echo "$RESPONSE" | cut -c1-4) + local RESPONSE + RESPONSE=$(resolve "${TEST}") - # Not blacklisted - if [ ! "$RESPONSE" ]; then + # Not blocklisted + if [[ -z "${RESPONSE}" ]]; then # Make sure the server is viable - ERROR="" - if [ "$VERIFY_BL" ]; then - TDL=$(echo "$BL" | grep -om 1 '\([a-zA-Z0-9]\+\(-[a-zA-Z0-9]\+\)*\.\)[a-zA-Z]\{2,\}$') - if [ ! "$(resolve "$TDL" ns)" ]; then - if test -z "$PLAIN"; then printf "\r"; fi - printf "%s%sUnreachable server%s\n" "$YELLOW" "$PREFIX" "$CLEAR"; - INVALID=$((INVALID + 1)) + local ERROR="" + if [[ "${VERIFY_BL}" ]]; then + local TDL + TDL=$(grep -om 1 "${REGEX_TDL}" <<< "${DOMAIN}") + if [[ ! "$(resolve "${TDL}" ns)" ]]; then + if [[ -z "${PLAIN}" ]]; then printf "\r"; fi + printf "%s%sUnreachable server%s\n" "${YELLOW}" "${PREFIX}" "${CLEAR}"; + echo "$BL" | cut -d= -f1 >> "$invalid_file" ERROR=TRUE fi fi - if [ ! "$ERROR" ]; then - if [ "$VERIFY_BL" ] || [ $VERBOSE -ge 1 ]; then - if test -z "$PLAIN"; then printf "\r"; fi - printf "%s%s✓%s\n" "$CLEAR" "$PREFIX" "$CLEAR"; + if [[ ! "${ERROR}" ]]; then + if [[ -n "${VERIFY_BL}" || "${VERBOSE}" -ge 1 ]]; then + if [[ -z "${PLAIN}" ]]; then printf "\r"; fi + printf "%s%s✓%s\n" "${CLEAR}" "${PREFIX}" "${CLEAR}"; fi - PASSED=$((PASSED + 1)) + cut -d= -f1 <<< "$BL" >> "$passed_file" fi; # Invalid response - elif [ "$START" != "127." ]; then - if [ $VERBOSE -ge 1 ]; then - if test -z "$PLAIN"; then printf "\r"; fi - printf "%s%sinvalid response (%s)%s\n" "$YELLOW" "$PREFIX" "$RESPONSE" "$CLEAR"; + elif [[ ! "${RESPONSE}" =~ ${FILTER} ]] || [[ ${RESPONSE:0:8} == "127.255." ]]; then + if [[ "${VERBOSE}" -ge 1 ]]; then + [[ -z "${PLAIN}" ]] && printf "\r" + printf "%s%sinvalid response (%s)%s\n" "${YELLOW}" "${PREFIX}" "${RESPONSE}" "${CLEAR}"; + printf "%s%${#PREFIX}s%s%s\n" "${YELLOW}" "TXT: " "$(text "${TEST}")" "${CLEAR}"; fi; - INVALID=$((INVALID + 1)) + cut -d= -f1 <<< "$BL" >> "$invalid_file" - # Blacklisted + # matched else - if [ $VERBOSE -ge 1 ]; then - if test -z "$PLAIN"; then printf "\r"; fi - printf "%s%sblacklisted (%s)%s\n" "$RED" "$PREFIX" "$RESPONSE" "$CLEAR"; - elif [ $VERBOSE -ge 0 ]; then - if test -z "$PLAIN"; then printf "\r "; printf "\r"; fi - printf "%s%s%s : %s\n" "$RED" "$BL" "$CLEAR" "$RESPONSE" + if [[ "${TYPE}" != "DNSWL" ]]; then + if [[ "${VERBOSE}" -ge 1 ]]; then + [[ -z "${PLAIN}" ]] && printf "\r" + printf "%s%sblocklisted (%s)%s\n" "${RED}" "${PREFIX}" "${RESPONSE}" "${CLEAR}"; + printf "%s%${#PREFIX}s%s%s\n" "${RED}" "TXT: " "$(text "${TEST}")" "${CLEAR}"; + elif [[ "${VERBOSE}" -ge 0 ]]; then + [[ -z "${PLAIN}" ]] && printf "\r%60s\r" " " + printf "%s%s%s : %s\n" "${RED}" "${DOMAIN}" "${CLEAR}" "${RESPONSE}" + fi + cut -d= -f1 <<< "$BL" >> "$failed_file" + else + if [[ "${VERBOSE}" -ge 1 ]]; then + [[ -z "${PLAIN}" ]] && printf "\r" + printf "%s%sallowlisted (%s)%s\n" "${GREEN}" "${PREFIX}" "${RESPONSE}" "${CLEAR}"; + printf "%s%${#PREFIX}s%s%s\n" "${GREEN}" "TXT: " "$(text "${TEST}")" "${CLEAR}"; + elif [[ "${VERBOSE}" -ge 0 ]]; then + [[ -z "${PLAIN}" ]] && printf "\r%60s\r" " " + printf "%s%s%s : %s\n" "${GREEN}" "${DOMAIN}" "${CLEAR}" "${RESPONSE}" + fi + cut -d= -f1 <<< "$BL" >> "$allow_file" fi - FAILED=$((FAILED + 1)) fi -done +} + +allow_file=$(mktemp) +failed_file=$(mktemp) +passed_file=$(mktemp) +invalid_file=$(mktemp) +export -f parallel_check resolve text progress +export REGEX_LIST REGEX_TDL REVERSED TARGET VERIFY_BL VERBOSE PLAIN COUNT YELLOW CLEAR RED GREEN SPINNER REGEX_IP REGEX_DOMAIN CMD_DIG DNSSERVER CONF_DNS_DURATION CONF_DNS_TRIES REGEX CMD_HOST CMD allow_file failed_file passed_file invalid_file +# shellcheck disable=SC2086 +parallel -j "$j" 'parallel_check {#} {}' ::: ${BLOCKLISTS} # Print results -if [ $VERBOSE -ge 0 ]; then - if test -z "$PLAIN"; then +# shellcheck disable=SC2002 +if [[ "${VERBOSE}" -ge 0 ]]; then + if [[ -z "${PLAIN}" ]]; then printf "\r \n" else printf " \n" fi echo "----------------------------------------------------------" - echo Results for "$TARGET" + echo Results for "${TARGET}" echo printf "%-15s" "Tested:"; echo "${COUNT}" - printf "%-15s" "Passed:"; echo "${GREEN}${PASSED}${CLEAR}" - printf "%-15s" "Invalid:"; echo "${YELLOW}${INVALID}${CLEAR}" - printf "%-15s" "Blacklisted:"; echo "${RED}${FAILED}${CLEAR}" + printf "%-15s" "Passed:"; echo "${GREEN}$(cat "$passed_file" | wc -l)${CLEAR}" + printf "%-15s" "Whitelisted:"; echo "${GREEN}$(cat "$allow_file" | wc -l)${CLEAR}" + printf "%-15s" "Invalid:"; echo "${YELLOW}$(cat "$invalid_file" | wc -l)${CLEAR}" + printf "%-15s" "Blocklisted:"; echo "${RED}$(cat "$failed_file" | wc -l)${CLEAR}" echo "----------------------------------------------------------" fi -exit $FAILED; +# shellcheck disable=SC2002 +failed=$(cat "$failed_file" | wc -l) +if [ "$failed" -gt 0 ]; then + touch $output + echo "${TARGET}" >> $output + cat "$failed_file" >> $output +fi + +rm -f "$passed_file" "$allow_file" "$invalid_file" "$failed_file" + +unset -f parallel_check resolve text progress +unset REGEX_LIST REGEX_TDL REVERSED TARGET VERIFY_BL VERBOSE PLAIN COUNT YELLOW CLEAR RED GREEN SPINNER REGEX_IP REGEX_DOMAIN CMD_DIG DNSSERVER CONF_DNS_DURATION CONF_DNS_TRIES REGEX CMD_HOST CMD allow_file failed_file passed_file invalid_file +exit "${failed}"