2018-06-13 05:47:08 +00:00
#!/usr/bin/env bash
2018-06-13 06:29:07 +00:00
# shellcheck disable=SC1090
2021-03-18 08:04:09 +00:00
2018-06-13 05:47:08 +00:00
# Pi-hole: A black hole for Internet advertisements
# (c) 2018 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
#
# Query Domain Lists
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
# Globals
piholeDir = "/etc/pihole"
2021-03-18 08:02:20 +00:00
GRAVITYDB = " ${ piholeDir } /gravity.db "
2018-06-13 05:47:08 +00:00
options = " $* "
all = ""
exact = ""
matchType = "match"
2021-03-18 08:02:20 +00:00
# Source pihole-FTL from install script
pihole_FTL = " ${ piholeDir } /pihole-FTL.conf "
if [ [ -f " ${ pihole_FTL } " ] ] ; then
2021-11-25 06:41:40 +00:00
source " ${ pihole_FTL } "
2021-03-18 08:02:20 +00:00
fi
# Set this only after sourcing pihole-FTL.conf as the gravity database path may
# have changed
gravityDBfile = " ${ GRAVITYDB } "
2018-06-13 05:47:08 +00:00
colfile = "/opt/pihole/COL_TABLE"
2018-06-13 06:25:43 +00:00
source " ${ colfile } "
2018-06-13 05:47:08 +00:00
if [ [ " ${ options } " = = "-h" ] ] || [ [ " ${ options } " = = "--help" ] ] ; then
2018-07-20 20:13:42 +00:00
echo " Usage: pihole -q [option] <domain>
2018-06-13 05:47:08 +00:00
Example: 'pihole -q -exact domain.com'
Query the adlists for a specified domain
Options:
2022-04-04 11:52:26 +00:00
-exact Search the adlists for exact domain matches
-all Return all query matches within the adlists
2018-06-13 05:47:08 +00:00
-h, --help Show this help dialog"
exit 0
fi
# Handle valid options
2022-08-08 17:57:17 +00:00
[ [ " ${ options } " = = *"-all" * ] ] && all = true
if [ [ " ${ options } " = = *"-exact" * ] ] ; then
exact = "exact" ; matchType = " exact ${ matchType } "
2018-06-13 05:47:08 +00:00
fi
# Strip valid options, leaving only the domain and invalid options
# This allows users to place the options before or after the domain
2023-06-01 13:10:05 +00:00
options = $( sed -E 's/ +-(all|exact) ?//g' <<< " ${ options } " )
2018-06-13 05:47:08 +00:00
# Handle remaining options
# If $options contain non ASCII characters, convert to punycode
case " ${ options } " in
2018-07-20 20:13:42 +00:00
"" ) str = "No domain specified" ; ;
*" " * ) str = "Unknown query option specified" ; ;
2023-03-07 19:40:16 +00:00
*[ ![ :ascii:] ] * ) rawDomainQuery = $( idn2 " ${ options } " ) ; ;
* ) rawDomainQuery = " ${ options } " ; ;
2018-06-13 05:47:08 +00:00
esac
2023-03-06 20:16:51 +00:00
# convert the domain to lowercase
2023-03-07 19:40:16 +00:00
domainQuery = $( echo " ${ rawDomainQuery } " | tr '[:upper:]' '[:lower:]' )
2023-03-06 20:16:51 +00:00
2018-06-13 05:47:08 +00:00
if [ [ -n " ${ str :- } " ] ] ; then
2018-07-20 20:13:42 +00:00
echo -e " ${ str } ${ COL_NC } \\nTry 'pihole -q --help' for more information. "
exit 1
2018-06-13 05:47:08 +00:00
fi
2023-05-02 20:44:35 +00:00
# Scan a domain again a list of RegEX
scanRegExList( ) {
local domain = " ${ 1 } " list = " ${ 2 } "
for entry in ${ list } ; do
if [ [ " ${ domain } " = ~ ${ entry } ] ] ; then
printf "%b\n" " ${ entry } " ;
fi
done
2023-03-06 20:16:51 +00:00
}
2019-05-04 10:47:25 +00:00
scanDatabaseTable( ) {
2023-03-21 19:34:50 +00:00
local domain table list_type querystr result extra abpquerystr abpfound abpentry searchstr
2019-05-04 16:25:11 +00:00
domain = " $( printf "%q" " ${ 1 } " ) "
2019-05-04 10:47:25 +00:00
table = " ${ 2 } "
2022-08-01 19:56:37 +00:00
list_type = " ${ 3 :- } "
2019-05-04 10:47:25 +00:00
2019-05-04 11:19:50 +00:00
# As underscores are legitimate parts of domains, we escape them when using the LIKE operator.
# Underscores are SQLite wildcards matching exactly one character. We obviously want to suppress this
2019-05-04 10:47:25 +00:00
# behavior. The "ESCAPE '\'" clause specifies that an underscore preceded by an '\' should be matched
2019-05-04 11:19:50 +00:00
# as a literal underscore character. We pretreat the $domain variable accordingly to escape underscores.
2019-12-12 10:58:41 +00:00
if [ [ " ${ table } " = = "gravity" ] ] ; then
2023-02-28 18:55:02 +00:00
# Are there ABP entries on gravity?
# Return 1 if abp_domain=1 or Zero if abp_domain=0 or not set
abpquerystr = "SELECT EXISTS (SELECT 1 FROM info WHERE property='abp_domains' and value='1')"
abpfound = " $( pihole-FTL sqlite3 " ${ gravityDBfile } " " ${ abpquerystr } " ) " 2> /dev/null
# Create search string for ABP entries only if needed
if [ " ${ abpfound } " -eq 1 ] ; then
abpentry = " ${ domain } "
searchstr = " '|| ${ abpentry } ^' "
# While a dot is found ...
while [ " ${ abpentry } " != " ${ abpentry /./ } " ]
do
# ... remove text before the dot (including the dot) and append the result to $searchstr
abpentry = $( echo " ${ abpentry } " | cut -f 2- -d '.' )
searchstr = " $searchstr , '|| ${ abpentry } ^' "
done
# The final search string will look like:
# "domain IN ('||sub2.sub1.domain.com^', '||sub1.domain.com^', '||domain.com^', '||com^') OR"
searchstr = " domain IN ( ${ searchstr } ) OR "
fi
2021-11-25 06:41:40 +00:00
case " ${ exact } " in
"exact" ) querystr = " SELECT gravity.domain,adlist.address,adlist.enabled FROM gravity LEFT JOIN adlist ON adlist.id = gravity.adlist_id WHERE domain = ' ${ domain } ' " ; ;
2023-02-28 18:55:02 +00:00
* ) querystr = " SELECT gravity.domain,adlist.address,adlist.enabled FROM gravity LEFT JOIN adlist ON adlist.id = gravity.adlist_id WHERE ${ searchstr } domain LIKE '% ${ domain //_/ \\ _ } %' ESCAPE '\\' " ; ;
2021-11-25 06:41:40 +00:00
esac
2019-12-12 10:58:41 +00:00
else
2021-11-25 06:41:40 +00:00
case " ${ exact } " in
2022-08-01 19:56:37 +00:00
"exact" ) querystr = " SELECT domain,enabled FROM domainlist WHERE type = ' ${ list_type } ' AND domain = ' ${ domain } ' " ; ;
2023-02-28 18:55:02 +00:00
* ) querystr = " SELECT domain,enabled FROM domainlist WHERE type = ' ${ list_type } ' AND domain LIKE '% ${ domain //_/ \\ _ } %' ESCAPE '\\' " ; ;
2021-11-25 06:41:40 +00:00
esac
2019-12-12 10:58:41 +00:00
fi
2019-05-04 10:47:25 +00:00
# Send prepared query to gravity database
2023-02-19 18:12:03 +00:00
result = " $( pihole-FTL sqlite3 -separator ',' " ${ gravityDBfile } " " ${ querystr } " ) " 2> /dev/null
2019-05-04 11:15:30 +00:00
if [ [ -z " ${ result } " ] ] ; then
2019-05-04 11:19:50 +00:00
# Return early when there are no matches in this table
2019-05-04 10:47:25 +00:00
return
fi
2019-05-04 11:15:30 +00:00
2019-12-12 10:58:41 +00:00
if [ [ " ${ table } " = = "gravity" ] ] ; then
2021-11-25 06:41:40 +00:00
echo " ${ result } "
return
2019-12-12 10:58:41 +00:00
fi
2019-05-04 11:19:50 +00:00
# Mark domain as having been white-/blacklist matched (global variable)
2018-07-20 20:13:42 +00:00
wbMatch = true
2019-05-30 19:44:47 +00:00
# Print table name
2022-08-08 17:57:17 +00:00
echo " ${ matchType ^ } found in ${ COL_BOLD } exact ${ table } ${ COL_NC } "
2019-05-30 19:44:47 +00:00
# Loop over results and print them
2019-05-04 11:19:50 +00:00
mapfile -t results <<< " ${ result } "
2018-07-20 20:13:42 +00:00
for result in " ${ results [@] } " ; do
2023-02-19 18:12:03 +00:00
domain = " ${ result /,* } "
if [ [ " ${ result #*, } " = = "0" ] ] ; then
2019-12-15 11:46:14 +00:00
extra = " (disabled)"
else
extra = ""
fi
echo " ${ domain } ${ extra } "
2018-07-20 20:13:42 +00:00
done
2019-05-04 10:47:25 +00:00
}
2018-06-13 05:47:08 +00:00
2019-08-22 12:06:42 +00:00
scanRegexDatabaseTable( ) {
2022-08-01 19:56:37 +00:00
local domain list list_type
2019-08-22 12:12:58 +00:00
domain = " ${ 1 } "
list = " ${ 2 } "
2022-08-01 19:56:37 +00:00
list_type = " ${ 3 :- } "
2019-08-22 12:12:58 +00:00
# Query all regex from the corresponding database tables
2022-08-01 19:56:37 +00:00
mapfile -t regexList < <( pihole-FTL sqlite3 " ${ gravityDBfile } " " SELECT domain FROM domainlist WHERE type = ${ list_type } " 2> /dev/null)
2019-08-22 12:12:58 +00:00
# If we have regexps to process
if [ [ " ${# regexList [@] } " -ne 0 ] ] ; then
# Split regexps over a new line
str_regexList = $( printf '%s\n' " ${ regexList [@] } " )
# Check domain against regexps
2023-05-02 20:44:35 +00:00
mapfile -t regexMatches < <( scanRegExList " ${ domain } " " ${ str_regexList } " )
2019-08-22 12:12:58 +00:00
# If there were regex matches
if [ [ " ${# regexMatches [@] } " -ne 0 ] ] ; then
# Split matching regexps over a new line
str_regexMatches = $( printf '%s\n' " ${ regexMatches [@] } " )
# Form a "matched" message
2019-12-15 11:46:14 +00:00
str_message = " ${ matchType ^ } found in ${ COL_BOLD } regex ${ list } ${ COL_NC } "
2019-08-22 12:12:58 +00:00
# Form a "results" message
str_result = " ${ COL_BOLD } ${ str_regexMatches } ${ COL_NC } "
# If we are displaying more than just the source of the block
2022-08-08 17:57:17 +00:00
# Set the wildcard match flag
wcMatch = true
# Echo the "matched" message, indented by one space
echo " ${ str_message } "
# Echo the "results" message, each line indented by three spaces
# shellcheck disable=SC2001
echo " ${ str_result } " | sed 's/^/ /'
2018-07-20 20:13:42 +00:00
fi
2019-08-22 12:12:58 +00:00
fi
2019-08-22 12:06:42 +00:00
}
# Scan Whitelist and Blacklist
2019-12-15 11:46:14 +00:00
scanDatabaseTable " ${ domainQuery } " "whitelist" "0"
scanDatabaseTable " ${ domainQuery } " "blacklist" "1"
2019-08-22 12:06:42 +00:00
# Scan Regex table
2019-12-15 11:46:14 +00:00
scanRegexDatabaseTable " ${ domainQuery } " "whitelist" "2"
scanRegexDatabaseTable " ${ domainQuery } " "blacklist" "3"
2018-06-13 05:47:08 +00:00
2019-12-12 10:58:41 +00:00
# Query block lists
2019-12-15 11:46:14 +00:00
mapfile -t results <<< " $( scanDatabaseTable " ${ domainQuery } " "gravity" ) "
2019-09-02 20:39:28 +00:00
# Handle notices
if [ [ -z " ${ wbMatch :- } " ] ] && [ [ -z " ${ wcMatch :- } " ] ] && [ [ -z " ${ results [*] } " ] ] ; then
2022-04-04 11:52:26 +00:00
echo -e " ${ INFO } No ${ exact /t/t } results found for ${ COL_BOLD } ${ domainQuery } ${ COL_NC } within the adlists "
2019-09-02 20:39:28 +00:00
exit 0
elif [ [ -z " ${ results [*] } " ] ] ; then
# Result found in WL/BL/Wildcards
exit 0
elif [ [ -z " ${ all } " ] ] && [ [ " ${# results [*] } " -ge 100 ] ] ; then
echo -e " ${ INFO } Over 100 ${ exact /t/t } results found for ${ COL_BOLD } ${ domainQuery } ${ COL_NC }
This can be overridden using the -all option"
exit 0
fi
2018-06-13 05:47:08 +00:00
# Print "Exact matches for" title
2022-08-08 17:57:17 +00:00
if [ [ -n " ${ exact } " ] ] ; then
2018-07-20 20:13:42 +00:00
plural = "" ; [ [ " ${# results [*] } " -gt 1 ] ] && plural = "es"
echo " ${ matchType ^ } ${ plural } for ${ COL_BOLD } ${ domainQuery } ${ COL_NC } found in: "
2018-06-13 05:47:08 +00:00
fi
for result in " ${ results [@] } " ; do
2023-02-19 18:12:03 +00:00
match = " ${ result /,*/ } "
extra = " ${ result #*, } "
adlistAddress = " ${ extra /,*/ } "
extra = " ${ extra #*, } "
2019-12-15 11:46:14 +00:00
if [ [ " ${ extra } " = = "0" ] ] ; then
2022-01-14 16:00:34 +00:00
extra = " (disabled)"
2019-12-12 11:08:19 +00:00
else
2021-11-25 06:41:40 +00:00
extra = ""
2019-12-12 11:08:19 +00:00
fi
2018-06-13 05:47:08 +00:00
2022-08-08 17:57:17 +00:00
if [ [ -n " ${ exact } " ] ] ; then
2022-01-14 16:00:34 +00:00
echo " - ${ adlistAddress } ${ extra } "
2018-06-13 05:47:08 +00:00
else
2019-12-12 10:58:41 +00:00
if [ [ ! " ${ adlistAddress } " = = " ${ adlistAddress_prev :- } " ] ] ; then
2018-07-20 20:13:42 +00:00
count = ""
2019-12-12 10:58:41 +00:00
echo " ${ matchType ^ } found in ${ COL_BOLD } ${ adlistAddress } ${ COL_NC } : "
adlistAddress_prev = " ${ adlistAddress } "
2018-07-20 20:13:42 +00:00
fi
: $(( count++))
# Print matching domain if $max_count has not been reached
[ [ -z " ${ all } " ] ] && max_count = "50"
if [ [ -z " ${ all } " ] ] && [ [ " ${ count } " -ge " ${ max_count } " ] ] ; then
[ [ " ${ count } " -gt " ${ max_count } " ] ] && continue
echo " ${ COL_GRAY } Over ${ count } results found, skipping rest of file ${ COL_NC } "
else
2022-01-14 16:00:34 +00:00
echo " ${ match } ${ extra } "
2018-07-20 20:13:42 +00:00
fi
2018-06-13 05:47:08 +00:00
fi
done
exit 0