From 520641fa5e26e54f92d53d8e4c4d16d2b702fbfd Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 19 Jun 2024 22:18:11 +0200 Subject: [PATCH 01/18] Try to use the CLI password for logins (if enabled and readable by the current user) Signed-off-by: DL6ER --- advanced/Scripts/api.sh | 12 +++++++++--- advanced/Scripts/query.sh | 16 ++++------------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/advanced/Scripts/api.sh b/advanced/Scripts/api.sh index 18a48ce7..efffa25a 100755 --- a/advanced/Scripts/api.sh +++ b/advanced/Scripts/api.sh @@ -75,9 +75,15 @@ TestAPIAvailability() { } Authentication() { - # Try to authenticate - LoginAPI + # Try to read the CLI password (if enabled and readable by the current user) + if [ -r /etc/pihole/cli_pw ]; then + password=$(cat /etc/pihole/cli_pw) + # Try to authenticate using the CLI password + LoginAPI + fi + + # If this did not work, ask the user for the password while [ "${validSession}" = false ] || [ -z "${validSession}" ] ; do echo "Authentication failed. Please enter your Pi-hole password" @@ -105,7 +111,7 @@ LoginAPI() { SID=$(echo "${sessionResponse}"| jq --raw-output .session.sid 2>/dev/null) } -DeleteSession() { +LogoutAPI() { # if a valid Session exists (no password required or successful Authentication) and # SID is not null (successful Authentication only), delete the session if [ "${validSession}" = true ] && [ ! "${SID}" = null ]; then diff --git a/advanced/Scripts/query.sh b/advanced/Scripts/query.sh index 493c75ea..123eee21 100755 --- a/advanced/Scripts/query.sh +++ b/advanced/Scripts/query.sh @@ -128,22 +128,14 @@ Main() { # Test if the authentication endpoint is available TestAPIAvailability - # Users can configure FTL in a way, that for accessing a) all endpoints (webserver.api.localAPIauth) - # or b) for the /search endpoint (webserver.api.searchAPIauth) no authentication is required. - # Therefore, we try to query directly without authentication but do authenticat if 401 is returned + # Authenticate with FTL + Authentication + # send query again data=$(GetFTLData "search/${domain}?N=${max_results}&partial=${partial}") - if [ "${data}" = 401 ]; then - # Unauthenticated, so authenticate with the FTL server required - Authentication - - # send query again - data=$(GetFTLData "search/${domain}?N=${max_results}&partial=${partial}") - fi - GenerateOutput "${data}" - DeleteSession + LogoutAPI } # Process all options (if present) From a2951cd3b8c220d09e7e70a24a747cda1a9bf129 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 19 Jun 2024 22:19:54 +0200 Subject: [PATCH 02/18] Hide successful login/logout messages to avoid cluttering the terminal Signed-off-by: DL6ER --- advanced/Scripts/api.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/advanced/Scripts/api.sh b/advanced/Scripts/api.sh index efffa25a..3d35742f 100755 --- a/advanced/Scripts/api.sh +++ b/advanced/Scripts/api.sh @@ -94,9 +94,6 @@ Authentication() { LoginAPI done - # Loop exited, authentication was successful - echo "Authentication successful." - } LoginAPI() { @@ -119,7 +116,6 @@ LogoutAPI() { deleteResponse=$(curl -skS -o /dev/null -w "%{http_code}" -X DELETE "${API_URL}auth" -H "Accept: application/json" -H "sid: ${SID}") case "${deleteResponse}" in - "204") printf "%b" "Session successfully deleted.\n";; "401") printf "%b" "Logout attempt without a valid session. Unauthorized!\n";; esac; fi From 4df7cee6c20bf3bbcf3ad806345f3b8dc82aced3 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 19 Jun 2024 22:21:43 +0200 Subject: [PATCH 03/18] Add partial matching hint if exact matching found nothing Signed-off-by: DL6ER --- advanced/Scripts/api.sh | 4 ++-- advanced/Scripts/query.sh | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/advanced/Scripts/api.sh b/advanced/Scripts/api.sh index 3d35742f..4162eff1 100755 --- a/advanced/Scripts/api.sh +++ b/advanced/Scripts/api.sh @@ -74,7 +74,7 @@ TestAPIAvailability() { fi } -Authentication() { +LoginAPI() { # Try to read the CLI password (if enabled and readable by the current user) if [ -r /etc/pihole/cli_pw ]; then password=$(cat /etc/pihole/cli_pw) @@ -96,7 +96,7 @@ Authentication() { } -LoginAPI() { +Authentication() { sessionResponse="$(curl -skS -X POST "${API_URL}auth" --user-agent "Pi-hole cli " --data "{\"password\":\"${password}\"}" )" if [ -z "${sessionResponse}" ]; then diff --git a/advanced/Scripts/query.sh b/advanced/Scripts/query.sh index 123eee21..c76e890e 100755 --- a/advanced/Scripts/query.sh +++ b/advanced/Scripts/query.sh @@ -112,6 +112,12 @@ GenerateOutput() { printf "\n\n" done fi + + # If no exact results were found, suggest using partial matching + if [ "${num_lists}" -eq 0 ] && [ "${num_gravity}" -eq 0 ] && [ "${partial}" = false ]; then + printf "%s\n" "Hint: Try partial matching with" + printf "%s\n\n" " ${COL_GREEN}pihole -q --partial ${domain}${COL_NC}" + fi } Main() { @@ -129,12 +135,14 @@ Main() { TestAPIAvailability # Authenticate with FTL - Authentication + LoginAPI # send query again data=$(GetFTLData "search/${domain}?N=${max_results}&partial=${partial}") GenerateOutput "${data}" + + # Delete session LogoutAPI } From 5cb9f4faaafd2fdb78c3e56b454ea65ea8687e05 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 19 Jun 2024 22:28:14 +0200 Subject: [PATCH 04/18] Modify pihole -f to use TOML config items Signed-off-by: DL6ER --- advanced/Scripts/piholeLogFlush.sh | 44 +++++++++++++++++------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/advanced/Scripts/piholeLogFlush.sh b/advanced/Scripts/piholeLogFlush.sh index 4d97fec5..892645af 100755 --- a/advanced/Scripts/piholeLogFlush.sh +++ b/advanced/Scripts/piholeLogFlush.sh @@ -11,27 +11,29 @@ colfile="/opt/pihole/COL_TABLE" source ${colfile} +readonly PI_HOLE_SCRIPT_DIR="/opt/pihole" +utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh" +source "${utilsfile}" + # In case we're running at the same time as a system logrotate, use a # separate logrotate state file to prevent stepping on each other's # toes. STATEFILE="/var/lib/logrotate/pihole" # Determine database location -# Obtain DBFILE=... setting from pihole-FTL.db -# Constructed to return nothing when -# a) the setting is not present in the config file, or -# b) the setting is commented out (e.g. "#DBFILE=...") -FTLconf="/etc/pihole/pihole-FTL.conf" -if [ -e "$FTLconf" ]; then - DBFILE="$(sed -n -e 's/^\s*DBFILE\s*=\s*//p' ${FTLconf})" -fi -# Test for empty string. Use standard path in this case. +DBFILE=$(getFTLConfigValue "files.database") if [ -z "$DBFILE" ]; then DBFILE="/etc/pihole/pihole-FTL.db" fi +# Determine log file location +LOGFILE=$(getFTLConfigValue "files.log.dnsmasq") +if [ -z "$LOGFILE" ]; then + LOGFILE="/var/log/pihole.log" +fi + if [[ "$*" != *"quiet"* ]]; then - echo -ne " ${INFO} Flushing /var/log/pihole/pihole.log ..." + echo -ne " ${INFO} Flushing "${LOGFILE}" ..." fi if [[ "$*" == *"once"* ]]; then # Nightly logrotation @@ -44,9 +46,9 @@ if [[ "$*" == *"once"* ]]; then # Note that moving the file is not an option, as # dnsmasq would happily continue writing into the # moved file (it will have the same file handler) - cp -p /var/log/pihole/pihole.log /var/log/pihole/pihole.log.1 - echo " " > /var/log/pihole/pihole.log - chmod 640 /var/log/pihole/pihole.log + cp -p "${LOGFILE}" "${LOGFILE}.1" + echo " " > "${LOGFILE}" + chmod 640 "${LOGFILE}" fi else # Manual flushing @@ -56,17 +58,21 @@ else /usr/sbin/logrotate --force --state "${STATEFILE}" /etc/pihole/logrotate else # Flush both pihole.log and pihole.log.1 (if existing) - echo " " > /var/log/pihole/pihole.log - if [ -f /var/log/pihole/pihole.log.1 ]; then - echo " " > /var/log/pihole/pihole.log.1 - chmod 640 /var/log/pihole/pihole.log.1 + echo " " > "${LOGFILE}" + if [ -f "${LOGFILE}.1" ]; then + echo " " > "${LOGFILE}.1" + chmod 640 "${LOGFILE}.1" fi fi + + # Stop FTL to make sure it doesn't write to the database while we're deleting data + service pihole-FTL stop + # Delete most recent 24 hours from FTL's database, leave even older data intact (don't wipe out all history) deleted=$(pihole-FTL sqlite3 -ni "${DBFILE}" "DELETE FROM query_storage WHERE timestamp >= strftime('%s','now')-86400; select changes() from query_storage limit 1") - # Restart pihole-FTL to force reloading history - sudo pihole restartdns + # Restart FTL + service pihole-FTL restart fi if [[ "$*" != *"quiet"* ]]; then From 8f24e8aa5f67e5386cd36f87bb6f9ab9d6d34e9a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 19 Jun 2024 22:41:42 +0200 Subject: [PATCH 05/18] Modify pihole -t to use TOML config items Signed-off-by: DL6ER --- pihole | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pihole b/pihole index 7c84771c..f7963d73 100755 --- a/pihole +++ b/pihole @@ -391,19 +391,20 @@ exit 0 tailFunc() { # Warn user if Pi-hole's logging is disabled - local logging_enabled=$(grep -c "^log-queries" /etc/dnsmasq.d/01-pihole.conf) - if [[ "${logging_enabled}" == "0" ]]; then - # No "log-queries" lines are found. - # Commented out lines (such as "#log-queries") are ignored + local logging_enabled=$(getFTLConfigValue dns.queryLogging) + if [[ "${logging_enabled}" != "true" ]]; then echo " ${CROSS} Warning: Query logging is disabled" fi echo -e " ${INFO} Press Ctrl-C to exit" + # Get logfile path + readonly LOGFILE=$(getFTLConfigValue files.log.dnsmasq) + # Strip date from each line # Color blocklist/blacklist/wildcard entries as red # Color A/AAAA/DHCP strings as white # Color everything else as gray - tail -f /var/log/pihole/pihole.log | grep --line-buffered "${1}" | sed -E \ + tail -f $LOGFILE | grep --line-buffered "${1}" | sed -E \ -e "s,($(date +'%b %d ')| dnsmasq\[[0-9]*\]),,g" \ -e "s,(.*(blacklisted |gravity blocked ).*),${COL_RED}&${COL_NC}," \ -e "s,.*(query\\[A|DHCP).*,${COL_NC}&${COL_NC}," \ From 92b15cf7441462fceefeb0cd936845b0a5be984a Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 19 Jun 2024 22:49:11 +0200 Subject: [PATCH 06/18] Modify pihole arpflush to stop FTL while performing the action and use the new TOML config values Signed-off-by: DL6ER --- advanced/Scripts/piholeARPTable.sh | 36 +++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/advanced/Scripts/piholeARPTable.sh b/advanced/Scripts/piholeARPTable.sh index b92dd124..c04c5b33 100755 --- a/advanced/Scripts/piholeARPTable.sh +++ b/advanced/Scripts/piholeARPTable.sh @@ -15,27 +15,29 @@ if [[ -f ${coltable} ]]; then source ${coltable} fi +readonly PI_HOLE_SCRIPT_DIR="/opt/pihole" +utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh" +source "${utilsfile}" + # Determine database location -# Obtain DBFILE=... setting from pihole-FTL.db -# Constructed to return nothing when -# a) the setting is not present in the config file, or -# b) the setting is commented out (e.g. "#DBFILE=...") -FTLconf="/etc/pihole/pihole-FTL.conf" -if [ -e "$FTLconf" ]; then - DBFILE="$(sed -n -e 's/^\s*DBFILE\s*=\s*//p' ${FTLconf})" -fi -# Test for empty string. Use standard path in this case. +DBFILE=$(getFTLConfigValue "files.database") if [ -z "$DBFILE" ]; then DBFILE="/etc/pihole/pihole-FTL.db" fi - flushARP(){ local output if [[ "${args[1]}" != "quiet" ]]; then echo -ne " ${INFO} Flushing network table ..." fi + # Stop FTL to prevent database access + if ! output=$(pihole-FTL service stop 2>&1); then + echo -e "${OVER} ${CROSS} Failed to stop FTL" + echo " Output: ${output}" + return 1 + fi + # Truncate network_addresses table in pihole-FTL.db # This needs to be done before we can truncate the network table due to # foreign key constraints @@ -54,6 +56,20 @@ flushARP(){ return 1 fi + # Flush ARP cache of the host + if ! output=$(ip -s -s neigh flush all 2>&1); then + echo -e "${OVER} ${CROSS} Failed to flush ARP cache" + echo " Output: ${output}" + return 1 + fi + + # Start FTL again + if ! output=$(pihole-FTL service restart 2>&1); then + echo -e "${OVER} ${CROSS} Failed to restart FTL" + echo " Output: ${output}" + return 1 + fi + if [[ "${args[1]}" != "quiet" ]]; then echo -e "${OVER} ${TICK} Flushed network table" fi From bfc18f8329ad6eca2d4c2e3ea9e641d86844a453 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Wed, 19 Jun 2024 23:04:39 +0200 Subject: [PATCH 07/18] Rewrite list functions to use the API Signed-off-by: DL6ER --- advanced/Scripts/api.sh | 18 +- advanced/Scripts/list.sh | 358 +++++++++++++++------------------------ pihole | 20 +-- 3 files changed, 164 insertions(+), 232 deletions(-) diff --git a/advanced/Scripts/api.sh b/advanced/Scripts/api.sh index 4162eff1..21447105 100755 --- a/advanced/Scripts/api.sh +++ b/advanced/Scripts/api.sh @@ -75,12 +75,16 @@ TestAPIAvailability() { } LoginAPI() { + if [ -z "${API_URL}" ]; then + TestAPIAvailability + fi + # Try to read the CLI password (if enabled and readable by the current user) if [ -r /etc/pihole/cli_pw ]; then password=$(cat /etc/pihole/cli_pw) # Try to authenticate using the CLI password - LoginAPI + Authentication fi # If this did not work, ask the user for the password @@ -91,7 +95,7 @@ LoginAPI() { secretRead; printf '\n' # Try to authenticate again - LoginAPI + Authentication done } @@ -144,6 +148,16 @@ GetFTLData() { fi } +PostFTLData() { + local data response status + # send the data to the API + response=$(curl -skS -w "%{http_code}" -X POST "${API_URL}$1" --data-raw "$2" -H "Accept: application/json" -H "sid: ${SID}" ) + # status are the last 3 characters + status=$(printf %s "${response#"${response%???}"}") + # data is everything from response without the last 3 characters + printf %s "${response%???}" +} + secretRead() { # POSIX compliant function to read user-input and diff --git a/advanced/Scripts/list.sh b/advanced/Scripts/list.sh index 76558e58..3bd4af75 100755 --- a/advanced/Scripts/list.sh +++ b/advanced/Scripts/list.sh @@ -5,261 +5,187 @@ # (c) 2017 Pi-hole, LLC (https://pi-hole.net) # Network-wide ad blocking via your own hardware. # -# Whitelist and blacklist domains +# allowlist and denylist domains # # This file is copyright under the latest version of the EUPL. # Please see LICENSE file for your rights under this license. -# Globals -piholeDir="/etc/pihole" -GRAVITYDB="${piholeDir}/gravity.db" -# Source pihole-FTL from install script -pihole_FTL="${piholeDir}/pihole-FTL.conf" -if [[ -f "${pihole_FTL}" ]]; then - source "${pihole_FTL}" +readonly PI_HOLE_SCRIPT_DIR="/opt/pihole" +readonly utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh" +source "${utilsfile}" + +readonly apifile="${PI_HOLE_SCRIPT_DIR}/api.sh" +source "${apifile}" + +# Determine database location +DBFILE=$(getFTLConfigValue "files.database") +if [ -z "$DBFILE" ]; then + DBFILE="/etc/pihole/pihole-FTL.db" fi -# Set this only after sourcing pihole-FTL.conf as the gravity database path may -# have changed -gravityDBfile="${GRAVITYDB}" +# Determine gravity database location +GRAVITYDB=$(getFTLConfigValue "files.gravity") +if [ -z "$GRAVITYDB" ]; then + GRAVITYDB="/etc/pihole/gravity.db" +fi -noReloadRequested=false addmode=true verbose=true wildcard=false -web=false domList=() typeId="" comment="" -declare -i domaincount -domaincount=0 -reload=false 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 -} - -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' -${listname^} one or more domains + echo "Usage: pihole ${abbrv} [options] +Example: 'pihole ${abbrv} site.com', or 'pihole ${abbrv} site1.com site2.com' +${typeId^} one or more ${kindId} domains Options: - -d, --delmode Remove domain(s) from the ${listname} - -nr, --noreload Update ${listname} without reloading the DNS server + -d, --delmode Remove domain(s) -q, --quiet Make output less verbose -h, --help Show this help dialog - -l, --list Display all your ${listname}listed domains + -l, --list Display domains --nuke Removes all entries in a list --comment \"text\" Add a comment to the domain. If adding multiple domains the same comment will be used for all" exit 0 } -ValidateDomain() { - # Convert to lowercase - domain="${1,,}" - local str validDomain - - # Check validity of domain (don't check for regex entries) - if [[ ( "${typeId}" == "${regex_blacklist}" || "${typeId}" == "${regex_whitelist}" ) && "${wildcard}" == false ]]; then - validDomain="${domain}" - else - # Check max length - if [[ "${#domain}" -le 253 ]]; then - 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 - # set error string - str="is not a valid argument or domain name!" - else - validDomain= - str="is too long!" - - fi +CreateDomainList() { + # Format domain into regex filter if requested + local dom=${1} + if [[ "${wildcard}" == true ]]; then + dom="(\\.|^)${dom//\./\\.}$" fi - - if [[ -n "${validDomain}" ]]; then - domList=("${domList[@]}" "${validDomain}") - else - echo -e " ${CROSS} ${domain} ${str}" - fi - - domaincount=$((domaincount+1)) -} - -ProcessDomainList() { - for dom in "${domList[@]}"; do - # Format domain into regex filter if requested - if [[ "${wildcard}" == true ]]; then - dom="(\\.|^)${dom//\./\\.}$" - fi - - # Logic: If addmode then add to desired list and remove from the other; - # if delmode then remove from desired list but do not add to the other - if ${addmode}; then - AddDomain "${dom}" - else - RemoveDomain "${dom}" - fi - done + domList=("${domList[@]}" "${dom}") } AddDomain() { - local domain num requestedListname existingTypeId existingListname - domain="$1" + local json num - # Is the domain in the list we want to add it to? - num="$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT COUNT(*) FROM domainlist WHERE domain = '${domain}';")" - requestedListname="$(GetListnameFromTypeId "${typeId}")" + # Authenticate with the API + LoginAPI - if [[ "${num}" -ne 0 ]]; then - existingTypeId="$(pihole-FTL sqlite3 -ni "${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!" + # Prepare request to POST /api/domains/{type}/{kind} + # Build JSON object of the following form + # { + # "domain": [ ], + # "comment": + # } + # where is an array of domain strings and is a string + # We use jq to build the JSON object + json=$(jq --null-input --compact-output --arg domains "${domList[*]}" --arg comment "${comment}" '{domain: $domains | split(" "), comment: $comment}') + + # Send the request + data=$(PostFTLData "domains/${typeId}/${kindId}" "${json}") + + # Display domain(s) added + # (they are listed in .processed.success, use jq) + num=$(echo "${data}" | jq '.processed.success | length') + if [[ "${num}" -gt 0 ]] && [[ "${verbose}" == true ]]; then + echo -e " ${TICK} Added ${num} domain(s):" + for i in $(seq 0 $((num-1))); do + echo -e " - ${COL_BLUE}$(echo "${data}" | jq --raw-output ".processed.success[$i].item")${COL_NC}" + done + fi + # Display failed domain(s) + # (they are listed in .processed.errors, use jq) + num=$(echo "${data}" | jq '.processed.errors | length') + if [[ "${num}" -gt 0 ]] && [[ "${verbose}" == true ]]; then + echo -e " ${CROSS} Failed to add ${num} domain(s):" + for i in $(seq 0 $((num-1))); do + echo -e " - ${COL_BLUE}$(echo "${data}" | jq --raw-output ".processed.errors[$i].item")${COL_NC}" + error=$(echo "${data}" | jq --raw-output ".processed.errors[$i].error") + if [[ "${error}" == "UNIQUE constraint failed: domainlist.domain, domainlist.type" ]]; then + error="Domain already in the specified list" fi - else - existingListname="$(GetListnameFromTypeId "${existingTypeId}")" - pihole-FTL sqlite3 -ni "${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 + echo -e " ${error}" + done fi - # Domain not found in the table, add it! - if [[ "${verbose}" == true ]]; then - 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) - if [[ -z "${comment}" ]]; then - pihole-FTL sqlite3 -ni "${gravityDBfile}" "INSERT INTO domainlist (domain,type) VALUES ('${domain}',${typeId});" - else - # also add comment when variable has been set through the "--comment" option - pihole-FTL sqlite3 -ni "${gravityDBfile}" "INSERT INTO domainlist (domain,type,comment) VALUES ('${domain}',${typeId},'${comment}');" - fi + # Log out + LogoutAPI } RemoveDomain() { - local domain num requestedListname - domain="$1" + local json num - # Is the domain in the list we want to remove it from? - num="$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT COUNT(*) FROM domainlist WHERE domain = '${domain}' AND type = ${typeId};")" + # Authenticate with the API + LoginAPI - requestedListname="$(GetListnameFromTypeId "${typeId}")" + # Prepare request to POST /api/domains:batchDelete + # Build JSON object of the following form + # [{ + # "item": , + # "type": "${typeId}", + # "kind": "${kindId}", + # }] + # where is the domain string and ${typeId} and ${kindId} are the type and kind IDs + # We use jq to build the JSON object) + json=$(jq --null-input --compact-output --arg domains "${domList[*]}" --arg typeId "${typeId}" --arg kindId "${kindId}" '[ $domains | split(" ")[] as $item | {item: $item, type: $typeId, kind: $kindId} ]') - if [[ "${num}" -eq 0 ]]; then - if [[ "${verbose}" == true ]]; then - echo -e " ${INFO} ${domain} does not exist in ${requestedListname}, no need to remove!" - fi - return + # Send the request + data=$(PostFTLData "domains:batchDelete" "${json}") + + # If there is an .error object in the returned data, display it + local error + error=$(jq --compact-output <<< "${data}" '.error') + if [[ $error != "null" && $error != "" ]]; then + echo -e " ${CROSS} Failed to remove domain(s):" + echo -e " $(jq <<< "${data}" '.error')" + elif [[ "${verbose}" == true ]]; then + echo -e " ${TICK} Removed ${#domList[@]} domain(s):" + # Loop through the domains and display them + for dom in "${domList[@]}"; do + echo -e " - ${COL_BLUE}${dom}${COL_NC}" + done fi - # Domain found in the table, remove it! - if [[ "${verbose}" == true ]]; then - echo -e " ${INFO} Removing ${domain} from the ${requestedListname}..." - fi - reload=true - # Remove it from the current list - pihole-FTL sqlite3 -ni "${gravityDBfile}" "DELETE FROM domainlist WHERE domain = '${domain}' AND type = ${typeId};" + # Log out + LogoutAPI } Displaylist() { - local count num_pipes domain enabled status nicedate requestedListname + local data - requestedListname="$(GetListnameFromTypeId "${typeId}")" - data="$(pihole-FTL sqlite3 -ni "${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 ${requestedListname}:" - count=1 - while IFS= read -r line - do - # Count number of pipes seen in this line - # This is necessary because we can only detect the pipe separating the fields - # from the end backwards as the domain (which is the first field) may contain - # pipe symbols as they are perfectly valid regex filter control characters - num_pipes="$(grep -c "^" <<< "$(grep -o "|" <<< "${line}")")" - - # Extract domain and enabled status based on the obtained number of pipe characters - domain="$(cut -d'|' -f"-$((num_pipes-1))" <<< "${line}")" - enabled="$(cut -d'|' -f"$((num_pipes))" <<< "${line}")" - datemod="$(cut -d'|' -f"$((num_pipes+1))" <<< "${line}")" - - # Translate boolean status into human readable string - if [[ "${enabled}" -eq 1 ]]; then - status="enabled" - else - status="disabled" - fi - - # Get nice representation of numerical date stored in database - nicedate=$(date --rfc-2822 -d "@${datemod}") - - echo " ${count}: ${domain} (${status}, last modified ${nicedate})" - count=$((count+1)) - done <<< "${data}" + # if either typeId or kindId is empty, we cannot display the list + if [[ -z "${typeId}" ]] || [[ -z "${kindId}" ]]; then + echo " ${CROSS} Unable to display list. Please specify a list type and kind." + exit 1 fi - exit 0; -} -NukeList() { - count=$(pihole-FTL sqlite3 -ni "${gravityDBfile}" "SELECT COUNT(1) FROM domainlist WHERE type = ${typeId};") - listname="$(GetListnameFromTypeId "${typeId}")" - if [ "$count" -gt 0 ];then - pihole-FTL sqlite3 -ni "${gravityDBfile}" "DELETE FROM domainlist WHERE type = ${typeId};" - echo " ${TICK} Removed ${count} domain(s) from the ${listname}" + # Authenticate with the API + LoginAPI + + # Send the request + data=$(GetFTLData "domains/${typeId}/${kindId}") + + # Display the list + num=$(echo "${data}" | jq '.domains | length') + if [[ "${num}" -gt 0 ]]; then + echo -e " ${TICK} Found ${num} domain(s) in the ${kindId} ${typeId}list:" + for i in $(seq 0 $((num-1))); do + echo -e " - ${COL_BLUE}$(echo "${data}" | jq --compact-output ".domains[$i].domain")${COL_NC}" + echo -e " Comment: $(echo "${data}" | jq --compact-output ".domains[$i].comment")" + echo -e " Groups: $(echo "${data}" | jq --compact-output ".domains[$i].groups")" + echo -e " Added: $(date -d @"$(echo "${data}" | jq --compact-output ".domains[$i].date_added")")" + echo -e " Last modified: $(date -d @"$(echo "${data}" | jq --compact-output ".domains[$i].date_modified")")" + done else - echo " ${INFO} ${listname} already empty. Nothing to do!" + echo -e " ${INFO} No domains found in the ${kindId} ${typeId}list" fi - exit 0; + + # Log out + LogoutAPI + + # Return early without adding/deleting domains + exit 0 } GetComment() { @@ -272,38 +198,30 @@ GetComment() { while (( "$#" )); do case "${1}" in - "-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" ) noReloadRequested=true;; + "-a" | "allowlist" ) kindId="exact"; typeId="allow"; abbrv="-a";; + "-b" | "denylist" ) kindId="exact"; typeId="deny"; abbrv="-b";; + "--allow-regex" | "allow-regex" ) kindId="regex"; typeId="allow"; abbrv="--allow-regex";; + "--allow-wild" | "allow-wild" ) kindId="regex"; typeId="allow"; wildcard=true; abbrv="--allow-wild";; + "--regex" | "regex" ) kindId="regex"; typeId="deny"; abbrv="--regex";; + "--wild" | "wildcard" ) kindId="regex"; typeId="deny"; wildcard=true; abbrv="--wild";; "-d" | "--delmode" ) addmode=false;; "-q" | "--quiet" ) verbose=false;; "-h" | "--help" ) helpFunc;; "-l" | "--list" ) Displaylist;; - "--nuke" ) NukeList;; - "--web" ) web=true;; "--comment" ) GetComment "${2}"; shift;; - * ) ValidateDomain "${1}";; + * ) CreateDomainList "${1}";; esac shift done shift -if [[ ${domaincount} == 0 ]]; then +if [[ ${#domList[@]} == 0 ]]; then helpFunc fi -ProcessDomainList - -# Used on web interface -if $web; then - echo "DONE" -fi - -if [[ ${reload} == true && ${noReloadRequested} == false ]]; then - pihole restartdns reload-lists +if ${addmode}; then + AddDomain +else + RemoveDomain fi diff --git a/pihole b/pihole index f7963d73..ce46fd0f 100755 --- a/pihole +++ b/pihole @@ -537,12 +537,12 @@ case "${1}" in "tricorder" ) tricorderFunc;; # we need to add all arguments that require sudo power to not trigger the * argument - "-w" | "whitelist" ) ;; - "-b" | "blacklist" ) ;; - "--wild" | "wildcard" ) ;; - "--regex" | "regex" ) ;; - "--white-regex" | "white-regex" ) ;; - "--white-wild" | "white-wild" ) ;; + "-a" | "allowlist" ) need_root=0;; + "-b" | "blocklist" | "denylist" ) need_root=0;; + "--wild" | "wildcard" ) need_root=0;; + "--regex" | "regex" ) need_root=0;; + "--allow-regex" | "allow-regex" ) need_root=0;; + "--allow-wild" | "allow-wild" ) need_root=0;; "-f" | "flush" ) ;; "-up" | "updatePihole" ) ;; "-r" | "reconfigure" ) ;; @@ -592,12 +592,12 @@ fi # Handle redirecting to specific functions based on arguments case "${1}" in - "-w" | "whitelist" ) listFunc "$@";; - "-b" | "blacklist" ) listFunc "$@";; + "-a" | "allowlist" ) listFunc "$@";; + "-b" | "blocklist" | "denylist" ) listFunc "$@";; "--wild" | "wildcard" ) listFunc "$@";; "--regex" | "regex" ) listFunc "$@";; - "--white-regex" | "white-regex" ) listFunc "$@";; - "--white-wild" | "white-wild" ) listFunc "$@";; + "--allow-regex" | "allow-regex" ) listFunc "$@";; + "--allow-wild" | "allow-wild" ) listFunc "$@";; "-d" | "debug" ) debugFunc "$@";; "-f" | "flush" ) flushFunc "$@";; "-up" | "updatePihole" ) updatePiholeFunc "$@";; From 25f384a923c3f0feccedc3ee4033cc482f2683c8 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Thu, 20 Jun 2024 19:41:47 +0200 Subject: [PATCH 08/18] Do not use CLI password when no password is set at all Signed-off-by: DL6ER --- advanced/Scripts/api.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/advanced/Scripts/api.sh b/advanced/Scripts/api.sh index 21447105..6f31a6d2 100755 --- a/advanced/Scripts/api.sh +++ b/advanced/Scripts/api.sh @@ -51,6 +51,12 @@ TestAPIAvailability() { API_PORT="" else # API is available at this URL combination + + if [ "${availabilityResonse}" = 200 ]; then + # API is available without authentication + needAuth=false + fi + break fi @@ -75,10 +81,16 @@ TestAPIAvailability() { } LoginAPI() { + # If the API URL is not set, test the availability if [ -z "${API_URL}" ]; then TestAPIAvailability fi + # Exit early if authentication is not needed + if [ "${needAuth}" = false ]; then + return + fi + # Try to read the CLI password (if enabled and readable by the current user) if [ -r /etc/pihole/cli_pw ]; then password=$(cat /etc/pihole/cli_pw) @@ -87,6 +99,8 @@ LoginAPI() { Authentication fi + + # If this did not work, ask the user for the password while [ "${validSession}" = false ] || [ -z "${validSession}" ] ; do echo "Authentication failed. Please enter your Pi-hole password" From 5dfcd02c40b9babd4f824805d53d4d919116a944 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 22 Jun 2024 10:31:03 +0200 Subject: [PATCH 09/18] Improve pihole -f Signed-off-by: DL6ER --- advanced/Scripts/api.sh | 8 +-- advanced/Scripts/piholeLogFlush.sh | 79 +++++++++++++++++++++++------- 2 files changed, 64 insertions(+), 23 deletions(-) diff --git a/advanced/Scripts/api.sh b/advanced/Scripts/api.sh index 6f31a6d2..ea057adc 100755 --- a/advanced/Scripts/api.sh +++ b/advanced/Scripts/api.sh @@ -21,7 +21,7 @@ TestAPIAvailability() { # as we are running locally, we can get the port value from FTL directly - local chaos_api_list availabilityResonse + local chaos_api_list availabilityResponse # Query the API URLs from FTL using CHAOS TXT local.api.ftl # The result is a space-separated enumeration of full URLs @@ -43,16 +43,16 @@ TestAPIAvailability() { API_URL="${API_URL#\"}" # Test if the API is available at this URL - availabilityResonse=$(curl -skS -o /dev/null -w "%{http_code}" "${API_URL}auth") + availabilityResponse=$(curl -skS -o /dev/null -w "%{http_code}" "${API_URL}auth") # Test if http status code was 200 (OK) or 401 (authentication required) - if [ ! "${availabilityResonse}" = 200 ] && [ ! "${availabilityResonse}" = 401 ]; then + if [ ! "${availabilityResponse}" = 200 ] && [ ! "${availabilityResponse}" = 401 ]; then # API is not available at this port/protocol combination API_PORT="" else # API is available at this URL combination - if [ "${availabilityResonse}" = 200 ]; then + if [ "${availabilityResponse}" = 200 ]; then # API is available without authentication needAuth=false fi diff --git a/advanced/Scripts/piholeLogFlush.sh b/advanced/Scripts/piholeLogFlush.sh index 892645af..34d96318 100755 --- a/advanced/Scripts/piholeLogFlush.sh +++ b/advanced/Scripts/piholeLogFlush.sh @@ -29,16 +29,21 @@ fi # Determine log file location LOGFILE=$(getFTLConfigValue "files.log.dnsmasq") if [ -z "$LOGFILE" ]; then - LOGFILE="/var/log/pihole.log" + LOGFILE="/var/log/pihole/pihole.log" +fi +FTLFILE=$(getFTLConfigValue "files.log.ftl") +if [ -z "$FTLFILE" ]; then + FTLFILE="/var/log/pihole/FTL.log" fi -if [[ "$*" != *"quiet"* ]]; then - echo -ne " ${INFO} Flushing "${LOGFILE}" ..." -fi if [[ "$*" == *"once"* ]]; then # Nightly logrotation if command -v /usr/sbin/logrotate >/dev/null; then # Logrotate once + + if [[ "$*" != *"quiet"* ]]; then + echo -ne " ${INFO} Running logrotate ..." + fi /usr/sbin/logrotate --force --state "${STATEFILE}" /etc/pihole/logrotate else # Copy pihole.log over to pihole.log.1 @@ -46,23 +51,60 @@ if [[ "$*" == *"once"* ]]; then # Note that moving the file is not an option, as # dnsmasq would happily continue writing into the # moved file (it will have the same file handler) + if [[ "$*" != *"quiet"* ]]; then + echo -ne " ${INFO} Rotating ${LOGFILE} ..." + fi cp -p "${LOGFILE}" "${LOGFILE}.1" echo " " > "${LOGFILE}" chmod 640 "${LOGFILE}" + if [[ "$*" != *"quiet"* ]]; then + echo -e "${OVER} ${TICK} Rotated ${LOGFILE} ..." + fi + # Copy FTL.log over to FTL.log.1 + # and empty out FTL.log + if [[ "$*" != *"quiet"* ]]; then + echo -ne " ${INFO} Rotating ${FTLFILE} ..." + fi + cp -p "${FTLFILE}" "${FTLFILE}.1" + echo " " > "${FTLFILE}" + chmod 640 "${FTLFILE}" + if [[ "$*" != *"quiet"* ]]; then + echo -e "${OVER} ${TICK} Rotated ${FTLFILE} ..." + fi fi else # Manual flushing - if command -v /usr/sbin/logrotate >/dev/null; then - # Logrotate twice to move all data out of sight of FTL - /usr/sbin/logrotate --force --state "${STATEFILE}" /etc/pihole/logrotate; sleep 3 - /usr/sbin/logrotate --force --state "${STATEFILE}" /etc/pihole/logrotate - else - # Flush both pihole.log and pihole.log.1 (if existing) - echo " " > "${LOGFILE}" - if [ -f "${LOGFILE}.1" ]; then - echo " " > "${LOGFILE}.1" - chmod 640 "${LOGFILE}.1" - fi + + # Flush both pihole.log and pihole.log.1 (if existing) + if [[ "$*" != *"quiet"* ]]; then + echo -ne " ${INFO} Flushing ${LOGFILE} ..." + fi + echo " " > "${LOGFILE}" + chmod 640 "${LOGFILE}" + if [ -f "${LOGFILE}.1" ]; then + echo " " > "${LOGFILE}.1" + chmod 640 "${LOGFILE}.1" + fi + if [[ "$*" != *"quiet"* ]]; then + echo -e "${OVER} ${TICK} Flushed ${LOGFILE} ..." + fi + + # Flush both FTL.log and FTL.log.1 (if existing) + if [[ "$*" != *"quiet"* ]]; then + echo -ne " ${INFO} Flushing ${FTLFILE} ..." + fi + echo " " > "${FTLFILE}" + chmod 640 "${FTLFILE}" + if [ -f "${FTLFILE}.1" ]; then + echo " " > "${FTLFILE}.1" + chmod 640 "${FTLFILE}.1" + fi + if [[ "$*" != *"quiet"* ]]; then + echo -e "${OVER} ${TICK} Flushed ${FTLFILE} ..." + fi + + if [[ "$*" != *"quiet"* ]]; then + echo -ne " ${INFO} Flushing database, DNS resolution temporarily unavailable ..." fi # Stop FTL to make sure it doesn't write to the database while we're deleting data @@ -73,9 +115,8 @@ else # Restart FTL service pihole-FTL restart + if [[ "$*" != *"quiet"* ]]; then + echo -e "${OVER} ${TICK} Deleted ${deleted} queries from long-term query database" + fi fi -if [[ "$*" != *"quiet"* ]]; then - echo -e "${OVER} ${TICK} Flushed /var/log/pihole/pihole.log" - echo -e " ${TICK} Deleted ${deleted} queries from database" -fi From 7e91b9ab47f710311ed9cc2d509c5ea3a4c5405e Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 22 Jun 2024 20:35:06 +0200 Subject: [PATCH 10/18] Update help text Signed-off-by: DL6ER --- pihole | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pihole b/pihole index ce46fd0f..d9a22c63 100755 --- a/pihole +++ b/pihole @@ -472,17 +472,17 @@ unsupportedFunc(){ helpFunc() { echo "Usage: pihole [options] -Example: 'pihole -w -h' +Example: 'pihole -a -h' Add '-h' after specific commands for more information on usage -Whitelist/Blacklist Options: - -w, whitelist Whitelist domain(s) - -b, blacklist Blacklist domain(s) +Domain Options: + -a, allowlist Allowlist domain(s) + -b, denylist Denylist domain(s) --regex, regex Regex blacklist domains(s) - --white-regex Regex whitelist domains(s) + --allow-regex Regex allowlist domains(s) --wild, wildcard Wildcard blacklist domain(s) - --white-wild Wildcard whitelist domain(s) - Add '-h' for more info on whitelist/blacklist usage + --allow-wild Wildcard allowlist domain(s) + Add '-h' for more info on allowlist/denylist usage Debugging Options: -d, debug Start a debugging session From 424e825bd9a28a416766067dc22821ae81bd139f Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 22 Jun 2024 20:41:19 +0200 Subject: [PATCH 11/18] Do not auto-sudo in the pihole command Signed-off-by: DL6ER --- pihole | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/pihole b/pihole index d9a22c63..cae0cfb2 100755 --- a/pihole +++ b/pihole @@ -552,8 +552,8 @@ case "${1}" in "disable" ) ;; "-d" | "debug" ) ;; "restartdns" ) ;; - "-g" | "updateGravity" ) need_root=0;; - "reloaddns" ) need_root=0;; + "-g" | "updateGravity" ) ;; + "reloaddns" ) ;; "setpassword" ) ;; "checkout" ) ;; "updatechecker" ) ;; @@ -562,32 +562,18 @@ case "${1}" in * ) helpFunc;; esac -# Must be root to use this tool for most functions -if [[ ! $EUID -eq 0 && need_root -eq 1 ]];then - if [[ -x "$(command -v sudo)" ]]; then - exec sudo bash "$0" "$@" - exit $? - else - echo -e " ${CROSS} sudo is needed to run pihole commands. Please run this script as root or install sudo." - exit 1 - fi -fi - # In the case of alpine running in a container, the USER variable appears to be blank # which prevents the next trap from working correctly. Set it by running whoami if [[ -z ${USER} ]]; then USER=$(whoami) fi -# Can also be user pihole for other functions -if [[ ${USER} != "pihole" && need_root -eq 0 ]];then - if [[ -x "$(command -v sudo)" ]]; then - exec sudo -u pihole bash "$0" "$@" - exit $? - else - echo -e " ${CROSS} sudo is needed to run pihole commands. Please run this script as root or install sudo." - exit 1 - fi +# Check if the current user is neither root nor pihole and if the command +# requires root. If so, exit with an error message. +if [[ $EUID -ne 0 && ${USER} != "pihole" && need_root -eq 1 ]];then + echo -e " ${CROSS} The Pi-hole command requires root privileges, try:" + echo -e " ${COL_GREEN}sudo pihole ${@}${COL_NC}" + exit 1 fi # Handle redirecting to specific functions based on arguments From b835fa06a6360004c9fa75e56225fba06b7374f0 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 22 Jun 2024 20:48:07 +0200 Subject: [PATCH 12/18] Further black -> deny renaming Signed-off-by: DL6ER --- pihole | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pihole b/pihole index cae0cfb2..00cbd893 100755 --- a/pihole +++ b/pihole @@ -401,12 +401,12 @@ tailFunc() { readonly LOGFILE=$(getFTLConfigValue files.log.dnsmasq) # Strip date from each line - # Color blocklist/blacklist/wildcard entries as red + # Color blocklist/denylist/wildcard entries as red # Color A/AAAA/DHCP strings as white # Color everything else as gray tail -f $LOGFILE | grep --line-buffered "${1}" | sed -E \ -e "s,($(date +'%b %d ')| dnsmasq\[[0-9]*\]),,g" \ - -e "s,(.*(blacklisted |gravity blocked ).*),${COL_RED}&${COL_NC}," \ + -e "s,(.*(denied |gravity blocked ).*),${COL_RED}&${COL_NC}," \ -e "s,.*(query\\[A|DHCP).*,${COL_NC}&${COL_NC}," \ -e "s,.*,${COL_GRAY}&${COL_NC}," exit 0 @@ -478,9 +478,9 @@ Add '-h' after specific commands for more information on usage Domain Options: -a, allowlist Allowlist domain(s) -b, denylist Denylist domain(s) - --regex, regex Regex blacklist domains(s) + --regex, regex Regex denylist domains(s) --allow-regex Regex allowlist domains(s) - --wild, wildcard Wildcard blacklist domain(s) + --wild, wildcard Wildcard denylist domain(s) --allow-wild Wildcard allowlist domain(s) Add '-h' for more info on allowlist/denylist usage From fe8e63853cf69e077eae5dedbe523f19ec0a5b0b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 22 Jun 2024 20:49:44 +0200 Subject: [PATCH 13/18] Use concatenate of arguments instead of using the array directly Signed-off-by: DL6ER --- pihole | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pihole b/pihole index 00cbd893..1313d487 100755 --- a/pihole +++ b/pihole @@ -572,7 +572,7 @@ fi # requires root. If so, exit with an error message. if [[ $EUID -ne 0 && ${USER} != "pihole" && need_root -eq 1 ]];then echo -e " ${CROSS} The Pi-hole command requires root privileges, try:" - echo -e " ${COL_GREEN}sudo pihole ${@}${COL_NC}" + echo -e " ${COL_GREEN}sudo pihole $*${COL_NC}" exit 1 fi From ccdbfd41309346ebb955e8910f2c130b92cd60d8 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 25 Jun 2024 13:56:49 +0200 Subject: [PATCH 14/18] Use natural langauge for list manipulations, like pihole allow example.com or pihole deny other.net. Also remove using pihole deny not bad.org Signed-off-by: DL6ER --- advanced/Scripts/list.sh | 9 ++++--- advanced/bash-completion/pihole | 6 ++--- manpages/pihole.8 | 42 +++++++++++++++------------------ pihole | 25 ++++++++++---------- 4 files changed, 39 insertions(+), 43 deletions(-) diff --git a/advanced/Scripts/list.sh b/advanced/Scripts/list.sh index 3bd4af75..144317ce 100755 --- a/advanced/Scripts/list.sh +++ b/advanced/Scripts/list.sh @@ -47,11 +47,10 @@ Example: 'pihole ${abbrv} site.com', or 'pihole ${abbrv} site1.com site2.com' ${typeId^} one or more ${kindId} domains Options: - -d, --delmode Remove domain(s) + not -d, --delmode Remove domain(s) -q, --quiet Make output less verbose -h, --help Show this help dialog -l, --list Display domains - --nuke Removes all entries in a list --comment \"text\" Add a comment to the domain. If adding multiple domains the same comment will be used for all" exit 0 @@ -198,13 +197,13 @@ GetComment() { while (( "$#" )); do case "${1}" in - "-a" | "allowlist" ) kindId="exact"; typeId="allow"; abbrv="-a";; - "-b" | "denylist" ) kindId="exact"; typeId="deny"; abbrv="-b";; + "allow" | "allowlist" ) kindId="exact"; typeId="allow"; abbrv="allow";; + "deny" | "denylist" ) kindId="exact"; typeId="deny"; abbrv="deny";; "--allow-regex" | "allow-regex" ) kindId="regex"; typeId="allow"; abbrv="--allow-regex";; "--allow-wild" | "allow-wild" ) kindId="regex"; typeId="allow"; wildcard=true; abbrv="--allow-wild";; "--regex" | "regex" ) kindId="regex"; typeId="deny"; abbrv="--regex";; "--wild" | "wildcard" ) kindId="regex"; typeId="deny"; wildcard=true; abbrv="--wild";; - "-d" | "--delmode" ) addmode=false;; + "-d" | "not" | "--delmode" ) addmode=false;; "-q" | "--quiet" ) verbose=false;; "-h" | "--help" ) helpFunc;; "-l" | "--list" ) Displaylist;; diff --git a/advanced/bash-completion/pihole b/advanced/bash-completion/pihole index 89e02d2f..064193b4 100644 --- a/advanced/bash-completion/pihole +++ b/advanced/bash-completion/pihole @@ -7,11 +7,11 @@ _pihole() { case "${prev}" in "pihole") - opts="blacklist checkout debug disable enable flush help logging query reconfigure regex restartdns status tail uninstall updateGravity updatePihole version wildcard whitelist arpflush" + opts="allow allow-regex allow-wild deny checkout debug disable enable flush help logging query reconfigure regex restartdns status tail uninstall updateGravity updatePihole version wildcard arpflush" COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) ;; - "whitelist"|"blacklist"|"wildcard"|"regex") - opts_lists="\--delmode \--noreload \--quiet \--list \--nuke" + "allow"|"deny"|"wildcard"|"regex"|"allow-regx"|"allow-wild") + opts_lists="\not \--delmode \--quiet \--list \--help" COMPREPLY=( $(compgen -W "${opts_lists}" -- ${cur}) ) ;; "checkout") diff --git a/manpages/pihole.8 b/manpages/pihole.8 index 55bbe6cb..1bf917db 100644 --- a/manpages/pihole.8 +++ b/manpages/pihole.8 @@ -52,47 +52,43 @@ pihole restartdns\fR [options] Available commands and options: .br -\fB-w, whitelist\fR [options] [ ] +\fBallow, allowlist\fR [options] [ ] .br - Adds or removes specified domain or domains to the Whitelist + Adds or removes specified domain or domains to the Allowlist .br -\fB-b, blacklist\fR [options] [ ] +\fBdeny, denylist\fR [options] [ ] .br - Adds or removes specified domain or domains to the blacklist + Adds or removes specified domain or domains to the denylist .br \fB--regex, regex\fR [options] [ ] .br - Add or removes specified regex filter to the regex blacklist + Add or removes specified regex filter to the regex denylist .br -\fB--white-regex\fR [options] [ ] +\fB--allow-regex\fR [options] [ ] .br - Add or removes specified regex filter to the regex whitelist + Add or removes specified regex filter to the regex allowlist .br \fB--wild, wildcard\fR [options] [ ] .br - Add or removes specified domain to the wildcard blacklist + Add or removes specified domain to the wildcard denylist .br -\fB--white-wild\fR [options] [ ] +\fB--allow-wild\fR [options] [ ] .br - Add or removes specified domain to the wildcard whitelist + Add or removes specified domain to the wildcard allowlist .br - (Whitelist/Blacklist manipulation options): + (Allow-/denylist manipulation options): .br - -d, --delmode Remove domain(s) from the list + not, -d, --delmode Remove domain(s) from the list .br - -nr, --noreload Update list without refreshing dnsmasq + -q, --quiet Make output less verbose .br - -q, --quiet Make output less verbose -.br - -l, --list Display all your listed domains -.br - --nuke Removes all entries in a list + -l, --list Display all your listed domains .br \fB-d, debug\fR [-a] @@ -279,17 +275,17 @@ Available commands and options: Some usage examples .br -Whitelist/blacklist manipulation +Allow-/denylist manipulation .br -\fBpihole -w iloveads.example.com\fR +\fBpihole allow iloveads.example.com\fR .br - Adds "iloveads.example.com" to whitelist + Allow "iloveads.example.com" .br -\fBpihole -b -d noads.example.com\fR +\fBpihole deny not noads.example.com\fR .br - Removes "noads.example.com" from blacklist + Removes "noads.example.com" from denylist .br \fBpihole --wild example.com\fR diff --git a/pihole b/pihole index 1313d487..5a3c847d 100755 --- a/pihole +++ b/pihole @@ -472,17 +472,17 @@ unsupportedFunc(){ helpFunc() { echo "Usage: pihole [options] -Example: 'pihole -a -h' +Example: 'pihole allow -h' Add '-h' after specific commands for more information on usage Domain Options: - -a, allowlist Allowlist domain(s) - -b, denylist Denylist domain(s) - --regex, regex Regex denylist domains(s) - --allow-regex Regex allowlist domains(s) - --wild, wildcard Wildcard denylist domain(s) - --allow-wild Wildcard allowlist domain(s) - Add '-h' for more info on allowlist/denylist usage + allow, allowlist Allow domain(s) + deny, denylist Deny domain(s) + --regex, regex Regex deny domains(s) + --allow-regex Regex allow domains(s) + --wild, wildcard Wildcard deny domain(s) + --allow-wild Wildcard allow domain(s) + Add '-h' for more info on allow/deny usage Debugging Options: -d, debug Start a debugging session @@ -537,8 +537,8 @@ case "${1}" in "tricorder" ) tricorderFunc;; # we need to add all arguments that require sudo power to not trigger the * argument - "-a" | "allowlist" ) need_root=0;; - "-b" | "blocklist" | "denylist" ) need_root=0;; + "allow" | "allowlist" ) need_root=0;; + "deny" | "denylist" ) need_root=0;; "--wild" | "wildcard" ) need_root=0;; "--regex" | "regex" ) need_root=0;; "--allow-regex" | "allow-regex" ) need_root=0;; @@ -578,8 +578,8 @@ fi # Handle redirecting to specific functions based on arguments case "${1}" in - "-a" | "allowlist" ) listFunc "$@";; - "-b" | "blocklist" | "denylist" ) listFunc "$@";; + "allow" | "allowlist" ) listFunc "$@";; + "deny" | "denylist" ) listFunc "$@";; "--wild" | "wildcard" ) listFunc "$@";; "--regex" | "regex" ) listFunc "$@";; "--allow-regex" | "allow-regex" ) listFunc "$@";; @@ -600,4 +600,5 @@ case "${1}" in "updatechecker" ) shift; updateCheckFunc "$@";; "arpflush" ) arpFunc "$@";; "-t" | "tail" ) tailFunc "$2";; + * ) helpFunc;; esac From c2ed30480deabaf5d53db6bcc617807ef52f6034 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 25 Jun 2024 14:10:35 +0200 Subject: [PATCH 15/18] Show when requested domains were not found on the list Signed-off-by: DL6ER --- advanced/Scripts/api.sh | 10 +++++++--- advanced/Scripts/list.sh | 19 ++++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/advanced/Scripts/api.sh b/advanced/Scripts/api.sh index ea057adc..5843c585 100755 --- a/advanced/Scripts/api.sh +++ b/advanced/Scripts/api.sh @@ -166,10 +166,14 @@ PostFTLData() { local data response status # send the data to the API response=$(curl -skS -w "%{http_code}" -X POST "${API_URL}$1" --data-raw "$2" -H "Accept: application/json" -H "sid: ${SID}" ) - # status are the last 3 characters - status=$(printf %s "${response#"${response%???}"}") # data is everything from response without the last 3 characters - printf %s "${response%???}" + if [ "${3}" = "status" ]; then + # Keep the status code appended if requested + printf %s "${response}" + else + # Strip the status code + printf %s "${response%???}" + fi } secretRead() { diff --git a/advanced/Scripts/list.sh b/advanced/Scripts/list.sh index 144317ce..d6a30325 100755 --- a/advanced/Scripts/list.sh +++ b/advanced/Scripts/list.sh @@ -66,7 +66,7 @@ CreateDomainList() { } AddDomain() { - local json num + local json num data # Authenticate with the API LoginAPI @@ -113,7 +113,7 @@ AddDomain() { } RemoveDomain() { - local json num + local json num data status # Authenticate with the API LoginAPI @@ -130,7 +130,10 @@ RemoveDomain() { json=$(jq --null-input --compact-output --arg domains "${domList[*]}" --arg typeId "${typeId}" --arg kindId "${kindId}" '[ $domains | split(" ")[] as $item | {item: $item, type: $typeId, kind: $kindId} ]') # Send the request - data=$(PostFTLData "domains:batchDelete" "${json}") + data=$(PostFTLData "domains:batchDelete" "${json}" "status") + # Separate the status from the data + status=$(printf %s "${data#"${data%???}"}") + data=$(printf %s "${data%???}") # If there is an .error object in the returned data, display it local error @@ -138,12 +141,10 @@ RemoveDomain() { if [[ $error != "null" && $error != "" ]]; then echo -e " ${CROSS} Failed to remove domain(s):" echo -e " $(jq <<< "${data}" '.error')" - elif [[ "${verbose}" == true ]]; then - echo -e " ${TICK} Removed ${#domList[@]} domain(s):" - # Loop through the domains and display them - for dom in "${domList[@]}"; do - echo -e " - ${COL_BLUE}${dom}${COL_NC}" - done + elif [[ "${verbose}" == true && "${status}" == "204" ]]; then + echo -e " ${TICK} Domain(s) removed from the ${kindId} ${typeId}list" + elif [[ "${verbose}" == true && "${status}" == "404" ]]; then + echo -e " ${TICK} Requested domain(s) not found on ${kindId} ${typeId}list" fi # Log out From 21fb5dabe19f97c53a76c9b0b81034352df5ce82 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 29 Jun 2024 09:32:13 +0200 Subject: [PATCH 16/18] Use "remove" and "delete" instead of "not" to trigger listed domain removals Signed-off-by: DL6ER --- advanced/Scripts/list.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/advanced/Scripts/list.sh b/advanced/Scripts/list.sh index d6a30325..8c930f04 100755 --- a/advanced/Scripts/list.sh +++ b/advanced/Scripts/list.sh @@ -47,7 +47,7 @@ Example: 'pihole ${abbrv} site.com', or 'pihole ${abbrv} site1.com site2.com' ${typeId^} one or more ${kindId} domains Options: - not -d, --delmode Remove domain(s) + remove, delete, -d Remove domain(s) -q, --quiet Make output less verbose -h, --help Show this help dialog -l, --list Display domains @@ -204,7 +204,7 @@ while (( "$#" )); do "--allow-wild" | "allow-wild" ) kindId="regex"; typeId="allow"; wildcard=true; abbrv="--allow-wild";; "--regex" | "regex" ) kindId="regex"; typeId="deny"; abbrv="--regex";; "--wild" | "wildcard" ) kindId="regex"; typeId="deny"; wildcard=true; abbrv="--wild";; - "-d" | "not" | "--delmode" ) addmode=false;; + "-d" | "remove" | "delete" ) addmode=false;; "-q" | "--quiet" ) verbose=false;; "-h" | "--help" ) helpFunc;; "-l" | "--list" ) Displaylist;; From 7ca4b59b34f4f23199c7a9c2f751df39f9bae858 Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 16 Jul 2024 10:27:07 +0200 Subject: [PATCH 17/18] Do not test API availability outside of LoginAPI Signed-off-by: DL6ER --- advanced/Scripts/query.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/advanced/Scripts/query.sh b/advanced/Scripts/query.sh index c76e890e..3340bdd2 100755 --- a/advanced/Scripts/query.sh +++ b/advanced/Scripts/query.sh @@ -131,9 +131,6 @@ Main() { # https://github.com/pi-hole/FTL/pull/1715 # no need to do it here - # Test if the authentication endpoint is available - TestAPIAvailability - # Authenticate with FTL LoginAPI From 897e23089c10a6ac8c7c51dbfe5c897c7051c22c Mon Sep 17 00:00:00 2001 From: DL6ER Date: Tue, 16 Jul 2024 10:55:28 +0200 Subject: [PATCH 18/18] Implement pihole enable/disable using the API Signed-off-by: DL6ER --- advanced/Scripts/pihole-reenable.sh | 23 ------ pihole | 123 +++++++++++++--------------- 2 files changed, 57 insertions(+), 89 deletions(-) delete mode 100755 advanced/Scripts/pihole-reenable.sh diff --git a/advanced/Scripts/pihole-reenable.sh b/advanced/Scripts/pihole-reenable.sh deleted file mode 100755 index 93ec3b95..00000000 --- a/advanced/Scripts/pihole-reenable.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -# Pi-hole: A black hole for Internet advertisements -# (c) 2020 Pi-hole, LLC (https://pi-hole.net) -# Network-wide ad blocking via your own hardware. -# -# This file is copyright under the latest version of the EUPL. -# Please see LICENSE file for your rights under this license. -# -# -# The pihole disable command has the option to set a specified time before -# blocking is automatically re-enabled. -# -# Present script is responsible for the sleep & re-enable part of the job and -# is automatically terminated if it is still running when pihole is enabled by -# other means. -# -# This ensures that pihole ends up in the correct state after a sequence of -# commands suchs as: `pihole disable 30s; pihole enable; pihole disable` - -readonly PI_HOLE_BIN_DIR="/usr/local/bin" - -sleep "${1}" -"${PI_HOLE_BIN_DIR}"/pihole enable diff --git a/pihole b/pihole index 5a3c847d..08ff5b76 100755 --- a/pihole +++ b/pihole @@ -19,9 +19,13 @@ PI_HOLE_BIN_DIR="/usr/local/bin" readonly colfile="${PI_HOLE_SCRIPT_DIR}/COL_TABLE" source "${colfile}" -utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh" +readonly utilsfile="${PI_HOLE_SCRIPT_DIR}/utils.sh" source "${utilsfile}" +# Source api functions +readonly apifile="${PI_HOLE_SCRIPT_DIR}/api.sh" +source "${apifile}" + versionsfile="/etc/pihole/versions" if [ -f "${versionsfile}" ]; then # Only source versionsfile if the file exits @@ -205,73 +209,60 @@ restartDNS() { piholeEnable() { if [[ "${2}" == "-h" ]] || [[ "${2}" == "--help" ]]; then - echo "Usage: pihole disable [time] -Example: 'pihole disable', or 'pihole disable 5m' -Disable Pi-hole subsystems + echo "Usage: pihole enable/disable [time] +Example: 'pihole enable', or 'pihole disable 5m' +En- or disable Pi-hole subsystems Time: - #s Disable Pi-hole functionality for # second(s) - #m Disable Pi-hole functionality for # minute(s)" + #s En-/disable Pi-hole functionality for # second(s) + #m En-/disable Pi-hole functionality for # minute(s)" exit 0 - elif [[ "${1}" == "0" ]]; then - # Disable Pi-hole - if ! getFTLConfigValue dns.blocking.active; then - echo -e " ${INFO} Blocking already disabled, nothing to do" - exit 0 - fi - if [[ $# -gt 1 ]]; then - local error=false - if [[ "${2}" == *"s" ]]; then - tt=${2%"s"} - if [[ "${tt}" =~ ^-?[0-9]+$ ]];then - local str="Disabling blocking for ${tt} seconds" - echo -e " ${INFO} ${str}..." - local str="Blocking will be re-enabled in ${tt} seconds" - nohup "${PI_HOLE_SCRIPT_DIR}"/pihole-reenable.sh ${tt} /dev/null & - else - local error=true - fi - elif [[ "${2}" == *"m" ]]; then - tt=${2%"m"} - if [[ "${tt}" =~ ^-?[0-9]+$ ]];then - local str="Disabling blocking for ${tt} minutes" - echo -e " ${INFO} ${str}..." - local str="Blocking will be re-enabled in ${tt} minutes" - tt=$((${tt}*60)) - nohup "${PI_HOLE_SCRIPT_DIR}"/pihole-reenable.sh ${tt} /dev/null & - else - local error=true - fi - elif [[ -n "${2}" ]]; then - local error=true - else - echo -e " ${INFO} Disabling blocking" - fi - - if [[ ${error} == true ]];then - echo -e " ${COL_LIGHT_RED}Unknown format for delayed reactivation of the blocking!${COL_NC}" - echo -e " Try 'pihole disable --help' for more information." - exit 1 - fi - - local str="Pi-hole Disabled" - setFTLConfigValue dns.blocking.active false - fi - else - # Enable Pi-hole - killall -q pihole-reenable - if getFTLConfigValue dns.blocking.active; then - echo -e " ${INFO} Blocking already enabled, nothing to do" - exit 0 - fi - echo -e " ${INFO} Enabling blocking" - local str="Pi-hole Enabled" - - setFTLConfigValue dns.blocking.active true fi - restartDNS reload-lists + # Get timer + local tt="null" + if [[ $# -gt 1 ]]; then + local error=false + if [[ "${2}" == *"s" ]]; then + tt=${2%"s"} + if [[ ! "${tt}" =~ ^-?[0-9]+$ ]];then + local error=true + fi + elif [[ "${2}" == *"m" ]]; then + tt=${2%"m"} + if [[ "${tt}" =~ ^-?[0-9]+$ ]];then + tt=$((${tt}*60)) + else + local error=true + fi + elif [[ -n "${2}" ]]; then + local error=true + fi + + if [[ ${error} == true ]];then + echo -e " ${COL_LIGHT_RED}Unknown format for blocking timer!${COL_NC}" + echo -e " Try 'pihole disable --help' for more information." + exit 1 + fi + fi + + # Authenticate with the API + LoginAPI + + # Send the request + data=$(PostFTLData "dns/blocking" "{ \"blocking\": ${1}, \"timer\": ${tt} }") + + # Check the response + local extra=" forever" + local timer="$(echo "${data}"| jq --raw-output '.timer' )" + if [[ "${timer}" != "null" ]]; then + extra=" for ${timer}s" + fi + local str="Pi-hole $(echo "${data}" | jq --raw-output '.blocking')${extra}" + + # Logout from the API + LogoutAPI echo -e "${OVER} ${TICK} ${str}" } @@ -548,8 +539,8 @@ case "${1}" in "-r" | "reconfigure" ) ;; "-l" | "logging" ) ;; "uninstall" ) ;; - "enable" ) ;; - "disable" ) ;; + "enable" ) need_root=0;; + "disable" ) need_root=0;; "-d" | "debug" ) ;; "restartdns" ) ;; "-g" | "updateGravity" ) ;; @@ -591,8 +582,8 @@ case "${1}" in "-g" | "updateGravity" ) updateGravityFunc "$@";; "-l" | "logging" ) piholeLogging "$@";; "uninstall" ) uninstallFunc;; - "enable" ) piholeEnable 1;; - "disable" ) piholeEnable 0 "$2";; + "enable" ) piholeEnable true "$2";; + "disable" ) piholeEnable false "$2";; "restartdns" ) restartDNS "$2";; "reloaddns" ) restartDNS "reload";; "setpassword" ) SetWebPassword "$@";;