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 = ""
blockpage = ""
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
# Scan an array of files for matching strings
scanList( ) {
2018-07-20 20:13:42 +00:00
# Escape full stops
2022-08-01 19:56:37 +00:00
local domain = " ${ 1 } " esc_domain = " ${ 1 //./ \\ . } " lists = " ${ 2 } " list_type = " ${ 3 :- } "
2018-07-20 20:13:42 +00:00
# Prevent grep from printing file path
cd " $piholeDir " || exit 1
2020-05-20 08:34:14 +00:00
# Prevent grep -i matching slowly: https://bit.ly/2xFXtUX
2018-07-20 20:13:42 +00:00
export LC_CTYPE = C
# /dev/null forces filename to be printed when only one list has been generated
2022-08-01 19:56:37 +00:00
case " ${ list_type } " in
2019-08-22 12:12:58 +00:00
"exact" ) grep -i -E -l " (^|(?<!#)\\s) ${ esc_domain } ( $|\\s|#) " ${ lists } /dev/null 2>/dev/null; ;
# Iterate through each regexp and check whether it matches the domainQuery
# If it does, print the matching regexp and continue looping
# Input 1 - regexps | Input 2 - domainQuery
2021-11-25 06:41:40 +00:00
"regex" )
2020-03-08 23:38:53 +00:00
for list in ${ lists } ; do
if [ [ " ${ domain } " = ~ ${ list } ] ] ; then
printf "%b\n" " ${ list } " ;
fi
done ; ;
2019-08-22 12:12:58 +00:00
* ) grep -i " ${ esc_domain } " ${ lists } /dev/null 2>/dev/null; ;
2018-07-20 20:13:42 +00:00
esac
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
if [ [ " ${ options } " = = *"-bp" * ] ] ; then
2018-07-20 20:13:42 +00:00
exact = "exact" ; blockpage = true
2018-06-13 05:47:08 +00:00
else
2018-07-20 20:13:42 +00:00
[ [ " ${ options } " = = *"-all" * ] ] && all = true
if [ [ " ${ options } " = = *"-exact" * ] ] ; then
exact = "exact" ; matchType = " exact ${ matchType } "
fi
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
options = $( sed -E 's/ ?-(bp|adlists?|all|exact) ?//g' <<< " ${ options } " )
# 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" ; ;
*[ ![ :ascii:] ] * ) domainQuery = $( idn2 " ${ options } " ) ; ;
* ) domainQuery = " ${ options } " ; ;
2018-06-13 05:47:08 +00:00
esac
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
2019-05-04 10:47:25 +00:00
scanDatabaseTable( ) {
2022-08-01 19:56:37 +00:00
local domain table list_type querystr result extra
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
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 } ' " ; ;
* ) querystr = " SELECT gravity.domain,adlist.address,adlist.enabled FROM gravity LEFT JOIN adlist ON adlist.id = gravity.adlist_id WHERE domain LIKE '% ${ domain //_/ \\ _ } %' ESCAPE '\\' " ; ;
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 } ' " ; ;
* ) 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
2022-01-30 09:42:13 +00:00
result = " $( pihole-FTL sqlite3 " ${ 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
2019-07-04 20:44:14 +00:00
if [ [ -z " ${ blockpage } " ] ] ; then
2019-12-15 11:46:14 +00:00
echo " ${ matchType ^ } found in ${ COL_BOLD } exact ${ table } ${ COL_NC } "
2019-07-04 20:44:14 +00:00
fi
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
if [ [ -n " ${ blockpage } " ] ] ; then
echo " π ${ result } "
exit 0
fi
2019-12-15 11:46:14 +00:00
domain = " ${ result /|* } "
if [ [ " ${ result #*| } " = = "0" ] ] ; then
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
mapfile -t regexMatches < <( scanList " ${ domain } " " ${ str_regexList } " "regex" )
# 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
if [ [ -z " ${ blockpage } " ] ] ; then
# Set the wildcard match flag
2018-07-20 20:13:42 +00:00
wcMatch = true
2019-08-22 12:12:58 +00:00
# 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/^/ /'
else
2019-08-22 12:19:51 +00:00
echo "π .wildcard"
2019-08-22 12:12:58 +00:00
exit 0
2018-07-20 20:13:42 +00:00
fi
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
if [ [ -n " ${ exact } " ] ] && [ [ -z " ${ blockpage } " ] ] ; 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
2019-12-12 11:08:19 +00:00
match = " ${ result /|*/ } "
extra = " ${ result #*| } "
adlistAddress = " ${ extra /|*/ } "
2019-12-15 11:46:14 +00:00
extra = " ${ extra #*| } "
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
2018-07-20 20:13:42 +00:00
if [ [ -n " ${ blockpage } " ] ] ; then
2019-12-12 11:18:46 +00:00
echo " 0 ${ adlistAddress } "
2018-07-20 20:13:42 +00:00
elif [ [ -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