1
0
mirror of https://github.com/pi-hole/pi-hole synced 2025-01-03 12:40:56 +00:00

Merge branch 'development' into new/internal-blocking

Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
DL6ER 2019-12-09 16:49:16 +00:00
commit f482156cca
No known key found for this signature in database
GPG Key ID: 00135ACBD90B28DD
15 changed files with 454 additions and 325 deletions

View File

@ -19,6 +19,7 @@
############################################################################### ###############################################################################
addn-hosts=/etc/pihole/local.list addn-hosts=/etc/pihole/local.list
addn-hosts=/etc/pihole/custom.list
domain-needed domain-needed

View File

@ -44,17 +44,24 @@ upgrade_gravityDB(){
version=3 version=3
fi fi
if [[ "$version" == "3" ]]; then if [[ "$version" == "3" ]]; then
# This migration script upgrades the gravity and list views # This migration script unifies the formally separated domain
# implementing necessary changes for per-client blocking # lists into a single table with a UNIQUE domain constraint
echo -e " ${INFO} Upgrading gravity database from version 3 to 4" echo -e " ${INFO} Upgrading gravity database from version 3 to 4"
sqlite3 "${database}" < "${scriptPath}/3_to_4.sql" sqlite3 "${database}" < "${scriptPath}/3_to_4.sql"
version=4 version=4
fi fi
if [[ "$version" == "4" ]]; then if [[ "$version" == "4" ]]; then
# This migration script upgrades the adlist view # This migration script upgrades the gravity and list views
# to return an ID used in gravity.sh # implementing necessary changes for per-client blocking
echo -e " ${INFO} Upgrading gravity database from version 4 to 5" echo -e " ${INFO} Upgrading gravity database from version 4 to 5"
sqlite3 "${database}" < "${scriptPath}/4_to_5.sql" sqlite3 "${database}" < "${scriptPath}/4_to_5.sql"
version=5 version=5
fi fi
if [[ "$version" == "5" ]]; then
# This migration script upgrades the adlist view
# to return an ID used in gravity.sh
echo -e " ${INFO} Upgrading gravity database from version 5 to 6"
sqlite3 "${database}" < "${scriptPath}/5_to_6.sql"
version=6
fi
} }

View File

@ -4,67 +4,93 @@ PRAGMA FOREIGN_KEYS=OFF;
BEGIN TRANSACTION; BEGIN TRANSACTION;
DROP TABLE gravity; CREATE TABLE domainlist
CREATE TABLE gravity
(
domain TEXT NOT NULL,
adlist_id INTEGER NOT NULL REFERENCES adlist (id),
PRIMARY KEY(domain, adlist_id)
);
DROP VIEW vw_gravity;
CREATE VIEW vw_gravity AS SELECT domain, adlist_by_group.group_id AS group_id
FROM gravity
LEFT JOIN adlist_by_group ON adlist_by_group.adlist_id = gravity.adlist_id
LEFT JOIN adlist ON adlist.id = gravity.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);
DROP VIEW vw_whitelist;
CREATE VIEW vw_whitelist AS SELECT domain, whitelist.id AS id, whitelist_by_group.group_id AS group_id
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;
DROP VIEW vw_blacklist;
CREATE VIEW vw_blacklist AS SELECT domain, blacklist.id AS id, blacklist_by_group.group_id AS group_id
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;
DROP VIEW vw_regex_whitelist;
CREATE VIEW vw_regex_whitelist AS SELECT DISTINCT domain, regex_whitelist.id AS id, regex_whitelist_by_group.group_id AS group_id
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;
DROP VIEW vw_regex_blacklist;
CREATE VIEW vw_regex_blacklist AS SELECT DISTINCT domain, regex_blacklist.id AS id, regex_blacklist_by_group.group_id AS group_id
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 TABLE client
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
ip TEXT NOL NULL UNIQUE type INTEGER NOT NULL DEFAULT 0,
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 client_by_group ALTER TABLE whitelist ADD COLUMN type INTEGER;
UPDATE whitelist SET type = 0;
INSERT INTO domainlist (type,domain,enabled,date_added,date_modified,comment)
SELECT type,domain,enabled,date_added,date_modified,comment FROM whitelist;
ALTER TABLE blacklist ADD COLUMN type INTEGER;
UPDATE blacklist SET type = 1;
INSERT INTO domainlist (type,domain,enabled,date_added,date_modified,comment)
SELECT type,domain,enabled,date_added,date_modified,comment FROM blacklist;
ALTER TABLE regex_whitelist ADD COLUMN type INTEGER;
UPDATE regex_whitelist SET type = 2;
INSERT INTO domainlist (type,domain,enabled,date_added,date_modified,comment)
SELECT type,domain,enabled,date_added,date_modified,comment FROM regex_whitelist;
ALTER TABLE regex_blacklist ADD COLUMN type INTEGER;
UPDATE regex_blacklist SET type = 3;
INSERT INTO domainlist (type,domain,enabled,date_added,date_modified,comment)
SELECT type,domain,enabled,date_added,date_modified,comment FROM regex_blacklist;
DROP TABLE whitelist_by_group;
DROP TABLE blacklist_by_group;
DROP TABLE regex_whitelist_by_group;
DROP TABLE regex_blacklist_by_group;
CREATE TABLE domainlist_by_group
( (
client_id INTEGER NOT NULL REFERENCES client (id), domainlist_id INTEGER NOT NULL REFERENCES domainlist (id),
group_id INTEGER NOT NULL REFERENCES "group" (id), group_id INTEGER NOT NULL REFERENCES "group" (id),
PRIMARY KEY (client_id, group_id) PRIMARY KEY (domainlist_id, group_id)
); );
DROP TRIGGER tr_whitelist_update;
DROP TRIGGER tr_blacklist_update;
DROP TRIGGER tr_regex_whitelist_update;
DROP TRIGGER tr_regex_blacklist_update;
CREATE TRIGGER tr_domainlist_update AFTER UPDATE ON domainlist
BEGIN
UPDATE domainlist SET date_modified = (cast(strftime('%s', 'now') as int)) WHERE domain = NEW.domain;
END;
DROP VIEW vw_whitelist;
CREATE VIEW vw_whitelist AS SELECT domain, domainlist.id AS id, domainlist_by_group.group_id AS group_id
FROM domainlist
LEFT JOIN domainlist_by_group ON domainlist_by_group.domainlist_id = domainlist.id
LEFT JOIN "group" ON "group".id = domainlist_by_group.group_id
WHERE domainlist.enabled = 1 AND (domainlist_by_group.group_id IS NULL OR "group".enabled = 1)
AND domainlist.type = 0
ORDER BY domainlist.id;
DROP VIEW vw_blacklist;
CREATE VIEW vw_blacklist AS SELECT domain, domainlist.id AS id, domainlist_by_group.group_id AS group_id
FROM domainlist
LEFT JOIN domainlist_by_group ON domainlist_by_group.domainlist_id = domainlist.id
LEFT JOIN "group" ON "group".id = domainlist_by_group.group_id
WHERE domainlist.enabled = 1 AND (domainlist_by_group.group_id IS NULL OR "group".enabled = 1)
AND domainlist.type = 1
ORDER BY domainlist.id;
DROP VIEW vw_regex_whitelist;
CREATE VIEW vw_regex_whitelist AS SELECT domain, domainlist.id AS id, domainlist_by_group.group_id AS group_id
FROM domainlist
LEFT JOIN domainlist_by_group ON domainlist_by_group.domainlist_id = domainlist.id
LEFT JOIN "group" ON "group".id = domainlist_by_group.group_id
WHERE domainlist.enabled = 1 AND (domainlist_by_group.group_id IS NULL OR "group".enabled = 1)
AND domainlist.type = 2
ORDER BY domainlist.id;
DROP VIEW vw_regex_blacklist;
CREATE VIEW vw_regex_blacklist AS SELECT domain, domainlist.id AS id, domainlist_by_group.group_id AS group_id
FROM domainlist
LEFT JOIN domainlist_by_group ON domainlist_by_group.domainlist_id = domainlist.id
LEFT JOIN "group" ON "group".id = domainlist_by_group.group_id
WHERE domainlist.enabled = 1 AND (domainlist_by_group.group_id IS NULL OR "group".enabled = 1)
AND domainlist.type = 3
ORDER BY domainlist.id;
UPDATE info SET value = 4 WHERE property = 'version'; UPDATE info SET value = 4 WHERE property = 'version';
COMMIT; COMMIT;

View File

@ -4,14 +4,35 @@ PRAGMA FOREIGN_KEYS=OFF;
BEGIN TRANSACTION; BEGIN TRANSACTION;
DROP VIEW vw_adlist; DROP TABLE gravity;
CREATE VIEW vw_adlist AS SELECT DISTINCT address, adlist.id AS id CREATE TABLE gravity
FROM adlist (
LEFT JOIN adlist_by_group ON adlist_by_group.adlist_id = adlist.id domain TEXT NOT NULL,
adlist_id INTEGER NOT NULL REFERENCES adlist (id),
PRIMARY KEY(domain, adlist_id)
);
DROP VIEW vw_gravity;
CREATE VIEW vw_gravity AS SELECT domain, adlist_by_group.group_id AS group_id
FROM gravity
LEFT JOIN adlist_by_group ON adlist_by_group.adlist_id = gravity.adlist_id
LEFT JOIN adlist ON adlist.id = gravity.adlist_id
LEFT JOIN "group" ON "group".id = adlist_by_group.group_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) WHERE adlist.enabled = 1 AND (adlist_by_group.group_id IS NULL OR "group".enabled = 1);
ORDER BY adlist.id;
CREATE TABLE client
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip TEXT NOL NULL UNIQUE
);
CREATE TABLE client_by_group
(
client_id INTEGER NOT NULL REFERENCES client (id),
group_id INTEGER NOT NULL REFERENCES "group" (id),
PRIMARY KEY (client_id, group_id)
);
UPDATE info SET value = 5 WHERE property = 'version'; UPDATE info SET value = 5 WHERE property = 'version';
COMMIT; COMMIT;

View File

@ -0,0 +1,18 @@
.timeout 30000
PRAGMA FOREIGN_KEYS=OFF;
BEGIN TRANSACTION;
DROP VIEW vw_adlist;
CREATE VIEW vw_adlist AS SELECT DISTINCT address, adlist.id AS id
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;
UPDATE info SET value = 6 WHERE property = 'version';
COMMIT;

View File

@ -21,68 +21,78 @@ web=false
domList=() domList=()
listType="" typeId=""
listname=""
colfile="/opt/pihole/COL_TABLE" colfile="/opt/pihole/COL_TABLE"
source ${colfile} source ${colfile}
# IDs are hard-wired to domain interpretation in the gravity database scheme
# Clients (including FTL) will read them through the corresponding views
readonly whitelist="0"
readonly blacklist="1"
readonly regex_whitelist="2"
readonly regex_blacklist="3"
GetListnameFromTypeId() {
if [[ "$1" == "${whitelist}" ]]; then
echo "whitelist"
elif [[ "$1" == "${blacklist}" ]]; then
echo "blacklist"
elif [[ "$1" == "${regex_whitelist}" ]]; then
echo "regex whitelist"
elif [[ "$1" == "${regex_blacklist}" ]]; then
echo "regex blacklist"
fi
}
GetListParamFromTypeId() {
if [[ "${typeId}" == "${whitelist}" ]]; then
echo "w"
elif [[ "${typeId}" == "${blacklist}" ]]; then
echo "b"
elif [[ "${typeId}" == "${regex_whitelist}" && "${wildcard}" == true ]]; then
echo "-white-wild"
elif [[ "${typeId}" == "${regex_whitelist}" ]]; then
echo "-white-regex"
elif [[ "${typeId}" == "${regex_blacklist}" && "${wildcard}" == true ]]; then
echo "-wild"
elif [[ "${typeId}" == "${regex_blacklist}" ]]; then
echo "-regex"
fi
}
helpFunc() { helpFunc() {
if [[ "${listType}" == "whitelist" ]]; then local listname param
param="w"
type="whitelist" listname="$(GetListnameFromTypeId "${typeId}")"
elif [[ "${listType}" == "regex_blacklist" && "${wildcard}" == true ]]; then param="$(GetListParamFromTypeId)"
param="-wild"
type="wildcard blacklist"
elif [[ "${listType}" == "regex_blacklist" ]]; then
param="-regex"
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
param="b"
type="blacklist"
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^} one or more domains ${listname^} one or more domains
Options: Options:
-d, --delmode Remove domain(s) from the ${type} -d, --delmode Remove domain(s) from the ${listname}
-nr, --noreload Update ${type} without reloading the DNS server -nr, --noreload Update ${listname} 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 ${listname}listed domains
--nuke Removes all entries in a list" --nuke Removes all entries in a list"
exit 0 exit 0
} }
EscapeRegexp() { ValidateDomain() {
# This way we may safely insert an arbitrary
# string in our regular expressions
# This sed is intentionally executed in three steps to ease maintainability
# The first sed removes any amount of leading dots
echo $* | sed 's/^\.*//' | sed "s/[]\.|$(){}?+*^]/\\\\&/g" | sed "s/\\//\\\\\//g"
}
HandleOther() {
# Convert to lowercase # Convert to lowercase
domain="${1,,}" domain="${1,,}"
# 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 [[ ( "${listType}" == "regex_blacklist" || "${listType}" == "regex_whitelist" ) && "${wildcard}" == false ]]; then if [[ ( "${typeId}" == "${regex_blacklist}" || "${typeId}" == "${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 # Use regex to check the validity of the passed domain. see https://regexr.com/3abjr
validDomain=$(grep -P "^[^\\.]{1,63}(\\.[^\\.]{1,63})*$" <<< "${validDomain}") # Length of each label validDomain=$(grep -P "^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9\-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$" <<< "${domain}")
fi fi
fi fi
@ -94,21 +104,6 @@ HandleOther() {
} }
ProcessDomainList() { ProcessDomainList() {
local is_regexlist
if [[ "${listType}" == "regex_blacklist" ]]; then
# Regex black filter list
listname="regex blacklist filters"
is_regexlist=true
elif [[ "${listType}" == "regex_whitelist" ]]; then
# Regex white filter list
listname="regex whitelist filters"
is_regexlist=true
else
# Whitelist / Blacklist
listname="${listType}"
is_regexlist=false
fi
for dom in "${domList[@]}"; do for dom in "${domList[@]}"; do
# Format domain into regex filter if requested # Format domain into regex filter if requested
if [[ "${wildcard}" == true ]]; then if [[ "${wildcard}" == true ]]; then
@ -118,77 +113,82 @@ ProcessDomainList() {
# Logic: If addmode then add to desired list and remove from the other; # 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 delmode then remove from desired list but do not add to the other
if ${addmode}; then if ${addmode}; then
AddDomain "${dom}" "${listType}" AddDomain "${dom}"
if ! ${is_regexlist}; then
RemoveDomain "${dom}" "${listAlt}"
fi
else else
RemoveDomain "${dom}" "${listType}" RemoveDomain "${dom}"
fi fi
done done
} }
AddDomain() { AddDomain() {
local domain list num local domain num requestedListname existingTypeId existingListname
# Use printf to escape domain. %q prints the argument in a form that can be reused as shell input
domain="$1" domain="$1"
list="$2"
# Is the domain in the list we want to add it to? # Is the domain in the list we want to add it to?
num="$(sqlite3 "${gravityDBfile}" "SELECT COUNT(*) FROM ${list} WHERE domain = '${domain}';")" num="$(sqlite3 "${gravityDBfile}" "SELECT COUNT(*) FROM domainlist WHERE domain = '${domain}';")"
requestedListname="$(GetListnameFromTypeId "${typeId}")"
if [[ "${num}" -ne 0 ]]; then if [[ "${num}" -ne 0 ]]; then
if [[ "${verbose}" == true ]]; then existingTypeId="$(sqlite3 "${gravityDBfile}" "SELECT type FROM domainlist WHERE domain = '${domain}';")"
echo -e " ${INFO} ${1} already exists in ${listname}, no need to add!" if [[ "${existingTypeId}" == "${typeId}" ]]; then
if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} ${1} already exists in ${requestedListname}, no need to add!"
fi
else
existingListname="$(GetListnameFromTypeId "${existingTypeId}")"
sqlite3 "${gravityDBfile}" "UPDATE domainlist SET type = ${typeId} WHERE domain='${domain}';"
if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} ${1} already exists in ${existingListname}, it has been moved to ${requestedListname}!"
fi
fi fi
return return
fi fi
# Domain not found in the table, add it! # Domain not found in the table, add it!
if [[ "${verbose}" == true ]]; then if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} Adding ${1} to the ${listname}..." echo -e " ${INFO} Adding ${domain} to the ${requestedListname}..."
fi fi
reload=true reload=true
# Insert only the domain here. The enabled and date_added fields will be filled # Insert only the domain here. The enabled and date_added fields will be filled
# with their default values (enabled = true, date_added = current timestamp) # with their default values (enabled = true, date_added = current timestamp)
sqlite3 "${gravityDBfile}" "INSERT INTO ${list} (domain) VALUES ('${domain}');" sqlite3 "${gravityDBfile}" "INSERT INTO domainlist (domain,type) VALUES ('${domain}',${typeId});"
} }
RemoveDomain() { RemoveDomain() {
local domain list num local domain num requestedListname
# Use printf to escape domain. %q prints the argument in a form that can be reused as shell input
domain="$1" domain="$1"
list="$2"
# Is the domain in the list we want to remove it from? # Is the domain in the list we want to remove it from?
num="$(sqlite3 "${gravityDBfile}" "SELECT COUNT(*) FROM ${list} WHERE domain = '${domain}';")" num="$(sqlite3 "${gravityDBfile}" "SELECT COUNT(*) FROM domainlist WHERE domain = '${domain}' AND type = ${typeId};")"
requestedListname="$(GetListnameFromTypeId "${typeId}")"
if [[ "${num}" -eq 0 ]]; then if [[ "${num}" -eq 0 ]]; then
if [[ "${verbose}" == true ]]; then if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} ${1} does not exist in ${list}, no need to remove!" echo -e " ${INFO} ${domain} does not exist in ${requestedListname}, no need to remove!"
fi fi
return return
fi fi
# Domain found in the table, remove it! # Domain found in the table, remove it!
if [[ "${verbose}" == true ]]; then if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} Removing ${1} from the ${listname}..." echo -e " ${INFO} Removing ${domain} from the ${requestedListname}..."
fi fi
reload=true reload=true
# Remove it from the current list # Remove it from the current list
sqlite3 "${gravityDBfile}" "DELETE FROM ${list} WHERE domain = '${domain}';" sqlite3 "${gravityDBfile}" "DELETE FROM domainlist WHERE domain = '${domain}' AND type = ${typeId};"
} }
Displaylist() { Displaylist() {
local list listname count num_pipes domain enabled status nicedate local count num_pipes domain enabled status nicedate requestedListname
listname="${listType}" requestedListname="$(GetListnameFromTypeId "${typeId}")"
data="$(sqlite3 "${gravityDBfile}" "SELECT domain,enabled,date_modified FROM ${listType};" 2> /dev/null)" data="$(sqlite3 "${gravityDBfile}" "SELECT domain,enabled,date_modified FROM domainlist WHERE type = ${typeId};" 2> /dev/null)"
if [[ -z $data ]]; then if [[ -z $data ]]; then
echo -e "Not showing empty list" echo -e "Not showing empty list"
else else
echo -e "Displaying ${listname}:" echo -e "Displaying ${requestedListname}:"
count=1 count=1
while IFS= read -r line while IFS= read -r line
do do
@ -221,17 +221,17 @@ Displaylist() {
} }
NukeList() { NukeList() {
sqlite3 "${gravityDBfile}" "DELETE FROM ${listType};" sqlite3 "${gravityDBfile}" "DELETE FROM domainlist WHERE type = ${typeId};"
} }
for var in "$@"; do for var in "$@"; do
case "${var}" in case "${var}" in
"-w" | "whitelist" ) listType="whitelist"; listAlt="blacklist";; "-w" | "whitelist" ) typeId=0;;
"-b" | "blacklist" ) listType="blacklist"; listAlt="whitelist";; "-b" | "blacklist" ) typeId=1;;
"--wild" | "wildcard" ) listType="regex_blacklist"; wildcard=true;; "--white-regex" | "white-regex" ) typeId=2;;
"--regex" | "regex" ) listType="regex_blacklist";; "--white-wild" | "white-wild" ) typeId=2; wildcard=true;;
"--white-regex" | "white-regex" ) listType="regex_whitelist";; "--wild" | "wildcard" ) typeId=3; wildcard=true;;
"--white-wild" | "white-wild" ) listType="regex_whitelist"; wildcard=true;; "--regex" | "regex" ) typeId=3;;
"-nr"| "--noreload" ) reload=false;; "-nr"| "--noreload" ) reload=false;;
"-d" | "--delmode" ) addmode=false;; "-d" | "--delmode" ) addmode=false;;
"-q" | "--quiet" ) verbose=false;; "-q" | "--quiet" ) verbose=false;;
@ -239,7 +239,7 @@ for var in "$@"; do
"-l" | "--list" ) Displaylist;; "-l" | "--list" ) Displaylist;;
"--nuke" ) NukeList;; "--nuke" ) NukeList;;
"--web" ) web=true;; "--web" ) web=true;;
* ) HandleOther "${var}";; * ) ValidateDomain "${var}";;
esac esac
done done

View File

@ -46,6 +46,12 @@ checkout() {
local corebranches local corebranches
local webbranches local webbranches
# Check if FTL is installed - do this early on as FTL is a hard dependency for Pi-hole
local funcOutput
funcOutput=$(get_binary_name) #Store output of get_binary_name here
local binary
binary="pihole-FTL${funcOutput##*pihole-FTL}" #binary name will be the last line of the output of get_binary_name (it always begins with pihole-FTL)
# Avoid globbing # Avoid globbing
set -f set -f
@ -86,7 +92,6 @@ checkout() {
fi fi
#echo -e " ${TICK} Pi-hole Core" #echo -e " ${TICK} Pi-hole Core"
get_binary_name
local path local path
path="development/${binary}" path="development/${binary}"
echo "development" > /etc/pihole/ftlbranch echo "development" > /etc/pihole/ftlbranch
@ -101,7 +106,6 @@ checkout() {
fetch_checkout_pull_branch "${webInterfaceDir}" "master" || { echo " ${CROSS} Unable to pull Web master branch"; exit 1; } fetch_checkout_pull_branch "${webInterfaceDir}" "master" || { echo " ${CROSS} Unable to pull Web master branch"; exit 1; }
fi fi
#echo -e " ${TICK} Web Interface" #echo -e " ${TICK} Web Interface"
get_binary_name
local path local path
path="master/${binary}" path="master/${binary}"
echo "master" > /etc/pihole/ftlbranch echo "master" > /etc/pihole/ftlbranch
@ -161,7 +165,6 @@ checkout() {
fi fi
checkout_pull_branch "${webInterfaceDir}" "${2}" checkout_pull_branch "${webInterfaceDir}" "${2}"
elif [[ "${1}" == "ftl" ]] ; then elif [[ "${1}" == "ftl" ]] ; then
get_binary_name
local path local path
path="${2}/${binary}" path="${2}/${binary}"

View File

@ -31,7 +31,6 @@ source "/opt/pihole/COL_TABLE"
# make_repo() sourced from basic-install.sh # make_repo() sourced from basic-install.sh
# update_repo() source from basic-install.sh # update_repo() source from basic-install.sh
# getGitFiles() sourced from basic-install.sh # getGitFiles() sourced from basic-install.sh
# get_binary_name() sourced from basic-install.sh
# FTLcheckUpdate() sourced from basic-install.sh # FTLcheckUpdate() sourced from basic-install.sh
GitCheckUpdateAvail() { GitCheckUpdateAvail() {
@ -129,7 +128,12 @@ main() {
fi fi
fi fi
if FTLcheckUpdate > /dev/null; then local funcOutput
funcOutput=$(get_binary_name) #Store output of get_binary_name here
local binary
binary="pihole-FTL${funcOutput##*pihole-FTL}" #binary name will be the last line of the output of get_binary_name (it always begins with pihole-FTL)
if FTLcheckUpdate "${binary}" > /dev/null; then
FTL_update=true FTL_update=true
echo -e " ${INFO} FTL:\\t\\t${COL_YELLOW}update available${COL_NC}" echo -e " ${INFO} FTL:\\t\\t${COL_YELLOW}update available${COL_NC}"
else else

View File

@ -17,6 +17,7 @@ readonly FTLconf="/etc/pihole/pihole-FTL.conf"
# 03 -> wildcards # 03 -> wildcards
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 dnscustomfile="/etc/pihole/custom.list"
readonly gravityDBfile="/etc/pihole/gravity.db" readonly gravityDBfile="/etc/pihole/gravity.db"
@ -404,13 +405,15 @@ SetWebUILayout() {
CustomizeAdLists() { CustomizeAdLists() {
local address local address
address="${args[3]}" address="${args[3]}"
local comment
comment="${args[4]}"
if [[ "${args[2]}" == "enable" ]]; then if [[ "${args[2]}" == "enable" ]]; then
sqlite3 "${gravityDBfile}" "UPDATE adlist SET enabled = 1 WHERE address = '${address}'" sqlite3 "${gravityDBfile}" "UPDATE adlist SET enabled = 1 WHERE address = '${address}'"
elif [[ "${args[2]}" == "disable" ]]; then elif [[ "${args[2]}" == "disable" ]]; then
sqlite3 "${gravityDBfile}" "UPDATE adlist SET enabled = 0 WHERE address = '${address}'" sqlite3 "${gravityDBfile}" "UPDATE adlist SET enabled = 0 WHERE address = '${address}'"
elif [[ "${args[2]}" == "add" ]]; then elif [[ "${args[2]}" == "add" ]]; then
sqlite3 "${gravityDBfile}" "INSERT OR IGNORE INTO adlist (address) VALUES ('${address}')" sqlite3 "${gravityDBfile}" "INSERT OR IGNORE INTO adlist (address, comment) VALUES ('${address}', '${comment}')"
elif [[ "${args[2]}" == "del" ]]; then elif [[ "${args[2]}" == "del" ]]; then
sqlite3 "${gravityDBfile}" "DELETE FROM adlist WHERE address = '${address}'" sqlite3 "${gravityDBfile}" "DELETE FROM adlist WHERE address = '${address}'"
else else
@ -524,10 +527,10 @@ Interfaces:
fi fi
if [[ "${args[2]}" == "all" ]]; then if [[ "${args[2]}" == "all" ]]; then
echo -e " ${INFO} Listening on all interfaces, permiting all origins. Please use a firewall!" echo -e " ${INFO} Listening on all interfaces, permitting all origins. Please use a firewall!"
change_setting "DNSMASQ_LISTENING" "all" change_setting "DNSMASQ_LISTENING" "all"
elif [[ "${args[2]}" == "local" ]]; then elif [[ "${args[2]}" == "local" ]]; then
echo -e " ${INFO} Listening on all interfaces, permiting origins from one hop away (LAN)" echo -e " ${INFO} Listening on all interfaces, permitting origins from one hop away (LAN)"
change_setting "DNSMASQ_LISTENING" "local" change_setting "DNSMASQ_LISTENING" "local"
else else
echo -e " ${INFO} Listening only on interface ${PIHOLE_INTERFACE}" echo -e " ${INFO} Listening only on interface ${PIHOLE_INTERFACE}"
@ -597,6 +600,28 @@ SetPrivacyLevel() {
fi fi
} }
AddCustomDNSAddress() {
echo -e " ${TICK} Adding custom DNS entry..."
ip="${args[2]}"
host="${args[3]}"
echo "${ip} ${host}" >> "${dnscustomfile}"
# Restart dnsmasq to load new custom DNS entries
RestartDNS
}
RemoveCustomDNSAddress() {
echo -e " ${TICK} Removing custom DNS entry..."
ip="${args[2]}"
host="${args[3]}"
sed -i "/${ip} ${host}/d" "${dnscustomfile}"
# Restart dnsmasq to update removed custom DNS entries
RestartDNS
}
main() { main() {
args=("$@") args=("$@")
@ -628,6 +653,8 @@ main() {
"audit" ) addAudit "$@";; "audit" ) addAudit "$@";;
"clearaudit" ) clearAudit;; "clearaudit" ) clearAudit;;
"-l" | "privacylevel" ) SetPrivacyLevel;; "-l" | "privacylevel" ) SetPrivacyLevel;;
"addcustomdns" ) AddCustomDNSAddress;;
"removecustomdns" ) RemoveCustomDNSAddress;;
* ) helpFunc;; * ) helpFunc;;
esac esac

View File

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# Pi-hole: A black hole for Internet advertisements # Pi-hole: A black hole for Internet advertisements
# (c) 2017 Pi-hole, LLC (https://pi-hole.net) # (c) 2017 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware. # Network-wide ad blocking via your own hardware.

View File

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
### BEGIN INIT INFO ### BEGIN INIT INFO
# Provides: pihole-FTL # Provides: pihole-FTL
# Required-Start: $remote_fs $syslog # Required-Start: $remote_fs $syslog

View File

@ -138,9 +138,6 @@ else
OVER="\\r\\033[K" OVER="\\r\\033[K"
fi fi
# Define global binary variable
binary="tbd"
# A simple function that just echoes out our logo in ASCII format # A simple function that just echoes out our logo in ASCII format
# This lets users know that it is a Pi-hole, LLC product # This lets users know that it is a Pi-hole, LLC product
show_ascii_berry() { show_ascii_berry() {
@ -250,7 +247,7 @@ if is_command apt-get ; then
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}" "${phpVer}-xml")
# The Web server user, # The Web server user,
LIGHTTPD_USER="www-data" LIGHTTPD_USER="www-data"
# group, # group,
@ -290,7 +287,7 @@ elif is_command rpm ; then
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)
PIHOLE_WEB_DEPS=(lighttpd lighttpd-fastcgi php-common php-cli php-pdo) PIHOLE_WEB_DEPS=(lighttpd lighttpd-fastcgi php-common php-cli php-pdo php-xml)
LIGHTTPD_USER="lighttpd" LIGHTTPD_USER="lighttpd"
LIGHTTPD_GROUP="lighttpd" LIGHTTPD_GROUP="lighttpd"
LIGHTTPD_CFG="lighttpd.conf.fedora" LIGHTTPD_CFG="lighttpd.conf.fedora"
@ -838,7 +835,8 @@ setDHCPCD() {
# Then use the ip command to immediately set the new address # Then use the ip command to immediately set the new address
ip addr replace dev "${PIHOLE_INTERFACE}" "${IPV4_ADDRESS}" ip addr replace dev "${PIHOLE_INTERFACE}" "${IPV4_ADDRESS}"
# Also give a warning that the user may need to reboot their system # Also give a warning that the user may need to reboot their system
printf " %b Set IP address to %s \\n You may need to restart after the install is complete\\n" "${TICK}" "${IPV4_ADDRESS%/*}" printf " %b Set IP address to %s\\n" "${TICK}" "${IPV4_ADDRESS%/*}"
printf " %b You may need to restart after the install is complete\\n" "${INFO}"
fi fi
} }
@ -984,8 +982,6 @@ setDNS() {
# 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; }
# Display the selection
printf " %b Using " "${INFO}"
# Depending on the user's choice, set the GLOBAl variables to the IP of the respective provider # Depending on the user's choice, set the GLOBAl variables to the IP of the respective provider
if [[ "${DNSchoices}" == "Custom" ]] if [[ "${DNSchoices}" == "Custom" ]]
then then
@ -1037,14 +1033,14 @@ setDNS() {
if [[ "${PIHOLE_DNS_2}" == "${strInvalid}" ]]; then if [[ "${PIHOLE_DNS_2}" == "${strInvalid}" ]]; then
PIHOLE_DNS_2="" PIHOLE_DNS_2=""
fi fi
# Since the settings will not work, stay in the loop # Since the settings will not work, stay in the loop
DNSSettingsCorrect=False DNSSettingsCorrect=False
# 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,
else else
# If the settings are wrong, the loop continues # If the settings are wrong, the loop continues
@ -1052,7 +1048,7 @@ setDNS() {
fi fi
fi fi
done done
else else
# Save the old Internal Field Separator in a variable # Save the old Internal Field Separator in a variable
OIFS=$IFS OIFS=$IFS
# and set the new one to newline # and set the new one to newline
@ -1062,7 +1058,6 @@ setDNS() {
DNSName="$(cut -d';' -f1 <<< "${DNSServer}")" DNSName="$(cut -d';' -f1 <<< "${DNSServer}")"
if [[ "${DNSchoices}" == "${DNSName}" ]] if [[ "${DNSchoices}" == "${DNSName}" ]]
then then
printf "%s\\n" "${DNSName}"
PIHOLE_DNS_1="$(cut -d';' -f2 <<< "${DNSServer}")" PIHOLE_DNS_1="$(cut -d';' -f2 <<< "${DNSServer}")"
PIHOLE_DNS_2="$(cut -d';' -f3 <<< "${DNSServer}")" PIHOLE_DNS_2="$(cut -d';' -f3 <<< "${DNSServer}")"
break break
@ -1071,6 +1066,11 @@ setDNS() {
# Restore the IFS to what it was # Restore the IFS to what it was
IFS=${OIFS} IFS=${OIFS}
fi fi
# Display final selection
local DNSIP=${PIHOLE_DNS_1}
[[ -z ${PIHOLE_DNS_2} ]] || DNSIP+=", ${PIHOLE_DNS_2}"
printf " %b Using upstream DNS: %s (%s)\\n" "${INFO}" "${DNSchoices}" "${DNSIP}"
} }
# Allow the user to enable/disable logging # Allow the user to enable/disable logging
@ -1628,7 +1628,7 @@ install_dependent_packages() {
if dpkg-query -W -f='${Status}' "${i}" 2>/dev/null | grep "ok installed" &> /dev/null; then if dpkg-query -W -f='${Status}' "${i}" 2>/dev/null | grep "ok installed" &> /dev/null; then
printf "%b %b Checking for %s\\n" "${OVER}" "${TICK}" "${i}" printf "%b %b Checking for %s\\n" "${OVER}" "${TICK}" "${i}"
else else
echo -e "${OVER} ${INFO} Checking for $i (will be installed)" printf "%b %b Checking for %s (will be installed)\\n" "${OVER}" "${INFO}" "${i}"
installArray+=("${i}") installArray+=("${i}")
fi fi
done done
@ -1645,9 +1645,9 @@ install_dependent_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\\n" "${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)\\n" "${OVER}" "${INFO}" "${i}"
installArray+=("${i}") installArray+=("${i}")
fi fi
done done
@ -1959,20 +1959,42 @@ installPihole() {
# SELinux # SELinux
checkSelinux() { checkSelinux() {
# If the getenforce command exists, local DEFAULT_SELINUX
if is_command getenforce ; then local CURRENT_SELINUX
# Store the current mode in a variable local SELINUX_ENFORCING=0
enforceMode=$(getenforce) # Check if a SELinux configuration file exists
printf "\\n %b SELinux mode detected: %s\\n" "${INFO}" "${enforceMode}" if [[ -f /etc/selinux/config ]]; then
# If a SELinux configuration file was found, check the default SELinux mode.
# If it's enforcing, DEFAULT_SELINUX=$(awk -F= '/^SELINUX=/ {print $2}' /etc/selinux/config)
if [[ "${enforceMode}" == "Enforcing" ]]; then case "${DEFAULT_SELINUX,,}" in
# Explain Pi-hole does not support it yet enforcing)
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 " %b %bDefault SELinux: %s%b\\n" "${CROSS}" "${COL_RED}" "${DEFAULT_SELINUX}" "${COL_NC}"
{ printf "\\n %bSELinux Enforcing detected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}"; exit 1; } SELINUX_ENFORCING=1
printf " %b Continuing installation with SELinux Enforcing\\n" "${INFO}" ;;
printf " %b Please refer to official SELinux documentation to create a custom policy\\n" "${INFO}" *) # 'permissive' and 'disabled'
fi printf " %b %bDefault SELinux: %s%b\\n" "${TICK}" "${COL_GREEN}" "${DEFAULT_SELINUX}" "${COL_NC}"
;;
esac
# Check the current state of SELinux
CURRENT_SELINUX=$(getenforce)
case "${CURRENT_SELINUX,,}" in
enforcing)
printf " %b %bCurrent SELinux: %s%b\\n" "${CROSS}" "${COL_RED}" "${CURRENT_SELINUX}" "${COL_NC}"
SELINUX_ENFORCING=1
;;
*) # 'permissive' and 'disabled'
printf " %b %bCurrent SELinux: %s%b\\n" "${TICK}" "${COL_GREEN}" "${CURRENT_SELINUX}" "${COL_NC}"
;;
esac
else
echo -e " ${INFO} ${COL_GREEN}SELinux not detected${COL_NC}";
fi
# Exit the installer if any SELinux checks toggled the flag
if [[ "${SELINUX_ENFORCING}" -eq 1 ]] && [[ -z "${PIHOLE_SELINUX}" ]]; then
printf " Pi-hole does not provide an SELinux policy as the required changes modify the security of your system.\\n"
printf " Please refer to https://wiki.centos.org/HowTos/SELinux if SELinux is required for your deployment.\\n"
printf "\\n %bSELinux Enforcing detected, exiting installer%b\\n" "${COL_LIGHT_RED}" "${COL_NC}";
exit 1;
fi fi
} }
@ -2167,7 +2189,10 @@ clone_or_update_repos() {
} }
# Download FTL binary to random temp directory and install FTL binary # Download FTL binary to random temp directory and install FTL binary
# Disable directive for SC2120 a value _can_ be passed to this function, but it is passed from an external script that sources this one
# shellcheck disable=SC2120
FTLinstall() { FTLinstall() {
# Local, named variables # Local, named variables
local latesttag local latesttag
local str="Downloading and Installing FTL" local str="Downloading and Installing FTL"
@ -2197,6 +2222,9 @@ FTLinstall() {
ftlBranch="master" ftlBranch="master"
fi fi
local binary
binary="${1}"
# Determine which version of FTL to download # Determine which version of FTL to download
if [[ "${ftlBranch}" == "master" ]];then if [[ "${ftlBranch}" == "master" ]];then
url="https://github.com/pi-hole/FTL/releases/download/${latesttag%$'\r'}" url="https://github.com/pi-hole/FTL/releases/download/${latesttag%$'\r'}"
@ -2275,6 +2303,8 @@ get_binary_name() {
local machine local machine
machine=$(uname -m) machine=$(uname -m)
local l_binary
local str="Detecting architecture" local str="Detecting architecture"
printf " %b %s..." "${INFO}" "${str}" printf " %b %s..." "${INFO}" "${str}"
# If the machine is arm or aarch # If the machine is arm or aarch
@ -2290,24 +2320,30 @@ get_binary_name() {
if [[ "${lib}" == "/lib/ld-linux-aarch64.so.1" ]]; then if [[ "${lib}" == "/lib/ld-linux-aarch64.so.1" ]]; then
printf "%b %b Detected ARM-aarch64 architecture\\n" "${OVER}" "${TICK}" printf "%b %b Detected ARM-aarch64 architecture\\n" "${OVER}" "${TICK}"
# set the binary to be used # set the binary to be used
binary="pihole-FTL-aarch64-linux-gnu" l_binary="pihole-FTL-aarch64-linux-gnu"
# #
elif [[ "${lib}" == "/lib/ld-linux-armhf.so.3" ]]; then elif [[ "${lib}" == "/lib/ld-linux-armhf.so.3" ]]; then
# #
if [[ "${rev}" -gt 6 ]]; then if [[ "${rev}" -gt 6 ]]; then
printf "%b %b Detected ARM-hf architecture (armv7+)\\n" "${OVER}" "${TICK}" printf "%b %b Detected ARM-hf architecture (armv7+)\\n" "${OVER}" "${TICK}"
# set the binary to be used # set the binary to be used
binary="pihole-FTL-arm-linux-gnueabihf" l_binary="pihole-FTL-arm-linux-gnueabihf"
# Otherwise, # Otherwise,
else else
printf "%b %b Detected ARM-hf architecture (armv6 or lower) Using ARM binary\\n" "${OVER}" "${TICK}" printf "%b %b Detected ARM-hf architecture (armv6 or lower) Using ARM binary\\n" "${OVER}" "${TICK}"
# set the binary to be used # set the binary to be used
binary="pihole-FTL-arm-linux-gnueabi" l_binary="pihole-FTL-arm-linux-gnueabi"
fi fi
else else
printf "%b %b Detected ARM architecture\\n" "${OVER}" "${TICK}" if [[ -f "/.dockerenv" ]]; then
# set the binary to be used printf "%b %b Detected ARM architecture in docker\\n" "${OVER}" "${TICK}"
binary="pihole-FTL-arm-linux-gnueabi" # set the binary to be used
binary="pihole-FTL-armel-native"
else
printf "%b %b Detected ARM architecture\\n" "${OVER}" "${TICK}"
# set the binary to be used
binary="pihole-FTL-arm-linux-gnueabi"
fi
fi fi
elif [[ "${machine}" == "x86_64" ]]; then elif [[ "${machine}" == "x86_64" ]]; then
# This gives the architecture of packages dpkg installs (for example, "i386") # This gives the architecture of packages dpkg installs (for example, "i386")
@ -2320,12 +2356,12 @@ get_binary_name() {
# in the past (see https://github.com/pi-hole/pi-hole/pull/2004) # in the past (see https://github.com/pi-hole/pi-hole/pull/2004)
if [[ "${dpkgarch}" == "i386" ]]; then if [[ "${dpkgarch}" == "i386" ]]; then
printf "%b %b Detected 32bit (i686) architecture\\n" "${OVER}" "${TICK}" printf "%b %b Detected 32bit (i686) architecture\\n" "${OVER}" "${TICK}"
binary="pihole-FTL-linux-x86_32" l_binary="pihole-FTL-linux-x86_32"
else else
# 64bit # 64bit
printf "%b %b Detected x86_64 architecture\\n" "${OVER}" "${TICK}" printf "%b %b Detected x86_64 architecture\\n" "${OVER}" "${TICK}"
# set the binary to be used # set the binary to be used
binary="pihole-FTL-linux-x86_64" l_binary="pihole-FTL-linux-x86_64"
fi fi
else else
# Something else - we try to use 32bit executable and warn the user # Something else - we try to use 32bit executable and warn the user
@ -2336,13 +2372,13 @@ get_binary_name() {
else else
printf "%b %b Detected 32bit (i686) architecture\\n" "${OVER}" "${TICK}" printf "%b %b Detected 32bit (i686) architecture\\n" "${OVER}" "${TICK}"
fi fi
binary="pihole-FTL-linux-x86_32" l_binary="pihole-FTL-linux-x86_32"
fi fi
echo ${l_binary}
} }
FTLcheckUpdate() { FTLcheckUpdate() {
get_binary_name
#In the next section we check to see if FTL is already installed (in case of pihole -r). #In the next section we check to see if FTL is already installed (in case of pihole -r).
#If the installed version matches the latest version, then check the installed sha1sum of the binary vs the remote sha1sum. If they do not match, then download #If the installed version matches the latest version, then check the installed sha1sum of the binary vs the remote sha1sum. If they do not match, then download
printf " %b Checking for existing FTL binary...\\n" "${INFO}" printf " %b Checking for existing FTL binary...\\n" "${INFO}"
@ -2358,6 +2394,9 @@ FTLcheckUpdate() {
ftlBranch="master" ftlBranch="master"
fi fi
local binary
binary="${1}"
local remoteSha1 local remoteSha1
local localSha1 local localSha1
@ -2436,8 +2475,10 @@ FTLcheckUpdate() {
FTLdetect() { FTLdetect() {
printf "\\n %b FTL Checks...\\n\\n" "${INFO}" printf "\\n %b FTL Checks...\\n\\n" "${INFO}"
if FTLcheckUpdate ; then printf " %b" "${2}"
FTLinstall || return 1
if FTLcheckUpdate "${1}"; then
FTLinstall "${1}" || return 1
fi fi
} }
@ -2600,8 +2641,15 @@ main() {
fi fi
# Create the pihole user # Create the pihole user
create_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 local funcOutput
funcOutput=$(get_binary_name) #Store output of get_binary_name here
local binary
binary="pihole-FTL${funcOutput##*pihole-FTL}" #binary name will be the last line of the output of get_binary_name (it always begins with pihole-FTL)
local theRest
theRest="${funcOutput%pihole-FTL*}" # Print the rest of get_binary_name's output to display (cut out from first instance of "pihole-FTL")
if ! FTLdetect "${binary}" "${theRest}"; then
printf " %b FTL Engine not installed\\n" "${CROSS}" printf " %b FTL Engine not installed\\n" "${CROSS}"
exit 1 exit 1
fi fi

6
pihole
View File

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# Pi-hole: A black hole for Internet advertisements # Pi-hole: A black hole for Internet advertisements
# (c) 2017 Pi-hole, LLC (https://pi-hole.net) # (c) 2017 Pi-hole, LLC (https://pi-hole.net)
@ -11,8 +11,8 @@
readonly PI_HOLE_SCRIPT_DIR="/opt/pihole" readonly PI_HOLE_SCRIPT_DIR="/opt/pihole"
# 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 functions (checkout),
# it might get set again when the installer is sourced. This causes an # they might get set again when the installer is sourced. This causes an
# error due to modifying a readonly variable. # error due to modifying a readonly variable.
setupVars="/etc/pihole/setupVars.conf" setupVars="/etc/pihole/setupVars.conf"
PI_HOLE_BIN_DIR="/usr/local/bin" PI_HOLE_BIN_DIR="/usr/local/bin"

View File

@ -254,73 +254,16 @@ def test_configureFirewall_IPTables_enabled_not_exist_no_errors(Pihole):
assert len(re.findall(r'tcp --dport 4711:4720', firewall_calls)) == 2 assert len(re.findall(r'tcp --dport 4711:4720', firewall_calls)) == 2
def test_selinux_enforcing_default_exit(Pihole): def test_selinux_not_detected(Pihole):
''' '''
confirms installer prompts to exit when SELinux is Enforcing by default confirms installer continues when SELinux configuration file does not exist
''' '''
# getenforce returns the running state of SELinux
mock_command('getenforce', {'*': ('Enforcing', '0')}, Pihole)
# Whiptail dialog returns Cancel for user prompt
mock_command('whiptail', {'*': ('', '1')}, Pihole)
check_selinux = Pihole.run(''' check_selinux = Pihole.run('''
rm -f /etc/selinux/config
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
checkSelinux checkSelinux
''') ''')
expected_stdout = info_box + ' SELinux mode detected: Enforcing' expected_stdout = info_box + ' SELinux not detected'
assert expected_stdout in check_selinux.stdout
expected_stdout = 'SELinux Enforcing detected, exiting installer'
assert expected_stdout in check_selinux.stdout
assert check_selinux.rc == 1
def test_selinux_enforcing_continue(Pihole):
'''
confirms installer prompts to continue with custom policy warning
'''
# getenforce returns the running state of SELinux
mock_command('getenforce', {'*': ('Enforcing', '0')}, Pihole)
# Whiptail dialog returns Continue for user prompt
mock_command('whiptail', {'*': ('', '0')}, Pihole)
check_selinux = Pihole.run('''
source /opt/pihole/basic-install.sh
checkSelinux
''')
expected_stdout = info_box + ' SELinux mode detected: Enforcing'
assert expected_stdout in check_selinux.stdout
expected_stdout = info_box + (' Continuing installation with SELinux '
'Enforcing')
assert expected_stdout in check_selinux.stdout
expected_stdout = info_box + (' Please refer to official SELinux '
'documentation to create a custom policy')
assert expected_stdout in check_selinux.stdout
assert check_selinux.rc == 0
def test_selinux_permissive(Pihole):
'''
confirms installer continues when SELinux is Permissive
'''
# getenforce returns the running state of SELinux
mock_command('getenforce', {'*': ('Permissive', '0')}, Pihole)
check_selinux = Pihole.run('''
source /opt/pihole/basic-install.sh
checkSelinux
''')
expected_stdout = info_box + ' SELinux mode detected: Permissive'
assert expected_stdout in check_selinux.stdout
assert check_selinux.rc == 0
def test_selinux_disabled(Pihole):
'''
confirms installer continues when SELinux is Disabled
'''
mock_command('getenforce', {'*': ('Disabled', '0')}, Pihole)
check_selinux = Pihole.run('''
source /opt/pihole/basic-install.sh
checkSelinux
''')
expected_stdout = info_box + ' SELinux mode detected: Disabled'
assert expected_stdout in check_selinux.stdout assert expected_stdout in check_selinux.stdout
assert check_selinux.rc == 0 assert check_selinux.rc == 0
@ -399,7 +342,10 @@ 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 create_pihole_user
FTLdetect funcOutput=$(get_binary_name)
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}"
''') ''')
expected_stdout = info_box + ' FTL Checks...' expected_stdout = info_box + ' FTL Checks...'
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
@ -420,7 +366,10 @@ def test_FTL_detect_armv6l_no_errors(Pihole):
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
create_pihole_user create_pihole_user
FTLdetect funcOutput=$(get_binary_name)
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}"
''') ''')
expected_stdout = info_box + ' FTL Checks...' expected_stdout = info_box + ' FTL Checks...'
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
@ -442,7 +391,10 @@ def test_FTL_detect_armv7l_no_errors(Pihole):
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
create_pihole_user create_pihole_user
FTLdetect funcOutput=$(get_binary_name)
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}"
''') ''')
expected_stdout = info_box + ' FTL Checks...' expected_stdout = info_box + ' FTL Checks...'
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
@ -459,7 +411,10 @@ 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 create_pihole_user
FTLdetect funcOutput=$(get_binary_name)
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}"
''') ''')
expected_stdout = info_box + ' FTL Checks...' expected_stdout = info_box + ' FTL Checks...'
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
@ -476,7 +431,10 @@ def test_FTL_detect_unknown_no_errors(Pihole):
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
create_pihole_user create_pihole_user
FTLdetect funcOutput=$(get_binary_name)
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}"
''') ''')
expected_stdout = 'Not able to detect architecture (unknown: mips)' expected_stdout = 'Not able to detect architecture (unknown: mips)'
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
@ -495,64 +453,14 @@ 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"
create_pihole_user create_pihole_user
FTLinstall FTLinstall "pihole-FTL-aarch64-linux-gnu"
''') ''')
expected_stdout = tick_box + ' Downloading and Installing FTL' expected_stdout = tick_box + ' Downloading and Installing FTL'
assert expected_stdout in download_binary.stdout assert expected_stdout in download_binary.stdout
assert 'error' not in download_binary.stdout.lower() assert 'error' not in download_binary.stdout.lower()
def test_FTL_download_unknown_fails_no_errors(Pihole):
'''
confirms unknown binary is not downloaded for FTL engine
'''
# mock whiptail answers and ensure installer dependencies
mock_command('whiptail', {'*': ('', '0')}, Pihole)
Pihole.run('''
source /opt/pihole/basic-install.sh
distro_check
install_dependent_packages ${INSTALLER_DEPS[@]}
''')
download_binary = Pihole.run('''
source /opt/pihole/basic-install.sh
binary="pihole-FTL-mips"
create_pihole_user
FTLinstall
''')
expected_stdout = cross_box + ' Downloading and Installing FTL'
assert expected_stdout in download_binary.stdout
error1 = 'Error: URL https://github.com/pi-hole/FTL/releases/download/'
assert error1 in download_binary.stdout
error2 = 'not found'
assert error2 in download_binary.stdout
def test_FTL_download_binary_unset_no_errors(Pihole):
'''
confirms unset binary variable does not download FTL engine
'''
# mock whiptail answers and ensure installer dependencies
mock_command('whiptail', {'*': ('', '0')}, Pihole)
Pihole.run('''
source /opt/pihole/basic-install.sh
distro_check
install_dependent_packages ${INSTALLER_DEPS[@]}
''')
download_binary = Pihole.run('''
source /opt/pihole/basic-install.sh
create_pihole_user
FTLinstall
''')
expected_stdout = cross_box + ' Downloading and Installing FTL'
assert expected_stdout in download_binary.stdout
error1 = 'Error: URL https://github.com/pi-hole/FTL/releases/download/'
assert error1 in download_binary.stdout
error2 = 'not found'
assert error2 in download_binary.stdout
def test_FTL_binary_installed_and_responsive_no_errors(Pihole): def test_FTL_binary_installed_and_responsive_no_errors(Pihole):
''' '''
confirms FTL binary is copied and functional in installed location confirms FTL binary is copied and functional in installed location
@ -560,7 +468,10 @@ 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 create_pihole_user
FTLdetect funcOutput=$(get_binary_name)
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}"
pihole-FTL version pihole-FTL version
''') ''')
expected_stdout = 'v' expected_stdout = 'v'

View File

@ -8,6 +8,69 @@ from conftest import (
) )
def mock_selinux_config(state, Pihole):
'''
Creates a mock SELinux config file with expected content
'''
# validate state string
valid_states = ['enforcing', 'permissive', 'disabled']
assert state in valid_states
# getenforce returns the running state of SELinux
mock_command('getenforce', {'*': (state.capitalize(), '0')}, Pihole)
# create mock configuration with desired content
Pihole.run('''
mkdir /etc/selinux
echo "SELINUX={state}" > /etc/selinux/config
'''.format(state=state.lower()))
@pytest.mark.parametrize("tag", [('centos'), ('fedora'), ])
def test_selinux_enforcing_exit(Pihole):
'''
confirms installer prompts to exit when SELinux is Enforcing by default
'''
mock_selinux_config("enforcing", Pihole)
check_selinux = Pihole.run('''
source /opt/pihole/basic-install.sh
checkSelinux
''')
expected_stdout = cross_box + ' Current SELinux: Enforcing'
assert expected_stdout in check_selinux.stdout
expected_stdout = 'SELinux Enforcing detected, exiting installer'
assert expected_stdout in check_selinux.stdout
assert check_selinux.rc == 1
@pytest.mark.parametrize("tag", [('centos'), ('fedora'), ])
def test_selinux_permissive(Pihole):
'''
confirms installer continues when SELinux is Permissive
'''
mock_selinux_config("permissive", Pihole)
check_selinux = Pihole.run('''
source /opt/pihole/basic-install.sh
checkSelinux
''')
expected_stdout = tick_box + ' Current SELinux: Permissive'
assert expected_stdout in check_selinux.stdout
assert check_selinux.rc == 0
@pytest.mark.parametrize("tag", [('centos'), ('fedora'), ])
def test_selinux_disabled(Pihole):
'''
confirms installer continues when SELinux is Disabled
'''
mock_selinux_config("disabled", Pihole)
check_selinux = Pihole.run('''
source /opt/pihole/basic-install.sh
checkSelinux
''')
expected_stdout = tick_box + ' Current SELinux: Disabled'
assert expected_stdout in check_selinux.stdout
assert check_selinux.rc == 0
@pytest.mark.parametrize("tag", [('fedora'), ]) @pytest.mark.parametrize("tag", [('fedora'), ])
def test_epel_and_remi_not_installed_fedora(Pihole): def test_epel_and_remi_not_installed_fedora(Pihole):
''' '''