diff --git a/advanced/01-pihole.conf b/advanced/01-pihole.conf index cd74e186..2c8b3749 100644 --- a/advanced/01-pihole.conf +++ b/advanced/01-pihole.conf @@ -19,6 +19,7 @@ ############################################################################### addn-hosts=/etc/pihole/local.list +addn-hosts=/etc/pihole/custom.list domain-needed diff --git a/advanced/Scripts/database_migration/gravity-db.sh b/advanced/Scripts/database_migration/gravity-db.sh index 1fe6a61f..508d9c56 100644 --- a/advanced/Scripts/database_migration/gravity-db.sh +++ b/advanced/Scripts/database_migration/gravity-db.sh @@ -44,17 +44,24 @@ upgrade_gravityDB(){ version=3 fi if [[ "$version" == "3" ]]; then - # This migration script upgrades the gravity and list views - # implementing necessary changes for per-client blocking + # This migration script unifies the formally separated domain + # lists into a single table with a UNIQUE domain constraint echo -e " ${INFO} Upgrading gravity database from version 3 to 4" sqlite3 "${database}" < "${scriptPath}/3_to_4.sql" version=4 fi if [[ "$version" == "4" ]]; then - # This migration script upgrades the adlist view - # to return an ID used in gravity.sh + # This migration script upgrades the gravity and list views + # implementing necessary changes for per-client blocking echo -e " ${INFO} Upgrading gravity database from version 4 to 5" sqlite3 "${database}" < "${scriptPath}/4_to_5.sql" version=5 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 } diff --git a/advanced/Scripts/database_migration/gravity/3_to_4.sql b/advanced/Scripts/database_migration/gravity/3_to_4.sql index 182d24a1..352b1baa 100644 --- a/advanced/Scripts/database_migration/gravity/3_to_4.sql +++ b/advanced/Scripts/database_migration/gravity/3_to_4.sql @@ -4,67 +4,93 @@ PRAGMA FOREIGN_KEYS=OFF; BEGIN TRANSACTION; -DROP TABLE gravity; -CREATE TABLE gravity +CREATE TABLE domainlist ( - domain TEXT NOT NULL, - adlist_id INTEGER NOT NULL REFERENCES adlist (id), - PRIMARY KEY(domain, adlist_id) + id INTEGER PRIMARY KEY AUTOINCREMENT, + 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 +); + +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 +( + domainlist_id INTEGER NOT NULL REFERENCES domainlist (id), + group_id INTEGER NOT NULL REFERENCES "group" (id), + PRIMARY KEY (domainlist_id, group_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 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, 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; +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, 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; +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 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; +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 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, - 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) -); +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'; -COMMIT; +COMMIT; \ No newline at end of file diff --git a/advanced/Scripts/database_migration/gravity/4_to_5.sql b/advanced/Scripts/database_migration/gravity/4_to_5.sql index 22b75d58..2ad906fc 100644 --- a/advanced/Scripts/database_migration/gravity/4_to_5.sql +++ b/advanced/Scripts/database_migration/gravity/4_to_5.sql @@ -4,14 +4,35 @@ 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 +DROP TABLE gravity; +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) - ORDER BY adlist.id; + WHERE adlist.enabled = 1 AND (adlist_by_group.group_id IS NULL OR "group".enabled = 1); + +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'; -COMMIT; +COMMIT; \ No newline at end of file diff --git a/advanced/Scripts/database_migration/gravity/5_to_6.sql b/advanced/Scripts/database_migration/gravity/5_to_6.sql new file mode 100644 index 00000000..d2bb3145 --- /dev/null +++ b/advanced/Scripts/database_migration/gravity/5_to_6.sql @@ -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; + diff --git a/advanced/Scripts/list.sh b/advanced/Scripts/list.sh index 6a606665..c5bf5b2a 100755 --- a/advanced/Scripts/list.sh +++ b/advanced/Scripts/list.sh @@ -21,68 +21,78 @@ web=false domList=() -listType="" -listname="" +typeId="" colfile="/opt/pihole/COL_TABLE" 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 +} -helpFunc() { - if [[ "${listType}" == "whitelist" ]]; then - param="w" - type="whitelist" - elif [[ "${listType}" == "regex_blacklist" && "${wildcard}" == true ]]; then - 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" +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() { + local listname param + + listname="$(GetListnameFromTypeId "${typeId}")" + param="$(GetListParamFromTypeId)" echo "Usage: pihole -${param} [options] Example: 'pihole -${param} site.com', or 'pihole -${param} site1.com site2.com' -${type^} one or more domains +${listname^} one or more domains Options: - -d, --delmode Remove domain(s) from the ${type} - -nr, --noreload Update ${type} without reloading the DNS server + -d, --delmode Remove domain(s) from the ${listname} + -nr, --noreload Update ${listname} without reloading the DNS server -q, --quiet Make output less verbose -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" exit 0 } -EscapeRegexp() { - # 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() { +ValidateDomain() { # Convert to lowercase domain="${1,,}" # Check validity of domain (don't check for regex entries) 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}" else - 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 + # Use regex to check the validity of the passed domain. see https://regexr.com/3abjr + 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 @@ -94,21 +104,6 @@ HandleOther() { } 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 # Format domain into regex filter if requested if [[ "${wildcard}" == true ]]; then @@ -118,77 +113,82 @@ ProcessDomainList() { # 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 - AddDomain "${dom}" "${listType}" - if ! ${is_regexlist}; then - RemoveDomain "${dom}" "${listAlt}" - fi + AddDomain "${dom}" else - RemoveDomain "${dom}" "${listType}" + RemoveDomain "${dom}" fi done } AddDomain() { - local domain list num - # Use printf to escape domain. %q prints the argument in a form that can be reused as shell input + local domain num requestedListname existingTypeId existingListname domain="$1" - list="$2" # 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 [[ "${verbose}" == true ]]; then - echo -e " ${INFO} ${1} already exists in ${listname}, no need to add!" + existingTypeId="$(sqlite3 "${gravityDBfile}" "SELECT type FROM domainlist WHERE domain = '${domain}';")" + 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 return fi # Domain not found in the table, add it! if [[ "${verbose}" == true ]]; then - echo -e " ${INFO} Adding ${1} to the ${listname}..." + echo -e " ${INFO} Adding ${domain} to the ${requestedListname}..." 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}');" + sqlite3 "${gravityDBfile}" "INSERT INTO domainlist (domain,type) VALUES ('${domain}',${typeId});" } RemoveDomain() { - local domain list num - # Use printf to escape domain. %q prints the argument in a form that can be reused as shell input + local domain num requestedListname domain="$1" - list="$2" # 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 [[ "${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 return fi # Domain found in the table, remove it! if [[ "${verbose}" == true ]]; then - echo -e " ${INFO} Removing ${1} from the ${listname}..." + echo -e " ${INFO} Removing ${domain} from the ${requestedListname}..." fi reload=true # 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() { - local list listname count num_pipes domain enabled status nicedate + local count num_pipes domain enabled status nicedate requestedListname - listname="${listType}" - data="$(sqlite3 "${gravityDBfile}" "SELECT domain,enabled,date_modified FROM ${listType};" 2> /dev/null)" + requestedListname="$(GetListnameFromTypeId "${typeId}")" + data="$(sqlite3 "${gravityDBfile}" "SELECT domain,enabled,date_modified FROM domainlist WHERE type = ${typeId};" 2> /dev/null)" if [[ -z $data ]]; then echo -e "Not showing empty list" else - echo -e "Displaying ${listname}:" + echo -e "Displaying ${requestedListname}:" count=1 while IFS= read -r line do @@ -221,17 +221,17 @@ Displaylist() { } NukeList() { - sqlite3 "${gravityDBfile}" "DELETE FROM ${listType};" + sqlite3 "${gravityDBfile}" "DELETE FROM domainlist WHERE type = ${typeId};" } for var in "$@"; do case "${var}" in - "-w" | "whitelist" ) listType="whitelist"; listAlt="blacklist";; - "-b" | "blacklist" ) listType="blacklist"; listAlt="whitelist";; - "--wild" | "wildcard" ) listType="regex_blacklist"; wildcard=true;; - "--regex" | "regex" ) listType="regex_blacklist";; - "--white-regex" | "white-regex" ) listType="regex_whitelist";; - "--white-wild" | "white-wild" ) listType="regex_whitelist"; wildcard=true;; + "-w" | "whitelist" ) typeId=0;; + "-b" | "blacklist" ) typeId=1;; + "--white-regex" | "white-regex" ) typeId=2;; + "--white-wild" | "white-wild" ) typeId=2; wildcard=true;; + "--wild" | "wildcard" ) typeId=3; wildcard=true;; + "--regex" | "regex" ) typeId=3;; "-nr"| "--noreload" ) reload=false;; "-d" | "--delmode" ) addmode=false;; "-q" | "--quiet" ) verbose=false;; @@ -239,7 +239,7 @@ for var in "$@"; do "-l" | "--list" ) Displaylist;; "--nuke" ) NukeList;; "--web" ) web=true;; - * ) HandleOther "${var}";; + * ) ValidateDomain "${var}";; esac done diff --git a/advanced/Scripts/piholeCheckout.sh b/advanced/Scripts/piholeCheckout.sh index 673ded0b..31009dd9 100644 --- a/advanced/Scripts/piholeCheckout.sh +++ b/advanced/Scripts/piholeCheckout.sh @@ -46,6 +46,12 @@ checkout() { local corebranches 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 set -f @@ -86,7 +92,6 @@ checkout() { fi #echo -e " ${TICK} Pi-hole Core" - get_binary_name local path path="development/${binary}" 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; } fi #echo -e " ${TICK} Web Interface" - get_binary_name local path path="master/${binary}" echo "master" > /etc/pihole/ftlbranch @@ -161,7 +165,6 @@ checkout() { fi checkout_pull_branch "${webInterfaceDir}" "${2}" elif [[ "${1}" == "ftl" ]] ; then - get_binary_name local path path="${2}/${binary}" diff --git a/advanced/Scripts/update.sh b/advanced/Scripts/update.sh index 4d352777..e45be5cf 100755 --- a/advanced/Scripts/update.sh +++ b/advanced/Scripts/update.sh @@ -31,7 +31,6 @@ source "/opt/pihole/COL_TABLE" # make_repo() sourced from basic-install.sh # update_repo() source from basic-install.sh # getGitFiles() sourced from basic-install.sh -# get_binary_name() sourced from basic-install.sh # FTLcheckUpdate() sourced from basic-install.sh GitCheckUpdateAvail() { @@ -129,7 +128,12 @@ main() { 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 echo -e " ${INFO} FTL:\\t\\t${COL_YELLOW}update available${COL_NC}" else diff --git a/advanced/Scripts/webpage.sh b/advanced/Scripts/webpage.sh index 411cc1f6..829ba57b 100755 --- a/advanced/Scripts/webpage.sh +++ b/advanced/Scripts/webpage.sh @@ -17,6 +17,7 @@ readonly FTLconf="/etc/pihole/pihole-FTL.conf" # 03 -> wildcards readonly dhcpstaticconfig="/etc/dnsmasq.d/04-pihole-static-dhcp.conf" readonly PI_HOLE_BIN_DIR="/usr/local/bin" +readonly dnscustomfile="/etc/pihole/custom.list" readonly gravityDBfile="/etc/pihole/gravity.db" @@ -404,13 +405,15 @@ SetWebUILayout() { CustomizeAdLists() { local address address="${args[3]}" + local comment + comment="${args[4]}" if [[ "${args[2]}" == "enable" ]]; then sqlite3 "${gravityDBfile}" "UPDATE adlist SET enabled = 1 WHERE address = '${address}'" elif [[ "${args[2]}" == "disable" ]]; then sqlite3 "${gravityDBfile}" "UPDATE adlist SET enabled = 0 WHERE address = '${address}'" 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 sqlite3 "${gravityDBfile}" "DELETE FROM adlist WHERE address = '${address}'" else @@ -524,10 +527,10 @@ Interfaces: fi 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" 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" else echo -e " ${INFO} Listening only on interface ${PIHOLE_INTERFACE}" @@ -597,6 +600,28 @@ SetPrivacyLevel() { 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() { args=("$@") @@ -628,6 +653,8 @@ main() { "audit" ) addAudit "$@";; "clearaudit" ) clearAudit;; "-l" | "privacylevel" ) SetPrivacyLevel;; + "addcustomdns" ) AddCustomDNSAddress;; + "removecustomdns" ) RemoveCustomDNSAddress;; * ) helpFunc;; esac diff --git a/advanced/Scripts/wildcard_regex_converter.sh b/advanced/Scripts/wildcard_regex_converter.sh index 8c9578a3..b4b6b4a1 100644 --- a/advanced/Scripts/wildcard_regex_converter.sh +++ b/advanced/Scripts/wildcard_regex_converter.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Pi-hole: A black hole for Internet advertisements # (c) 2017 Pi-hole, LLC (https://pi-hole.net) # Network-wide ad blocking via your own hardware. diff --git a/advanced/Templates/pihole-FTL.service b/advanced/Templates/pihole-FTL.service index 8a4c7ce6..5dbf080e 100644 --- a/advanced/Templates/pihole-FTL.service +++ b/advanced/Templates/pihole-FTL.service @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash ### BEGIN INIT INFO # Provides: pihole-FTL # Required-Start: $remote_fs $syslog diff --git a/automated install/basic-install.sh b/automated install/basic-install.sh index cc78afbf..72d46840 100755 --- a/automated install/basic-install.sh +++ b/automated install/basic-install.sh @@ -138,9 +138,6 @@ else OVER="\\r\\033[K" fi -# Define global binary variable -binary="tbd" - # A simple function that just echoes out our logo in ASCII format # This lets users know that it is a Pi-hole, LLC product 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) # 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 - 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, LIGHTTPD_USER="www-data" # group, @@ -290,7 +287,7 @@ elif is_command rpm ; then PKG_COUNT="${PKG_MANAGER} check-update | egrep '(.i686|.x86|.noarch|.arm|.src)' | wc -l" 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_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_GROUP="lighttpd" LIGHTTPD_CFG="lighttpd.conf.fedora" @@ -838,7 +835,8 @@ setDHCPCD() { # Then use the ip command to immediately set the new address ip addr replace dev "${PIHOLE_INTERFACE}" "${IPV4_ADDRESS}" # 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 } @@ -984,8 +982,6 @@ setDNS() { # exit if Cancel is selected { 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 if [[ "${DNSchoices}" == "Custom" ]] then @@ -1037,14 +1033,14 @@ setDNS() { if [[ "${PIHOLE_DNS_2}" == "${strInvalid}" ]]; then PIHOLE_DNS_2="" fi - # Since the settings will not work, stay in the loop - DNSSettingsCorrect=False + # Since the settings will not work, stay in the loop + DNSSettingsCorrect=False # Otherwise, else # 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 - # and break from the loop since the servers are valid - DNSSettingsCorrect=True + # and break from the loop since the servers are valid + DNSSettingsCorrect=True # Otherwise, else # If the settings are wrong, the loop continues @@ -1052,7 +1048,7 @@ setDNS() { fi fi done - else + else # Save the old Internal Field Separator in a variable OIFS=$IFS # and set the new one to newline @@ -1062,7 +1058,6 @@ setDNS() { DNSName="$(cut -d';' -f1 <<< "${DNSServer}")" if [[ "${DNSchoices}" == "${DNSName}" ]] then - printf "%s\\n" "${DNSName}" PIHOLE_DNS_1="$(cut -d';' -f2 <<< "${DNSServer}")" PIHOLE_DNS_2="$(cut -d';' -f3 <<< "${DNSServer}")" break @@ -1071,6 +1066,11 @@ setDNS() { # Restore the IFS to what it was IFS=${OIFS} 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 @@ -1628,7 +1628,7 @@ install_dependent_packages() { 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}" 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}") fi done @@ -1645,9 +1645,9 @@ install_dependent_packages() { for i in "$@"; do printf " %b Checking for %s..." "${INFO}" "${i}" 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 - 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}") fi done @@ -1959,20 +1959,42 @@ installPihole() { # SELinux checkSelinux() { - # If the getenforce command exists, - if is_command getenforce ; then - # Store the current mode in a variable - enforceMode=$(getenforce) - printf "\\n %b SELinux mode detected: %s\\n" "${INFO}" "${enforceMode}" - - # If it's enforcing, - if [[ "${enforceMode}" == "Enforcing" ]]; then - # 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}" || \ - { 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 Please refer to official SELinux documentation to create a custom policy\\n" "${INFO}" - fi + local DEFAULT_SELINUX + local CURRENT_SELINUX + local SELINUX_ENFORCING=0 + # Check if a SELinux configuration file exists + if [[ -f /etc/selinux/config ]]; then + # If a SELinux configuration file was found, check the default SELinux mode. + DEFAULT_SELINUX=$(awk -F= '/^SELINUX=/ {print $2}' /etc/selinux/config) + case "${DEFAULT_SELINUX,,}" in + enforcing) + printf " %b %bDefault SELinux: %s%b\\n" "${CROSS}" "${COL_RED}" "${DEFAULT_SELINUX}" "${COL_NC}" + SELINUX_ENFORCING=1 + ;; + *) # 'permissive' and 'disabled' + 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 } @@ -2167,7 +2189,10 @@ clone_or_update_repos() { } # 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() { + # Local, named variables local latesttag local str="Downloading and Installing FTL" @@ -2197,6 +2222,9 @@ FTLinstall() { ftlBranch="master" fi + local binary + binary="${1}" + # Determine which version of FTL to download if [[ "${ftlBranch}" == "master" ]];then url="https://github.com/pi-hole/FTL/releases/download/${latesttag%$'\r'}" @@ -2275,6 +2303,8 @@ get_binary_name() { local machine machine=$(uname -m) + local l_binary + local str="Detecting architecture" printf " %b %s..." "${INFO}" "${str}" # If the machine is arm or aarch @@ -2290,24 +2320,30 @@ get_binary_name() { if [[ "${lib}" == "/lib/ld-linux-aarch64.so.1" ]]; then printf "%b %b Detected ARM-aarch64 architecture\\n" "${OVER}" "${TICK}" # 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 # if [[ "${rev}" -gt 6 ]]; then printf "%b %b Detected ARM-hf architecture (armv7+)\\n" "${OVER}" "${TICK}" # set the binary to be used - binary="pihole-FTL-arm-linux-gnueabihf" + l_binary="pihole-FTL-arm-linux-gnueabihf" # Otherwise, else printf "%b %b Detected ARM-hf architecture (armv6 or lower) Using ARM binary\\n" "${OVER}" "${TICK}" # set the binary to be used - binary="pihole-FTL-arm-linux-gnueabi" + l_binary="pihole-FTL-arm-linux-gnueabi" fi else - printf "%b %b Detected ARM architecture\\n" "${OVER}" "${TICK}" - # set the binary to be used - binary="pihole-FTL-arm-linux-gnueabi" + if [[ -f "/.dockerenv" ]]; then + printf "%b %b Detected ARM architecture in docker\\n" "${OVER}" "${TICK}" + # 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 elif [[ "${machine}" == "x86_64" ]]; then # 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) if [[ "${dpkgarch}" == "i386" ]]; then printf "%b %b Detected 32bit (i686) architecture\\n" "${OVER}" "${TICK}" - binary="pihole-FTL-linux-x86_32" + l_binary="pihole-FTL-linux-x86_32" else # 64bit printf "%b %b Detected x86_64 architecture\\n" "${OVER}" "${TICK}" # set the binary to be used - binary="pihole-FTL-linux-x86_64" + l_binary="pihole-FTL-linux-x86_64" fi else # Something else - we try to use 32bit executable and warn the user @@ -2336,13 +2372,13 @@ get_binary_name() { else printf "%b %b Detected 32bit (i686) architecture\\n" "${OVER}" "${TICK}" fi - binary="pihole-FTL-linux-x86_32" + l_binary="pihole-FTL-linux-x86_32" fi + + echo ${l_binary} } FTLcheckUpdate() { - get_binary_name - #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 printf " %b Checking for existing FTL binary...\\n" "${INFO}" @@ -2358,6 +2394,9 @@ FTLcheckUpdate() { ftlBranch="master" fi + local binary + binary="${1}" + local remoteSha1 local localSha1 @@ -2436,8 +2475,10 @@ FTLcheckUpdate() { FTLdetect() { printf "\\n %b FTL Checks...\\n\\n" "${INFO}" - if FTLcheckUpdate ; then - FTLinstall || return 1 + printf " %b" "${2}" + + if FTLcheckUpdate "${1}"; then + FTLinstall "${1}" || return 1 fi } @@ -2600,8 +2641,15 @@ main() { 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 - 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}" exit 1 fi diff --git a/pihole b/pihole index 971595d5..f0195843 100755 --- a/pihole +++ b/pihole @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Pi-hole: A black hole for Internet advertisements # (c) 2017 Pi-hole, LLC (https://pi-hole.net) @@ -11,8 +11,8 @@ readonly PI_HOLE_SCRIPT_DIR="/opt/pihole" -# 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 +# setupVars and PI_HOLE_BIN_DIR are not readonly here because in some functions (checkout), +# they might get set again when the installer is sourced. This causes an # error due to modifying a readonly variable. setupVars="/etc/pihole/setupVars.conf" PI_HOLE_BIN_DIR="/usr/local/bin" diff --git a/test/test_automated_install.py b/test/test_automated_install.py index e8a4dede..567ea241 100644 --- a/test/test_automated_install.py +++ b/test/test_automated_install.py @@ -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 -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(''' - source /opt/pihole/basic-install.sh - checkSelinux - ''') - expected_stdout = info_box + ' SELinux mode detected: 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 - - -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(''' + rm -f /etc/selinux/config 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' + expected_stdout = info_box + ' SELinux not detected' assert expected_stdout in check_selinux.stdout assert check_selinux.rc == 0 @@ -399,7 +342,10 @@ def test_FTL_detect_aarch64_no_errors(Pihole): detectPlatform = Pihole.run(''' source /opt/pihole/basic-install.sh 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...' assert expected_stdout in detectPlatform.stdout @@ -420,7 +366,10 @@ def test_FTL_detect_armv6l_no_errors(Pihole): detectPlatform = Pihole.run(''' source /opt/pihole/basic-install.sh 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...' assert expected_stdout in detectPlatform.stdout @@ -442,7 +391,10 @@ def test_FTL_detect_armv7l_no_errors(Pihole): detectPlatform = Pihole.run(''' source /opt/pihole/basic-install.sh 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...' assert expected_stdout in detectPlatform.stdout @@ -459,7 +411,10 @@ def test_FTL_detect_x86_64_no_errors(Pihole): detectPlatform = Pihole.run(''' source /opt/pihole/basic-install.sh 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...' assert expected_stdout in detectPlatform.stdout @@ -476,7 +431,10 @@ def test_FTL_detect_unknown_no_errors(Pihole): detectPlatform = Pihole.run(''' source /opt/pihole/basic-install.sh 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)' assert expected_stdout in detectPlatform.stdout @@ -495,64 +453,14 @@ def test_FTL_download_aarch64_no_errors(Pihole): ''') download_binary = Pihole.run(''' source /opt/pihole/basic-install.sh - binary="pihole-FTL-aarch64-linux-gnu" create_pihole_user - FTLinstall + FTLinstall "pihole-FTL-aarch64-linux-gnu" ''') expected_stdout = tick_box + ' Downloading and Installing FTL' assert expected_stdout in download_binary.stdout 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): ''' 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(''' source /opt/pihole/basic-install.sh create_pihole_user - FTLdetect + funcOutput=$(get_binary_name) + binary="pihole-FTL${funcOutput##*pihole-FTL}" + theRest="${funcOutput%pihole-FTL*}" + FTLdetect "${binary}" "${theRest}" pihole-FTL version ''') expected_stdout = 'v' diff --git a/test/test_centos_fedora_support.py b/test/test_centos_fedora_support.py index df53d73f..aee16212 100644 --- a/test/test_centos_fedora_support.py +++ b/test/test_centos_fedora_support.py @@ -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'), ]) def test_epel_and_remi_not_installed_fedora(Pihole): '''