merge devel into 4.3.2 And Resolve merge conflicts

Signed-off-by: Adam Warner <me@adamwarner.co.uk>
pull/2938/head
Adam Warner 5 years ago
commit 61a40c1b43
No known key found for this signature in database
GPG Key ID: 872950F3ECF2B173

@ -18,8 +18,6 @@
# WITHIN /etc/dnsmasq.d/yourname.conf # # WITHIN /etc/dnsmasq.d/yourname.conf #
############################################################################### ###############################################################################
addn-hosts=/etc/pihole/gravity.list
addn-hosts=/etc/pihole/black.list
addn-hosts=/etc/pihole/local.list addn-hosts=/etc/pihole/local.list
domain-needed domain-needed

@ -0,0 +1,42 @@
#!/usr/bin/env bash
# shellcheck disable=SC1090
# Pi-hole: A black hole for Internet advertisements
# (c) 2019 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
#
# Updates gravity.db database
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
upgrade_gravityDB(){
local database piholeDir auditFile version
database="${1}"
piholeDir="${2}"
auditFile="${piholeDir}/auditlog.list"
# Get database version
version="$(sqlite3 "${database}" "SELECT \"value\" FROM \"info\" WHERE \"property\" = 'version';")"
if [[ "$version" == "1" ]]; then
# This migration script upgrades the gravity.db file by
# adding the domain_audit table
sqlite3 "${database}" < "/etc/.pihole/advanced/Scripts/database_migration/gravity/1_to_2.sql"
version=2
# Store audit domains in database table
if [ -e "${auditFile}" ]; then
echo -e " ${INFO} Migrating content of ${auditFile} into new database"
# database_table_from_file is defined in gravity.sh
database_table_from_file "domain_audit" "${auditFile}"
fi
fi
if [[ "$version" == "2" ]]; then
# This migration script upgrades the gravity.db file by
# renaming the regex table to regex_blacklist, and
# creating a new regex_whitelist table + corresponding linking table and views
sqlite3 "${database}" < "/etc/.pihole/advanced/Scripts/database_migration/gravity/2_to_3.sql"
version=3
fi
}

@ -0,0 +1,14 @@
.timeout 30000
BEGIN TRANSACTION;
CREATE TABLE domain_audit
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
domain TEXT UNIQUE NOT NULL,
date_added INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int))
);
UPDATE info SET value = 2 WHERE property = 'version';
COMMIT;

@ -0,0 +1,65 @@
.timeout 30000
PRAGMA FOREIGN_KEYS=OFF;
BEGIN TRANSACTION;
ALTER TABLE regex RENAME TO regex_blacklist;
CREATE TABLE regex_blacklist_by_group
(
regex_blacklist_id INTEGER NOT NULL REFERENCES regex_blacklist (id),
group_id INTEGER NOT NULL REFERENCES "group" (id),
PRIMARY KEY (regex_blacklist_id, group_id)
);
INSERT INTO regex_blacklist_by_group SELECT * FROM regex_by_group;
DROP TABLE regex_by_group;
DROP VIEW vw_regex;
DROP TRIGGER tr_regex_update;
CREATE VIEW vw_regex_blacklist AS SELECT DISTINCT domain
FROM regex_blacklist
LEFT JOIN regex_blacklist_by_group ON regex_blacklist_by_group.regex_blacklist_id = regex_blacklist.id
LEFT JOIN "group" ON "group".id = regex_blacklist_by_group.group_id
WHERE regex_blacklist.enabled = 1 AND (regex_blacklist_by_group.group_id IS NULL OR "group".enabled = 1)
ORDER BY regex_blacklist.id;
CREATE TRIGGER tr_regex_blacklist_update AFTER UPDATE ON regex_blacklist
BEGIN
UPDATE regex_blacklist SET date_modified = (cast(strftime('%s', 'now') as int)) WHERE domain = NEW.domain;
END;
CREATE TABLE regex_whitelist
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
domain TEXT UNIQUE NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT 1,
date_added INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)),
date_modified INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)),
comment TEXT
);
CREATE TABLE regex_whitelist_by_group
(
regex_whitelist_id INTEGER NOT NULL REFERENCES regex_whitelist (id),
group_id INTEGER NOT NULL REFERENCES "group" (id),
PRIMARY KEY (regex_whitelist_id, group_id)
);
CREATE VIEW vw_regex_whitelist AS SELECT DISTINCT domain
FROM regex_whitelist
LEFT JOIN regex_whitelist_by_group ON regex_whitelist_by_group.regex_whitelist_id = regex_whitelist.id
LEFT JOIN "group" ON "group".id = regex_whitelist_by_group.group_id
WHERE regex_whitelist.enabled = 1 AND (regex_whitelist_by_group.group_id IS NULL OR "group".enabled = 1)
ORDER BY regex_whitelist.id;
CREATE TRIGGER tr_regex_whitelist_update AFTER UPDATE ON regex_whitelist
BEGIN
UPDATE regex_whitelist SET date_modified = (cast(strftime('%s', 'now') as int)) WHERE domain = NEW.domain;
END;
UPDATE info SET value = 3 WHERE property = 'version';
COMMIT;

@ -11,46 +11,51 @@
# Globals # Globals
basename=pihole basename=pihole
piholeDir=/etc/"${basename}" piholeDir=/etc/"${basename}"
whitelist="${piholeDir}"/whitelist.txt gravityDBfile="${piholeDir}/gravity.db"
blacklist="${piholeDir}"/blacklist.txt
readonly regexlist="/etc/pihole/regex.list"
reload=false reload=false
addmode=true addmode=true
verbose=true verbose=true
wildcard=false wildcard=false
web=false
domList=() domList=()
listMain="" listType=""
listAlt="" listname=""
colfile="/opt/pihole/COL_TABLE" colfile="/opt/pihole/COL_TABLE"
source ${colfile} source ${colfile}
helpFunc() { helpFunc() {
if [[ "${listMain}" == "${whitelist}" ]]; then if [[ "${listType}" == "whitelist" ]]; then
param="w" param="w"
type="white" type="whitelist"
elif [[ "${listMain}" == "${regexlist}" && "${wildcard}" == true ]]; then elif [[ "${listType}" == "regex_blacklist" && "${wildcard}" == true ]]; then
param="-wild" param="-wild"
type="wildcard black" type="wildcard blacklist"
elif [[ "${listMain}" == "${regexlist}" ]]; then elif [[ "${listType}" == "regex_blacklist" ]]; then
param="-regex" param="-regex"
type="regex black" type="regex blacklist filter"
elif [[ "${listType}" == "regex_whitelist" && "${wildcard}" == true ]]; then
param="-white-wild"
type="wildcard whitelist"
elif [[ "${listType}" == "regex_whitelist" ]]; then
param="-white-regex"
type="regex whitelist filter"
else else
param="b" param="b"
type="black" type="blacklist"
fi fi
echo "Usage: pihole -${param} [options] <domain> <domain2 ...> echo "Usage: pihole -${param} [options] <domain> <domain2 ...>
Example: 'pihole -${param} site.com', or 'pihole -${param} site1.com site2.com' Example: 'pihole -${param} site.com', or 'pihole -${param} site1.com site2.com'
${type^}list one or more domains ${type^} one or more domains
Options: Options:
-d, --delmode Remove domain(s) from the ${type}list -d, --delmode Remove domain(s) from the ${type}
-nr, --noreload Update ${type}list without refreshing dnsmasq -nr, --noreload Update ${type} without reloading the DNS server
-q, --quiet Make output less verbose -q, --quiet Make output less verbose
-h, --help Show this help dialog -h, --help Show this help dialog
-l, --list Display all your ${type}listed domains -l, --list Display all your ${type}listed domains
@ -73,7 +78,7 @@ HandleOther() {
# Check validity of domain (don't check for regex entries) # Check validity of domain (don't check for regex entries)
if [[ "${#domain}" -le 253 ]]; then if [[ "${#domain}" -le 253 ]]; then
if [[ "${listMain}" == "${regexlist}" && "${wildcard}" == false ]]; then if [[ ( "${listType}" == "regex_blacklist" || "${listType}" == "regex_whitelist" ) && "${wildcard}" == false ]]; then
validDomain="${domain}" validDomain="${domain}"
else else
validDomain=$(grep -P "^((-|_)*[a-z\\d]((-|_)*[a-z\\d])*(-|_)*)(\\.(-|_)*([a-z\\d]((-|_)*[a-z\\d])*))*$" <<< "${domain}") # Valid chars check validDomain=$(grep -P "^((-|_)*[a-z\\d]((-|_)*[a-z\\d])*(-|_)*)(\\.(-|_)*([a-z\\d]((-|_)*[a-z\\d])*))*$" <<< "${domain}") # Valid chars check
@ -88,175 +93,152 @@ HandleOther() {
fi fi
} }
PoplistFile() { ProcessDomainList() {
# Check whitelist file exists, and if not, create it local is_regexlist
if [[ ! -f "${whitelist}" ]]; then if [[ "${listType}" == "regex_blacklist" ]]; then
touch "${whitelist}" # Regex black filter list
fi listname="regex blacklist filters"
is_regexlist=true
# Check blacklist file exists, and if not, create it elif [[ "${listType}" == "regex_whitelist" ]]; then
if [[ ! -f "${blacklist}" ]]; then # Regex white filter list
touch "${blacklist}" listname="regex whitelist filters"
is_regexlist=true
else
# Whitelist / Blacklist
listname="${listType}"
is_regexlist=false
fi fi
for dom in "${domList[@]}"; do for dom in "${domList[@]}"; do
# Logic: If addmode then add to desired list and remove from the other; if delmode then remove from desired list but do not add to the other # Format domain into regex filter if requested
if [[ "${wildcard}" == true ]]; then
dom="(^|\\.)${dom//\./\\.}$"
fi
# Logic: If addmode then add to desired list and remove from the other;
# if delmode then remove from desired list but do not add to the other
if ${addmode}; then if ${addmode}; then
AddDomain "${dom}" "${listMain}" AddDomain "${dom}" "${listType}"
RemoveDomain "${dom}" "${listAlt}" if ! ${is_regexlist}; then
RemoveDomain "${dom}" "${listAlt}"
fi
else else
RemoveDomain "${dom}" "${listMain}" RemoveDomain "${dom}" "${listType}"
fi fi
done done
} }
AddDomain() { AddDomain() {
local domain list num
# Use printf to escape domain. %q prints the argument in a form that can be reused as shell input
domain="$1"
list="$2" list="$2"
domain=$(EscapeRegexp "$1")
[[ "${list}" == "${whitelist}" ]] && listname="whitelist"
[[ "${list}" == "${blacklist}" ]] && listname="blacklist"
if [[ "${list}" == "${whitelist}" || "${list}" == "${blacklist}" ]]; then
[[ "${list}" == "${whitelist}" && -z "${type}" ]] && type="--whitelist-only"
[[ "${list}" == "${blacklist}" && -z "${type}" ]] && type="--blacklist-only"
bool=true
# Is the domain in the list we want to add it to?
grep -Ex -q "${domain}" "${list}" > /dev/null 2>&1 || bool=false
if [[ "${bool}" == false ]]; then
# Domain not found in the whitelist file, add it!
if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} Adding ${1} to ${listname}..."
fi
reload=true
# Add it to the list we want to add it to
echo "$1" >> "${list}"
else
if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} ${1} already exists in ${listname}, no need to add!"
fi
fi
elif [[ "${list}" == "${regexlist}" ]]; then
[[ -z "${type}" ]] && type="--wildcard-only"
bool=true
domain="${1}"
[[ "${wildcard}" == true ]] && domain="(^|\\.)${domain//\./\\.}$" # Is the domain in the list we want to add it to?
num="$(sqlite3 "${gravityDBfile}" "SELECT COUNT(*) FROM ${list} WHERE domain = '${domain}';")"
# Is the domain in the list? if [[ "${num}" -ne 0 ]]; then
# Search only for exactly matching lines if [[ "${verbose}" == true ]]; then
grep -Fx "${domain}" "${regexlist}" > /dev/null 2>&1 || bool=false echo -e " ${INFO} ${1} already exists in ${listname}, no need to add!"
fi
return
fi
if [[ "${bool}" == false ]]; then # Domain not found in the table, add it!
if [[ "${verbose}" == true ]]; then if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} Adding ${domain} to regex list..." echo -e " ${INFO} Adding ${1} to the ${listname}..."
fi
reload="restart"
echo "$domain" >> "${regexlist}"
else
if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} ${domain} already exists in regex list, no need to add!"
fi
fi
fi fi
reload=true
# Insert only the domain here. The enabled and date_added fields will be filled
# with their default values (enabled = true, date_added = current timestamp)
sqlite3 "${gravityDBfile}" "INSERT INTO ${list} (domain) VALUES ('${domain}');"
} }
RemoveDomain() { RemoveDomain() {
local domain list num
# Use printf to escape domain. %q prints the argument in a form that can be reused as shell input
domain="$1"
list="$2" list="$2"
domain=$(EscapeRegexp "$1")
# Is the domain in the list we want to remove it from?
[[ "${list}" == "${whitelist}" ]] && listname="whitelist" num="$(sqlite3 "${gravityDBfile}" "SELECT COUNT(*) FROM ${list} WHERE domain = '${domain}';")"
[[ "${list}" == "${blacklist}" ]] && listname="blacklist"
if [[ "${num}" -eq 0 ]]; then
if [[ "${list}" == "${whitelist}" || "${list}" == "${blacklist}" ]]; then if [[ "${verbose}" == true ]]; then
bool=true echo -e " ${INFO} ${1} does not exist in ${list}, no need to remove!"
[[ "${list}" == "${whitelist}" && -z "${type}" ]] && type="--whitelist-only" fi
[[ "${list}" == "${blacklist}" && -z "${type}" ]] && type="--blacklist-only" return
# Is it in the list? Logic follows that if its whitelisted it should not be blacklisted and vice versa
grep -Ex -q "${domain}" "${list}" > /dev/null 2>&1 || bool=false
if [[ "${bool}" == true ]]; then
# Remove it from the other one
echo -e " ${INFO} Removing $1 from ${listname}..."
# /I flag: search case-insensitive
sed -i "/${domain}/Id" "${list}"
reload=true
else
if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} ${1} does not exist in ${listname}, no need to remove!"
fi
fi
elif [[ "${list}" == "${regexlist}" ]]; then
[[ -z "${type}" ]] && type="--wildcard-only"
domain="${1}"
[[ "${wildcard}" == true ]] && domain="(^|\\.)${domain//\./\\.}$"
bool=true
# Is it in the list?
grep -Fx "${domain}" "${regexlist}" > /dev/null 2>&1 || bool=false
if [[ "${bool}" == true ]]; then
# Remove it from the other one
echo -e " ${INFO} Removing $domain from regex list..."
local lineNumber
lineNumber=$(grep -Fnx "$domain" "${list}" | cut -f1 -d:)
sed -i "${lineNumber}d" "${list}"
reload=true
else
if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} ${domain} does not exist in regex list, no need to remove!"
fi
fi
fi fi
}
# Update Gravity # Domain found in the table, remove it!
Reload() { if [[ "${verbose}" == true ]]; then
echo "" echo -e " ${INFO} Removing ${1} from the ${listname}..."
pihole -g --skip-download "${type:-}" fi
reload=true
# Remove it from the current list
sqlite3 "${gravityDBfile}" "DELETE FROM ${list} WHERE domain = '${domain}';"
} }
Displaylist() { Displaylist() {
if [[ -f ${listMain} ]]; then local list listname count num_pipes domain enabled status nicedate
if [[ "${listMain}" == "${whitelist}" ]]; then
string="gravity resistant domains" listname="${listType}"
else data="$(sqlite3 "${gravityDBfile}" "SELECT domain,enabled,date_modified FROM ${listType};" 2> /dev/null)"
string="domains caught in the sinkhole"
fi if [[ -z $data ]]; then
verbose=false echo -e "Not showing empty list"
echo -e "Displaying $string:\n" else
echo -e "Displaying ${listname}:"
count=1 count=1
while IFS= read -r RD || [ -n "${RD}" ]; do while IFS= read -r line
echo " ${count}: ${RD}" do
# Count number of pipes seen in this line
# This is necessary because we can only detect the pipe separating the fields
# from the end backwards as the domain (which is the first field) may contain
# pipe symbols as they are perfectly valid regex filter control characters
num_pipes="$(grep -c "^" <<< "$(grep -o "|" <<< "${line}")")"
# Extract domain and enabled status based on the obtained number of pipe characters
domain="$(cut -d'|' -f"-$((num_pipes-1))" <<< "${line}")"
enabled="$(cut -d'|' -f"$((num_pipes))" <<< "${line}")"
datemod="$(cut -d'|' -f"$((num_pipes+1))" <<< "${line}")"
# Translate boolean status into human readable string
if [[ "${enabled}" -eq 1 ]]; then
status="enabled"
else
status="disabled"
fi
# Get nice representation of numerical date stored in database
nicedate=$(date --rfc-2822 -d "@${datemod}")
echo " ${count}: ${domain} (${status}, last modified ${nicedate})"
count=$((count+1)) count=$((count+1))
done < "${listMain}" done <<< "${data}"
else
echo -e " ${COL_LIGHT_RED}${listMain} does not exist!${COL_NC}"
fi fi
exit 0; exit 0;
} }
NukeList() { NukeList() {
if [[ -f "${listMain}" ]]; then sqlite3 "${gravityDBfile}" "DELETE FROM ${listType};"
# Back up original list
cp "${listMain}" "${listMain}.bck~"
# Empty out file
echo "" > "${listMain}"
fi
} }
for var in "$@"; do for var in "$@"; do
case "${var}" in case "${var}" in
"-w" | "whitelist" ) listMain="${whitelist}"; listAlt="${blacklist}";; "-w" | "whitelist" ) listType="whitelist"; listAlt="blacklist";;
"-b" | "blacklist" ) listMain="${blacklist}"; listAlt="${whitelist}";; "-b" | "blacklist" ) listType="blacklist"; listAlt="whitelist";;
"--wild" | "wildcard" ) listMain="${regexlist}"; wildcard=true;; "--wild" | "wildcard" ) listType="regex_blacklist"; wildcard=true;;
"--regex" | "regex" ) listMain="${regexlist}";; "--regex" | "regex" ) listType="regex_blacklist";;
"--white-regex" | "white-regex" ) listType="regex_whitelist";;
"--white-wild" | "white-wild" ) listType="regex_whitelist"; wildcard=true;;
"-nr"| "--noreload" ) reload=false;; "-nr"| "--noreload" ) reload=false;;
"-d" | "--delmode" ) addmode=false;; "-d" | "--delmode" ) addmode=false;;
"-q" | "--quiet" ) verbose=false;; "-q" | "--quiet" ) verbose=false;;
"-h" | "--help" ) helpFunc;; "-h" | "--help" ) helpFunc;;
"-l" | "--list" ) Displaylist;; "-l" | "--list" ) Displaylist;;
"--nuke" ) NukeList;; "--nuke" ) NukeList;;
"--web" ) web=true;;
* ) HandleOther "${var}";; * ) HandleOther "${var}";;
esac esac
done done
@ -267,9 +249,13 @@ if [[ $# = 0 ]]; then
helpFunc helpFunc
fi fi
PoplistFile ProcessDomainList
# Used on web interface
if $web; then
echo "DONE"
fi
if [[ "${reload}" != false ]]; then if [[ "${reload}" != false ]]; then
# Ensure that "restart" is used for Wildcard updates pihole restartdns reload
Reload "${reload}"
fi fi

@ -0,0 +1,73 @@
#!/usr/bin/env bash
# shellcheck disable=SC1090
# Pi-hole: A black hole for Internet advertisements
# (c) 2019 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
#
# ARP table interaction
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
coltable="/opt/pihole/COL_TABLE"
if [[ -f ${coltable} ]]; then
source ${coltable}
fi
# Determine database location
# Obtain DBFILE=... setting from pihole-FTL.db
# Constructed to return nothing when
# a) the setting is not present in the config file, or
# b) the setting is commented out (e.g. "#DBFILE=...")
FTLconf="/etc/pihole/pihole-FTL.conf"
if [ -e "$FTLconf" ]; then
DBFILE="$(sed -n -e 's/^\s*DBFILE\s*=\s*//p' ${FTLconf})"
fi
# Test for empty string. Use standard path in this case.
if [ -z "$DBFILE" ]; then
DBFILE="/etc/pihole/pihole-FTL.db"
fi
flushARP(){
local output
if [[ "${args[1]}" != "quiet" ]]; then
echo -ne " ${INFO} Flushing network table ..."
fi
# Flush ARP cache to avoid re-adding of dead entries
if ! output=$(ip neigh flush all 2>&1); then
echo -e "${OVER} ${CROSS} Failed to clear ARP cache"
echo " Output: ${output}"
return 1
fi
# Truncate network_addresses table in pihole-FTL.db
# This needs to be done before we can truncate the network table due to
# foreign key contraints
if ! output=$(sqlite3 "${DBFILE}" "DELETE FROM network_addresses" 2>&1); then
echo -e "${OVER} ${CROSS} Failed to truncate network_addresses table"
echo " Database location: ${DBFILE}"
echo " Output: ${output}"
return 1
fi
# Truncate network table in pihole-FTL.db
if ! output=$(sqlite3 "${DBFILE}" "DELETE FROM network" 2>&1); then
echo -e "${OVER} ${CROSS} Failed to truncate network table"
echo " Database location: ${DBFILE}"
echo " Output: ${output}"
return 1
fi
if [[ "${args[1]}" != "quiet" ]]; then
echo -e "${OVER} ${TICK} Flushed network table"
fi
}
args=("$@")
case "${args[0]}" in
"arpflush" ) flushARP;;
esac

@ -90,6 +90,7 @@ checkout() {
local path local path
path="development/${binary}" path="development/${binary}"
echo "development" > /etc/pihole/ftlbranch echo "development" > /etc/pihole/ftlbranch
chmod 644 /etc/pihole/ftlbranch
elif [[ "${1}" == "master" ]] ; then elif [[ "${1}" == "master" ]] ; then
# Shortcut to check out master branches # Shortcut to check out master branches
echo -e " ${INFO} Shortcut \"master\" detected - checking out master branches..." echo -e " ${INFO} Shortcut \"master\" detected - checking out master branches..."
@ -104,6 +105,7 @@ checkout() {
local path local path
path="master/${binary}" path="master/${binary}"
echo "master" > /etc/pihole/ftlbranch echo "master" > /etc/pihole/ftlbranch
chmod 644 /etc/pihole/ftlbranch
elif [[ "${1}" == "core" ]] ; then elif [[ "${1}" == "core" ]] ; then
str="Fetching branches from ${piholeGitUrl}" str="Fetching branches from ${piholeGitUrl}"
echo -ne " ${INFO} $str" echo -ne " ${INFO} $str"
@ -166,6 +168,7 @@ checkout() {
if check_download_exists "$path"; then if check_download_exists "$path"; then
echo " ${TICK} Branch ${2} exists" echo " ${TICK} Branch ${2} exists"
echo "${2}" > /etc/pihole/ftlbranch echo "${2}" > /etc/pihole/ftlbranch
chmod 644 /etc/pihole/ftlbranch
FTLinstall "${binary}" FTLinstall "${binary}"
restart_service pihole-FTL restart_service pihole-FTL
enable_service pihole-FTL enable_service pihole-FTL

@ -89,16 +89,40 @@ PIHOLE_WILDCARD_CONFIG_FILE="${DNSMASQ_D_DIRECTORY}/03-wildcard.conf"
WEB_SERVER_CONFIG_FILE="${WEB_SERVER_CONFIG_DIRECTORY}/lighttpd.conf" WEB_SERVER_CONFIG_FILE="${WEB_SERVER_CONFIG_DIRECTORY}/lighttpd.conf"
#WEB_SERVER_CUSTOM_CONFIG_FILE="${WEB_SERVER_CONFIG_DIRECTORY}/external.conf" #WEB_SERVER_CUSTOM_CONFIG_FILE="${WEB_SERVER_CONFIG_DIRECTORY}/external.conf"
PIHOLE_DEFAULT_AD_LISTS="${PIHOLE_DIRECTORY}/adlists.default"
PIHOLE_USER_DEFINED_AD_LISTS="${PIHOLE_DIRECTORY}/adlists.list"
PIHOLE_BLACKLIST_FILE="${PIHOLE_DIRECTORY}/blacklist.txt"
PIHOLE_BLOCKLIST_FILE="${PIHOLE_DIRECTORY}/gravity.list"
PIHOLE_INSTALL_LOG_FILE="${PIHOLE_DIRECTORY}/install.log" PIHOLE_INSTALL_LOG_FILE="${PIHOLE_DIRECTORY}/install.log"
PIHOLE_RAW_BLOCKLIST_FILES="${PIHOLE_DIRECTORY}/list.*" PIHOLE_RAW_BLOCKLIST_FILES="${PIHOLE_DIRECTORY}/list.*"
PIHOLE_LOCAL_HOSTS_FILE="${PIHOLE_DIRECTORY}/local.list" PIHOLE_LOCAL_HOSTS_FILE="${PIHOLE_DIRECTORY}/local.list"
PIHOLE_LOGROTATE_FILE="${PIHOLE_DIRECTORY}/logrotate" PIHOLE_LOGROTATE_FILE="${PIHOLE_DIRECTORY}/logrotate"
PIHOLE_SETUP_VARS_FILE="${PIHOLE_DIRECTORY}/setupVars.conf" PIHOLE_SETUP_VARS_FILE="${PIHOLE_DIRECTORY}/setupVars.conf"
PIHOLE_WHITELIST_FILE="${PIHOLE_DIRECTORY}/whitelist.txt" PIHOLE_FTL_CONF_FILE="${PIHOLE_DIRECTORY}/pihole-FTL.conf"
# Read the value of an FTL config key. The value is printed to stdout.
#
# Args:
# 1. The key to read
# 2. The default if the setting or config does not exist
get_ftl_conf_value() {
local key=$1
local default=$2
local value
# Obtain key=... setting from pihole-FTL.conf
if [[ -e "$PIHOLE_FTL_CONF_FILE" ]]; then
# Constructed to return nothing when
# a) the setting is not present in the config file, or
# b) the setting is commented out (e.g. "#DBFILE=...")
value="$(sed -n -e "s/^\\s*$key=\\s*//p" ${PIHOLE_FTL_CONF_FILE})"
fi
# Test for missing value. Use default value in this case.
if [[ -z "$value" ]]; then
value="$default"
fi
echo "$value"
}
PIHOLE_GRAVITY_DB_FILE="$(get_ftl_conf_value "GRAVITYDB" "${PIHOLE_DIRECTORY}/gravity.db")"
PIHOLE_COMMAND="${BIN_DIRECTORY}/pihole" PIHOLE_COMMAND="${BIN_DIRECTORY}/pihole"
PIHOLE_COLTABLE_FILE="${BIN_DIRECTORY}/COL_TABLE" PIHOLE_COLTABLE_FILE="${BIN_DIRECTORY}/COL_TABLE"
@ -109,7 +133,7 @@ FTL_PORT="${RUN_DIRECTORY}/pihole-FTL.port"
PIHOLE_LOG="${LOG_DIRECTORY}/pihole.log" PIHOLE_LOG="${LOG_DIRECTORY}/pihole.log"
PIHOLE_LOG_GZIPS="${LOG_DIRECTORY}/pihole.log.[0-9].*" PIHOLE_LOG_GZIPS="${LOG_DIRECTORY}/pihole.log.[0-9].*"
PIHOLE_DEBUG_LOG="${LOG_DIRECTORY}/pihole_debug.log" PIHOLE_DEBUG_LOG="${LOG_DIRECTORY}/pihole_debug.log"
PIHOLE_FTL_LOG="${LOG_DIRECTORY}/pihole-FTL.log" PIHOLE_FTL_LOG="$(get_ftl_conf_value "LOGFILE" "${LOG_DIRECTORY}/pihole-FTL.log")"
PIHOLE_WEB_SERVER_ACCESS_LOG_FILE="${WEB_SERVER_LOG_DIRECTORY}/access.log" PIHOLE_WEB_SERVER_ACCESS_LOG_FILE="${WEB_SERVER_LOG_DIRECTORY}/access.log"
PIHOLE_WEB_SERVER_ERROR_LOG_FILE="${WEB_SERVER_LOG_DIRECTORY}/error.log" PIHOLE_WEB_SERVER_ERROR_LOG_FILE="${WEB_SERVER_LOG_DIRECTORY}/error.log"
@ -142,16 +166,11 @@ REQUIRED_FILES=("${PIHOLE_CRON_FILE}"
"${PIHOLE_DHCP_CONFIG_FILE}" "${PIHOLE_DHCP_CONFIG_FILE}"
"${PIHOLE_WILDCARD_CONFIG_FILE}" "${PIHOLE_WILDCARD_CONFIG_FILE}"
"${WEB_SERVER_CONFIG_FILE}" "${WEB_SERVER_CONFIG_FILE}"
"${PIHOLE_DEFAULT_AD_LISTS}"
"${PIHOLE_USER_DEFINED_AD_LISTS}"
"${PIHOLE_BLACKLIST_FILE}"
"${PIHOLE_BLOCKLIST_FILE}"
"${PIHOLE_INSTALL_LOG_FILE}" "${PIHOLE_INSTALL_LOG_FILE}"
"${PIHOLE_RAW_BLOCKLIST_FILES}" "${PIHOLE_RAW_BLOCKLIST_FILES}"
"${PIHOLE_LOCAL_HOSTS_FILE}" "${PIHOLE_LOCAL_HOSTS_FILE}"
"${PIHOLE_LOGROTATE_FILE}" "${PIHOLE_LOGROTATE_FILE}"
"${PIHOLE_SETUP_VARS_FILE}" "${PIHOLE_SETUP_VARS_FILE}"
"${PIHOLE_WHITELIST_FILE}"
"${PIHOLE_COMMAND}" "${PIHOLE_COMMAND}"
"${PIHOLE_COLTABLE_FILE}" "${PIHOLE_COLTABLE_FILE}"
"${FTL_PID}" "${FTL_PID}"
@ -793,7 +812,7 @@ dig_at() {
# This helps emulate queries to different domains that a user might query # This helps emulate queries to different domains that a user might query
# It will also give extra assurance that Pi-hole is correctly resolving and blocking domains # It will also give extra assurance that Pi-hole is correctly resolving and blocking domains
local random_url local random_url
random_url=$(shuf -n 1 "${PIHOLE_BLOCKLIST_FILE}") random_url=$(sqlite3 "${PIHOLE_GRAVITY_DB_FILE}" "SELECT domain FROM vw_gravity ORDER BY RANDOM() LIMIT 1")
# First, do a dig on localhost to see if Pi-hole can use itself to block a domain # First, do a dig on localhost to see if Pi-hole can use itself to block a domain
if local_dig=$(dig +tries=1 +time=2 -"${protocol}" "${random_url}" @${local_address} +short "${record_type}"); then if local_dig=$(dig +tries=1 +time=2 -"${protocol}" "${random_url}" @${local_address} +short "${record_type}"); then
@ -975,8 +994,7 @@ list_files_in_dir() {
if [[ -d "${dir_to_parse}/${each_file}" ]]; then if [[ -d "${dir_to_parse}/${each_file}" ]]; then
# If it's a directoy, do nothing # If it's a directoy, do nothing
: :
elif [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_BLOCKLIST_FILE}" ]] || \ elif [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_DEBUG_LOG}" ]] || \
[[ "${dir_to_parse}/${each_file}" == "${PIHOLE_DEBUG_LOG}" ]] || \
[[ "${dir_to_parse}/${each_file}" == "${PIHOLE_RAW_BLOCKLIST_FILES}" ]] || \ [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_RAW_BLOCKLIST_FILES}" ]] || \
[[ "${dir_to_parse}/${each_file}" == "${PIHOLE_INSTALL_LOG_FILE}" ]] || \ [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_INSTALL_LOG_FILE}" ]] || \
[[ "${dir_to_parse}/${each_file}" == "${PIHOLE_SETUP_VARS_FILE}" ]] || \ [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_SETUP_VARS_FILE}" ]] || \
@ -1061,31 +1079,77 @@ head_tail_log() {
IFS="$OLD_IFS" IFS="$OLD_IFS"
} }
analyze_gravity_list() { show_db_entries() {
echo_current_diagnostic "Gravity list" local title="${1}"
local head_line local query="${2}"
local tail_line local widths="${3}"
# Put the current Internal Field Separator into another variable so it can be restored later
echo_current_diagnostic "${title}"
OLD_IFS="$IFS" OLD_IFS="$IFS"
# Get the lines that are in the file(s) and store them in an array for parsing later
IFS=$'\r\n' IFS=$'\r\n'
local entries=()
mapfile -t entries < <(\
sqlite3 "${PIHOLE_GRAVITY_DB_FILE}" \
-cmd ".headers on" \
-cmd ".mode column" \
-cmd ".width ${widths}" \
"${query}"\
)
for line in "${entries[@]}"; do
log_write " ${line}"
done
IFS="$OLD_IFS"
}
show_groups() {
show_db_entries "Groups" "SELECT * FROM \"group\"" "4 4 30 50"
}
show_adlists() {
show_db_entries "Adlists" "SELECT id,address,enabled,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,comment FROM adlist" "4 100 7 19 19 50"
show_db_entries "Adlist groups" "SELECT * FROM adlist_by_group" "4 4"
}
show_whitelist() {
show_db_entries "Exact whitelist" "SELECT id,domain,enabled,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,comment FROM whitelist" "4 100 7 19 19 50"
show_db_entries "Exact whitelist groups" "SELECT * FROM whitelist_by_group" "4 4"
show_db_entries "Regex whitelist" "SELECT id,domain,enabled,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,comment FROM regex_whitelist" "4 100 7 19 19 50"
show_db_entries "Regex whitelist groups" "SELECT * FROM regex_whitelist_by_group" "4 4"
}
show_blacklist() {
show_db_entries "Exact blacklist" "SELECT id,domain,enabled,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,comment FROM blacklist" "4 100 7 19 19 50"
show_db_entries "Exact blacklist groups" "SELECT * FROM blacklist_by_group" "4 4"
show_db_entries "Regex blacklist" "SELECT id,domain,enabled,datetime(date_added,'unixepoch','localtime') date_added,datetime(date_modified,'unixepoch','localtime') date_modified,comment FROM regex_blacklist" "4 100 7 19 19 50"
show_db_entries "Regex blacklist groups" "SELECT * FROM regex_blacklist_by_group" "4 4"
}
analyze_gravity_list() {
echo_current_diagnostic "Gravity List and Database"
local gravity_permissions local gravity_permissions
gravity_permissions=$(ls -ld "${PIHOLE_BLOCKLIST_FILE}") gravity_permissions=$(ls -ld "${PIHOLE_GRAVITY_DB_FILE}")
log_write "${COL_GREEN}${gravity_permissions}${COL_NC}" log_write "${COL_GREEN}${gravity_permissions}${COL_NC}"
local gravity_head=()
mapfile -t gravity_head < <(head -n 4 ${PIHOLE_BLOCKLIST_FILE}) local gravity_size
log_write " ${COL_CYAN}-----head of $(basename ${PIHOLE_BLOCKLIST_FILE})------${COL_NC}" gravity_size=$(sqlite3 "${PIHOLE_GRAVITY_DB_FILE}" "SELECT COUNT(*) FROM vw_gravity")
for head_line in "${gravity_head[@]}"; do log_write " Size (excluding blacklist): ${COL_CYAN}${gravity_size}${COL_NC} entries"
log_write " ${head_line}"
done
log_write "" log_write ""
local gravity_tail=()
mapfile -t gravity_tail < <(tail -n 4 ${PIHOLE_BLOCKLIST_FILE}) OLD_IFS="$IFS"
log_write " ${COL_CYAN}-----tail of $(basename ${PIHOLE_BLOCKLIST_FILE})------${COL_NC}" IFS=$'\r\n'
for tail_line in "${gravity_tail[@]}"; do local gravity_sample=()
log_write " ${tail_line}" mapfile -t gravity_sample < <(sqlite3 "${PIHOLE_GRAVITY_DB_FILE}" "SELECT domain FROM vw_gravity LIMIT 10")
log_write " ${COL_CYAN}----- First 10 Domains -----${COL_NC}"
for line in "${gravity_sample[@]}"; do
log_write " ${line}"
done done
# Set the IFS back to what it was
log_write ""
IFS="$OLD_IFS" IFS="$OLD_IFS"
} }
@ -1236,6 +1300,10 @@ process_status
parse_setup_vars parse_setup_vars
check_x_headers check_x_headers
analyze_gravity_list analyze_gravity_list
show_groups
show_adlists
show_whitelist
show_blacklist
show_content_of_pihole_files show_content_of_pihole_files
parse_locale parse_locale
analyze_pihole_log analyze_pihole_log

@ -39,8 +39,9 @@ if [[ "$@" == *"once"* ]]; then
# Note that moving the file is not an option, as # Note that moving the file is not an option, as
# dnsmasq would happily continue writing into the # dnsmasq would happily continue writing into the
# moved file (it will have the same file handler) # moved file (it will have the same file handler)
cp /var/log/pihole.log /var/log/pihole.log.1 cp -p /var/log/pihole.log /var/log/pihole.log.1
echo " " > /var/log/pihole.log echo " " > /var/log/pihole.log
chmod 644 /var/log/pihole.log
fi fi
else else
# Manual flushing # Manual flushing
@ -53,6 +54,7 @@ else
echo " " > /var/log/pihole.log echo " " > /var/log/pihole.log
if [ -f /var/log/pihole.log.1 ]; then if [ -f /var/log/pihole.log.1 ]; then
echo " " > /var/log/pihole.log.1 echo " " > /var/log/pihole.log.1
chmod 644 /var/log/pihole.log.1
fi fi
fi fi
# Delete most recent 24 hours from FTL's database, leave even older data intact (don't wipe out all history) # Delete most recent 24 hours from FTL's database, leave even older data intact (don't wipe out all history)

@ -11,8 +11,7 @@
# Globals # Globals
piholeDir="/etc/pihole" piholeDir="/etc/pihole"
adListsList="$piholeDir/adlists.list" gravityDBfile="${piholeDir}/gravity.db"
wildcardlist="/etc/dnsmasq.d/03-pihole-wildcard.conf"
options="$*" options="$*"
adlist="" adlist=""
all="" all=""
@ -23,27 +22,10 @@ matchType="match"
colfile="/opt/pihole/COL_TABLE" colfile="/opt/pihole/COL_TABLE"
source "${colfile}" source "${colfile}"
# Print each subdomain
# e.g: foo.bar.baz.com = "foo.bar.baz.com bar.baz.com baz.com com"
processWildcards() {
IFS="." read -r -a array <<< "${1}"
for (( i=${#array[@]}-1; i>=0; i-- )); do
ar=""
for (( j=${#array[@]}-1; j>${#array[@]}-i-2; j-- )); do
if [[ $j == $((${#array[@]}-1)) ]]; then
ar="${array[$j]}"
else
ar="${array[$j]}.${ar}"
fi
done
echo "${ar}"
done
}
# Scan an array of files for matching strings # Scan an array of files for matching strings
scanList(){ scanList(){
# Escape full stops # Escape full stops
local domain="${1//./\\.}" lists="${2}" type="${3:-}" local domain="${1}" esc_domain="${1//./\\.}" lists="${2}" type="${3:-}"
# Prevent grep from printing file path # Prevent grep from printing file path
cd "$piholeDir" || exit 1 cd "$piholeDir" || exit 1
@ -54,9 +36,14 @@ scanList(){
# /dev/null forces filename to be printed when only one list has been generated # /dev/null forces filename to be printed when only one list has been generated
# shellcheck disable=SC2086 # shellcheck disable=SC2086
case "${type}" in case "${type}" in
"exact" ) grep -i -E "(^|\\s)${domain}($|\\s|#)" ${lists} /dev/null 2>/dev/null;; "exact" ) grep -i -E -l "(^|(?<!#)\\s)${esc_domain}($|\\s|#)" ${lists} /dev/null 2>/dev/null;;
"wc" ) grep -i -o -m 1 "/${domain}/" ${lists} 2>/dev/null;; # Create array of regexps
* ) grep -i "${domain}" ${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
"regex" ) awk 'NR==FNR{regexps[$0];next}{for (r in regexps)if($0 ~ r)print r}' \
<(echo "${lists}") <(echo "${domain}") 2>/dev/null;;
* ) grep -i "${esc_domain}" ${lists} /dev/null 2>/dev/null;;
esac esac
} }
@ -73,11 +60,6 @@ Options:
exit 0 exit 0
fi fi
if [[ ! -e "$adListsList" ]]; then
echo -e "${COL_LIGHT_RED}The file $adListsList was not found${COL_NC}"
exit 1
fi
# Handle valid options # Handle valid options
if [[ "${options}" == *"-bp"* ]]; then if [[ "${options}" == *"-bp"* ]]; then
exact="exact"; blockpage=true exact="exact"; blockpage=true
@ -107,49 +89,93 @@ if [[ -n "${str:-}" ]]; then
exit 1 exit 1
fi fi
# Scan Whitelist and Blacklist scanDatabaseTable() {
lists="whitelist.txt blacklist.txt" local domain table type querystr result
mapfile -t results <<< "$(scanList "${domainQuery}" "${lists}" "${exact}")" domain="$(printf "%q" "${1}")"
if [[ -n "${results[*]}" ]]; then table="${2}"
type="${3:-}"
# 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
# behavior. The "ESCAPE '\'" clause specifies that an underscore preceded by an '\' should be matched
# as a literal underscore character. We pretreat the $domain variable accordingly to escape underscores.
case "${type}" in
"exact" ) querystr="SELECT domain FROM vw_${table} WHERE domain = '${domain}'";;
* ) querystr="SELECT domain FROM vw_${table} WHERE domain LIKE '%${domain//_/\\_}%' ESCAPE '\\'";;
esac
# Send prepared query to gravity database
result="$(sqlite3 "${gravityDBfile}" "${querystr}")" 2> /dev/null
if [[ -z "${result}" ]]; then
# Return early when there are no matches in this table
return
fi
# Mark domain as having been white-/blacklist matched (global variable)
wbMatch=true wbMatch=true
# Loop through each result in order to print unique file title once
# Print table name
if [[ -z "${blockpage}" ]]; then
echo " ${matchType^} found in ${COL_BOLD}${table^}${COL_NC}"
fi
# Loop over results and print them
mapfile -t results <<< "${result}"
for result in "${results[@]}"; do for result in "${results[@]}"; do
fileName="${result%%.*}"
if [[ -n "${blockpage}" ]]; then if [[ -n "${blockpage}" ]]; then
echo "π ${result}" echo "π ${result}"
exit 0 exit 0
elif [[ -n "${exact}" ]]; then
echo " ${matchType^} found in ${COL_BOLD}${fileName^}${COL_NC}"
else
# Only print filename title once per file
if [[ ! "${fileName}" == "${fileName_prev:-}" ]]; then
echo " ${matchType^} found in ${COL_BOLD}${fileName^}${COL_NC}"
fileName_prev="${fileName}"
fi
echo " ${result#*:}"
fi fi
echo " ${result}"
done done
fi }
# Scan Wildcards scanRegexDatabaseTable() {
if [[ -e "${wildcardlist}" ]]; then local domain list
# Determine all subdomains, domain and TLDs domain="${1}"
mapfile -t wildcards <<< "$(processWildcards "${domainQuery}")" list="${2}"
for match in "${wildcards[@]}"; do
# Search wildcard list for matches # Query all regex from the corresponding database tables
mapfile -t results <<< "$(scanList "${match}" "${wildcardlist}" "wc")" mapfile -t regexList < <(sqlite3 "${gravityDBfile}" "SELECT domain FROM vw_regex_${list}" 2> /dev/null)
if [[ -n "${results[*]}" ]]; then
if [[ -z "${wcMatch:-}" ]] && [[ -z "${blockpage}" ]]; then # 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
str_message="${matchType^} found in ${COL_BOLD}Regex ${list}${COL_NC}"
# 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
wcMatch=true wcMatch=true
echo " ${matchType^} found in ${COL_BOLD}Wildcards${COL_NC}:" # 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
echo "π .wildcard"
exit 0
fi fi
case "${blockpage}" in
true ) echo "π ${wildcardlist##*/}"; exit 0;;
* ) echo " *.${match}";;
esac
fi fi
done fi
fi }
# Scan Whitelist and Blacklist
scanDatabaseTable "${domainQuery}" "whitelist" "${exact}"
scanDatabaseTable "${domainQuery}" "blacklist" "${exact}"
# Scan Regex table
scanRegexDatabaseTable "${domainQuery}" "whitelist"
scanRegexDatabaseTable "${domainQuery}" "blacklist"
# Get version sorted *.domains filenames (without dir path) # Get version sorted *.domains filenames (without dir path)
lists=("$(cd "$piholeDir" || exit 0; printf "%s\\n" -- *.domains | sort -V)") lists=("$(cd "$piholeDir" || exit 0; printf "%s\\n" -- *.domains | sort -V)")
@ -186,11 +212,8 @@ fi
# Get adlist file content as array # Get adlist file content as array
if [[ -n "${adlist}" ]] || [[ -n "${blockpage}" ]]; then if [[ -n "${adlist}" ]] || [[ -n "${blockpage}" ]]; then
for adlistUrl in $(< "${adListsList}"); do # Retrieve source URLs from gravity database
if [[ "${adlistUrl:0:4}" =~ (http|www.) ]]; then mapfile -t adlists <<< "$(sqlite3 "${gravityDBfile}" "SELECT address FROM vw_adlist;" 2> /dev/null)"
adlists+=("${adlistUrl}")
fi
done
fi fi
# Print "Exact matches for" title # Print "Exact matches for" title

@ -51,6 +51,7 @@ if [[ "$2" == "remote" ]]; then
GITHUB_CORE_VERSION="$(json_extract tag_name "$(curl -s 'https://api.github.com/repos/pi-hole/pi-hole/releases/latest' 2> /dev/null)")" GITHUB_CORE_VERSION="$(json_extract tag_name "$(curl -s 'https://api.github.com/repos/pi-hole/pi-hole/releases/latest' 2> /dev/null)")"
echo -n "${GITHUB_CORE_VERSION}" > "${GITHUB_VERSION_FILE}" echo -n "${GITHUB_CORE_VERSION}" > "${GITHUB_VERSION_FILE}"
chmod 644 "${GITHUB_VERSION_FILE}"
if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
GITHUB_WEB_VERSION="$(json_extract tag_name "$(curl -s 'https://api.github.com/repos/pi-hole/AdminLTE/releases/latest' 2> /dev/null)")" GITHUB_WEB_VERSION="$(json_extract tag_name "$(curl -s 'https://api.github.com/repos/pi-hole/AdminLTE/releases/latest' 2> /dev/null)")"
@ -66,6 +67,7 @@ else
CORE_BRANCH="$(get_local_branch /etc/.pihole)" CORE_BRANCH="$(get_local_branch /etc/.pihole)"
echo -n "${CORE_BRANCH}" > "${LOCAL_BRANCH_FILE}" echo -n "${CORE_BRANCH}" > "${LOCAL_BRANCH_FILE}"
chmod 644 "${LOCAL_BRANCH_FILE}"
if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
WEB_BRANCH="$(get_local_branch /var/www/html/admin)" WEB_BRANCH="$(get_local_branch /var/www/html/admin)"
@ -79,6 +81,7 @@ else
CORE_VERSION="$(get_local_version /etc/.pihole)" CORE_VERSION="$(get_local_version /etc/.pihole)"
echo -n "${CORE_VERSION}" > "${LOCAL_VERSION_FILE}" echo -n "${CORE_VERSION}" > "${LOCAL_VERSION_FILE}"
chmod 644 "${LOCAL_VERSION_FILE}"
if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
WEB_VERSION="$(get_local_version /var/www/html/admin)" WEB_VERSION="$(get_local_version /var/www/html/admin)"

@ -18,6 +18,8 @@ readonly FTLconf="/etc/pihole/pihole-FTL.conf"
readonly dhcpstaticconfig="/etc/dnsmasq.d/04-pihole-static-dhcp.conf" readonly dhcpstaticconfig="/etc/dnsmasq.d/04-pihole-static-dhcp.conf"
readonly PI_HOLE_BIN_DIR="/usr/local/bin" readonly PI_HOLE_BIN_DIR="/usr/local/bin"
readonly gravityDBfile="/etc/pihole/gravity.db"
coltable="/opt/pihole/COL_TABLE" coltable="/opt/pihole/COL_TABLE"
if [[ -f ${coltable} ]]; then if [[ -f ${coltable} ]]; then
source ${coltable} source ${coltable}
@ -86,9 +88,9 @@ SetTemperatureUnit() {
HashPassword() { HashPassword() {
# Compute password hash twice to avoid rainbow table vulnerability # Compute password hash twice to avoid rainbow table vulnerability
return=$(echo -n ${1} | sha256sum | sed 's/\s.*$//') return=$(echo -n "${1}" | sha256sum | sed 's/\s.*$//')
return=$(echo -n ${return} | sha256sum | sed 's/\s.*$//') return=$(echo -n "${return}" | sha256sum | sed 's/\s.*$//')
echo ${return} echo "${return}"
} }
SetWebPassword() { SetWebPassword() {
@ -142,18 +144,18 @@ ProcessDNSSettings() {
delete_dnsmasq_setting "server" delete_dnsmasq_setting "server"
COUNTER=1 COUNTER=1
while [[ 1 ]]; do while true ; do
var=PIHOLE_DNS_${COUNTER} var=PIHOLE_DNS_${COUNTER}
if [ -z "${!var}" ]; then if [ -z "${!var}" ]; then
break; break;
fi fi
add_dnsmasq_setting "server" "${!var}" add_dnsmasq_setting "server" "${!var}"
let COUNTER=COUNTER+1 (( COUNTER++ ))
done done
# The option LOCAL_DNS_PORT is deprecated # The option LOCAL_DNS_PORT is deprecated
# We apply it once more, and then convert it into the current format # We apply it once more, and then convert it into the current format
if [ ! -z "${LOCAL_DNS_PORT}" ]; then if [ -n "${LOCAL_DNS_PORT}" ]; then
add_dnsmasq_setting "server" "127.0.0.1#${LOCAL_DNS_PORT}" add_dnsmasq_setting "server" "127.0.0.1#${LOCAL_DNS_PORT}"
add_setting "PIHOLE_DNS_${COUNTER}" "127.0.0.1#${LOCAL_DNS_PORT}" add_setting "PIHOLE_DNS_${COUNTER}" "127.0.0.1#${LOCAL_DNS_PORT}"
delete_setting "LOCAL_DNS_PORT" delete_setting "LOCAL_DNS_PORT"
@ -183,7 +185,7 @@ trust-anchor=.,20326,8,2,E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC68345710423
delete_dnsmasq_setting "host-record" delete_dnsmasq_setting "host-record"
if [ ! -z "${HOSTRECORD}" ]; then if [ -n "${HOSTRECORD}" ]; then
add_dnsmasq_setting "host-record" "${HOSTRECORD}" add_dnsmasq_setting "host-record" "${HOSTRECORD}"
fi fi
@ -211,6 +213,11 @@ trust-anchor=.,20326,8,2,E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC68345710423
add_dnsmasq_setting "server=/${CONDITIONAL_FORWARDING_DOMAIN}/${CONDITIONAL_FORWARDING_IP}" add_dnsmasq_setting "server=/${CONDITIONAL_FORWARDING_DOMAIN}/${CONDITIONAL_FORWARDING_IP}"
add_dnsmasq_setting "server=/${CONDITIONAL_FORWARDING_REVERSE}/${CONDITIONAL_FORWARDING_IP}" add_dnsmasq_setting "server=/${CONDITIONAL_FORWARDING_REVERSE}/${CONDITIONAL_FORWARDING_IP}"
fi fi
# Prevent Firefox from automatically switching over to DNS-over-HTTPS
# This follows https://support.mozilla.org/en-US/kb/configuring-networks-disable-dns-over-https
# (sourced 7th September 2019)
add_dnsmasq_setting "server=/use-application-dns.net/"
} }
SetDNSServers() { SetDNSServers() {
@ -323,6 +330,7 @@ dhcp-option=option:router,${DHCP_ROUTER}
dhcp-leasefile=/etc/pihole/dhcp.leases dhcp-leasefile=/etc/pihole/dhcp.leases
#quiet-dhcp #quiet-dhcp
" > "${dhcpconfig}" " > "${dhcpconfig}"
chmod 644 "${dhcpconfig}"
if [[ "${PIHOLE_DOMAIN}" != "none" ]]; then if [[ "${PIHOLE_DOMAIN}" != "none" ]]; then
echo "domain=${PIHOLE_DOMAIN}" >> "${dhcpconfig}" echo "domain=${PIHOLE_DOMAIN}" >> "${dhcpconfig}"
@ -394,19 +402,17 @@ SetWebUILayout() {
} }
CustomizeAdLists() { CustomizeAdLists() {
list="/etc/pihole/adlists.list" local address
address="${args[3]}"
if [[ "${args[2]}" == "enable" ]]; then if [[ "${args[2]}" == "enable" ]]; then
sed -i "\\@${args[3]}@s/^#http/http/g" "${list}" sqlite3 "${gravityDBfile}" "UPDATE adlist SET enabled = 1 WHERE address = '${address}'"
elif [[ "${args[2]}" == "disable" ]]; then elif [[ "${args[2]}" == "disable" ]]; then
sed -i "\\@${args[3]}@s/^http/#http/g" "${list}" sqlite3 "${gravityDBfile}" "UPDATE adlist SET enabled = 0 WHERE address = '${address}'"
elif [[ "${args[2]}" == "add" ]]; then elif [[ "${args[2]}" == "add" ]]; then
if [[ $(grep -c "^${args[3]}$" "${list}") -eq 0 ]] ; then sqlite3 "${gravityDBfile}" "INSERT OR IGNORE INTO adlist (address) VALUES ('${address}')"
echo "${args[3]}" >> ${list}
fi
elif [[ "${args[2]}" == "del" ]]; then elif [[ "${args[2]}" == "del" ]]; then
var=$(echo "${args[3]}" | sed 's/\//\\\//g') sqlite3 "${gravityDBfile}" "DELETE FROM adlist WHERE address = '${address}'"
sed -i "/${var}/Id" "${list}"
else else
echo "Not permitted" echo "Not permitted"
return 1 return 1
@ -538,23 +544,50 @@ Interfaces:
} }
Teleporter() { Teleporter() {
local datetimestamp=$(date "+%Y-%m-%d_%H-%M-%S") local datetimestamp
datetimestamp=$(date "+%Y-%m-%d_%H-%M-%S")
php /var/www/html/admin/scripts/pi-hole/php/teleporter.php > "pi-hole-teleporter_${datetimestamp}.tar.gz" php /var/www/html/admin/scripts/pi-hole/php/teleporter.php > "pi-hole-teleporter_${datetimestamp}.tar.gz"
} }
checkDomain()
{
local domain validDomain
# Convert to lowercase
domain="${1,,}"
validDomain=$(grep -P "^((-|_)*[a-z\\d]((-|_)*[a-z\\d])*(-|_)*)(\\.(-|_)*([a-z\\d]((-|_)*[a-z\\d])*))*$" <<< "${domain}") # Valid chars check
validDomain=$(grep -P "^[^\\.]{1,63}(\\.[^\\.]{1,63})*$" <<< "${validDomain}") # Length of each label
echo "${validDomain}"
}
addAudit() addAudit()
{ {
shift # skip "-a" shift # skip "-a"
shift # skip "audit" shift # skip "audit"
for var in "$@" local domains validDomain
domains=""
for domain in "$@"
do do
echo "${var}" >> /etc/pihole/auditlog.list # Check domain to be added. Only continue if it is valid
validDomain="$(checkDomain "${domain}")"
if [[ -n "${validDomain}" ]]; then
# Put comma in between domains when there is
# more than one domains to be added
# SQL INSERT allows adding multiple rows at once using the format
## INSERT INTO table (domain) VALUES ('abc.de'),('fgh.ij'),('klm.no'),('pqr.st');
if [[ -n "${domains}" ]]; then
domains="${domains},"
fi
domains="${domains}('${domain}')"
fi
done done
# Insert only the domain here. The date_added field will be
# filled with its default value (date_added = current timestamp)
sqlite3 "${gravityDBfile}" "INSERT INTO domain_audit (domain) VALUES ${domains};"
} }
clearAudit() clearAudit()
{ {
echo -n "" > /etc/pihole/auditlog.list sqlite3 "${gravityDBfile}" "DELETE FROM domain_audit;"
} }
SetPrivacyLevel() { SetPrivacyLevel() {

@ -0,0 +1,143 @@
PRAGMA FOREIGN_KEYS=ON;
CREATE TABLE "group"
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
enabled BOOLEAN NOT NULL DEFAULT 1,
name TEXT NOT NULL,
description TEXT
);
CREATE TABLE whitelist
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
domain TEXT UNIQUE NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT 1,
date_added INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)),
date_modified INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)),
comment TEXT
);
CREATE TABLE whitelist_by_group
(
whitelist_id INTEGER NOT NULL REFERENCES whitelist (id),
group_id INTEGER NOT NULL REFERENCES "group" (id),
PRIMARY KEY (whitelist_id, group_id)
);
CREATE TABLE blacklist
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
domain TEXT UNIQUE NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT 1,
date_added INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)),
date_modified INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)),
comment TEXT
);
CREATE TABLE blacklist_by_group
(
blacklist_id INTEGER NOT NULL REFERENCES blacklist (id),
group_id INTEGER NOT NULL REFERENCES "group" (id),
PRIMARY KEY (blacklist_id, group_id)
);
CREATE TABLE regex
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
domain TEXT UNIQUE NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT 1,
date_added INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)),
date_modified INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)),
comment TEXT
);
CREATE TABLE regex_by_group
(
regex_id INTEGER NOT NULL REFERENCES regex (id),
group_id INTEGER NOT NULL REFERENCES "group" (id),
PRIMARY KEY (regex_id, group_id)
);
CREATE TABLE adlist
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
address TEXT UNIQUE NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT 1,
date_added INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)),
date_modified INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)),
comment TEXT
);
CREATE TABLE adlist_by_group
(
adlist_id INTEGER NOT NULL REFERENCES adlist (id),
group_id INTEGER NOT NULL REFERENCES "group" (id),
PRIMARY KEY (adlist_id, group_id)
);
CREATE TABLE gravity
(
domain TEXT PRIMARY KEY
);
CREATE TABLE info
(
property TEXT PRIMARY KEY,
value TEXT NOT NULL
);
INSERT INTO info VALUES("version","1");
CREATE VIEW vw_gravity AS SELECT domain
FROM gravity
WHERE domain NOT IN (SELECT domain from vw_whitelist);
CREATE VIEW vw_whitelist AS SELECT DISTINCT domain
FROM whitelist
LEFT JOIN whitelist_by_group ON whitelist_by_group.whitelist_id = whitelist.id
LEFT JOIN "group" ON "group".id = whitelist_by_group.group_id
WHERE whitelist.enabled = 1 AND (whitelist_by_group.group_id IS NULL OR "group".enabled = 1)
ORDER BY whitelist.id;
CREATE TRIGGER tr_whitelist_update AFTER UPDATE ON whitelist
BEGIN
UPDATE whitelist SET date_modified = (cast(strftime('%s', 'now') as int)) WHERE domain = NEW.domain;
END;
CREATE VIEW vw_blacklist AS SELECT DISTINCT domain
FROM blacklist
LEFT JOIN blacklist_by_group ON blacklist_by_group.blacklist_id = blacklist.id
LEFT JOIN "group" ON "group".id = blacklist_by_group.group_id
WHERE blacklist.enabled = 1 AND (blacklist_by_group.group_id IS NULL OR "group".enabled = 1)
ORDER BY blacklist.id;
CREATE TRIGGER tr_blacklist_update AFTER UPDATE ON blacklist
BEGIN
UPDATE blacklist SET date_modified = (cast(strftime('%s', 'now') as int)) WHERE domain = NEW.domain;
END;
CREATE VIEW vw_regex AS SELECT DISTINCT domain
FROM regex
LEFT JOIN regex_by_group ON regex_by_group.regex_id = regex.id
LEFT JOIN "group" ON "group".id = regex_by_group.group_id
WHERE regex.enabled = 1 AND (regex_by_group.group_id IS NULL OR "group".enabled = 1)
ORDER BY regex.id;
CREATE TRIGGER tr_regex_update AFTER UPDATE ON regex
BEGIN
UPDATE regex SET date_modified = (cast(strftime('%s', 'now') as int)) WHERE domain = NEW.domain;
END;
CREATE VIEW vw_adlist AS SELECT DISTINCT address
FROM adlist
LEFT JOIN adlist_by_group ON adlist_by_group.adlist_id = adlist.id
LEFT JOIN "group" ON "group".id = adlist_by_group.group_id
WHERE adlist.enabled = 1 AND (adlist_by_group.group_id IS NULL OR "group".enabled = 1)
ORDER BY adlist.id;
CREATE TRIGGER tr_adlist_update AFTER UPDATE ON adlist
BEGIN
UPDATE adlist SET date_modified = (cast(strftime('%s', 'now') as int)) WHERE address = NEW.address;
END;

@ -7,7 +7,7 @@ _pihole() {
case "${prev}" in case "${prev}" in
"pihole") "pihole")
opts="admin blacklist checkout chronometer debug disable enable flush help logging query reconfigure regex restartdns status tail uninstall updateGravity updatePihole version wildcard whitelist" opts="admin blacklist checkout chronometer debug disable enable flush help logging query reconfigure regex restartdns status tail uninstall updateGravity updatePihole version wildcard whitelist arpflush"
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
;; ;;
"whitelist"|"blacklist"|"wildcard"|"regex") "whitelist"|"blacklist"|"wildcard"|"regex")

@ -102,20 +102,30 @@ if ($blocklistglob === array()) {
die("[ERROR] There are no domain lists generated lists within <code>/etc/pihole/</code>! Please update gravity by running <code>pihole -g</code>, or repair Pi-hole using <code>pihole -r</code>."); die("[ERROR] There are no domain lists generated lists within <code>/etc/pihole/</code>! Please update gravity by running <code>pihole -g</code>, or repair Pi-hole using <code>pihole -r</code>.");
} }
// Set location of adlists file // Get possible non-standard location of FTL's database
if (is_file("/etc/pihole/adlists.list")) { $FTLsettings = parse_ini_file("/etc/pihole/pihole-FTL.conf");
$adLists = "/etc/pihole/adlists.list"; if (isset($FTLsettings["GRAVITYDB"])) {
} elseif (is_file("/etc/pihole/adlists.default")) { $gravityDBFile = $FTLsettings["GRAVITYDB"];
$adLists = "/etc/pihole/adlists.default";
} else { } else {
die("[ERROR] File not found: <code>/etc/pihole/adlists.list</code>"); $gravityDBFile = "/etc/pihole/gravity.db";
} }
// Get all URLs starting with "http" or "www" from adlists and re-index array numerically // Connect to gravity.db
$adlistsUrls = array_values(preg_grep("/(^http)|(^www)/i", file($adLists, FILE_IGNORE_NEW_LINES))); try {
$db = new SQLite3($gravityDBFile, SQLITE3_OPEN_READONLY);
} catch (Exception $exception) {
die("[ERROR]: Failed to connect to gravity.db");
}
// Get all adlist addresses
$adlistResults = $db->query("SELECT address FROM vw_adlist");
$adlistsUrls = array();
while ($row = $adlistResults->fetchArray()) {
array_push($adlistsUrls, $row[0]);
}
if (empty($adlistsUrls)) if (empty($adlistsUrls))
die("[ERROR]: There are no adlist URL's found within <code>$adLists</code>"); die("[ERROR]: There are no adlists enabled");
// Get total number of blocklists (Including Whitelist, Blacklist & Wildcard lists) // Get total number of blocklists (Including Whitelist, Blacklist & Wildcard lists)
$adlistsCount = count($adlistsUrls) + 3; $adlistsCount = count($adlistsUrls) + 3;

@ -124,7 +124,7 @@ done
# If the color table file exists, # If the color table file exists,
if [[ -f "${coltable}" ]]; then if [[ -f "${coltable}" ]]; then
# source it # source it
source ${coltable} source "${coltable}"
# Otherwise, # Otherwise,
else else
# Set these values so the installer can still run in color # Set these values so the installer can still run in color
@ -188,26 +188,26 @@ if is_command apt-get ; then
# A variable to store the command used to update the package cache # A variable to store the command used to update the package cache
UPDATE_PKG_CACHE="${PKG_MANAGER} update" UPDATE_PKG_CACHE="${PKG_MANAGER} update"
# An array for something... # An array for something...
PKG_INSTALL=(${PKG_MANAGER} --yes --no-install-recommends install) PKG_INSTALL=("${PKG_MANAGER}" --yes --no-install-recommends install)
# grep -c will return 1 retVal on 0 matches, block this throwing the set -e with an OR TRUE # grep -c will return 1 retVal on 0 matches, block this throwing the set -e with an OR TRUE
PKG_COUNT="${PKG_MANAGER} -s -o Debug::NoLocking=true upgrade | grep -c ^Inst || true" PKG_COUNT="${PKG_MANAGER} -s -o Debug::NoLocking=true upgrade | grep -c ^Inst || true"
# Some distros vary slightly so these fixes for dependencies may apply # Some distros vary slightly so these fixes for dependencies may apply
# on Ubuntu 18.04.1 LTS we need to add the universe repository to gain access to dialog and dhcpcd5 # on Ubuntu 18.04.1 LTS we need to add the universe repository to gain access to dialog and dhcpcd5
APT_SOURCES="/etc/apt/sources.list" APT_SOURCES="/etc/apt/sources.list"
if awk 'BEGIN{a=1;b=0}/bionic main/{a=0}/bionic.*universe/{b=1}END{exit a + b}' ${APT_SOURCES}; then if awk 'BEGIN{a=1;b=0}/bionic main/{a=0}/bionic.*universe/{b=1}END{exit a + b}' ${APT_SOURCES}; then
if ! whiptail --defaultno --title "Dependencies Require Update to Allowed Repositories" --yesno "Would you like to enable 'universe' repository?\\n\\nThis repository is required by the following packages:\\n\\n- dhcpcd5\\n- dialog" ${r} ${c}; then if ! whiptail --defaultno --title "Dependencies Require Update to Allowed Repositories" --yesno "Would you like to enable 'universe' repository?\\n\\nThis repository is required by the following packages:\\n\\n- dhcpcd5\\n- dialog" "${r}" "${c}"; then
printf " %b Aborting installation: dependencies could not be installed.\\n" "${CROSS}" printf " %b Aborting installation: dependencies could not be installed.\\n" "${CROSS}"
exit # exit the installer exit # exit the installer
else else
printf " %b Enabling universe package repository for Ubuntu Bionic\\n" "${INFO}" printf " %b Enabling universe package repository for Ubuntu Bionic\\n" "${INFO}"
cp ${APT_SOURCES} ${APT_SOURCES}.backup # Backup current repo list cp -p ${APT_SOURCES} ${APT_SOURCES}.backup # Backup current repo list
printf " %b Backed up current configuration to %s\\n" "${TICK}" "${APT_SOURCES}.backup" printf " %b Backed up current configuration to %s\\n" "${TICK}" "${APT_SOURCES}.backup"
add-apt-repository universe add-apt-repository universe
printf " %b Enabled %s\\n" "${TICK}" "'universe' repository" printf " %b Enabled %s\\n" "${TICK}" "'universe' repository"
fi fi
fi fi
# Debian 7 doesn't have iproute2 so if the dry run install is successful, # Debian 7 doesn't have iproute2 so if the dry run install is successful,
if ${PKG_MANAGER} install --dry-run iproute2 > /dev/null 2>&1; then if "${PKG_MANAGER}" install --dry-run iproute2 > /dev/null 2>&1; then
# we can install it # we can install it
iproute_pkg="iproute2" iproute_pkg="iproute2"
# Otherwise, # Otherwise,
@ -228,7 +228,7 @@ if is_command apt-get ; then
# Check if installed php is v 7.0, or newer to determine packages to install # Check if installed php is v 7.0, or newer to determine packages to install
if [[ "$phpInsNewer" != true ]]; then if [[ "$phpInsNewer" != true ]]; then
# Prefer the php metapackage if it's there # Prefer the php metapackage if it's there
if ${PKG_MANAGER} install --dry-run php > /dev/null 2>&1; then if "${PKG_MANAGER}" install --dry-run php > /dev/null 2>&1; then
phpVer="php" phpVer="php"
# fall back on the php5 packages # fall back on the php5 packages
else else
@ -239,19 +239,19 @@ if is_command apt-get ; then
phpVer="php$phpInsMajor.$phpInsMinor" phpVer="php$phpInsMajor.$phpInsMinor"
fi fi
# We also need the correct version for `php-sqlite` (which differs across distros) # We also need the correct version for `php-sqlite` (which differs across distros)
if ${PKG_MANAGER} install --dry-run ${phpVer}-sqlite3 > /dev/null 2>&1; then if "${PKG_MANAGER}" install --dry-run "${phpVer}-sqlite3" > /dev/null 2>&1; then
phpSqlite="sqlite3" phpSqlite="sqlite3"
else else
phpSqlite="sqlite" phpSqlite="sqlite"
fi fi
# Since our install script is so large, we need several other programs to successfully get a machine provisioned # Since our install script is so large, we need several other programs to successfully get a machine provisioned
# These programs are stored in an array so they can be looped through later # These programs are stored in an array so they can be looped through later
INSTALLER_DEPS=(apt-utils dialog debconf dhcpcd5 git ${iproute_pkg} whiptail) INSTALLER_DEPS=(apt-utils dialog debconf dhcpcd5 git "${iproute_pkg}" whiptail)
# Pi-hole itself has several dependencies that also need to be installed # Pi-hole itself has several dependencies that also need to be installed
PIHOLE_DEPS=(cron curl dnsutils iputils-ping lsof netcat psmisc sudo unzip wget idn2 sqlite3 libcap2-bin dns-root-data resolvconf libcap2) PIHOLE_DEPS=(cron curl dnsutils iputils-ping lsof netcat psmisc sudo unzip wget idn2 sqlite3 libcap2-bin dns-root-data resolvconf libcap2)
# The Web dashboard has some that also need to be installed # The Web dashboard has some that also need to be installed
# It's useful to separate the two since our repos are also setup as "Core" code and "Web" code # It's useful to separate the two since our repos are also setup as "Core" code and "Web" code
PIHOLE_WEB_DEPS=(lighttpd ${phpVer}-common ${phpVer}-cgi ${phpVer}-${phpSqlite}) PIHOLE_WEB_DEPS=(lighttpd "${phpVer}-common" "${phpVer}-cgi" "${phpVer}-${phpSqlite}")
# The Web server user, # The Web server user,
LIGHTTPD_USER="www-data" LIGHTTPD_USER="www-data"
# group, # group,
@ -287,7 +287,7 @@ elif is_command rpm ; then
# Fedora and family update cache on every PKG_INSTALL call, no need for a separate update. # Fedora and family update cache on every PKG_INSTALL call, no need for a separate update.
UPDATE_PKG_CACHE=":" UPDATE_PKG_CACHE=":"
PKG_INSTALL=(${PKG_MANAGER} install -y) PKG_INSTALL=("${PKG_MANAGER}" install -y)
PKG_COUNT="${PKG_MANAGER} check-update | egrep '(.i686|.x86|.noarch|.arm|.src)' | wc -l" PKG_COUNT="${PKG_MANAGER} check-update | egrep '(.i686|.x86|.noarch|.arm|.src)' | wc -l"
INSTALLER_DEPS=(dialog git iproute newt procps-ng which chkconfig) INSTALLER_DEPS=(dialog git iproute newt procps-ng which chkconfig)
PIHOLE_DEPS=(bind-utils cronie curl findutils nmap-ncat sudo unzip wget libidn2 psmisc sqlite libcap) PIHOLE_DEPS=(bind-utils cronie curl findutils nmap-ncat sudo unzip wget libidn2 psmisc sqlite libcap)
@ -325,7 +325,7 @@ elif is_command rpm ; then
# The default php on CentOS 7.x is 5.4 which is EOL # The default php on CentOS 7.x is 5.4 which is EOL
# Check if the version of PHP available via installed repositories is >= to PHP 7 # Check if the version of PHP available via installed repositories is >= to PHP 7
AVAILABLE_PHP_VERSION=$(${PKG_MANAGER} info php | grep -i version | grep -o '[0-9]\+' | head -1) AVAILABLE_PHP_VERSION=$("${PKG_MANAGER}" info php | grep -i version | grep -o '[0-9]\+' | head -1)
if [[ $AVAILABLE_PHP_VERSION -ge $SUPPORTED_CENTOS_PHP_VERSION ]]; then if [[ $AVAILABLE_PHP_VERSION -ge $SUPPORTED_CENTOS_PHP_VERSION ]]; then
# Since PHP 7 is available by default, install via default PHP package names # Since PHP 7 is available by default, install via default PHP package names
: # do nothing as PHP is current : # do nothing as PHP is current
@ -335,7 +335,7 @@ elif is_command rpm ; then
rpm -q ${REMI_PKG} &> /dev/null || rc=$? rpm -q ${REMI_PKG} &> /dev/null || rc=$?
if [[ $rc -ne 0 ]]; then if [[ $rc -ne 0 ]]; then
# The PHP version available via default repositories is older than version 7 # The PHP version available via default repositories is older than version 7
if ! whiptail --defaultno --title "PHP 7 Update (recommended)" --yesno "PHP 7.x is recommended for both security and language features.\\nWould you like to install PHP7 via Remi's RPM repository?\\n\\nSee: https://rpms.remirepo.net for more information" ${r} ${c}; then if ! whiptail --defaultno --title "PHP 7 Update (recommended)" --yesno "PHP 7.x is recommended for both security and language features.\\nWould you like to install PHP7 via Remi's RPM repository?\\n\\nSee: https://rpms.remirepo.net for more information" "${r}" "${c}"; then
# User decided to NOT update PHP from REMI, attempt to install the default available PHP version # User decided to NOT update PHP from REMI, attempt to install the default available PHP version
printf " %b User opt-out of PHP 7 upgrade on CentOS. Deprecated PHP may be in use.\\n" "${INFO}" printf " %b User opt-out of PHP 7 upgrade on CentOS. Deprecated PHP may be in use.\\n" "${INFO}"
: # continue with unsupported php version : # continue with unsupported php version
@ -358,7 +358,7 @@ elif is_command rpm ; then
fi fi
else else
# Warn user of unsupported version of Fedora or CentOS # Warn user of unsupported version of Fedora or CentOS
if ! whiptail --defaultno --title "Unsupported RPM based distribution" --yesno "Would you like to continue installation on an unsupported RPM based distribution?\\n\\nPlease ensure the following packages have been installed manually:\\n\\n- lighttpd\\n- lighttpd-fastcgi\\n- PHP version 7+" ${r} ${c}; then if ! whiptail --defaultno --title "Unsupported RPM based distribution" --yesno "Would you like to continue installation on an unsupported RPM based distribution?\\n\\nPlease ensure the following packages have been installed manually:\\n\\n- lighttpd\\n- lighttpd-fastcgi\\n- PHP version 7+" "${r}" "${c}"; then
printf " %b Aborting installation due to unsupported RPM based distribution\\n" "${CROSS}" printf " %b Aborting installation due to unsupported RPM based distribution\\n" "${CROSS}"
exit # exit the installer exit # exit the installer
else else
@ -420,6 +420,9 @@ make_repo() {
fi fi
# Clone the repo and return the return code from this command # Clone the repo and return the return code from this command
git clone -q --depth 20 "${remoteRepo}" "${directory}" &> /dev/null || return $? git clone -q --depth 20 "${remoteRepo}" "${directory}" &> /dev/null || return $?
# Data in the repositories is public anyway so we can make it readable by everyone (+r to keep executable permission if already set by git)
chmod -R a+rX "${directory}"
# Show a colored message showing it's status # Show a colored message showing it's status
printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}" printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
# Always return 0? Not sure this is correct # Always return 0? Not sure this is correct
@ -453,6 +456,8 @@ update_repo() {
git pull --quiet &> /dev/null || return $? git pull --quiet &> /dev/null || return $?
# Show a completion message # Show a completion message
printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}" printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
# Data in the repositories is public anyway so we can make it readable by everyone (+r to keep executable permission if already set by git)
chmod -R a+rX "${directory}"
# Move back into the original directory # Move back into the original directory
cd "${curdir}" &> /dev/null || return 1 cd "${curdir}" &> /dev/null || return 1
return 0 return 0
@ -500,6 +505,8 @@ resetRepo() {
printf " %b %s..." "${INFO}" "${str}" printf " %b %s..." "${INFO}" "${str}"
# Use git to remove the local changes # Use git to remove the local changes
git reset --hard &> /dev/null || return $? git reset --hard &> /dev/null || return $?
# Data in the repositories is public anyway so we can make it readable by everyone (+r to keep executable permission if already set by git)
chmod -R a+rX "${directory}"
# And show the status # And show the status
printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}" printf "%b %b %s\\n" "${OVER}" "${TICK}" "${str}"
# Returning success anyway? # Returning success anyway?
@ -543,15 +550,15 @@ get_available_interfaces() {
# A function for displaying the dialogs the user sees when first running the installer # A function for displaying the dialogs the user sees when first running the installer
welcomeDialogs() { welcomeDialogs() {
# Display the welcome dialog using an appropriately sized window via the calculation conducted earlier in the script # Display the welcome dialog using an appropriately sized window via the calculation conducted earlier in the script
whiptail --msgbox --backtitle "Welcome" --title "Pi-hole automated installer" "\\n\\nThis installer will transform your device into a network-wide ad blocker!" ${r} ${c} whiptail --msgbox --backtitle "Welcome" --title "Pi-hole automated installer" "\\n\\nThis installer will transform your device into a network-wide ad blocker!" "${r}" "${c}"
# Request that users donate if they enjoy the software since we all work on it in our free time # Request that users donate if they enjoy the software since we all work on it in our free time
whiptail --msgbox --backtitle "Plea" --title "Free and open source" "\\n\\nThe Pi-hole is free, but powered by your donations: http://pi-hole.net/donate" ${r} ${c} whiptail --msgbox --backtitle "Plea" --title "Free and open source" "\\n\\nThe Pi-hole is free, but powered by your donations: http://pi-hole.net/donate" "${r}" "${c}"
# Explain the need for a static address # Explain the need for a static address
whiptail --msgbox --backtitle "Initiating network interface" --title "Static IP Needed" "\\n\\nThe Pi-hole is a SERVER so it needs a STATIC IP ADDRESS to function properly. whiptail --msgbox --backtitle "Initiating network interface" --title "Static IP Needed" "\\n\\nThe Pi-hole is a SERVER so it needs a STATIC IP ADDRESS to function properly.
In the next section, you can choose to use your current network settings (DHCP) or to manually edit them." ${r} ${c} In the next section, you can choose to use your current network settings (DHCP) or to manually edit them." "${r}" "${c}"
} }
# We need to make sure there is enough space before installing, so there is a function to check this # We need to make sure there is enough space before installing, so there is a function to check this
@ -638,7 +645,7 @@ chooseInterface() {
# Feed the available interfaces into this while loop # Feed the available interfaces into this while loop
done <<< "${availableInterfaces}" done <<< "${availableInterfaces}"
# The whiptail command that will be run, stored in a variable # The whiptail command that will be run, stored in a variable
chooseInterfaceCmd=(whiptail --separate-output --radiolist "Choose An Interface (press space to select)" ${r} ${c} ${interfaceCount}) chooseInterfaceCmd=(whiptail --separate-output --radiolist "Choose An Interface (press space to select)" "${r}" "${c}" "${interfaceCount}")
# Now run the command using the interfaces saved into the array # Now run the command using the interfaces saved into the array
chooseInterfaceOptions=$("${chooseInterfaceCmd[@]}" "${interfacesArray[@]}" 2>&1 >/dev/tty) || \ chooseInterfaceOptions=$("${chooseInterfaceCmd[@]}" "${interfacesArray[@]}" 2>&1 >/dev/tty) || \
# If the user chooses Cancel, exit # If the user chooses Cancel, exit
@ -719,7 +726,7 @@ useIPv6dialog() {
# If the IPV6_ADDRESS contains a value # If the IPV6_ADDRESS contains a value
if [[ ! -z "${IPV6_ADDRESS}" ]]; then if [[ ! -z "${IPV6_ADDRESS}" ]]; then
# Display that IPv6 is supported and will be used # Display that IPv6 is supported and will be used
whiptail --msgbox --backtitle "IPv6..." --title "IPv6 Supported" "$IPV6_ADDRESS will be used to block ads." ${r} ${c} whiptail --msgbox --backtitle "IPv6..." --title "IPv6 Supported" "$IPV6_ADDRESS will be used to block ads." "${r}" "${c}"
fi fi
} }
@ -729,7 +736,7 @@ use4andor6() {
local useIPv4 local useIPv4
local useIPv6 local useIPv6
# Let use select IPv4 and/or IPv6 via a checklist # Let use select IPv4 and/or IPv6 via a checklist
cmd=(whiptail --separate-output --checklist "Select Protocols (press space to select)" ${r} ${c} 2) cmd=(whiptail --separate-output --checklist "Select Protocols (press space to select)" "${r}" "${c}" 2)
# In an array, show the options available: # In an array, show the options available:
# IPv4 (on by default) # IPv4 (on by default)
options=(IPv4 "Block ads over IPv4" on options=(IPv4 "Block ads over IPv4" on
@ -778,11 +785,11 @@ getStaticIPv4Settings() {
# This is useful for users that are using DHCP reservations; then we can just use the information gathered via our functions # This is useful for users that are using DHCP reservations; then we can just use the information gathered via our functions
if whiptail --backtitle "Calibrating network interface" --title "Static IP Address" --yesno "Do you want to use your current network settings as a static address? if whiptail --backtitle "Calibrating network interface" --title "Static IP Address" --yesno "Do you want to use your current network settings as a static address?
IP address: ${IPV4_ADDRESS} IP address: ${IPV4_ADDRESS}
Gateway: ${IPv4gw}" ${r} ${c}; then Gateway: ${IPv4gw}" "${r}" "${c}"; then
# If they choose yes, let the user know that the IP address will not be available via DHCP and may cause a conflict. # If they choose yes, let the user know that the IP address will not be available via DHCP and may cause a conflict.
whiptail --msgbox --backtitle "IP information" --title "FYI: IP Conflict" "It is possible your router could still try to assign this IP to a device, which would cause a conflict. But in most cases the router is smart enough to not do that. whiptail --msgbox --backtitle "IP information" --title "FYI: IP Conflict" "It is possible your router could still try to assign this IP to a device, which would cause a conflict. But in most cases the router is smart enough to not do that.
If you are worried, either manually set the address, or modify the DHCP reservation pool so it does not include the IP you want. If you are worried, either manually set the address, or modify the DHCP reservation pool so it does not include the IP you want.
It is also possible to use a DHCP reservation, but if you are going to do that, you might as well set a static address." ${r} ${c} It is also possible to use a DHCP reservation, but if you are going to do that, you might as well set a static address." "${r}" "${c}"
# Nothing else to do since the variables are already set above # Nothing else to do since the variables are already set above
else else
# Otherwise, we need to ask the user to input their desired settings. # Otherwise, we need to ask the user to input their desired settings.
@ -791,13 +798,13 @@ It is also possible to use a DHCP reservation, but if you are going to do that,
until [[ "${ipSettingsCorrect}" = True ]]; do until [[ "${ipSettingsCorrect}" = True ]]; do
# Ask for the IPv4 address # Ask for the IPv4 address
IPV4_ADDRESS=$(whiptail --backtitle "Calibrating network interface" --title "IPv4 address" --inputbox "Enter your desired IPv4 address" ${r} ${c} "${IPV4_ADDRESS}" 3>&1 1>&2 2>&3) || \ IPV4_ADDRESS=$(whiptail --backtitle "Calibrating network interface" --title "IPv4 address" --inputbox "Enter your desired IPv4 address" "${r}" "${c}" "${IPV4_ADDRESS}" 3>&1 1>&2 2>&3) || \
# Cancelling IPv4 settings window # Cancelling IPv4 settings window
{ ipSettingsCorrect=False; echo -e " ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; } { ipSettingsCorrect=False; echo -e " ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
printf " %b Your static IPv4 address: %s\\n" "${INFO}" "${IPV4_ADDRESS}" printf " %b Your static IPv4 address: %s\\n" "${INFO}" "${IPV4_ADDRESS}"
# Ask for the gateway # Ask for the gateway
IPv4gw=$(whiptail --backtitle "Calibrating network interface" --title "IPv4 gateway (router)" --inputbox "Enter your desired IPv4 default gateway" ${r} ${c} "${IPv4gw}" 3>&1 1>&2 2>&3) || \ IPv4gw=$(whiptail --backtitle "Calibrating network interface" --title "IPv4 gateway (router)" --inputbox "Enter your desired IPv4 default gateway" "${r}" "${c}" "${IPv4gw}" 3>&1 1>&2 2>&3) || \
# Cancelling gateway settings window # Cancelling gateway settings window
{ ipSettingsCorrect=False; echo -e " ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; } { ipSettingsCorrect=False; echo -e " ${COL_LIGHT_RED}Cancel was selected, exiting installer${COL_NC}"; exit 1; }
printf " %b Your static IPv4 gateway: %s\\n" "${INFO}" "${IPv4gw}" printf " %b Your static IPv4 gateway: %s\\n" "${INFO}" "${IPv4gw}"
@ -805,7 +812,7 @@ It is also possible to use a DHCP reservation, but if you are going to do that,
# Give the user a chance to review their settings before moving on # Give the user a chance to review their settings before moving on
if whiptail --backtitle "Calibrating network interface" --title "Static IP Address" --yesno "Are these settings correct? if whiptail --backtitle "Calibrating network interface" --title "Static IP Address" --yesno "Are these settings correct?
IP address: ${IPV4_ADDRESS} IP address: ${IPV4_ADDRESS}
Gateway: ${IPv4gw}" ${r} ${c}; then Gateway: ${IPv4gw}" "${r}" "${c}"; then
# After that's done, the loop ends and we move on # After that's done, the loop ends and we move on
ipSettingsCorrect=True ipSettingsCorrect=True
else else
@ -853,7 +860,7 @@ setIFCFG() {
# Put the IP in variables without the CIDR notation # Put the IP in variables without the CIDR notation
printf -v CIDR "%s" "${IPV4_ADDRESS##*/}" printf -v CIDR "%s" "${IPV4_ADDRESS##*/}"
# Backup existing interface configuration: # Backup existing interface configuration:
cp "${IFCFG_FILE}" "${IFCFG_FILE}".pihole.orig cp -p "${IFCFG_FILE}" "${IFCFG_FILE}".pihole.orig
# Build Interface configuration file using the GLOBAL variables we have # Build Interface configuration file using the GLOBAL variables we have
{ {
echo "# Configured via Pi-hole installer" echo "# Configured via Pi-hole installer"
@ -867,6 +874,8 @@ setIFCFG() {
echo "DNS2=$PIHOLE_DNS_2" echo "DNS2=$PIHOLE_DNS_2"
echo "USERCTL=no" echo "USERCTL=no"
}> "${IFCFG_FILE}" }> "${IFCFG_FILE}"
chmod 644 "${IFCFG_FILE}"
chown root:root "${IFCFG_FILE}"
# Use ip to immediately set the new address # Use ip to immediately set the new address
ip addr replace dev "${PIHOLE_INTERFACE}" "${IPV4_ADDRESS}" ip addr replace dev "${PIHOLE_INTERFACE}" "${IPV4_ADDRESS}"
# If NetworkMangler command line interface exists and ready to mangle, # If NetworkMangler command line interface exists and ready to mangle,
@ -931,7 +940,7 @@ valid_ip() {
# and set the new one to a dot (period) # and set the new one to a dot (period)
IFS='.' IFS='.'
# Put the IP into an array # Put the IP into an array
ip=(${ip}) read -r -a ip <<< "${ip}"
# Restore the IFS to what it was # Restore the IFS to what it was
IFS=${OIFS} IFS=${OIFS}
## Evaluate each octet by checking if it's less than or equal to 255 (the max for each octet) ## Evaluate each octet by checking if it's less than or equal to 255 (the max for each octet)
@ -941,7 +950,7 @@ valid_ip() {
stat=$? stat=$?
fi fi
# Return the exit code # Return the exit code
return ${stat} return "${stat}"
} }
# A function to choose the upstream DNS provider(s) # A function to choose the upstream DNS provider(s)
@ -971,7 +980,7 @@ setDNS() {
# Restore the IFS to what it was # Restore the IFS to what it was
IFS=${OIFS} IFS=${OIFS}
# In a whiptail dialog, show the options # In a whiptail dialog, show the options
DNSchoices=$(whiptail --separate-output --menu "Select Upstream DNS Provider. To use your own, select Custom." ${r} ${c} 7 \ DNSchoices=$(whiptail --separate-output --menu "Select Upstream DNS Provider. To use your own, select Custom." "${r}" "${c}" 7 \
"${DNSChooseOptions[@]}" 2>&1 >/dev/tty) || \ "${DNSChooseOptions[@]}" 2>&1 >/dev/tty) || \
# exit if Cancel is selected # exit if Cancel is selected
{ printf " %bCancel was selected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; } { printf " %bCancel was selected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; }
@ -1001,7 +1010,7 @@ setDNS() {
fi fi
# Dialog for the user to enter custom upstream servers # Dialog for the user to enter custom upstream servers
piholeDNS=$(whiptail --backtitle "Specify Upstream DNS Provider(s)" --inputbox "Enter your desired upstream DNS provider(s), separated by a comma.\\n\\nFor example '8.8.8.8, 8.8.4.4'" ${r} ${c} "${prePopulate}" 3>&1 1>&2 2>&3) || \ piholeDNS=$(whiptail --backtitle "Specify Upstream DNS Provider(s)" --inputbox "Enter your desired upstream DNS provider(s), separated by a comma.\\n\\nFor example '8.8.8.8, 8.8.4.4'" "${r}" "${c}" "${prePopulate}" 3>&1 1>&2 2>&3) || \
{ printf " %bCancel was selected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; } { printf " %bCancel was selected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; }
# Clean user input and replace whitespace with comma. # Clean user input and replace whitespace with comma.
piholeDNS=$(sed 's/[, \t]\+/,/g' <<< "${piholeDNS}") piholeDNS=$(sed 's/[, \t]\+/,/g' <<< "${piholeDNS}")
@ -1034,7 +1043,7 @@ setDNS() {
# Otherwise, # Otherwise,
else else
# Show the settings # Show the settings
if (whiptail --backtitle "Specify Upstream DNS Provider(s)" --title "Upstream DNS Provider(s)" --yesno "Are these settings correct?\\n DNS Server 1: $PIHOLE_DNS_1\\n DNS Server 2: ${PIHOLE_DNS_2}" ${r} ${c}); then if (whiptail --backtitle "Specify Upstream DNS Provider(s)" --title "Upstream DNS Provider(s)" --yesno "Are these settings correct?\\n DNS Server 1: $PIHOLE_DNS_1\\n DNS Server 2: ${PIHOLE_DNS_2}" "${r}" "${c}"); then
# and break from the loop since the servers are valid # and break from the loop since the servers are valid
DNSSettingsCorrect=True DNSSettingsCorrect=True
# Otherwise, # Otherwise,
@ -1125,7 +1134,7 @@ setAdminFlag() {
local WebChoices local WebChoices
# Similar to the logging function, ask what the user wants # Similar to the logging function, ask what the user wants
WebToggleCommand=(whiptail --separate-output --radiolist "Do you wish to install the web admin interface?" ${r} ${c} 6) WebToggleCommand=(whiptail --separate-output --radiolist "Do you wish to install the web admin interface?" "${r}" "${c}" 6)
# with the default being enabled # with the default being enabled
WebChooseOptions=("On (Recommended)" "" on WebChooseOptions=("On (Recommended)" "" on
Off "" off) Off "" off)
@ -1190,6 +1199,7 @@ chooseBlocklists() {
do do
appendToListsFile "${choice}" appendToListsFile "${choice}"
done done
chmod 644 "${adlistFile}"
} }
# Accept a string parameter, it must be one of the default lists # Accept a string parameter, it must be one of the default lists
@ -1228,6 +1238,7 @@ version_check_dnsmasq() {
local dnsmasq_conf="/etc/dnsmasq.conf" local dnsmasq_conf="/etc/dnsmasq.conf"
local dnsmasq_conf_orig="/etc/dnsmasq.conf.orig" local dnsmasq_conf_orig="/etc/dnsmasq.conf.orig"
local dnsmasq_pihole_id_string="addn-hosts=/etc/pihole/gravity.list" local dnsmasq_pihole_id_string="addn-hosts=/etc/pihole/gravity.list"
local dnsmasq_pihole_id_string2="# Dnsmasq config for Pi-hole's FTLDNS"
local dnsmasq_original_config="${PI_HOLE_LOCAL_REPO}/advanced/dnsmasq.conf.original" local dnsmasq_original_config="${PI_HOLE_LOCAL_REPO}/advanced/dnsmasq.conf.original"
local dnsmasq_pihole_01_snippet="${PI_HOLE_LOCAL_REPO}/advanced/01-pihole.conf" local dnsmasq_pihole_01_snippet="${PI_HOLE_LOCAL_REPO}/advanced/01-pihole.conf"
local dnsmasq_pihole_01_location="/etc/dnsmasq.d/01-pihole.conf" local dnsmasq_pihole_01_location="/etc/dnsmasq.d/01-pihole.conf"
@ -1235,16 +1246,17 @@ version_check_dnsmasq() {
# If the dnsmasq config file exists # If the dnsmasq config file exists
if [[ -f "${dnsmasq_conf}" ]]; then if [[ -f "${dnsmasq_conf}" ]]; then
printf " %b Existing dnsmasq.conf found..." "${INFO}" printf " %b Existing dnsmasq.conf found..." "${INFO}"
# If gravity.list is found within this file, we presume it's from older versions on Pi-hole, # If a specific string is found within this file, we presume it's from older versions on Pi-hole,
if grep -q ${dnsmasq_pihole_id_string} ${dnsmasq_conf}; then if grep -q "${dnsmasq_pihole_id_string}" "${dnsmasq_conf}" ||
grep -q "${dnsmasq_pihole_id_string2}" "${dnsmasq_conf}"; then
printf " it is from a previous Pi-hole install.\\n" printf " it is from a previous Pi-hole install.\\n"
printf " %b Backing up dnsmasq.conf to dnsmasq.conf.orig..." "${INFO}" printf " %b Backing up dnsmasq.conf to dnsmasq.conf.orig..." "${INFO}"
# so backup the original file # so backup the original file
mv -f ${dnsmasq_conf} ${dnsmasq_conf_orig} mv -f "${dnsmasq_conf}" "${dnsmasq_conf_orig}"
printf "%b %b Backing up dnsmasq.conf to dnsmasq.conf.orig...\\n" "${OVER}" "${TICK}" printf "%b %b Backing up dnsmasq.conf to dnsmasq.conf.orig...\\n" "${OVER}" "${TICK}"
printf " %b Restoring default dnsmasq.conf..." "${INFO}" printf " %b Restoring default dnsmasq.conf..." "${INFO}"
# and replace it with the default # and replace it with the default
cp ${dnsmasq_original_config} ${dnsmasq_conf} install -D -m 644 -T "${dnsmasq_original_config}" "${dnsmasq_conf}"
printf "%b %b Restoring default dnsmasq.conf...\\n" "${OVER}" "${TICK}" printf "%b %b Restoring default dnsmasq.conf...\\n" "${OVER}" "${TICK}"
# Otherwise, # Otherwise,
else else
@ -1255,47 +1267,47 @@ version_check_dnsmasq() {
# If a file cannot be found, # If a file cannot be found,
printf " %b No dnsmasq.conf found... restoring default dnsmasq.conf..." "${INFO}" printf " %b No dnsmasq.conf found... restoring default dnsmasq.conf..." "${INFO}"
# restore the default one # restore the default one
cp ${dnsmasq_original_config} ${dnsmasq_conf} install -D -m 644 -T "${dnsmasq_original_config}" "${dnsmasq_conf}"
printf "%b %b No dnsmasq.conf found... restoring default dnsmasq.conf...\\n" "${OVER}" "${TICK}" printf "%b %b No dnsmasq.conf found... restoring default dnsmasq.conf...\\n" "${OVER}" "${TICK}"
fi fi
printf " %b Copying 01-pihole.conf to /etc/dnsmasq.d/01-pihole.conf..." "${INFO}" printf " %b Copying 01-pihole.conf to /etc/dnsmasq.d/01-pihole.conf..." "${INFO}"
# Check to see if dnsmasq directory exists (it may not due to being a fresh install and dnsmasq no longer being a dependency) # Check to see if dnsmasq directory exists (it may not due to being a fresh install and dnsmasq no longer being a dependency)
if [[ ! -d "/etc/dnsmasq.d" ]];then if [[ ! -d "/etc/dnsmasq.d" ]];then
mkdir "/etc/dnsmasq.d" install -d -m 755 "/etc/dnsmasq.d"
fi fi
# Copy the new Pi-hole DNS config file into the dnsmasq.d directory # Copy the new Pi-hole DNS config file into the dnsmasq.d directory
cp ${dnsmasq_pihole_01_snippet} ${dnsmasq_pihole_01_location} install -D -m 644 -T "${dnsmasq_pihole_01_snippet}" "${dnsmasq_pihole_01_location}"
printf "%b %b Copying 01-pihole.conf to /etc/dnsmasq.d/01-pihole.conf\\n" "${OVER}" "${TICK}" printf "%b %b Copying 01-pihole.conf to /etc/dnsmasq.d/01-pihole.conf\\n" "${OVER}" "${TICK}"
# Replace our placeholder values with the GLOBAL DNS variables that we populated earlier # Replace our placeholder values with the GLOBAL DNS variables that we populated earlier
# First, swap in the interface to listen on # First, swap in the interface to listen on
sed -i "s/@INT@/$PIHOLE_INTERFACE/" ${dnsmasq_pihole_01_location} sed -i "s/@INT@/$PIHOLE_INTERFACE/" "${dnsmasq_pihole_01_location}"
if [[ "${PIHOLE_DNS_1}" != "" ]]; then if [[ "${PIHOLE_DNS_1}" != "" ]]; then
# Then swap in the primary DNS server # Then swap in the primary DNS server
sed -i "s/@DNS1@/$PIHOLE_DNS_1/" ${dnsmasq_pihole_01_location} sed -i "s/@DNS1@/$PIHOLE_DNS_1/" "${dnsmasq_pihole_01_location}"
else else
# #
sed -i '/^server=@DNS1@/d' ${dnsmasq_pihole_01_location} sed -i '/^server=@DNS1@/d' "${dnsmasq_pihole_01_location}"
fi fi
if [[ "${PIHOLE_DNS_2}" != "" ]]; then if [[ "${PIHOLE_DNS_2}" != "" ]]; then
# Then swap in the primary DNS server # Then swap in the primary DNS server
sed -i "s/@DNS2@/$PIHOLE_DNS_2/" ${dnsmasq_pihole_01_location} sed -i "s/@DNS2@/$PIHOLE_DNS_2/" "${dnsmasq_pihole_01_location}"
else else
# #
sed -i '/^server=@DNS2@/d' ${dnsmasq_pihole_01_location} sed -i '/^server=@DNS2@/d' "${dnsmasq_pihole_01_location}"
fi fi
# #
sed -i 's/^#conf-dir=\/etc\/dnsmasq.d$/conf-dir=\/etc\/dnsmasq.d/' ${dnsmasq_conf} sed -i 's/^#conf-dir=\/etc\/dnsmasq.d$/conf-dir=\/etc\/dnsmasq.d/' "${dnsmasq_conf}"
# If the user does not want to enable logging, # If the user does not want to enable logging,
if [[ "${QUERY_LOGGING}" == false ]] ; then if [[ "${QUERY_LOGGING}" == false ]] ; then
# Disable it by commenting out the directive in the DNS config file # Disable it by commenting out the directive in the DNS config file
sed -i 's/^log-queries/#log-queries/' ${dnsmasq_pihole_01_location} sed -i 's/^log-queries/#log-queries/' "${dnsmasq_pihole_01_location}"
# Otherwise, # Otherwise,
else else
# enable it by uncommenting the directive in the DNS config file # enable it by uncommenting the directive in the DNS config file
sed -i 's/^#log-queries/log-queries/' ${dnsmasq_pihole_01_location} sed -i 's/^#log-queries/log-queries/' "${dnsmasq_pihole_01_location}"
fi fi
} }
@ -1363,6 +1375,7 @@ installConfigs() {
# Format: Name;Primary IPv4;Secondary IPv4;Primary IPv6;Secondary IPv6 # Format: Name;Primary IPv4;Secondary IPv4;Primary IPv6;Secondary IPv6
# Some values may be empty (for example: DNS servers without IPv6 support) # Some values may be empty (for example: DNS servers without IPv6 support)
echo "${DNS_SERVERS}" > "${PI_HOLE_CONFIG_DIR}/dns-servers.conf" echo "${DNS_SERVERS}" > "${PI_HOLE_CONFIG_DIR}/dns-servers.conf"
chmod 644 "${PI_HOLE_CONFIG_DIR}/dns-servers.conf"
# Install empty file if it does not exist # Install empty file if it does not exist
if [[ ! -r "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf" ]]; then if [[ ! -r "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf" ]]; then
@ -1381,19 +1394,18 @@ installConfigs() {
if [[ "${INSTALL_WEB_SERVER}" == true ]]; then if [[ "${INSTALL_WEB_SERVER}" == true ]]; then
# and if the Web server conf directory does not exist, # and if the Web server conf directory does not exist,
if [[ ! -d "/etc/lighttpd" ]]; then if [[ ! -d "/etc/lighttpd" ]]; then
# make it # make it and set the owners
mkdir /etc/lighttpd install -d -m 755 -o "${USER}" -g root /etc/lighttpd
# and set the owners
chown "${USER}":root /etc/lighttpd
# Otherwise, if the config file already exists # Otherwise, if the config file already exists
elif [[ -f "/etc/lighttpd/lighttpd.conf" ]]; then elif [[ -f "/etc/lighttpd/lighttpd.conf" ]]; then
# back up the original # back up the original
mv /etc/lighttpd/lighttpd.conf /etc/lighttpd/lighttpd.conf.orig mv /etc/lighttpd/lighttpd.conf /etc/lighttpd/lighttpd.conf.orig
fi fi
# and copy in the config file Pi-hole needs # and copy in the config file Pi-hole needs
cp ${PI_HOLE_LOCAL_REPO}/advanced/${LIGHTTPD_CFG} /etc/lighttpd/lighttpd.conf install -D -m 644 -T ${PI_HOLE_LOCAL_REPO}/advanced/${LIGHTTPD_CFG} /etc/lighttpd/lighttpd.conf
# Make sure the external.conf file exists, as lighttpd v1.4.50 crashes without it # Make sure the external.conf file exists, as lighttpd v1.4.50 crashes without it
touch /etc/lighttpd/external.conf touch /etc/lighttpd/external.conf
chmod 644 /etc/lighttpd/external.conf
# if there is a custom block page in the html/pihole directory, replace 404 handler in lighttpd config # if there is a custom block page in the html/pihole directory, replace 404 handler in lighttpd config
if [[ -f "${PI_HOLE_BLOCKPAGE_DIR}/custom.php" ]]; then if [[ -f "${PI_HOLE_BLOCKPAGE_DIR}/custom.php" ]]; then
sed -i 's/^\(server\.error-handler-404\s*=\s*\).*$/\1"pihole\/custom\.php"/' /etc/lighttpd/lighttpd.conf sed -i 's/^\(server\.error-handler-404\s*=\s*\).*$/\1"pihole\/custom\.php"/' /etc/lighttpd/lighttpd.conf
@ -1424,16 +1436,16 @@ install_manpage() {
fi fi
if [[ ! -d "/usr/local/share/man/man8" ]]; then if [[ ! -d "/usr/local/share/man/man8" ]]; then
# if not present, create man8 directory # if not present, create man8 directory
mkdir /usr/local/share/man/man8 install -d -m 755 /usr/local/share/man/man8
fi fi
if [[ ! -d "/usr/local/share/man/man5" ]]; then if [[ ! -d "/usr/local/share/man/man5" ]]; then
# if not present, create man8 directory # if not present, create man5 directory
mkdir /usr/local/share/man/man5 install -d -m 755 /usr/local/share/man/man5
fi fi
# Testing complete, copy the files & update the man db # Testing complete, copy the files & update the man db
cp ${PI_HOLE_LOCAL_REPO}/manpages/pihole.8 /usr/local/share/man/man8/pihole.8 install -D -m 644 -T ${PI_HOLE_LOCAL_REPO}/manpages/pihole.8 /usr/local/share/man/man8/pihole.8
cp ${PI_HOLE_LOCAL_REPO}/manpages/pihole-FTL.8 /usr/local/share/man/man8/pihole-FTL.8 install -D -m 644 -T ${PI_HOLE_LOCAL_REPO}/manpages/pihole-FTL.8 /usr/local/share/man/man8/pihole-FTL.8
cp ${PI_HOLE_LOCAL_REPO}/manpages/pihole-FTL.conf.5 /usr/local/share/man/man5/pihole-FTL.conf.5 install -D -m 644 -T ${PI_HOLE_LOCAL_REPO}/manpages/pihole-FTL.conf.5 /usr/local/share/man/man5/pihole-FTL.conf.5
if mandb -q &>/dev/null; then if mandb -q &>/dev/null; then
# Updated successfully # Updated successfully
printf "%b %b man pages installed and database updated\\n" "${OVER}" "${TICK}" printf "%b %b man pages installed and database updated\\n" "${OVER}" "${TICK}"
@ -1638,7 +1650,7 @@ install_dependent_packages() {
# Install Fedora/CentOS packages # Install Fedora/CentOS packages
for i in "$@"; do for i in "$@"; do
printf " %b Checking for %s..." "${INFO}" "${i}" printf " %b Checking for %s..." "${INFO}" "${i}"
if ${PKG_MANAGER} -q list installed "${i}" &> /dev/null; then if "${PKG_MANAGER}" -q list installed "${i}" &> /dev/null; then
printf "%b %b Checking for %s" "${OVER}" "${TICK}" "${i}" printf "%b %b Checking for %s" "${OVER}" "${TICK}" "${i}"
else else
printf "%b %b Checking for %s (will be installed)" "${OVER}" "${INFO}" "${i}" printf "%b %b Checking for %s (will be installed)" "${OVER}" "${INFO}" "${i}"
@ -1662,7 +1674,7 @@ installPiholeWeb() {
# Install the directory # Install the directory
install -d -m 0755 ${PI_HOLE_BLOCKPAGE_DIR} install -d -m 0755 ${PI_HOLE_BLOCKPAGE_DIR}
# and the blockpage # and the blockpage
install -D ${PI_HOLE_LOCAL_REPO}/advanced/{index,blockingpage}.* ${PI_HOLE_BLOCKPAGE_DIR}/ install -D -m 644 ${PI_HOLE_LOCAL_REPO}/advanced/{index,blockingpage}.* ${PI_HOLE_BLOCKPAGE_DIR}/
# Remove superseded file # Remove superseded file
if [[ -e "${PI_HOLE_BLOCKPAGE_DIR}/index.js" ]]; then if [[ -e "${PI_HOLE_BLOCKPAGE_DIR}/index.js" ]]; then
@ -1681,7 +1693,7 @@ installPiholeWeb() {
# Otherwise, # Otherwise,
else else
# don't do anything # don't do anything
printf "%b %b %s\\n" "${OVER}" "${CROSS}" "${str}" printf "%b %b %s\\n" "${OVER}" "${INFO}" "${str}"
printf " No default index.lighttpd.html file found... not backing up\\n" printf " No default index.lighttpd.html file found... not backing up\\n"
fi fi
@ -1689,7 +1701,7 @@ installPiholeWeb() {
local str="Installing sudoer file" local str="Installing sudoer file"
printf "\\n %b %s..." "${INFO}" "${str}" printf "\\n %b %s..." "${INFO}" "${str}"
# Make the .d directory if it doesn't exist # Make the .d directory if it doesn't exist
mkdir -p /etc/sudoers.d/ install -d -m 755 /etc/sudoers.d/
# and copy in the pihole sudoers file # and copy in the pihole sudoers file
install -m 0640 ${PI_HOLE_LOCAL_REPO}/advanced/Templates/pihole.sudo /etc/sudoers.d/pihole install -m 0640 ${PI_HOLE_LOCAL_REPO}/advanced/Templates/pihole.sudo /etc/sudoers.d/pihole
# Add lighttpd user (OS dependent) to sudoers file # Add lighttpd user (OS dependent) to sudoers file
@ -1712,7 +1724,8 @@ installCron() {
local str="Installing latest Cron script" local str="Installing latest Cron script"
printf "\\n %b %s..." "${INFO}" "${str}" printf "\\n %b %s..." "${INFO}" "${str}"
# Copy the cron file over from the local repo # Copy the cron file over from the local repo
cp ${PI_HOLE_LOCAL_REPO}/advanced/Templates/pihole.cron /etc/cron.d/pihole # File must not be world or group writeable and must be owned by root
install -D -m 644 -T -o root -g root ${PI_HOLE_LOCAL_REPO}/advanced/Templates/pihole.cron /etc/cron.d/pihole
# Randomize gravity update time # Randomize gravity update time
sed -i "s/59 1 /$((1 + RANDOM % 58)) $((3 + RANDOM % 2))/" /etc/cron.d/pihole sed -i "s/59 1 /$((1 + RANDOM % 58)) $((3 + RANDOM % 2))/" /etc/cron.d/pihole
# Randomize update checker time # Randomize update checker time
@ -1755,7 +1768,7 @@ configureFirewall() {
# If a firewall is running, # If a firewall is running,
if firewall-cmd --state &> /dev/null; then if firewall-cmd --state &> /dev/null; then
# ask if the user wants to install Pi-hole's default firewall rules # ask if the user wants to install Pi-hole's default firewall rules
whiptail --title "Firewall in use" --yesno "We have detected a running firewall\\n\\nPi-hole currently requires HTTP and DNS port access.\\n\\n\\n\\nInstall Pi-hole default firewall rules?" ${r} ${c} || \ whiptail --title "Firewall in use" --yesno "We have detected a running firewall\\n\\nPi-hole currently requires HTTP and DNS port access.\\n\\n\\n\\nInstall Pi-hole default firewall rules?" "${r}" "${c}" || \
{ printf " %b Not installing firewall rulesets.\\n" "${INFO}"; return 0; } { printf " %b Not installing firewall rulesets.\\n" "${INFO}"; return 0; }
printf " %b Configuring FirewallD for httpd and pihole-FTL\\n" "${TICK}" printf " %b Configuring FirewallD for httpd and pihole-FTL\\n" "${TICK}"
# Allow HTTP and DNS traffic # Allow HTTP and DNS traffic
@ -1768,7 +1781,7 @@ configureFirewall() {
# If chain Policy is not ACCEPT or last Rule is not ACCEPT # If chain Policy is not ACCEPT or last Rule is not ACCEPT
# then check and insert our Rules above the DROP/REJECT Rule. # then check and insert our Rules above the DROP/REJECT Rule.
if iptables -S INPUT | head -n1 | grep -qv '^-P.*ACCEPT$' || iptables -S INPUT | tail -n1 | grep -qv '^-\(A\|P\).*ACCEPT$'; then if iptables -S INPUT | head -n1 | grep -qv '^-P.*ACCEPT$' || iptables -S INPUT | tail -n1 | grep -qv '^-\(A\|P\).*ACCEPT$'; then
whiptail --title "Firewall in use" --yesno "We have detected a running firewall\\n\\nPi-hole currently requires HTTP and DNS port access.\\n\\n\\n\\nInstall Pi-hole default firewall rules?" ${r} ${c} || \ whiptail --title "Firewall in use" --yesno "We have detected a running firewall\\n\\nPi-hole currently requires HTTP and DNS port access.\\n\\n\\n\\nInstall Pi-hole default firewall rules?" "${r}" "${c}" || \
{ printf " %b Not installing firewall rulesets.\\n" "${INFO}"; return 0; } { printf " %b Not installing firewall rulesets.\\n" "${INFO}"; return 0; }
printf " %b Installing new IPTables firewall rulesets\\n" "${TICK}" printf " %b Installing new IPTables firewall rulesets\\n" "${TICK}"
# Check chain first, otherwise a new rule will duplicate old ones # Check chain first, otherwise a new rule will duplicate old ones
@ -1820,6 +1833,7 @@ finalExports() {
echo "INSTALL_WEB_INTERFACE=${INSTALL_WEB_INTERFACE}" echo "INSTALL_WEB_INTERFACE=${INSTALL_WEB_INTERFACE}"
echo "LIGHTTPD_ENABLED=${LIGHTTPD_ENABLED}" echo "LIGHTTPD_ENABLED=${LIGHTTPD_ENABLED}"
}>> "${setupVars}" }>> "${setupVars}"
chmod 644 "${setupVars}"
# Set the privacy level # Set the privacy level
sed -i '/PRIVACYLEVEL/d' "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf" sed -i '/PRIVACYLEVEL/d' "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf"
@ -1842,7 +1856,7 @@ installLogrotate() {
local str="Installing latest logrotate script" local str="Installing latest logrotate script"
printf "\\n %b %s..." "${INFO}" "${str}" printf "\\n %b %s..." "${INFO}" "${str}"
# Copy the file over from the local repo # Copy the file over from the local repo
cp ${PI_HOLE_LOCAL_REPO}/advanced/Templates/logrotate /etc/pihole/logrotate install -D -m 644 -T ${PI_HOLE_LOCAL_REPO}/advanced/Templates/logrotate /etc/pihole/logrotate
# Different operating systems have different user / group # Different operating systems have different user / group
# settings for logrotate that makes it impossible to create # settings for logrotate that makes it impossible to create
# a static logrotate file that will work with e.g. # a static logrotate file that will work with e.g.
@ -1861,29 +1875,26 @@ installLogrotate() {
# At some point in the future this list can be pruned, for now we'll need it to ensure updates don't break. # At some point in the future this list can be pruned, for now we'll need it to ensure updates don't break.
# Refactoring of install script has changed the name of a couple of variables. Sort them out here. # Refactoring of install script has changed the name of a couple of variables. Sort them out here.
accountForRefactor() { accountForRefactor() {
sed -i 's/piholeInterface/PIHOLE_INTERFACE/g' ${setupVars} sed -i 's/piholeInterface/PIHOLE_INTERFACE/g' "${setupVars}"
sed -i 's/IPv4_address/IPV4_ADDRESS/g' ${setupVars} sed -i 's/IPv4_address/IPV4_ADDRESS/g' "${setupVars}"
sed -i 's/IPv4addr/IPV4_ADDRESS/g' ${setupVars} sed -i 's/IPv4addr/IPV4_ADDRESS/g' "${setupVars}"
sed -i 's/IPv6_address/IPV6_ADDRESS/g' ${setupVars} sed -i 's/IPv6_address/IPV6_ADDRESS/g' "${setupVars}"
sed -i 's/piholeIPv6/IPV6_ADDRESS/g' ${setupVars} sed -i 's/piholeIPv6/IPV6_ADDRESS/g' "${setupVars}"
sed -i 's/piholeDNS1/PIHOLE_DNS_1/g' ${setupVars} sed -i 's/piholeDNS1/PIHOLE_DNS_1/g' "${setupVars}"
sed -i 's/piholeDNS2/PIHOLE_DNS_2/g' ${setupVars} sed -i 's/piholeDNS2/PIHOLE_DNS_2/g' "${setupVars}"
sed -i 's/^INSTALL_WEB=/INSTALL_WEB_INTERFACE=/' ${setupVars} sed -i 's/^INSTALL_WEB=/INSTALL_WEB_INTERFACE=/' "${setupVars}"
# Add 'INSTALL_WEB_SERVER', if its not been applied already: https://github.com/pi-hole/pi-hole/pull/2115 # Add 'INSTALL_WEB_SERVER', if its not been applied already: https://github.com/pi-hole/pi-hole/pull/2115
if ! grep -q '^INSTALL_WEB_SERVER=' ${setupVars}; then if ! grep -q '^INSTALL_WEB_SERVER=' ${setupVars}; then
local webserver_installed=false local webserver_installed=false
if grep -q '^INSTALL_WEB_INTERFACE=true' ${setupVars}; then if grep -q '^INSTALL_WEB_INTERFACE=true' ${setupVars}; then
webserver_installed=true webserver_installed=true
fi fi
echo -e "INSTALL_WEB_SERVER=$webserver_installed" >> ${setupVars} echo -e "INSTALL_WEB_SERVER=$webserver_installed" >> "${setupVars}"
fi fi
} }
# Install base files and web interface # Install base files and web interface
installPihole() { installPihole() {
# Create the pihole user
create_pihole_user
# If the user wants to install the Web interface, # If the user wants to install the Web interface,
if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
if [[ ! -d "${webroot}" ]]; then if [[ ! -d "${webroot}" ]]; then
@ -1895,8 +1906,14 @@ installPihole() {
# Set the owner and permissions # Set the owner and permissions
chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} ${webroot} chown ${LIGHTTPD_USER}:${LIGHTTPD_GROUP} ${webroot}
chmod 0775 ${webroot} chmod 0775 ${webroot}
# Repair permissions if /var/www/html is not world readable
chmod a+rx /var/www
chmod a+rx /var/www/html
# Give pihole access to the Web server group # Give pihole access to the Web server group
usermod -a -G ${LIGHTTPD_GROUP} pihole usermod -a -G ${LIGHTTPD_GROUP} pihole
# Give lighttpd access to the pihole group so the web interface can
# manage the gravity.db database
usermod -a -G pihole ${LIGHTTPD_USER}
# If the lighttpd command is executable, # If the lighttpd command is executable,
if is_command lighty-enable-mod ; then if is_command lighty-enable-mod ; then
# enable fastcgi and fastcgi-php # enable fastcgi and fastcgi-php
@ -1957,7 +1974,7 @@ checkSelinux() {
# If it's enforcing, # If it's enforcing,
if [[ "${enforceMode}" == "Enforcing" ]]; then if [[ "${enforceMode}" == "Enforcing" ]]; then
# Explain Pi-hole does not support it yet # Explain Pi-hole does not support it yet
whiptail --defaultno --title "SELinux Enforcing Detected" --yesno "SELinux is being ENFORCED on your system! \\n\\nPi-hole currently does not support SELinux, but you may still continue with the installation.\\n\\nNote: Web Admin will not be fully functional unless you set your policies correctly\\n\\nContinue installing Pi-hole?" ${r} ${c} || \ whiptail --defaultno --title "SELinux Enforcing Detected" --yesno "SELinux is being ENFORCED on your system! \\n\\nPi-hole currently does not support SELinux, but you may still continue with the installation.\\n\\nNote: Web Admin will not be fully functional unless you set your policies correctly\\n\\nContinue installing Pi-hole?" "${r}" "${c}" || \
{ printf "\\n %bSELinux Enforcing detected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; } { printf "\\n %bSELinux Enforcing detected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; }
printf " %b Continuing installation with SELinux Enforcing\\n" "${INFO}" printf " %b Continuing installation with SELinux Enforcing\\n" "${INFO}"
printf " %b Please refer to official SELinux documentation to create a custom policy\\n" "${INFO}" printf " %b Please refer to official SELinux documentation to create a custom policy\\n" "${INFO}"
@ -1996,7 +2013,7 @@ If you set a new IP address, you should restart the Pi.
The install log is in /etc/pihole. The install log is in /etc/pihole.
${additional}" ${r} ${c} ${additional}" "${r}" "${c}"
} }
update_dialogs() { update_dialogs() {
@ -2017,7 +2034,7 @@ update_dialogs() {
opt2b="This will reset your Pi-hole and allow you to enter new settings." opt2b="This will reset your Pi-hole and allow you to enter new settings."
# Display the information to the user # Display the information to the user
UpdateCmd=$(whiptail --title "Existing Install Detected!" --menu "\\n\\nWe have detected an existing install.\\n\\nPlease choose from the following options: \\n($strAdd)" ${r} ${c} 2 \ UpdateCmd=$(whiptail --title "Existing Install Detected!" --menu "\\n\\nWe have detected an existing install.\\n\\nPlease choose from the following options: \\n($strAdd)" "${r}" "${c}" 2 \
"${opt1a}" "${opt1b}" \ "${opt1a}" "${opt1b}" \
"${opt2a}" "${opt2b}" 3>&2 2>&1 1>&3) || \ "${opt2a}" "${opt2b}" 3>&2 2>&1 1>&3) || \
{ printf " %bCancel was selected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; } { printf " %bCancel was selected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; }
@ -2106,6 +2123,8 @@ checkout_pull_branch() {
printf " %b %s" "${INFO}" "$str" printf " %b %s" "${INFO}" "$str"
git checkout "${branch}" --quiet || return 1 git checkout "${branch}" --quiet || return 1
printf "%b %b %s\\n" "${OVER}" "${TICK}" "$str" printf "%b %b %s\\n" "${OVER}" "${TICK}" "$str"
# Data in the repositories is public anyway so we can make it readable by everyone (+r to keep executable permission if already set by git)
chmod -R a+rX "${directory}"
git_pull=$(git pull || return 1) git_pull=$(git pull || return 1)
@ -2202,6 +2221,8 @@ FTLinstall() {
# Before stopping FTL, we download the macvendor database # Before stopping FTL, we download the macvendor database
curl -sSL "https://ftl.pi-hole.net/macvendor.db" -o "${PI_HOLE_CONFIG_DIR}/macvendor.db" || true curl -sSL "https://ftl.pi-hole.net/macvendor.db" -o "${PI_HOLE_CONFIG_DIR}/macvendor.db" || true
chmod 644 "${PI_HOLE_CONFIG_DIR}/macvendor.db"
chown pihole:pihole "${PI_HOLE_CONFIG_DIR}/macvendor.db"
# Stop pihole-FTL service if available # Stop pihole-FTL service if available
stop_service pihole-FTL &> /dev/null stop_service pihole-FTL &> /dev/null
@ -2252,6 +2273,7 @@ disable_dnsmasq() {
fi fi
# Create /etc/dnsmasq.conf # Create /etc/dnsmasq.conf
echo "conf-dir=/etc/dnsmasq.d" > "${conffile}" echo "conf-dir=/etc/dnsmasq.d" > "${conffile}"
chmod 644 "${conffile}"
} }
get_binary_name() { get_binary_name() {
@ -2441,6 +2463,7 @@ copy_to_install_log() {
# Copy the contents of file descriptor 3 into the install log # Copy the contents of file descriptor 3 into the install log
# Since we use color codes such as '\e[1;33m', they should be removed # Since we use color codes such as '\e[1;33m', they should be removed
sed 's/\[[0-9;]\{1,5\}m//g' < /proc/$$/fd/3 > "${installLogLoc}" sed 's/\[[0-9;]\{1,5\}m//g' < /proc/$$/fd/3 > "${installLogLoc}"
chmod 644 "${installLogLoc}"
} }
main() { main() {
@ -2525,7 +2548,7 @@ main() {
# Display welcome dialogs # Display welcome dialogs
welcomeDialogs welcomeDialogs
# Create directory for Pi-hole storage # Create directory for Pi-hole storage
mkdir -p /etc/pihole/ install -d -m 755 /etc/pihole/
# Determine available interfaces # Determine available interfaces
get_available_interfaces get_available_interfaces
# Find interfaces and let the user choose one # Find interfaces and let the user choose one
@ -2547,7 +2570,7 @@ main() {
installDefaultBlocklists installDefaultBlocklists
# Source ${setupVars} to use predefined user variables in the functions # Source ${setupVars} to use predefined user variables in the functions
source ${setupVars} source "${setupVars}"
# Get the privacy level if it exists (default is 0) # Get the privacy level if it exists (default is 0)
if [[ -f "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf" ]]; then if [[ -f "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf" ]]; then
@ -2581,6 +2604,8 @@ main() {
else else
LIGHTTPD_ENABLED=false LIGHTTPD_ENABLED=false
fi fi
# Create the pihole user
create_pihole_user
# Check if FTL is installed - do this early on as FTL is a hard dependency for Pi-hole # Check if FTL is installed - do this early on as FTL is a hard dependency for Pi-hole
if ! FTLdetect; then if ! FTLdetect; then
printf " %b FTL Engine not installed\\n" "${CROSS}" printf " %b FTL Engine not installed\\n" "${CROSS}"
@ -2602,7 +2627,7 @@ main() {
pw=$(tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c 8) pw=$(tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c 8)
# shellcheck disable=SC1091 # shellcheck disable=SC1091
. /opt/pihole/webpage.sh . /opt/pihole/webpage.sh
echo "WEBPASSWORD=$(HashPassword ${pw})" >> ${setupVars} echo "WEBPASSWORD=$(HashPassword "${pw}")" >> "${setupVars}"
fi fi
fi fi

@ -156,7 +156,7 @@ removeNoPurge() {
# Restore Resolved # Restore Resolved
if [[ -e /etc/systemd/resolved.conf.orig ]]; then if [[ -e /etc/systemd/resolved.conf.orig ]]; then
${SUDO} cp /etc/systemd/resolved.conf.orig /etc/systemd/resolved.conf ${SUDO} cp -p /etc/systemd/resolved.conf.orig /etc/systemd/resolved.conf
systemctl reload-or-restart systemd-resolved systemctl reload-or-restart systemd-resolved
fi fi

@ -17,37 +17,35 @@ coltable="/opt/pihole/COL_TABLE"
source "${coltable}" source "${coltable}"
regexconverter="/opt/pihole/wildcard_regex_converter.sh" regexconverter="/opt/pihole/wildcard_regex_converter.sh"
source "${regexconverter}" source "${regexconverter}"
# shellcheck disable=SC1091
source "/etc/.pihole/advanced/Scripts/database_migration/gravity-db.sh"
basename="pihole" basename="pihole"
PIHOLE_COMMAND="/usr/local/bin/${basename}" PIHOLE_COMMAND="/usr/local/bin/${basename}"
piholeDir="/etc/${basename}" piholeDir="/etc/${basename}"
adListFile="${piholeDir}/adlists.list" # Legacy (pre v5.0) list file locations
adListDefault="${piholeDir}/adlists.default"
whitelistFile="${piholeDir}/whitelist.txt" whitelistFile="${piholeDir}/whitelist.txt"
blacklistFile="${piholeDir}/blacklist.txt" blacklistFile="${piholeDir}/blacklist.txt"
regexFile="${piholeDir}/regex.list" regexFile="${piholeDir}/regex.list"
adListFile="${piholeDir}/adlists.list"
adList="${piholeDir}/gravity.list"
blackList="${piholeDir}/black.list"
localList="${piholeDir}/local.list" localList="${piholeDir}/local.list"
VPNList="/etc/openvpn/ipp.txt" VPNList="/etc/openvpn/ipp.txt"
piholeGitDir="/etc/.pihole"
gravityDBfile="${piholeDir}/gravity.db"
gravityDBschema="${piholeGitDir}/advanced/Templates/gravity.db.sql"
optimize_database=false
domainsExtension="domains" domainsExtension="domains"
matterAndLight="${basename}.0.matterandlight.txt" matterAndLight="${basename}.0.matterandlight.txt"
parsedMatter="${basename}.1.parsedmatter.txt" parsedMatter="${basename}.1.parsedmatter.txt"
whitelistMatter="${basename}.2.whitelistmatter.txt"
accretionDisc="${basename}.3.accretionDisc.txt"
preEventHorizon="list.preEventHorizon" preEventHorizon="list.preEventHorizon"
skipDownload="false"
resolver="pihole-FTL" resolver="pihole-FTL"
haveSourceUrls=true
# Source setupVars from install script # Source setupVars from install script
setupVars="${piholeDir}/setupVars.conf" setupVars="${piholeDir}/setupVars.conf"
if [[ -f "${setupVars}" ]];then if [[ -f "${setupVars}" ]];then
@ -83,26 +81,137 @@ if [[ -r "${piholeDir}/pihole.conf" ]]; then
echo -e " ${COL_LIGHT_RED}Ignoring overrides specified within pihole.conf! ${COL_NC}" echo -e " ${COL_LIGHT_RED}Ignoring overrides specified within pihole.conf! ${COL_NC}"
fi fi
# Determine if Pi-hole blocking is disabled # Generate new sqlite3 file from schema template
# If this is the case, we want to update generate_gravity_database() {
# gravity.list.bck and black.list.bck instead of sqlite3 "${gravityDBfile}" < "${gravityDBschema}"
# gravity.list and black.list
detect_pihole_blocking_status() { # Ensure proper permissions are set for the newly created database
if [[ "${BLOCKING_ENABLED}" == false ]]; then chown pihole:pihole "${gravityDBfile}"
echo -e " ${INFO} Pi-hole blocking is disabled" chmod g+w "${piholeDir}" "${gravityDBfile}"
adList="${adList}.bck" }
blackList="${blackList}.bck"
update_gravity_timestamp() {
# Update timestamp when the gravity table was last updated successfully
output=$( { sqlite3 "${gravityDBfile}" <<< "INSERT OR REPLACE INTO info (property,value) values (\"updated\",cast(strftime('%s', 'now') as int));"; } 2>&1 )
status="$?"
if [[ "${status}" -ne 0 ]]; then
echo -e "\\n ${CROSS} Unable to update gravity timestamp in database ${gravityDBfile}\\n ${output}"
fi
}
# Import domains from file and store them in the specified database table
database_table_from_file() {
# Define locals
local table source backup_path backup_file
table="${1}"
source="${2}"
backup_path="${piholeDir}/migration_backup"
backup_file="${backup_path}/$(basename "${2}")"
# Truncate table
output=$( { sqlite3 "${gravityDBfile}" <<< "DELETE FROM ${table};"; } 2>&1 )
status="$?"
if [[ "${status}" -ne 0 ]]; then
echo -e "\\n ${CROSS} Unable to truncate ${table} database ${gravityDBfile}\\n ${output}"
gravity_Cleanup "error"
fi
local tmpFile
tmpFile="$(mktemp -p "/tmp" --suffix=".gravity")"
local timestamp
timestamp="$(date --utc +'%s')"
local inputfile
if [[ "${table}" == "gravity" ]]; then
# No need to modify the input data for the gravity table
inputfile="${source}"
else else
echo -e " ${INFO} Pi-hole blocking is enabled" # Apply format for white-, blacklist, regex, and adlist tables
# Read file line by line
local rowid
declare -i rowid
rowid=1
grep -v '^ *#' < "${source}" | while IFS= read -r domain
do
# Only add non-empty lines
if [[ -n "${domain}" ]]; then
if [[ "${table}" == "domain_audit" ]]; then
# domain_audit table format (no enable or modified fields)
echo "${rowid},\"${domain}\",${timestamp}" >> "${tmpFile}"
else
# White-, black-, and regexlist format
echo "${rowid},\"${domain}\",1,${timestamp},${timestamp},\"Migrated from ${source}\"" >> "${tmpFile}"
fi
rowid+=1
fi
done
inputfile="${tmpFile}"
fi fi
# Store domains in database table specified by ${table}
# Use printf as .mode and .import need to be on separate lines
# see https://unix.stackexchange.com/a/445615/83260
output=$( { printf ".timeout 10000\\n.mode csv\\n.import \"%s\" %s\\n" "${inputfile}" "${table}" | sqlite3 "${gravityDBfile}"; } 2>&1 )
status="$?"
if [[ "${status}" -ne 0 ]]; then
echo -e "\\n ${CROSS} Unable to fill table ${table} in database ${gravityDBfile}\\n ${output}"
gravity_Cleanup "error"
fi
# Delete tmpfile
rm "${tmpFile}" > /dev/null 2>&1 || \
echo -e " ${CROSS} Unable to remove ${tmpFile}"
# Move source file to backup directory, create directory if not existing
mkdir -p "${backup_path}"
mv "${source}" "${backup_file}" 2> /dev/null || \
echo -e " ${CROSS} Unable to backup ${source} to ${backup_path}"
}
# Migrate pre-v5.0 list files to database-based Pi-hole versions
migrate_to_database() {
# Create database file only if not present
if [ ! -e "${gravityDBfile}" ]; then
# Create new database file - note that this will be created in version 1
echo -e " ${INFO} Creating new gravity database"
generate_gravity_database
# Migrate list files to new database
if [ -e "${adListFile}" ]; then
# Store adlist domains in database
echo -e " ${INFO} Migrating content of ${adListFile} into new database"
database_table_from_file "adlist" "${adListFile}"
fi
if [ -e "${blacklistFile}" ]; then
# Store blacklisted domains in database
echo -e " ${INFO} Migrating content of ${blacklistFile} into new database"
database_table_from_file "blacklist" "${blacklistFile}"
fi
if [ -e "${whitelistFile}" ]; then
# Store whitelisted domains in database
echo -e " ${INFO} Migrating content of ${whitelistFile} into new database"
database_table_from_file "whitelist" "${whitelistFile}"
fi
if [ -e "${regexFile}" ]; then
# Store regex domains in database
# Important note: We need to add the domains to the "regex" table
# as it will only later be renamed to "regex_blacklist"!
echo -e " ${INFO} Migrating content of ${regexFile} into new database"
database_table_from_file "regex" "${regexFile}"
fi
fi
# Check if gravity database needs to be updated
upgrade_gravityDB "${gravityDBfile}" "${piholeDir}"
} }
# Determine if DNS resolution is available before proceeding # Determine if DNS resolution is available before proceeding
gravity_CheckDNSResolutionAvailable() { gravity_CheckDNSResolutionAvailable() {
local lookupDomain="pi.hole" local lookupDomain="pi.hole"
# Determine if $localList does not exist # Determine if $localList does not exist, and ensure it is not empty
if [[ ! -e "${localList}" ]]; then if [[ ! -e "${localList}" ]] || [[ -s "${localList}" ]]; then
lookupDomain="raw.githubusercontent.com" lookupDomain="raw.githubusercontent.com"
fi fi
@ -153,19 +262,13 @@ gravity_CheckDNSResolutionAvailable() {
gravity_CheckDNSResolutionAvailable gravity_CheckDNSResolutionAvailable
} }
# Retrieve blocklist URLs and parse domains from adlists.list # Retrieve blocklist URLs and parse domains from adlist.list
gravity_GetBlocklistUrls() { gravity_GetBlocklistUrls() {
echo -e " ${INFO} ${COL_BOLD}Neutrino emissions detected${COL_NC}..." echo -e " ${INFO} ${COL_BOLD}Neutrino emissions detected${COL_NC}..."
if [[ -f "${adListDefault}" ]] && [[ -f "${adListFile}" ]]; then # Retrieve source URLs from gravity database
# Remove superceded $adListDefault file # We source only enabled adlists, sqlite3 stores boolean values as 0 (false) or 1 (true)
rm "${adListDefault}" 2> /dev/null || \ mapfile -t sources <<< "$(sqlite3 "${gravityDBfile}" "SELECT address FROM vw_adlist;" 2> /dev/null)"
echo -e " ${CROSS} Unable to remove ${adListDefault}"
fi
# Retrieve source URLs from $adListFile
# Logic: Remove comments and empty lines
mapfile -t sources <<< "$(grep -v -E "^(#|$)" "${adListFile}" 2> /dev/null)"
# Parse source domains from $sources # Parse source domains from $sources
mapfile -t sourceDomains <<< "$( mapfile -t sourceDomains <<< "$(
@ -182,11 +285,12 @@ gravity_GetBlocklistUrls() {
if [[ -n "${sources[*]}" ]] && [[ -n "${sourceDomains[*]}" ]]; then if [[ -n "${sources[*]}" ]] && [[ -n "${sourceDomains[*]}" ]]; then
echo -e "${OVER} ${TICK} ${str}" echo -e "${OVER} ${TICK} ${str}"
return 0
else else
echo -e "${OVER} ${CROSS} ${str}" echo -e "${OVER} ${CROSS} ${str}"
echo -e " ${INFO} No source list found, or it is empty" echo -e " ${INFO} No source list found, or it is empty"
echo "" echo ""
haveSourceUrls=false return 1
fi fi
} }
@ -214,11 +318,9 @@ gravity_SetDownloadOptions() {
*) cmd_ext="";; *) cmd_ext="";;
esac esac
if [[ "${skipDownload}" == false ]]; then echo -e " ${INFO} Target: ${domain} (${url##*/})"
echo -e " ${INFO} Target: ${domain} (${url##*/})" gravity_DownloadBlocklistFromUrl "${url}" "${cmd_ext}" "${agent}"
gravity_DownloadBlocklistFromUrl "${url}" "${cmd_ext}" "${agent}" echo ""
echo ""
fi
done done
gravity_Blackbody=true gravity_Blackbody=true
} }
@ -335,14 +437,17 @@ gravity_ParseFileIntoDomains() {
# Most of the lists downloaded are already in hosts file format but the spacing/formating is not contigious # Most of the lists downloaded are already in hosts file format but the spacing/formating is not contigious
# This helps with that and makes it easier to read # This helps with that and makes it easier to read
# It also helps with debugging so each stage of the script can be researched more in depth # It also helps with debugging so each stage of the script can be researched more in depth
# Awk -F splits on given IFS, we grab the right hand side (chops trailing #coments and /'s to grab the domain only. # 1) Remove carriage returns
# Last awk command takes non-commented lines and if they have 2 fields, take the right field (the domain) and leave # 2) Convert all characters to lowercase
# the left (IP address), otherwise grab the single field. # 3) Remove lines containing "#" or "/"
# 4) Remove leading tabs, spaces, etc.
< ${source} awk -F '#' '{print $1}' | \ # 5) Delete lines not matching domain names
awk -F '/' '{print $1}' | \ < "${source}" tr -d '\r' | \
awk '($1 !~ /^#/) { if (NF>1) {print $2} else {print $1}}' | \ tr '[:upper:]' '[:lower:]' | \
sed -nr -e 's/\.{2,}/./g' -e '/\./p' > ${destination} sed -r '/(\/|#).*$/d' | \
sed -r 's/^.*\s+//g' | \
sed -r '/([^\.]+\.)+[^\.]{2,}/!d' > "${destination}"
chmod 644 "${destination}"
return 0 return 0
fi fi
@ -374,11 +479,13 @@ gravity_ParseFileIntoDomains() {
# Print if nonempty # Print if nonempty
length { print } length { print }
' "${source}" 2> /dev/null > "${destination}" ' "${source}" 2> /dev/null > "${destination}"
chmod 644 "${destination}"
echo -e "${OVER} ${TICK} Format: URL" echo -e "${OVER} ${TICK} Format: URL"
else else
# Default: Keep hosts/domains file in same format as it was downloaded # Default: Keep hosts/domains file in same format as it was downloaded
output=$( { mv "${source}" "${destination}"; } 2>&1 ) output=$( { mv "${source}" "${destination}"; } 2>&1 )
chmod 644 "${destination}"
if [[ ! -e "${destination}" ]]; then if [[ ! -e "${destination}" ]]; then
echo -e "\\n ${CROSS} Unable to move tmp file to ${piholeDir} echo -e "\\n ${CROSS} Unable to move tmp file to ${piholeDir}
@ -393,12 +500,11 @@ gravity_ConsolidateDownloadedBlocklists() {
local str lastLine local str lastLine
str="Consolidating blocklists" str="Consolidating blocklists"
if [[ "${haveSourceUrls}" == true ]]; then echo -ne " ${INFO} ${str}..."
echo -ne " ${INFO} ${str}..."
fi
# Empty $matterAndLight if it already exists, otherwise, create it # Empty $matterAndLight if it already exists, otherwise, create it
: > "${piholeDir}/${matterAndLight}" : > "${piholeDir}/${matterAndLight}"
chmod 644 "${piholeDir}/${matterAndLight}"
# Loop through each *.domains file # Loop through each *.domains file
for i in "${activeDomains[@]}"; do for i in "${activeDomains[@]}"; do
@ -414,9 +520,8 @@ gravity_ConsolidateDownloadedBlocklists() {
fi fi
fi fi
done done
if [[ "${haveSourceUrls}" == true ]]; then echo -e "${OVER} ${TICK} ${str}"
echo -e "${OVER} ${TICK} ${str}"
fi
} }
# Parse consolidated list into (filtered, unique) domains-only format # Parse consolidated list into (filtered, unique) domains-only format
@ -424,67 +529,46 @@ gravity_SortAndFilterConsolidatedList() {
local str num local str num
str="Extracting domains from blocklists" str="Extracting domains from blocklists"
if [[ "${haveSourceUrls}" == true ]]; then echo -ne " ${INFO} ${str}..."
echo -ne " ${INFO} ${str}..."
fi
# Parse into hosts file # Parse into file
gravity_ParseFileIntoDomains "${piholeDir}/${matterAndLight}" "${piholeDir}/${parsedMatter}" gravity_ParseFileIntoDomains "${piholeDir}/${matterAndLight}" "${piholeDir}/${parsedMatter}"
# Format $parsedMatter line total as currency # Format $parsedMatter line total as currency
num=$(printf "%'.0f" "$(wc -l < "${piholeDir}/${parsedMatter}")") num=$(printf "%'.0f" "$(wc -l < "${piholeDir}/${parsedMatter}")")
if [[ "${haveSourceUrls}" == true ]]; then
echo -e "${OVER} ${TICK} ${str}"
fi
echo -e " ${INFO} Number of domains being pulled in by gravity: ${COL_BLUE}${num}${COL_NC}"
str="Removing duplicate domains" echo -e "${OVER} ${TICK} ${str}"
if [[ "${haveSourceUrls}" == true ]]; then echo -e " ${INFO} Gravity pulled in ${COL_BLUE}${num}${COL_NC} domains"
echo -ne " ${INFO} ${str}..."
fi
str="Removing duplicate domains"
echo -ne " ${INFO} ${str}..."
sort -u "${piholeDir}/${parsedMatter}" > "${piholeDir}/${preEventHorizon}" sort -u "${piholeDir}/${parsedMatter}" > "${piholeDir}/${preEventHorizon}"
chmod 644 "${piholeDir}/${preEventHorizon}"
echo -e "${OVER} ${TICK} ${str}"
if [[ "${haveSourceUrls}" == true ]]; then # Format $preEventHorizon line total as currency
echo -e "${OVER} ${TICK} ${str}" num=$(printf "%'.0f" "$(wc -l < "${piholeDir}/${preEventHorizon}")")
# Format $preEventHorizon line total as currency str="Storing ${COL_BLUE}${num}${COL_NC} unique blocking domains in database"
num=$(printf "%'.0f" "$(wc -l < "${piholeDir}/${preEventHorizon}")")
echo -e " ${INFO} Number of unique domains trapped in the Event Horizon: ${COL_BLUE}${num}${COL_NC}"
fi
}
# Whitelist user-defined domains
gravity_Whitelist() {
local num str
if [[ ! -f "${whitelistFile}" ]]; then
echo -e " ${INFO} Nothing to whitelist!"
return 0
fi
num=$(wc -l < "${whitelistFile}")
str="Number of whitelisted domains: ${num}"
echo -ne " ${INFO} ${str}..." echo -ne " ${INFO} ${str}..."
database_table_from_file "gravity" "${piholeDir}/${preEventHorizon}"
# Print everything from preEventHorizon into whitelistMatter EXCEPT domains in $whitelistFile echo -e "${OVER} ${TICK} ${str}"
comm -23 "${piholeDir}/${preEventHorizon}" <(sort "${whitelistFile}") > "${piholeDir}/${whitelistMatter}"
echo -e "${OVER} ${INFO} ${str}"
} }
# Output count of blacklisted domains and regex filters # Report number of entries in a table
gravity_ShowBlockCount() { gravity_Table_Count() {
local table="${1}"
local str="${2}"
local num local num
num="$(sqlite3 "${gravityDBfile}" "SELECT COUNT(*) FROM ${table} WHERE enabled = 1;")"
echo -e " ${INFO} Number of ${str}: ${num}"
}
if [[ -f "${blacklistFile}" ]]; then # Output count of blacklisted domains and regex filters
num=$(printf "%'.0f" "$(wc -l < "${blacklistFile}")") gravity_ShowCount() {
echo -e " ${INFO} Number of blacklisted domains: ${num}" gravity_Table_Count "blacklist" "exact blacklisted domains"
fi gravity_Table_Count "regex_blacklist" "regex blacklist filters"
gravity_Table_Count "whitelist" "exact whitelisted domains"
if [[ -f "${regexFile}" ]]; then gravity_Table_Count "regex_whitelist" "regex whitelist filters"
num=$(grep -cv "^#" "${regexFile}")
echo -e " ${INFO} Number of regex filters: ${num}"
fi
} }
# Parse list of domains into hosts format # Parse list of domains into hosts format
@ -504,7 +588,7 @@ gravity_ParseDomainsIntoHosts() {
} }
# Create "localhost" entries into hosts format # Create "localhost" entries into hosts format
gravity_ParseLocalDomains() { gravity_generateLocalList() {
local hostname local hostname
if [[ -s "/etc/hostname" ]]; then if [[ -s "/etc/hostname" ]]; then
@ -520,6 +604,7 @@ gravity_ParseLocalDomains() {
# Empty $localList if it already exists, otherwise, create it # Empty $localList if it already exists, otherwise, create it
: > "${localList}" : > "${localList}"
chmod 644 "${localList}"
gravity_ParseDomainsIntoHosts "${localList}.tmp" "${localList}" gravity_ParseDomainsIntoHosts "${localList}.tmp" "${localList}"
@ -529,40 +614,6 @@ gravity_ParseLocalDomains() {
fi fi
} }
# Create primary blacklist entries
gravity_ParseBlacklistDomains() {
local output status
# Empty $accretionDisc if it already exists, otherwise, create it
: > "${piholeDir}/${accretionDisc}"
if [[ -f "${piholeDir}/${whitelistMatter}" ]]; then
mv "${piholeDir}/${whitelistMatter}" "${piholeDir}/${accretionDisc}"
else
# There was no whitelist file, so use preEventHorizon instead of whitelistMatter.
cp "${piholeDir}/${preEventHorizon}" "${piholeDir}/${accretionDisc}"
fi
# Move the file over as /etc/pihole/gravity.list so dnsmasq can use it
output=$( { mv "${piholeDir}/${accretionDisc}" "${adList}"; } 2>&1 )
status="$?"
if [[ "${status}" -ne 0 ]]; then
echo -e "\\n ${CROSS} Unable to move ${accretionDisc} from ${piholeDir}\\n ${output}"
gravity_Cleanup "error"
fi
}
# Create user-added blacklist entries
gravity_ParseUserDomains() {
if [[ ! -f "${blacklistFile}" ]]; then
return 0
fi
# Copy the file over as /etc/pihole/black.list so dnsmasq can use it
cp "${blacklistFile}" "${blackList}" 2> /dev/null || \
echo -e "\\n ${CROSS} Unable to move ${blacklistFile##*/} to ${piholeDir}"
}
# Trap Ctrl-C # Trap Ctrl-C
gravity_Trap() { gravity_Trap() {
trap '{ echo -e "\\n\\n ${INFO} ${COL_LIGHT_RED}User-abort detected${COL_NC}"; gravity_Cleanup "error"; }' INT trap '{ echo -e "\\n\\n ${INFO} ${COL_LIGHT_RED}User-abort detected${COL_NC}"; gravity_Cleanup "error"; }' INT
@ -594,6 +645,21 @@ gravity_Cleanup() {
echo -e "${OVER} ${TICK} ${str}" echo -e "${OVER} ${TICK} ${str}"
if ${optimize_database} ; then
str="Optimizing domains database"
echo -ne " ${INFO} ${str}..."
# Run VACUUM command on database to optimize it
output=$( { sqlite3 "${gravityDBfile}" "VACUUM;"; } 2>&1 )
status="$?"
if [[ "${status}" -ne 0 ]]; then
echo -e "\\n ${CROSS} Unable to optimize gravity database ${gravityDBfile}\\n ${output}"
error="error"
else
echo -e "${OVER} ${TICK} ${str}"
fi
fi
# Only restart DNS service if offline # Only restart DNS service if offline
if ! pidof ${resolver} &> /dev/null; then if ! pidof ${resolver} &> /dev/null; then
"${PIHOLE_COMMAND}" restartdns "${PIHOLE_COMMAND}" restartdns
@ -620,17 +686,17 @@ Options:
for var in "$@"; do for var in "$@"; do
case "${var}" in case "${var}" in
"-f" | "--force" ) forceDelete=true;; "-f" | "--force" ) forceDelete=true;;
"-o" | "--optimize" ) optimize_database=true;;
"-h" | "--help" ) helpFunc;; "-h" | "--help" ) helpFunc;;
"-sd" | "--skip-download" ) skipDownload=true;;
"-b" | "--blacklist-only" ) listType="blacklist";;
"-w" | "--whitelist-only" ) listType="whitelist";;
"-wild" | "--wildcard-only" ) listType="wildcard"; dnsRestartType="restart";;
esac esac
done done
# Trap Ctrl-C # Trap Ctrl-C
gravity_Trap gravity_Trap
# Move possibly existing legacy files to the gravity database
migrate_to_database
if [[ "${forceDelete:-}" == true ]]; then if [[ "${forceDelete:-}" == true ]]; then
str="Deleting existing list cache" str="Deleting existing list cache"
echo -ne "${INFO} ${str}..." echo -ne "${INFO} ${str}..."
@ -639,56 +705,26 @@ if [[ "${forceDelete:-}" == true ]]; then
echo -e "${OVER} ${TICK} ${str}" echo -e "${OVER} ${TICK} ${str}"
fi fi
detect_pihole_blocking_status # Gravity downloads blocklists next
gravity_CheckDNSResolutionAvailable
# Determine which functions to run if gravity_GetBlocklistUrls; then
if [[ "${skipDownload}" == false ]]; then gravity_SetDownloadOptions
# Gravity needs to download blocklists # Build preEventHorizon
gravity_CheckDNSResolutionAvailable
gravity_GetBlocklistUrls
if [[ "${haveSourceUrls}" == true ]]; then
gravity_SetDownloadOptions
fi
gravity_ConsolidateDownloadedBlocklists gravity_ConsolidateDownloadedBlocklists
gravity_SortAndFilterConsolidatedList gravity_SortAndFilterConsolidatedList
else
# Gravity needs to modify Blacklist/Whitelist/Wildcards
echo -e " ${INFO} Using cached Event Horizon list..."
numberOf=$(printf "%'.0f" "$(wc -l < "${piholeDir}/${preEventHorizon}")")
echo -e " ${INFO} ${COL_BLUE}${numberOf}${COL_NC} unique domains trapped in the Event Horizon"
fi fi
# Perform when downloading blocklists, or modifying the whitelist # Create local.list
if [[ "${skipDownload}" == false ]] || [[ "${listType}" == "whitelist" ]]; then gravity_generateLocalList
gravity_Whitelist gravity_ShowCount
fi
convert_wildcard_to_regex
gravity_ShowBlockCount
# Perform when downloading blocklists, or modifying the white/blacklist (not wildcards)
if [[ "${skipDownload}" == false ]] || [[ "${listType}" == *"list" ]]; then
str="Parsing domains into hosts format"
echo -ne " ${INFO} ${str}..."
gravity_ParseUserDomains
# Perform when downloading blocklists
if [[ ! "${listType:-}" == "blacklist" ]]; then
gravity_ParseLocalDomains
gravity_ParseBlacklistDomains
fi
echo -e "${OVER} ${TICK} ${str}" update_gravity_timestamp
gravity_Cleanup
fi
gravity_Cleanup
echo "" echo ""
# Determine if DNS has been restarted by this instance of gravity # Determine if DNS has been restarted by this instance of gravity
if [[ -z "${dnsWasOffline:-}" ]]; then if [[ -z "${dnsWasOffline:-}" ]]; then
# Use "force-reload" when restarting dnsmasq for everything but Wildcards "${PIHOLE_COMMAND}" restartdns reload
"${PIHOLE_COMMAND}" restartdns "${dnsRestartType:-force-reload}"
fi fi
"${PIHOLE_COMMAND}" status "${PIHOLE_COMMAND}" status

@ -66,14 +66,24 @@ Available commands and options:
Adds or removes specified domain or domains to the blacklist Adds or removes specified domain or domains to the blacklist
.br .br
\fB--regex, regex\fR [options] [<regex1> <regex2 ...>]
.br
Add or removes specified regex filter to the regex blacklist
.br
\fB--white-regex\fR [options] [<regex1> <regex2 ...>]
.br
Add or removes specified regex filter to the regex whitelist
.br
\fB--wild, wildcard\fR [options] [<domain1> <domain2 ...>] \fB--wild, wildcard\fR [options] [<domain1> <domain2 ...>]
.br .br
Add or removes specified domain to the wildcard blacklist Add or removes specified domain to the wildcard blacklist
.br .br
\fB--regex, regex\fR [options] [<regex1> <regex2 ...>] \fB--white-wild\fR [options] [<domain1> <domain2 ...>]
.br .br
Add or removes specified regex filter to the regex blacklist Add or removes specified domain to the wildcard whitelist
.br .br
(Whitelist/Blacklist manipulation options): (Whitelist/Blacklist manipulation options):
@ -351,6 +361,12 @@ Switching Pi-hole subsystem branches
.br .br
Switch to core development branch Switch to core development branch
.br .br
\fBpihole arpflush\fR
.br
Flush information stored in Pi-hole's network tables
.br
.SH "SEE ALSO" .SH "SEE ALSO"
\fBlighttpd\fR(8), \fBpihole-FTL\fR(8) \fBlighttpd\fR(8), \fBpihole-FTL\fR(8)

@ -10,8 +10,6 @@
# Please see LICENSE file for your rights under this license. # Please see LICENSE file for your rights under this license.
readonly PI_HOLE_SCRIPT_DIR="/opt/pihole" readonly PI_HOLE_SCRIPT_DIR="/opt/pihole"
readonly gravitylist="/etc/pihole/gravity.list"
readonly blacklist="/etc/pihole/black.list"
# setupVars and PI_HOLE_BIN_DIR are not readonly here because in some funcitons (checkout), # setupVars and PI_HOLE_BIN_DIR are not readonly here because in some funcitons (checkout),
# it might get set again when the installer is sourced. This causes an # it might get set again when the installer is sourced. This causes an
@ -57,6 +55,11 @@ flushFunc() {
exit 0 exit 0
} }
arpFunc() {
"${PI_HOLE_SCRIPT_DIR}"/piholeARPTable.sh "$@"
exit 0
}
updatePiholeFunc() { updatePiholeFunc() {
shift shift
"${PI_HOLE_SCRIPT_DIR}"/update.sh "$@" "${PI_HOLE_SCRIPT_DIR}"/update.sh "$@"
@ -145,14 +148,6 @@ Time:
echo -e " ${INFO} Blocking already disabled, nothing to do" echo -e " ${INFO} Blocking already disabled, nothing to do"
exit 0 exit 0
fi fi
if [[ -e "${gravitylist}" ]]; then
mv "${gravitylist}" "${gravitylist}.bck"
echo "" > "${gravitylist}"
fi
if [[ -e "${blacklist}" ]]; then
mv "${blacklist}" "${blacklist}.bck"
echo "" > "${blacklist}"
fi
if [[ $# > 1 ]]; then if [[ $# > 1 ]]; then
local error=false local error=false
if [[ "${2}" == *"s" ]]; then if [[ "${2}" == *"s" ]]; then
@ -201,12 +196,6 @@ Time:
echo -e " ${INFO} Enabling blocking" echo -e " ${INFO} Enabling blocking"
local str="Pi-hole Enabled" local str="Pi-hole Enabled"
if [[ -e "${gravitylist}.bck" ]]; then
mv "${gravitylist}.bck" "${gravitylist}"
fi
if [[ -e "${blacklist}.bck" ]]; then
mv "${blacklist}.bck" "${blacklist}"
fi
sed -i "/BLOCKING_ENABLED=/d" "${setupVars}" sed -i "/BLOCKING_ENABLED=/d" "${setupVars}"
echo "BLOCKING_ENABLED=true" >> "${setupVars}" echo "BLOCKING_ENABLED=true" >> "${setupVars}"
fi fi
@ -310,7 +299,7 @@ tailFunc() {
# Colour everything else as gray # Colour everything else as gray
tail -f /var/log/pihole.log | sed -E \ tail -f /var/log/pihole.log | sed -E \
-e "s,($(date +'%b %d ')| dnsmasq[.*[0-9]]),,g" \ -e "s,($(date +'%b %d ')| dnsmasq[.*[0-9]]),,g" \
-e "s,(.*(gravity.list|black.list|regex.list| config ).* is (0.0.0.0|::|NXDOMAIN|${IPV4_ADDRESS%/*}|${IPV6_ADDRESS:-NULL}).*),${COL_RED}&${COL_NC}," \ -e "s,(.*(gravity |black |regex | config ).* is (0.0.0.0|::|NXDOMAIN|${IPV4_ADDRESS%/*}|${IPV6_ADDRESS:-NULL}).*),${COL_RED}&${COL_NC}," \
-e "s,.*(query\\[A|DHCP).*,${COL_NC}&${COL_NC}," \ -e "s,.*(query\\[A|DHCP).*,${COL_NC}&${COL_NC}," \
-e "s,.*,${COL_GRAY}&${COL_NC}," -e "s,.*,${COL_GRAY}&${COL_NC},"
exit 0 exit 0
@ -383,8 +372,10 @@ Add '-h' after specific commands for more information on usage
Whitelist/Blacklist Options: Whitelist/Blacklist Options:
-w, whitelist Whitelist domain(s) -w, whitelist Whitelist domain(s)
-b, blacklist Blacklist domain(s) -b, blacklist Blacklist domain(s)
--wild, wildcard Wildcard blacklist domain(s) --regex, regex Regex blacklist domains(s)
--regex, regex Regex blacklist domains(s) --white-regex Regex whitelist domains(s)
--wild, wildcard Wildcard blacklist domain(s)
--white-wild Wildcard whitelist domain(s)
Add '-h' for more info on whitelist/blacklist usage Add '-h' for more info on whitelist/blacklist usage
Debugging Options: Debugging Options:
@ -416,7 +407,8 @@ Options:
Add '-h' for more info on disable usage Add '-h' for more info on disable usage
restartdns Restart Pi-hole subsystems restartdns Restart Pi-hole subsystems
checkout Switch Pi-hole subsystems to a different Github branch checkout Switch Pi-hole subsystems to a different Github branch
Add '-h' for more info on checkout usage"; Add '-h' for more info on checkout usage
arpflush Flush information stored in Pi-hole's network tables";
exit 0 exit 0
} }
@ -443,8 +435,10 @@ fi
case "${1}" in case "${1}" in
"-w" | "whitelist" ) listFunc "$@";; "-w" | "whitelist" ) listFunc "$@";;
"-b" | "blacklist" ) listFunc "$@";; "-b" | "blacklist" ) listFunc "$@";;
"--wild" | "wildcard" ) listFunc "$@";; "--wild" | "wildcard" ) listFunc "$@";;
"--regex" | "regex" ) listFunc "$@";; "--regex" | "regex" ) listFunc "$@";;
"--white-regex" | "white-regex" ) listFunc "$@";;
"--white-wild" | "white-wild" ) listFunc "$@";;
"-d" | "debug" ) debugFunc "$@";; "-d" | "debug" ) debugFunc "$@";;
"-f" | "flush" ) flushFunc "$@";; "-f" | "flush" ) flushFunc "$@";;
"-up" | "updatePihole" ) updatePiholeFunc "$@";; "-up" | "updatePihole" ) updatePiholeFunc "$@";;
@ -465,5 +459,6 @@ case "${1}" in
"checkout" ) piholeCheckoutFunc "$@";; "checkout" ) piholeCheckoutFunc "$@";;
"tricorder" ) tricorderFunc;; "tricorder" ) tricorderFunc;;
"updatechecker" ) updateCheckFunc "$@";; "updatechecker" ) updateCheckFunc "$@";;
"arpflush" ) arpFunc "$@";;
* ) helpFunc;; * ) helpFunc;;
esac esac

@ -338,7 +338,7 @@ def test_installPiholeWeb_fresh_install_no_errors(Pihole):
expected_stdout = tick_box + (' Creating directory for blocking page, ' expected_stdout = tick_box + (' Creating directory for blocking page, '
'and copying files') 'and copying files')
assert expected_stdout in installWeb.stdout assert expected_stdout in installWeb.stdout
expected_stdout = cross_box + ' Backing up index.lighttpd.html' expected_stdout = info_box + ' Backing up index.lighttpd.html'
assert expected_stdout in installWeb.stdout assert expected_stdout in installWeb.stdout
expected_stdout = ('No default index.lighttpd.html file found... ' expected_stdout = ('No default index.lighttpd.html file found... '
'not backing up') 'not backing up')
@ -398,6 +398,7 @@ def test_FTL_detect_aarch64_no_errors(Pihole):
) )
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
create_pihole_user
FTLdetect FTLdetect
''') ''')
expected_stdout = info_box + ' FTL Checks...' expected_stdout = info_box + ' FTL Checks...'
@ -418,6 +419,7 @@ def test_FTL_detect_armv6l_no_errors(Pihole):
mock_command('ldd', {'/bin/ls': ('/lib/ld-linux-armhf.so.3', '0')}, Pihole) mock_command('ldd', {'/bin/ls': ('/lib/ld-linux-armhf.so.3', '0')}, Pihole)
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
create_pihole_user
FTLdetect FTLdetect
''') ''')
expected_stdout = info_box + ' FTL Checks...' expected_stdout = info_box + ' FTL Checks...'
@ -439,6 +441,7 @@ def test_FTL_detect_armv7l_no_errors(Pihole):
mock_command('ldd', {'/bin/ls': ('/lib/ld-linux-armhf.so.3', '0')}, Pihole) mock_command('ldd', {'/bin/ls': ('/lib/ld-linux-armhf.so.3', '0')}, Pihole)
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
create_pihole_user
FTLdetect FTLdetect
''') ''')
expected_stdout = info_box + ' FTL Checks...' expected_stdout = info_box + ' FTL Checks...'
@ -455,6 +458,7 @@ def test_FTL_detect_x86_64_no_errors(Pihole):
''' '''
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
create_pihole_user
FTLdetect FTLdetect
''') ''')
expected_stdout = info_box + ' FTL Checks...' expected_stdout = info_box + ' FTL Checks...'
@ -471,6 +475,7 @@ def test_FTL_detect_unknown_no_errors(Pihole):
mock_command('uname', {'-m': ('mips', '0')}, Pihole) mock_command('uname', {'-m': ('mips', '0')}, Pihole)
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
create_pihole_user
FTLdetect FTLdetect
''') ''')
expected_stdout = 'Not able to detect architecture (unknown: mips)' expected_stdout = 'Not able to detect architecture (unknown: mips)'
@ -491,6 +496,7 @@ def test_FTL_download_aarch64_no_errors(Pihole):
download_binary = Pihole.run(''' download_binary = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
binary="pihole-FTL-aarch64-linux-gnu" binary="pihole-FTL-aarch64-linux-gnu"
create_pihole_user
FTLinstall FTLinstall
''') ''')
expected_stdout = tick_box + ' Downloading and Installing FTL' expected_stdout = tick_box + ' Downloading and Installing FTL'
@ -512,6 +518,7 @@ def test_FTL_download_unknown_fails_no_errors(Pihole):
download_binary = Pihole.run(''' download_binary = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
binary="pihole-FTL-mips" binary="pihole-FTL-mips"
create_pihole_user
FTLinstall FTLinstall
''') ''')
expected_stdout = cross_box + ' Downloading and Installing FTL' expected_stdout = cross_box + ' Downloading and Installing FTL'
@ -535,6 +542,7 @@ def test_FTL_download_binary_unset_no_errors(Pihole):
''') ''')
download_binary = Pihole.run(''' download_binary = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
create_pihole_user
FTLinstall FTLinstall
''') ''')
expected_stdout = cross_box + ' Downloading and Installing FTL' expected_stdout = cross_box + ' Downloading and Installing FTL'
@ -551,6 +559,7 @@ def test_FTL_binary_installed_and_responsive_no_errors(Pihole):
''' '''
installed_binary = Pihole.run(''' installed_binary = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
create_pihole_user
FTLdetect FTLdetect
pihole-FTL version pihole-FTL version
''') ''')
@ -691,3 +700,42 @@ def test_IPv6_ULA_GUA_test(Pihole):
''') ''')
expected_stdout = 'Found IPv6 ULA address, using it for blocking IPv6 ads' expected_stdout = 'Found IPv6 ULA address, using it for blocking IPv6 ads'
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
def test_validate_ip_valid(Pihole):
'''
Given a valid IP address, valid_ip returns success
'''
output = Pihole.run('''
source /opt/pihole/basic-install.sh
valid_ip "192.168.1.1"
''')
assert output.rc == 0
def test_validate_ip_invalid_octet(Pihole):
'''
Given an invalid IP address (large octet), valid_ip returns an error
'''
output = Pihole.run('''
source /opt/pihole/basic-install.sh
valid_ip "1092.168.1.1"
''')
assert output.rc == 1
def test_validate_ip_invalid_letters(Pihole):
'''
Given an invalid IP address (contains letters), valid_ip returns an error
'''
output = Pihole.run('''
source /opt/pihole/basic-install.sh
valid_ip "not an IP"
''')
assert output.rc == 1

Loading…
Cancel
Save