Merge pull request #2308 from pi-hole/release/v4.0

Release/v4.0
This commit is contained in:
Dan Schaper 2018-08-05 10:40:22 -07:00 committed by GitHub
commit ddbdb51d20
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 6495 additions and 4889 deletions

View file

@ -9,7 +9,7 @@ end_of_line = lf
insert_final_newline = true insert_final_newline = true
indent_style = space indent_style = space
indent_size = tab indent_size = tab
tab_width = 2 tab_width = 4
charset = utf-8 charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true

2
.github/dco.yml vendored Normal file
View file

@ -0,0 +1,2 @@
require:
members: false

5
.gitignore vendored
View file

@ -3,6 +3,11 @@
*.swp *.swp
__pycache__ __pycache__
.cache .cache
.pytest_cache
.tox
.eggs
*.egg-info
# Created by https://www.gitignore.io/api/jetbrains+iml # Created by https://www.gitignore.io/api/jetbrains+iml

View file

@ -1,11 +1,5 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<option name="OTHER_INDENT_OPTIONS">
<value>
<option name="INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</value>
</option>
<MarkdownNavigatorCodeStyleSettings> <MarkdownNavigatorCodeStyleSettings>
<option name="RIGHT_MARGIN" value="72" /> <option name="RIGHT_MARGIN" value="72" />
</MarkdownNavigatorCodeStyleSettings> </MarkdownNavigatorCodeStyleSettings>

View file

@ -1,3 +1,6 @@
linters: linters:
shellcheck: shellcheck:
shell: bash shell: bash
phpcs:
csslint:
flake8:

View file

@ -7,4 +7,6 @@ python:
install: install:
- pip install -r requirements.txt - pip install -r requirements.txt
script: py.test -vv script:
# tox.ini handles setup, ordering of docker build first, and then run tests
- tox

View file

@ -1,5 +1,3 @@
_This template was created based on the work of [`udemy-dl`](https://github.com/nishad/udemy-dl/blob/master/LICENSE)._
# Contributors Guide # Contributors Guide
Please read and understand the contribution guide before creating an issue or pull request. Please read and understand the contribution guide before creating an issue or pull request.

View file

@ -3,7 +3,7 @@
<b>Network-wide ad blocking via your own Linux hardware</b><br/> <b>Network-wide ad blocking via your own Linux hardware</b><br/>
</p> </p>
The Pi-hole is a [DNS sinkhole](https://en.wikipedia.org/wiki/DNS_Sinkhole) that protects your devices from unwanted content, without installing any client-side software. The Pi-hole[®](https://pi-hole.net/trademark-rules-and-brand-guidelines/) is a [DNS sinkhole](https://en.wikipedia.org/wiki/DNS_Sinkhole) that protects your devices from unwanted content, without installing any client-side software.
- **Easy-to-install**: our versatile installer walks you through the process, and [takes less than ten minutes](https://www.youtube.com/watch?v=vKWjx1AQYgs) - **Easy-to-install**: our versatile installer walks you through the process, and [takes less than ten minutes](https://www.youtube.com/watch?v=vKWjx1AQYgs)
- **Resolute**: content is blocked in _non-browser locations_, such as ad-laden mobile apps and smart TVs - **Resolute**: content is blocked in _non-browser locations_, such as ad-laden mobile apps and smart TVs
@ -60,16 +60,21 @@ Make no mistake: **your support is absolutely vital to help keep us innovating!*
### Donations ### Donations
Sending a donation using our links below is **extremely helpful** in offsetting a portion of our monthly expenses: Sending a donation using our links below is **extremely helpful** in offsetting a portion of our monthly expenses:
&nbsp;<img src="https://pi-hole.github.io/graphics/Badges/paypal-badge-black.svg" width="24" height="24" alt="PP"/> <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3J2L3Z4DHW9UY">Donate via PayPal</a><br/> - <img src="https://pi-hole.github.io/graphics/Badges/paypal-badge-black.svg" width="24" height="24" alt="PP"/> <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3J2L3Z4DHW9UY">Donate via PayPal</a><br/>
&nbsp;<img src="https://pi-hole.github.io/graphics/Badges/bitcoin-badge-black.svg" width="24" height="24" alt="BTC"/> Bitcoin Address: <code>1GKnevUnVaQM2pQieMyeHkpr8DXfkpfAtL</code> - <img src="https://pi-hole.github.io/graphics/Badges/bitcoin-badge-black.svg" width="24" height="24" alt="BTC"/> [Bitcoin](https://commerce.coinbase.com/checkout/fb7facaf-bebd-46be-bb77-b358f4546763): <code>
3MDPzjXu2hjw5sGLJvKUi1uXbvQPzVrbpF</code></br>
- <img src="https://pi-hole.github.io/graphics/Badges/bitcoin-badge-black.svg" width="24" height="24" alt="BTC"/> [Bitcoin Cash](https://commerce.coinbase.com/checkout/fb7facaf-bebd-46be-bb77-b358f4546763): <code>qzqsz4aju2eecc6uhs7tus4vlwhhela24sdruf4qp5</code></br>
- <img src="https://pi-hole.github.io/graphics/Badges/ethereum-badge-black.svg" width="24" height="24" alt="BTC"/> [Ethereum](https://commerce.coinbase.com/checkout/fb7facaf-bebd-46be-bb77-b358f4546763): <code>0x79d4e90A4a0C732819526c93e21A3F1356A2FAe1</code>
### Alternative support ### Alternative support
If you'd rather not donate (_which is okay!_), there are other ways you can help support us: If you'd rather not [donate](https://pi-hole.net/donate/) (_which is okay!_), there are other ways you can help support us:
- [Patreon](https://patreon.com/pihole) _Become a patron for rewards_
- [Digital Ocean](http://www.digitalocean.com/?refcode=344d234950e1) affiliate link - [Digital Ocean](http://www.digitalocean.com/?refcode=344d234950e1) _affiliate link_
- [Vultr](http://www.vultr.com/?ref=7190426) affiliate link - [UNIXstickers.com](http://unixstickers.refr.cc/jacobs) _save $5 when you spend $9 using our affiliate link_
- [UNIXstickers.com](http://unixstickers.refr.cc/jacobs) affiliate link - [Pi-hole Swag Store](https://pi-hole.net/shop/) _affiliate link_
- [Pi-hole Swag Store](https://pi-hole.net/shop/) - [Amazon](http://www.amazon.com/exec/obidos/redirect-home/pihole09-20) _affiliate link_
- [DNS Made Easy](https://cp.dnsmadeeasy.com/u/133706) _affiliate link_
- [Vultr](http://www.vultr.com/?ref=7190426) _affiliate link_
- Spreading the word about our software, and how you have benefited from it - Spreading the word about our software, and how you have benefited from it
### Contributing via GitHub ### Contributing via GitHub
@ -93,9 +98,6 @@ While we are primarily reachable on our <a href="https://discourse.pi-hole.net/"
<li><a href="https://discourse.pi-hole.net/c/faqs">Frequently Asked Questions</a></li> <li><a href="https://discourse.pi-hole.net/c/faqs">Frequently Asked Questions</a></li>
<li><a href="https://github.com/pi-hole/pi-hole/wiki">Pi-hole Wiki</a></li> <li><a href="https://github.com/pi-hole/pi-hole/wiki">Pi-hole Wiki</a></li>
<li><a href="https://discourse.pi-hole.net/c/feature-requests?order=votes">Feature Requests</a></li> <li><a href="https://discourse.pi-hole.net/c/feature-requests?order=votes">Feature Requests</a></li>
</ul>
<br/>
<ul>
<li><a href="https://discourse.pi-hole.net/">Discourse User Forum</a></li> <li><a href="https://discourse.pi-hole.net/">Discourse User Forum</a></li>
<li><a href="https://www.reddit.com/r/pihole/">Reddit</a></li> <li><a href="https://www.reddit.com/r/pihole/">Reddit</a></li>
<li><a href="https://gitter.im/pi-hole/pi-hole">Gitter</a> (Real-time chat)</li> <li><a href="https://gitter.im/pi-hole/pi-hole">Gitter</a> (Real-time chat)</li>
@ -127,7 +129,7 @@ You can read our [Core Feature Breakdown](https://github.com/pi-hole/pi-hole/wik
### The Web Interface Dashboard ### The Web Interface Dashboard
This [optional dashboard](https://github.com/pi-hole/AdminLTE) allows you to view stats, change settings, and configure your Pi-hole. It's the power of the Command Line Interface, with none of the learning curve! This [optional dashboard](https://github.com/pi-hole/AdminLTE) allows you to view stats, change settings, and configure your Pi-hole. It's the power of the Command Line Interface, with none of the learning curve!
<a href="https://pi-hole.github.io/graphics/Screenshots/dashboard.png"><img src="https://pi-hole.github.io/graphics/Screenshots/dashboard.png" width="888" height="522" alt="Pi-hole Dashboard"/></a> <img src="https://pi-hole.github.io/graphics/Screenshots/pihole-dashboard.png" alt="Pi-hole Dashboard"/></a>
Some notable features include: Some notable features include:
* Mobile friendly interface * Mobile friendly interface
@ -142,11 +144,11 @@ Some notable features include:
There are several ways to [access the dashboard](https://discourse.pi-hole.net/t/how-do-i-access-pi-holes-dashboard-admin-interface/3168): There are several ways to [access the dashboard](https://discourse.pi-hole.net/t/how-do-i-access-pi-holes-dashboard-admin-interface/3168):
1. `http://<IP_ADDPRESS_OF_YOUR_PI_HOLE>/admin/` 1. `http://<IP_ADDPRESS_OF_YOUR_PI_HOLE>/admin/`
2. `http:/pi.hole/admin/` (when using Pi-hole as your DNS server) 2. `http://pi.hole/admin/` (when using Pi-hole as your DNS server)
3. `http://pi.hole/` (when using Pi-hole as your DNS server) 3. `http://pi.hole/` (when using Pi-hole as your DNS server)
## The Faster-Than-Light Engine ## Faster-than-light Engine
The [FTL Engine](https://github.com/pi-hole/FTL) is a lightweight, purpose-built daemon used to provide statistics needed for the Web Interface, and its API can be easily integrated into your own projects. As the name implies, FTL does this all *very quickly*! FTLDNS[™](https://pi-hole.net/trademark-rules-and-brand-guidelines/) is a lightweight, purpose-built daemon used to provide statistics needed for the Web Interface, and its API can be easily integrated into your own projects. As the name implies, FTLDNS does this all *very quickly*!
Some of the statistics you can integrate include: Some of the statistics you can integrate include:
* Total number of domains being blocked * Total number of domains being blocked
@ -172,31 +174,13 @@ Pi-hole being a **advertising-aware DNS/Web server**, makes use of the following
* [AdminLTE Dashboard](https://github.com/almasaeed2010/AdminLTE) - premium admin control panel based on Bootstrap 3.x * [AdminLTE Dashboard](https://github.com/almasaeed2010/AdminLTE) - premium admin control panel based on Bootstrap 3.x
While quite outdated at this point, [this original blog post about Pi-hole](https://jacobsalmela.com/2015/06/16/block-millions-ads-network-wide-with-a-raspberry-pi-hole-2-0/) goes into **great detail** about how Pi-hole was originally setup and how it works. Syntactically, it's no longer accurate, but the same basic principles and logic still apply to Pi-hole's current state. While quite outdated at this point, [this original blog post about Pi-hole](https://jacobsalmela.com/2015/06/16/block-millions-ads-network-wide-with-a-raspberry-pi-hole-2-0/) goes into **great detail** about how Pi-hole was originally setup and how it works. Syntactically, it's no longer accurate, but the same basic principles and logic still apply to Pi-hole's current state.
-----
## Pi-hole Projects
- [The Big Blocklist Collection](https://wally3k.github.io)
- [Docker Pi-hole container (x86 and ARM)](https://hub.docker.com/r/diginc/pi-hole/)
- [Pi-Hole in the cloud](http://blog.codybunch.com/2015/07/28/Pi-Hole-in-the-cloud/)
- [Pie in the Sky-Hole [A Pi-Hole in the cloud for ad-blocking via DNS]](https://dlaa.me/blog/post/skyhole)
- [Pi-hole Enable/Disable Button](http://thetimmy.silvernight.org/pages/endisbutton/)
- [Minibian Pi-hole](https://munkjensen.net/wiki/index.php/See_my_Pi-Hole#Minibian_Pi-hole)
- [CHiP-hole: Network-wide Ad-blocker](https://www.hackster.io/jacobsalmela/chip-hole-network-wide-ad-blocker-98e037)
- [Chrome Extension: Pi-Hole List Editor](https://chrome.google.com/webstore/detail/pi-hole-list-editor/hlnoeoejkllgkjbnnnhfolapllcnaglh) ([Source Code](https://github.com/packtloss/pihole-extension))
- [Splunk: Pi-hole Visualiser](https://splunkbase.splunk.com/app/3023/)
- [Adblocking with Pi-hole and Ubuntu 14.04 on VirtualBox](https://hbalagtas.blogspot.com.au/2016/02/adblocking-with-pi-hole-and-ubuntu-1404.html)
- [Pi-hole stats in your Mac's menu bar](https://getbitbar.com/plugins/Network/pi-hole.1m.py)
- [Pi-hole unRAID Template](https://forums.lime-technology.com/topic/36810-support-spants-nodered-mqtt-dashing-couchdb/)
- [Copernicus: Windows Tray Application](https://github.com/goldbattle/copernicus)
- [Let your blink1 device blink when Pi-hole filters ads](https://gist.github.com/elpatron68/ec0b4c582e5abf604885ac1e068d233f)
- [Pi-hole metrics](https://github.com/nlamirault/pihole_exporter) exporter for [Prometheus](https://prometheus.io/)
- [Magic Mirror with DNS Filtering](https://zonksec.com/blog/magic-mirror-dns-filtering/#dnssoftware)
- [Pi-hole Droid: Android client](https://github.com/friimaind/pi-hole-droid)
- [Windows DNS Swapper](https://github.com/roots84/DNS-Swapper), see [#1400](https://github.com/pi-hole/pi-hole/issues/1400)
----- -----
## Coverage ## Coverage
- [Software Engineering Daily: Interview with the creator of Pi-hole](https://softwareengineeringdaily.com/2018/05/29/pi-hole-ad-blocker-hardware-with-jacob-salmela/)
- [Bloomberg Business Week: Brotherhood of the Ad blockers](https://www.bloomberg.com/news/features/2018-05-10/inside-the-brotherhood-of-pi-hole-ad-blockers)
- [Securing DNS across all of my devices with Pi-Hole + DNS-over-HTTPS + 1.1.1.1](https://scotthelme.co.uk/securing-dns-across-all-of-my-devices-with-pihole-dns-over-https-1-1-1-1/)
- [Adafruit: installing Pi-hole on a Pi Zero W](https://learn.adafruit.com/pi-hole-ad-blocker-with-pi-zero-w/install-pi-hole)
- [Lifehacker: Turn A Raspberry Pi Into An Ad Blocker With A Single Command](https://www.lifehacker.com.au/2015/02/turn-a-raspberry-pi-into-an-ad-blocker-with-a-single-command/) - [Lifehacker: Turn A Raspberry Pi Into An Ad Blocker With A Single Command](https://www.lifehacker.com.au/2015/02/turn-a-raspberry-pi-into-an-ad-blocker-with-a-single-command/)
- [MakeUseOf: Adblock Everywhere: The Raspberry Pi-Hole Way](http://www.makeuseof.com/tag/adblock-everywhere-raspberry-pi-hole-way/) - [MakeUseOf: Adblock Everywhere: The Raspberry Pi-Hole Way](http://www.makeuseof.com/tag/adblock-everywhere-raspberry-pi-hole-way/)
- [Catchpoint: Ad-Blocking on Apple iOS9: Valuing the End User Experience](http://blog.catchpoint.com/2015/09/14/ad-blocking-apple/) - [Catchpoint: Ad-Blocking on Apple iOS9: Valuing the End User Experience](http://blog.catchpoint.com/2015/09/14/ad-blocking-apple/)
@ -215,3 +199,12 @@ While quite outdated at this point, [this original blog post about Pi-hole](http
- [CryptoAUSTRALIA: How We Tried 5 Privacy Focused Raspberry Pi Projects](https://blog.cryptoaustralia.org.au/2017/10/05/5-privacy-focused-raspberry-pi-projects/) - [CryptoAUSTRALIA: How We Tried 5 Privacy Focused Raspberry Pi Projects](https://blog.cryptoaustralia.org.au/2017/10/05/5-privacy-focused-raspberry-pi-projects/)
- [CryptoAUSTRALIA: Pi-hole Workshop](https://blog.cryptoaustralia.org.au/2017/11/02/pi-hole-network-wide-ad-blocker/) - [CryptoAUSTRALIA: Pi-hole Workshop](https://blog.cryptoaustralia.org.au/2017/11/02/pi-hole-network-wide-ad-blocker/)
- [Know How 355: Killing ads with a Raspberry Pi-Hole!](https://www.twit.tv/shows/know-how/episodes/355) - [Know How 355: Killing ads with a Raspberry Pi-Hole!](https://www.twit.tv/shows/know-how/episodes/355)
-----
## Pi-hole Projects
- [The Big Blocklist Collection](https://wally3k.github.io)
- [Pie in the Sky-Hole](https://dlaa.me/blog/post/skyhole)
- [Copernicus: Windows Tray Application](https://github.com/goldbattle/copernicus)
- [Magic Mirror with DNS Filtering](https://zonksec.com/blog/magic-mirror-dns-filtering/#dnssoftware)
- [Windows DNS Swapper](https://github.com/roots84/DNS-Swapper)

View file

@ -1,23 +0,0 @@
# The below list amalgamates several lists we used previously.
# See `https://github.com/StevenBlack/hosts` for details
##StevenBlack's list
https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
##MalwareDomains
https://mirror1.malwaredomains.com/files/justdomains
##Cameleon
http://sysctl.org/cameleon/hosts
##Zeustracker
https://zeustracker.abuse.ch/blocklist.php?download=domainblocklist
##Disconnect.me Tracking
https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt
##Disconnect.me Ads
https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt
##Hosts-file.net
https://hosts-file.net/ad_servers.txt

View file

@ -179,6 +179,7 @@ get_init_stats() {
90009[2-3]|920093) sys_model=" Zero";; # 512MB 90009[2-3]|920093) sys_model=" Zero";; # 512MB
9000c1) sys_model=" Zero W";; # 512MB 9000c1) sys_model=" Zero W";; # 512MB
a02082|a[2-3]2082) sys_model=" 3, Model B";; # 1GB a02082|a[2-3]2082) sys_model=" 3, Model B";; # 1GB
a020d3) sys_model=" 3, Model B+";; # 1GB
*) sys_model="";; *) sys_model="";;
esac esac
sys_type="Raspberry Pi$sys_model" sys_type="Raspberry Pi$sys_model"
@ -477,10 +478,7 @@ chronoFunc() {
${COL_LIGHT_RED}Press Ctrl-C to exit${COL_NC} ${COL_LIGHT_RED}Press Ctrl-C to exit${COL_NC}
${COL_DARK_GRAY}$scr_line_str${COL_NC}" ${COL_DARK_GRAY}$scr_line_str${COL_NC}"
else else
echo -e "|¯¯¯(¯)_|¯|_ ___|¯|___$phc_ver_str echo -e "|¯¯¯(¯)_|¯|_ ___|¯|___$phc_ver_str| ¯_/¯|_| ' \\/ _ \\ / -_)$lte_ver_str|_| |_| |_||_\\___/_\\___|$ftl_ver_str ${COL_DARK_GRAY}$scr_line_str${COL_NC}"
| ¯_/¯|_| ' \\/ _ \\ / -_)$lte_ver_str
|_| |_| |_||_\\___/_\\___|$ftl_ver_str
${COL_DARK_GRAY}$scr_line_str${COL_NC}"
fi fi
printFunc " Hostname: " "$sys_name" "$host_info" printFunc " Hostname: " "$sys_name" "$host_info"

View file

@ -13,10 +13,11 @@ basename=pihole
piholeDir=/etc/"${basename}" piholeDir=/etc/"${basename}"
whitelist="${piholeDir}"/whitelist.txt whitelist="${piholeDir}"/whitelist.txt
blacklist="${piholeDir}"/blacklist.txt blacklist="${piholeDir}"/blacklist.txt
readonly wildcardlist="/etc/dnsmasq.d/03-pihole-wildcard.conf" readonly regexlist="/etc/pihole/regex.list"
reload=false reload=false
addmode=true addmode=true
verbose=true verbose=true
wildcard=false
domList=() domList=()
@ -31,9 +32,12 @@ helpFunc() {
if [[ "${listMain}" == "${whitelist}" ]]; then if [[ "${listMain}" == "${whitelist}" ]]; then
param="w" param="w"
type="white" type="white"
elif [[ "${listMain}" == "${wildcardlist}" ]]; then elif [[ "${listMain}" == "${regexlist}" && "${wildcard}" == true ]]; then
param="wild" param="-wild"
type="wildcard black" type="wildcard black"
elif [[ "${listMain}" == "${regexlist}" ]]; then
param="-regex"
type="regex black"
else else
param="b" param="b"
type="black" type="black"
@ -57,7 +61,8 @@ Options:
EscapeRegexp() { EscapeRegexp() {
# This way we may safely insert an arbitrary # This way we may safely insert an arbitrary
# string in our regular expressions # string in our regular expressions
# Also remove leading "." if present # 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" echo $* | sed 's/^\.*//' | sed "s/[]\.|$(){}?+*^]/\\\\&/g" | sed "s/\\//\\\\\//g"
} }
@ -65,10 +70,14 @@ HandleOther() {
# Convert to lowercase # Convert to lowercase
domain="${1,,}" domain="${1,,}"
# Check validity of domain # Check validity of domain (don't check for regex entries)
if [[ "${#domain}" -le 253 ]]; then if [[ "${#domain}" -le 253 ]]; then
validDomain=$(grep -P "^((-|_)*[a-z\d]((-|_)*[a-z\d])*(-|_)*)(\.(-|_)*([a-z\d]((-|_)*[a-z\d])*))*$" <<< "${domain}") # Valid chars check if [[ "${listMain}" == "${regexlist}" && "${wildcard}" == false ]]; then
validDomain=$(grep -P "^[^\.]{1,63}(\.[^\.]{1,63})*$" <<< "${validDomain}") # Length of each label 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
fi
fi fi
if [[ -n "${validDomain}" ]]; then if [[ -n "${validDomain}" ]]; then
@ -94,9 +103,6 @@ PoplistFile() {
if ${addmode}; then if ${addmode}; then
AddDomain "${dom}" "${listMain}" AddDomain "${dom}" "${listMain}"
RemoveDomain "${dom}" "${listAlt}" RemoveDomain "${dom}" "${listAlt}"
if [[ "${listMain}" == "${whitelist}" || "${listMain}" == "${blacklist}" ]]; then
RemoveDomain "${dom}" "${wildcardlist}"
fi
else else
RemoveDomain "${dom}" "${listMain}" RemoveDomain "${dom}" "${listMain}"
fi fi
@ -109,7 +115,6 @@ AddDomain() {
[[ "${list}" == "${whitelist}" ]] && listname="whitelist" [[ "${list}" == "${whitelist}" ]] && listname="whitelist"
[[ "${list}" == "${blacklist}" ]] && listname="blacklist" [[ "${list}" == "${blacklist}" ]] && listname="blacklist"
[[ "${list}" == "${wildcardlist}" ]] && listname="wildcard blacklist"
if [[ "${list}" == "${whitelist}" || "${list}" == "${blacklist}" ]]; then if [[ "${list}" == "${whitelist}" || "${list}" == "${blacklist}" ]]; then
[[ "${list}" == "${whitelist}" && -z "${type}" ]] && type="--whitelist-only" [[ "${list}" == "${whitelist}" && -z "${type}" ]] && type="--whitelist-only"
@ -121,7 +126,7 @@ AddDomain() {
if [[ "${bool}" == false ]]; then if [[ "${bool}" == false ]]; then
# Domain not found in the whitelist file, add it! # Domain not found in the whitelist file, add it!
if [[ "${verbose}" == true ]]; then if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} Adding $1 to $listname..." echo -e " ${INFO} Adding ${1} to ${listname}..."
fi fi
reload=true reload=true
# Add it to the list we want to add it to # Add it to the list we want to add it to
@ -131,28 +136,26 @@ AddDomain() {
echo -e " ${INFO} ${1} already exists in ${listname}, no need to add!" echo -e " ${INFO} ${1} already exists in ${listname}, no need to add!"
fi fi
fi fi
elif [[ "${list}" == "${wildcardlist}" ]]; then elif [[ "${list}" == "${regexlist}" ]]; then
source "${piholeDir}/setupVars.conf"
# Remove the /* from the end of the IP addresses
IPV4_ADDRESS=${IPV4_ADDRESS%/*}
IPV6_ADDRESS=${IPV6_ADDRESS%/*}
[[ -z "${type}" ]] && type="--wildcard-only" [[ -z "${type}" ]] && type="--wildcard-only"
bool=true bool=true
domain="${1}"
[[ "${wildcard}" == true ]] && domain="(^|\\.)${domain//\./\\.}$"
# Is the domain in the list? # Is the domain in the list?
grep -e "address=\/${domain}\/" "${wildcardlist}" > /dev/null 2>&1 || bool=false # Search only for exactly matching lines
grep -Fx "${domain}" "${regexlist}" > /dev/null 2>&1 || bool=false
if [[ "${bool}" == false ]]; then if [[ "${bool}" == false ]]; then
if [[ "${verbose}" == true ]]; then if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} Adding $1 to wildcard blacklist..." echo -e " ${INFO} Adding ${domain} to regex list..."
fi fi
reload="restart" reload="restart"
echo "address=/$1/${IPV4_ADDRESS}" >> "${wildcardlist}" echo "$domain" >> "${regexlist}"
if [[ "${#IPV6_ADDRESS}" > 0 ]]; then
echo "address=/$1/${IPV6_ADDRESS}" >> "${wildcardlist}"
fi
else else
if [[ "${verbose}" == true ]]; then if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} ${1} already exists in wildcard blacklist, no need to add!" echo -e " ${INFO} ${domain} already exists in regex list, no need to add!"
fi fi
fi fi
fi fi
@ -164,7 +167,6 @@ RemoveDomain() {
[[ "${list}" == "${whitelist}" ]] && listname="whitelist" [[ "${list}" == "${whitelist}" ]] && listname="whitelist"
[[ "${list}" == "${blacklist}" ]] && listname="blacklist" [[ "${list}" == "${blacklist}" ]] && listname="blacklist"
[[ "${list}" == "${wildcardlist}" ]] && listname="wildcard blacklist"
if [[ "${list}" == "${whitelist}" || "${list}" == "${blacklist}" ]]; then if [[ "${list}" == "${whitelist}" || "${list}" == "${blacklist}" ]]; then
bool=true bool=true
@ -174,7 +176,7 @@ RemoveDomain() {
grep -Ex -q "${domain}" "${list}" > /dev/null 2>&1 || bool=false grep -Ex -q "${domain}" "${list}" > /dev/null 2>&1 || bool=false
if [[ "${bool}" == true ]]; then if [[ "${bool}" == true ]]; then
# Remove it from the other one # Remove it from the other one
echo -e " ${INFO} Removing $1 from $listname..." echo -e " ${INFO} Removing $1 from ${listname}..."
# /I flag: search case-insensitive # /I flag: search case-insensitive
sed -i "/${domain}/Id" "${list}" sed -i "/${domain}/Id" "${list}"
reload=true reload=true
@ -183,20 +185,25 @@ RemoveDomain() {
echo -e " ${INFO} ${1} does not exist in ${listname}, no need to remove!" echo -e " ${INFO} ${1} does not exist in ${listname}, no need to remove!"
fi fi
fi fi
elif [[ "${list}" == "${wildcardlist}" ]]; then elif [[ "${list}" == "${regexlist}" ]]; then
[[ -z "${type}" ]] && type="--wildcard-only" [[ -z "${type}" ]] && type="--wildcard-only"
domain="${1}"
[[ "${wildcard}" == true ]] && domain="(^|\\.)${domain//\./\\.}$"
bool=true bool=true
# Is it in the list? # Is it in the list?
grep -e "address=\/${domain}\/" "${wildcardlist}" > /dev/null 2>&1 || bool=false grep -Fx "${domain}" "${regexlist}" > /dev/null 2>&1 || bool=false
if [[ "${bool}" == true ]]; then if [[ "${bool}" == true ]]; then
# Remove it from the other one # Remove it from the other one
echo -e " ${INFO} Removing $1 from $listname..." echo -e " ${INFO} Removing $domain from regex list..."
# /I flag: search case-insensitive local lineNumber
sed -i "/address=\/${domain}/Id" "${list}" lineNumber=$(grep -Fnx "$domain" "${list}" | cut -f1 -d:)
sed -i "${lineNumber}d" "${list}"
reload=true reload=true
else else
if [[ "${verbose}" == true ]]; then if [[ "${verbose}" == true ]]; then
echo -e " ${INFO} ${1} does not exist in ${listname}, no need to remove!" echo -e " ${INFO} ${domain} does not exist in regex list, no need to remove!"
fi fi
fi fi
fi fi
@ -218,7 +225,7 @@ Displaylist() {
verbose=false verbose=false
echo -e "Displaying $string:\n" echo -e "Displaying $string:\n"
count=1 count=1
while IFS= read -r RD; do while IFS= read -r RD || [ -n "${RD}" ]; do
echo " ${count}: ${RD}" echo " ${count}: ${RD}"
count=$((count+1)) count=$((count+1))
done < "${listMain}" done < "${listMain}"
@ -241,7 +248,8 @@ for var in "$@"; do
case "${var}" in case "${var}" in
"-w" | "whitelist" ) listMain="${whitelist}"; listAlt="${blacklist}";; "-w" | "whitelist" ) listMain="${whitelist}"; listAlt="${blacklist}";;
"-b" | "blacklist" ) listMain="${blacklist}"; listAlt="${whitelist}";; "-b" | "blacklist" ) listMain="${blacklist}"; listAlt="${whitelist}";;
"-wild" | "wildcard" ) listMain="${wildcardlist}";; "--wild" | "wildcard" ) listMain="${regexlist}"; wildcard=true;;
"--regex" | "regex" ) listMain="${regexlist}";;
"-nr"| "--noreload" ) reload=false;; "-nr"| "--noreload" ) reload=false;;
"-d" | "--delmode" ) addmode=false;; "-d" | "--delmode" ) addmode=false;;
"-q" | "--quiet" ) verbose=false;; "-q" | "--quiet" ) verbose=false;;

View file

@ -17,180 +17,13 @@ source "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh"
# piholeGitURL set in basic-install.sh # piholeGitURL set in basic-install.sh
# is_repo() sourced from basic-install.sh # is_repo() sourced from basic-install.sh
# setupVars set in basic-install.sh # setupVars set in basic-install.sh
# check_download_exists sourced from basic-install.sh
# fully_fetch_repo sourced from basic-install.sh
# get_available_branches sourced from basic-install.sh
# fetch_checkout_pull_branch sourced from basic-install.sh
# checkout_pull_branch sourced from basic-install.sh
source "${setupVars}" source "${setupVars}"
update="false"
coltable="/opt/pihole/COL_TABLE"
source ${coltable}
check_download_exists() {
status=$(curl --head --silent "https://ftl.pi-hole.net/${1}" | head -n 1)
if grep -q "404" <<< "$status"; then
return 1
else
return 0
fi
}
FTLinstall() {
# Download and install FTL binary
local binary
binary="${1}"
local path
path="${2}"
local str
str="Installing FTL"
echo -ne " ${INFO} ${str}..."
if curl -sSL --fail "https://ftl.pi-hole.net/${path}" -o "/tmp/${binary}"; then
# Get sha1 of the binary we just downloaded for verification.
curl -sSL --fail "https://ftl.pi-hole.net/${path}.sha1" -o "/tmp/${binary}.sha1"
# Check if we just downloaded text, or a binary file.
cd /tmp || return 1
if sha1sum --status --quiet -c "${binary}".sha1; then
echo -n "transferred... "
stop_service pihole-FTL &> /dev/null
install -T -m 0755 "/tmp/${binary}" "/usr/bin/pihole-FTL"
rm "/tmp/${binary}" "/tmp/${binary}.sha1"
start_service pihole-FTL &> /dev/null
echo -e "${OVER} ${TICK} ${str}"
return 0
else
echo -e "${OVER} ${CROSS} ${str}"
echo -e " ${COL_LIGHT_RED}Error: Download of binary from ftl.pi-hole.net failed${COL_NC}"
return 1
fi
else
echo -e "${OVER} ${CROSS} ${str}"
echo -e " ${COL_LIGHT_RED}Error: URL not found${COL_NC}"
fi
}
get_binary_name() {
local machine
machine=$(uname -m)
local str
str="Detecting architecture"
echo -ne " ${INFO} ${str}..."
if [[ "${machine}" == "arm"* || "${machine}" == *"aarch"* ]]; then
# ARM
local rev
rev=$(uname -m | sed "s/[^0-9]//g;")
local lib
lib=$(ldd /bin/ls | grep -E '^\s*/lib' | awk '{ print $1 }')
if [[ "${lib}" == "/lib/ld-linux-aarch64.so.1" ]]; then
echo -e "${OVER} ${TICK} Detected ARM-aarch64 architecture"
binary="pihole-FTL-aarch64-linux-gnu"
elif [[ "${lib}" == "/lib/ld-linux-armhf.so.3" ]]; then
if [[ "$rev" -gt "6" ]]; then
echo -e "${OVER} ${TICK} Detected ARM-hf architecture (armv7+)"
binary="pihole-FTL-arm-linux-gnueabihf"
else
echo -e "${OVER} ${TICK} Detected ARM-hf architecture (armv6 or lower) Using ARM binary"
binary="pihole-FTL-arm-linux-gnueabi"
fi
else
echo -e "${OVER} ${TICK} Detected ARM architecture"
binary="pihole-FTL-arm-linux-gnueabi"
fi
elif [[ "${machine}" == "ppc" ]]; then
# PowerPC
echo -e "${OVER} ${TICK} Detected PowerPC architecture"
binary="pihole-FTL-powerpc-linux-gnu"
elif [[ "${machine}" == "x86_64" ]]; then
# 64bit
echo -e "${OVER} ${TICK} Detected x86_64 architecture"
binary="pihole-FTL-linux-x86_64"
else
# Something else - we try to use 32bit executable and warn the user
if [[ ! "${machine}" == "i686" ]]; then
echo -e "${OVER} ${CROSS} ${str}...
${COL_LIGHT_RED}Not able to detect architecture (unknown: ${machine}), trying 32bit executable
Contact support if you experience issues (e.g: FTL not running)${COL_NC}"
else
echo -e "${OVER} ${TICK} Detected 32bit (i686) architecture"
fi
binary="pihole-FTL-linux-x86_32"
fi
}
fully_fetch_repo() {
# Add upstream branches to shallow clone
local directory="${1}"
cd "${directory}" || return 1
if is_repo "${directory}"; then
git remote set-branches origin '*' || return 1
git fetch --quiet || return 1
else
return 1
fi
return 0
}
get_available_branches() {
# Return available branches
local directory
directory="${1}"
local output
cd "${directory}" || return 1
# Get reachable remote branches, but store STDERR as STDOUT variable
output=$( { git remote show origin | grep 'tracked' | sed 's/tracked//;s/ //g'; } 2>&1 )
echo "$output"
return
}
fetch_checkout_pull_branch() {
# Check out specified branch
local directory
directory="${1}"
local branch
branch="${2}"
# Set the reference for the requested branch, fetch, check it put and pull it
cd "${directory}" || return 1
git remote set-branches origin "${branch}" || return 1
git stash --all --quiet &> /dev/null || true
git clean --quiet --force -d || true
git fetch --quiet || return 1
checkout_pull_branch "${directory}" "${branch}" || return 1
}
checkout_pull_branch() {
# Check out specified branch
local directory
directory="${1}"
local branch
branch="${2}"
local oldbranch
cd "${directory}" || return 1
oldbranch="$(git symbolic-ref HEAD)"
str="Switching to branch: '${branch}' from '${oldbranch}'"
echo -ne " ${INFO} $str"
git checkout "${branch}" --quiet || return 1
echo -e "${OVER} ${TICK} $str"
if [[ "$(git diff "${oldbranch}" | grep -c "^")" -gt "0" ]]; then
update="true"
fi
git_pull=$(git pull || return 1)
if [[ "$git_pull" == *"up-to-date"* ]]; then
echo -e " ${INFO} ${git_pull}"
else
echo -e "$git_pull\\n"
fi
return 0
}
warning1() { warning1() {
echo " Please note that changing branches severely alters your Pi-hole subsystems" echo " Please note that changing branches severely alters your Pi-hole subsystems"
@ -218,21 +51,21 @@ checkout() {
# This is unlikely # This is unlikely
if ! is_repo "${PI_HOLE_FILES_DIR}" ; then if ! is_repo "${PI_HOLE_FILES_DIR}" ; then
echo -e " ${COL_LIGHT_RED}Error: Core Pi-hole repo is missing from system! echo -e " ${COL_LIGHT_RED}Error: Core Pi-hole repo is missing from system!"
Please re-run install script from https://github.com/pi-hole/pi-hole${COL_NC}" echo -e " Please re-run install script from https://github.com/pi-hole/pi-hole${COL_NC}"
exit 1; exit 1;
fi fi
if [[ "${INSTALL_WEB}" == "true" ]]; then if [[ "${INSTALL_WEB_INTERFACE}" == "true" ]]; then
if ! is_repo "${webInterfaceDir}" ; then if ! is_repo "${webInterfaceDir}" ; then
echo -e " ${COL_LIGHT_RED}Error: Web Admin repo is missing from system! echo -e " ${COL_LIGHT_RED}Error: Web Admin repo is missing from system!"
Please re-run install script from https://github.com/pi-hole/pi-hole${COL_NC}" echo -e " Please re-run install script from https://github.com/pi-hole/pi-hole${COL_NC}"
exit 1; exit 1;
fi fi
fi fi
if [[ -z "${1}" ]]; then if [[ -z "${1}" ]]; then
echo -e " ${COL_LIGHT_RED}Invalid option${COL_NC} echo -e " ${COL_LIGHT_RED}Invalid option${COL_NC}"
Try 'pihole checkout --help' for more information." echo -e " Try 'pihole checkout --help' for more information."
exit 1 exit 1
fi fi
@ -246,7 +79,7 @@ checkout() {
echo "" echo ""
echo -e " ${INFO} Pi-hole Core" echo -e " ${INFO} Pi-hole Core"
fetch_checkout_pull_branch "${PI_HOLE_FILES_DIR}" "development" || { echo " ${CROSS} Unable to pull Core developement branch"; exit 1; } fetch_checkout_pull_branch "${PI_HOLE_FILES_DIR}" "development" || { echo " ${CROSS} Unable to pull Core developement branch"; exit 1; }
if [[ "${INSTALL_WEB}" == "true" ]]; then if [[ "${INSTALL_WEB_INTERFACE}" == "true" ]]; then
echo "" echo ""
echo -e " ${INFO} Web interface" echo -e " ${INFO} Web interface"
fetch_checkout_pull_branch "${webInterfaceDir}" "devel" || { echo " ${CROSS} Unable to pull Web development branch"; exit 1; } fetch_checkout_pull_branch "${webInterfaceDir}" "devel" || { echo " ${CROSS} Unable to pull Web development branch"; exit 1; }
@ -257,13 +90,12 @@ checkout() {
local path local path
path="development/${binary}" path="development/${binary}"
echo "development" > /etc/pihole/ftlbranch echo "development" > /etc/pihole/ftlbranch
FTLinstall "${binary}" "${path}"
elif [[ "${1}" == "master" ]] ; then elif [[ "${1}" == "master" ]] ; then
# Shortcut to check out master branches # Shortcut to check out master branches
echo -e " ${INFO} Shortcut \"master\" detected - checking out master branches..." echo -e " ${INFO} Shortcut \"master\" detected - checking out master branches..."
echo -e " ${INFO} Pi-hole core" echo -e " ${INFO} Pi-hole core"
fetch_checkout_pull_branch "${PI_HOLE_FILES_DIR}" "master" || { echo " ${CROSS} Unable to pull Core master branch"; exit 1; } fetch_checkout_pull_branch "${PI_HOLE_FILES_DIR}" "master" || { echo " ${CROSS} Unable to pull Core master branch"; exit 1; }
if [[ ${INSTALL_WEB} == "true" ]]; then if [[ ${INSTALL_WEB_INTERFACE} == "true" ]]; then
echo -e " ${INFO} Web interface" echo -e " ${INFO} Web interface"
fetch_checkout_pull_branch "${webInterfaceDir}" "master" || { echo " ${CROSS} Unable to pull Web master branch"; exit 1; } fetch_checkout_pull_branch "${webInterfaceDir}" "master" || { echo " ${CROSS} Unable to pull Web master branch"; exit 1; }
fi fi
@ -272,7 +104,6 @@ checkout() {
local path local path
path="master/${binary}" path="master/${binary}"
echo "master" > /etc/pihole/ftlbranch echo "master" > /etc/pihole/ftlbranch
FTLinstall "${binary}" "${path}"
elif [[ "${1}" == "core" ]] ; then elif [[ "${1}" == "core" ]] ; then
str="Fetching branches from ${piholeGitUrl}" str="Fetching branches from ${piholeGitUrl}"
echo -ne " ${INFO} $str" echo -ne " ${INFO} $str"
@ -283,8 +114,8 @@ checkout() {
corebranches=($(get_available_branches "${PI_HOLE_FILES_DIR}")) corebranches=($(get_available_branches "${PI_HOLE_FILES_DIR}"))
if [[ "${corebranches[*]}" == *"master"* ]]; then if [[ "${corebranches[*]}" == *"master"* ]]; then
echo -e "${OVER} ${TICK} $str echo -e "${OVER} ${TICK} $str"
${INFO} ${#corebranches[@]} branches available for Pi-hole Core" echo -e "${INFO} ${#corebranches[@]} branches available for Pi-hole Core"
else else
# Print STDERR output from get_available_branches # Print STDERR output from get_available_branches
echo -e "${OVER} ${CROSS} $str\\n\\n${corebranches[*]}" echo -e "${OVER} ${CROSS} $str\\n\\n${corebranches[*]}"
@ -300,7 +131,7 @@ checkout() {
exit 1 exit 1
fi fi
checkout_pull_branch "${PI_HOLE_FILES_DIR}" "${2}" checkout_pull_branch "${PI_HOLE_FILES_DIR}" "${2}"
elif [[ "${1}" == "web" ]] && [[ "${INSTALL_WEB}" == "true" ]] ; then elif [[ "${1}" == "web" ]] && [[ "${INSTALL_WEB_INTERFACE}" == "true" ]] ; then
str="Fetching branches from ${webInterfaceGitUrl}" str="Fetching branches from ${webInterfaceGitUrl}"
echo -ne " ${INFO} $str" echo -ne " ${INFO} $str"
if ! fully_fetch_repo "${webInterfaceDir}" ; then if ! fully_fetch_repo "${webInterfaceDir}" ; then
@ -310,8 +141,8 @@ checkout() {
webbranches=($(get_available_branches "${webInterfaceDir}")) webbranches=($(get_available_branches "${webInterfaceDir}"))
if [[ "${webbranches[*]}" == *"master"* ]]; then if [[ "${webbranches[*]}" == *"master"* ]]; then
echo -e "${OVER} ${TICK} $str echo -e "${OVER} ${TICK} $str"
${INFO} ${#webbranches[@]} branches available for Web Admin" echo -e "${INFO} ${#webbranches[@]} branches available for Web Admin"
else else
# Print STDERR output from get_available_branches # Print STDERR output from get_available_branches
echo -e "${OVER} ${CROSS} $str\\n\\n${webbranches[*]}" echo -e "${OVER} ${CROSS} $str\\n\\n${webbranches[*]}"
@ -335,7 +166,9 @@ checkout() {
if check_download_exists "$path"; then if check_download_exists "$path"; then
echo " ${TICK} Branch ${2} exists" echo " ${TICK} Branch ${2} exists"
echo "${2}" > /etc/pihole/ftlbranch echo "${2}" > /etc/pihole/ftlbranch
FTLinstall "${binary}" "${path}" FTLinstall "${binary}"
start_service pihole-FTL
enable_service pihole-FTL
else else
echo " ${CROSS} Requested branch \"${2}\" is not available" echo " ${CROSS} Requested branch \"${2}\" is not available"
ftlbranches=( $(git ls-remote https://github.com/pi-hole/ftl | grep 'heads' | sed 's/refs\/heads\///;s/ //g' | awk '{print $2}') ) ftlbranches=( $(git ls-remote https://github.com/pi-hole/ftl | grep 'heads' | sed 's/refs\/heads\///;s/ //g' | awk '{print $2}') )
@ -350,7 +183,7 @@ checkout() {
fi fi
# Force updating everything # Force updating everything
if [[ ( ! "${1}" == "web" && ! "${1}" == "ftl" ) && "${update}" == "true" ]]; then if [[ ! "${1}" == "web" && ! "${1}" == "ftl" ]]; then
echo -e " ${INFO} Running installer to upgrade your installation" echo -e " ${INFO} Running installer to upgrade your installation"
if "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh" --unattended; then if "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh" --unattended; then
exit 0 exit 0

View file

@ -8,6 +8,7 @@
# This file is copyright under the latest version of the EUPL. # This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license. # Please see LICENSE file for your rights under this license.
# shellcheck source=/dev/null
# -e option instructs bash to immediately exit if any command [1] has a non-zero exit status # -e option instructs bash to immediately exit if any command [1] has a non-zero exit status
# -u a reference to any variable you haven't previously defined # -u a reference to any variable you haven't previously defined
@ -37,7 +38,7 @@ else
TICK="[${COL_GREEN}${COL_NC}]" TICK="[${COL_GREEN}${COL_NC}]"
CROSS="[${COL_RED}${COL_NC}]" CROSS="[${COL_RED}${COL_NC}]"
INFO="[i]" INFO="[i]"
OVER="\r\033[K" #OVER="\r\033[K"
fi fi
OBFUSCATED_PLACEHOLDER="<DOMAIN OBFUSCATED>" OBFUSCATED_PLACEHOLDER="<DOMAIN OBFUSCATED>"
@ -74,7 +75,7 @@ WEB_SERVER_LOG_DIRECTORY="${LOG_DIRECTORY}/lighttpd"
WEB_SERVER_CONFIG_DIRECTORY="/etc/lighttpd" WEB_SERVER_CONFIG_DIRECTORY="/etc/lighttpd"
HTML_DIRECTORY="/var/www/html" HTML_DIRECTORY="/var/www/html"
WEB_GIT_DIRECTORY="${HTML_DIRECTORY}/admin" WEB_GIT_DIRECTORY="${HTML_DIRECTORY}/admin"
BLOCK_PAGE_DIRECTORY="${HTML_DIRECTORY}/pihole" #BLOCK_PAGE_DIRECTORY="${HTML_DIRECTORY}/pihole"
# Files required by Pi-hole # Files required by Pi-hole
# https://discourse.pi-hole.net/t/what-files-does-pi-hole-use/1684 # https://discourse.pi-hole.net/t/what-files-does-pi-hole-use/1684
@ -85,14 +86,14 @@ PIHOLE_DHCP_CONFIG_FILE="${DNSMASQ_D_DIRECTORY}/02-pihole-dhcp.conf"
PIHOLE_WILDCARD_CONFIG_FILE="${DNSMASQ_D_DIRECTORY}/03-wildcard.conf" PIHOLE_WILDCARD_CONFIG_FILE="${DNSMASQ_D_DIRECTORY}/03-wildcard.conf"
WEB_SERVER_CONFIG_FILE="${WEB_SERVER_CONFIG_DIRECTORY}/lighttpd.conf" WEB_SERVER_CONFIG_FILE="${WEB_SERVER_CONFIG_DIRECTORY}/lighttpd.conf"
WEB_SERVER_CUSTOM_CONFIG_FILE="${WEB_SERVER_CONFIG_DIRECTORY}/external.conf" #WEB_SERVER_CUSTOM_CONFIG_FILE="${WEB_SERVER_CONFIG_DIRECTORY}/external.conf"
PIHOLE_DEFAULT_AD_LISTS="${PIHOLE_DIRECTORY}/adlists.default" PIHOLE_DEFAULT_AD_LISTS="${PIHOLE_DIRECTORY}/adlists.default"
PIHOLE_USER_DEFINED_AD_LISTS="${PIHOLE_DIRECTORY}/adlists.list" PIHOLE_USER_DEFINED_AD_LISTS="${PIHOLE_DIRECTORY}/adlists.list"
PIHOLE_BLACKLIST_FILE="${PIHOLE_DIRECTORY}/blacklist.txt" PIHOLE_BLACKLIST_FILE="${PIHOLE_DIRECTORY}/blacklist.txt"
PIHOLE_BLOCKLIST_FILE="${PIHOLE_DIRECTORY}/gravity.list" PIHOLE_BLOCKLIST_FILE="${PIHOLE_DIRECTORY}/gravity.list"
PIHOLE_INSTALL_LOG_FILE="${PIHOLE_DIRECTORY}/install.log" PIHOLE_INSTALL_LOG_FILE="${PIHOLE_DIRECTORY}/install.log"
PIHOLE_RAW_BLOCKLIST_FILES=${PIHOLE_DIRECTORY}/list.* PIHOLE_RAW_BLOCKLIST_FILES="${PIHOLE_DIRECTORY}/list.*"
PIHOLE_LOCAL_HOSTS_FILE="${PIHOLE_DIRECTORY}/local.list" PIHOLE_LOCAL_HOSTS_FILE="${PIHOLE_DIRECTORY}/local.list"
PIHOLE_LOGROTATE_FILE="${PIHOLE_DIRECTORY}/logrotate" PIHOLE_LOGROTATE_FILE="${PIHOLE_DIRECTORY}/logrotate"
PIHOLE_SETUP_VARS_FILE="${PIHOLE_DIRECTORY}/setupVars.conf" PIHOLE_SETUP_VARS_FILE="${PIHOLE_DIRECTORY}/setupVars.conf"
@ -105,7 +106,7 @@ FTL_PID="${RUN_DIRECTORY}/pihole-FTL.pid"
FTL_PORT="${RUN_DIRECTORY}/pihole-FTL.port" FTL_PORT="${RUN_DIRECTORY}/pihole-FTL.port"
PIHOLE_LOG="${LOG_DIRECTORY}/pihole.log" PIHOLE_LOG="${LOG_DIRECTORY}/pihole.log"
PIHOLE_LOG_GZIPS=${LOG_DIRECTORY}/pihole.log.[0-9].* PIHOLE_LOG_GZIPS="${LOG_DIRECTORY}/pihole.log.[0-9].*"
PIHOLE_DEBUG_LOG="${LOG_DIRECTORY}/pihole_debug.log" PIHOLE_DEBUG_LOG="${LOG_DIRECTORY}/pihole_debug.log"
PIHOLE_DEBUG_LOG_SANITIZED="${LOG_DIRECTORY}/pihole_debug-sanitized.log" PIHOLE_DEBUG_LOG_SANITIZED="${LOG_DIRECTORY}/pihole_debug-sanitized.log"
PIHOLE_FTL_LOG="${LOG_DIRECTORY}/pihole-FTL.log" PIHOLE_FTL_LOG="${LOG_DIRECTORY}/pihole-FTL.log"
@ -115,53 +116,52 @@ PIHOLE_WEB_SERVER_ERROR_LOG_FILE="${WEB_SERVER_LOG_DIRECTORY}/error.log"
# An array of operating system "pretty names" that we officialy support # An array of operating system "pretty names" that we officialy support
# We can loop through the array at any time to see if it matches a value # We can loop through the array at any time to see if it matches a value
SUPPORTED_OS=("Raspbian" "Ubuntu" "Fedora" "Debian" "CentOS") #SUPPORTED_OS=("Raspbian" "Ubuntu" "Fedora" "Debian" "CentOS")
# Store Pi-hole's processes in an array for easy use and parsing # Store Pi-hole's processes in an array for easy use and parsing
PIHOLE_PROCESSES=( "dnsmasq" "lighttpd" "pihole-FTL" ) PIHOLE_PROCESSES=( "dnsmasq" "lighttpd" "pihole-FTL" )
# Store the required directories in an array so it can be parsed through # Store the required directories in an array so it can be parsed through
REQUIRED_DIRECTORIES=(${CORE_GIT_DIRECTORY} #REQUIRED_DIRECTORIES=("${CORE_GIT_DIRECTORY}"
${CRON_D_DIRECTORY} #"${CRON_D_DIRECTORY}"
${DNSMASQ_D_DIRECTORY} #"${DNSMASQ_D_DIRECTORY}"
${PIHOLE_DIRECTORY} #"${PIHOLE_DIRECTORY}"
${PIHOLE_SCRIPTS_DIRECTORY} #"${PIHOLE_SCRIPTS_DIRECTORY}"
${BIN_DIRECTORY} #"${BIN_DIRECTORY}"
${RUN_DIRECTORY} #"${RUN_DIRECTORY}"
${LOG_DIRECTORY} #"${LOG_DIRECTORY}"
${WEB_SERVER_LOG_DIRECTORY} #"${WEB_SERVER_LOG_DIRECTORY}"
${WEB_SERVER_CONFIG_DIRECTORY} #"${WEB_SERVER_CONFIG_DIRECTORY}"
${HTML_DIRECTORY} #"${HTML_DIRECTORY}"
${WEB_GIT_DIRECTORY} #"${WEB_GIT_DIRECTORY}"
${BLOCK_PAGE_DIRECTORY}) #"${BLOCK_PAGE_DIRECTORY}")
# Store the required directories in an array so it can be parsed through # Store the required directories in an array so it can be parsed through
mapfile -t array <<< "$var" REQUIRED_FILES=("${PIHOLE_CRON_FILE}"
REQUIRED_FILES=(${PIHOLE_CRON_FILE} "${PIHOLE_DNS_CONFIG_FILE}"
${PIHOLE_DNS_CONFIG_FILE} "${PIHOLE_DHCP_CONFIG_FILE}"
${PIHOLE_DHCP_CONFIG_FILE} "${PIHOLE_WILDCARD_CONFIG_FILE}"
${PIHOLE_WILDCARD_CONFIG_FILE} "${WEB_SERVER_CONFIG_FILE}"
${WEB_SERVER_CONFIG_FILE} "${PIHOLE_DEFAULT_AD_LISTS}"
${PIHOLE_DEFAULT_AD_LISTS} "${PIHOLE_USER_DEFINED_AD_LISTS}"
${PIHOLE_USER_DEFINED_AD_LISTS} "${PIHOLE_BLACKLIST_FILE}"
${PIHOLE_BLACKLIST_FILE} "${PIHOLE_BLOCKLIST_FILE}"
${PIHOLE_BLOCKLIST_FILE} "${PIHOLE_INSTALL_LOG_FILE}"
${PIHOLE_INSTALL_LOG_FILE} "${PIHOLE_RAW_BLOCKLIST_FILES}"
${PIHOLE_RAW_BLOCKLIST_FILES} "${PIHOLE_LOCAL_HOSTS_FILE}"
${PIHOLE_LOCAL_HOSTS_FILE} "${PIHOLE_LOGROTATE_FILE}"
${PIHOLE_LOGROTATE_FILE} "${PIHOLE_SETUP_VARS_FILE}"
${PIHOLE_SETUP_VARS_FILE} "${PIHOLE_WHITELIST_FILE}"
${PIHOLE_WHITELIST_FILE} "${PIHOLE_COMMAND}"
${PIHOLE_COMMAND} "${PIHOLE_COLTABLE_FILE}"
${PIHOLE_COLTABLE_FILE} "${FTL_PID}"
${FTL_PID} "${FTL_PORT}"
${FTL_PORT} "${PIHOLE_LOG}"
${PIHOLE_LOG} "${PIHOLE_LOG_GZIPS}"
${PIHOLE_LOG_GZIPS} "${PIHOLE_DEBUG_LOG}"
${PIHOLE_DEBUG_LOG} "${PIHOLE_FTL_LOG}"
${PIHOLE_FTL_LOG} "${PIHOLE_WEB_SERVER_ACCESS_LOG_FILE}"
${PIHOLE_WEB_SERVER_ACCESS_LOG_FILE} "${PIHOLE_WEB_SERVER_ERROR_LOG_FILE}")
${PIHOLE_WEB_SERVER_ERROR_LOG_FILE})
DISCLAIMER="This process collects information from your Pi-hole, and optionally uploads it to a unique and random directory on tricorder.pi-hole.net. DISCLAIMER="This process collects information from your Pi-hole, and optionally uploads it to a unique and random directory on tricorder.pi-hole.net.
@ -176,7 +176,7 @@ show_disclaimer(){
source_setup_variables() { source_setup_variables() {
# Display the current test that is running # Display the current test that is running
log_write "\n${COL_PURPLE}*** [ INITIALIZING ]${COL_NC} Sourcing setup variables" log_write "\\n${COL_PURPLE}*** [ INITIALIZING ]${COL_NC} Sourcing setup variables"
# If the variable file exists, # If the variable file exists,
if ls "${PIHOLE_SETUP_VARS_FILE}" 1> /dev/null 2>&1; then if ls "${PIHOLE_SETUP_VARS_FILE}" 1> /dev/null 2>&1; then
log_write "${INFO} Sourcing ${PIHOLE_SETUP_VARS_FILE}..."; log_write "${INFO} Sourcing ${PIHOLE_SETUP_VARS_FILE}...";
@ -231,7 +231,7 @@ initialize_debug() {
echo_current_diagnostic() { echo_current_diagnostic() {
# Colors are used for visually distinguishing each test in the output # Colors are used for visually distinguishing each test in the output
# These colors do not show in the GUI, but the formatting will # These colors do not show in the GUI, but the formatting will
log_write "\n${COL_PURPLE}*** [ DIAGNOSING ]:${COL_NC} ${1}" log_write "\\n${COL_PURPLE}*** [ DIAGNOSING ]:${COL_NC} ${1}"
} }
compare_local_version_to_git_version() { compare_local_version_to_git_version() {
@ -245,6 +245,7 @@ compare_local_version_to_git_version() {
local search_term="Pi-hole" local search_term="Pi-hole"
elif [[ "${pihole_component}" == "Web" ]]; then elif [[ "${pihole_component}" == "Web" ]]; then
# We need to search for "AdminLTE" so store it in a variable as well # We need to search for "AdminLTE" so store it in a variable as well
#shellcheck disable=2034
local search_term="AdminLTE" local search_term="AdminLTE"
fi fi
# Display what we are checking # Display what we are checking
@ -365,7 +366,8 @@ check_critical_program_versions() {
is_os_supported() { is_os_supported() {
local os_to_check="${1}" local os_to_check="${1}"
# Strip just the base name of the system using sed # Strip just the base name of the system using sed
the_os=$(echo ${os_to_check} | sed 's/ .*//') # shellcheck disable=SC2001
the_os=$(echo "${os_to_check}" | sed 's/ .*//')
# If the variable is one of our supported OSes, # If the variable is one of our supported OSes,
case "${the_os}" in case "${the_os}" in
# Print it in green # Print it in green
@ -384,6 +386,8 @@ get_distro_attributes() {
OLD_IFS="$IFS" OLD_IFS="$IFS"
# Store the distro info in an array and make it global since the OS won't change, # Store the distro info in an array and make it global since the OS won't change,
# but we'll keep it within the function for better unit testing # but we'll keep it within the function for better unit testing
local distro_info
#shellcheck disable=SC2016
IFS=$'\r\n' command eval 'distro_info=( $(cat /etc/*release) )' IFS=$'\r\n' command eval 'distro_info=( $(cat /etc/*release) )'
# Set a named variable for better readability # Set a named variable for better readability
@ -391,7 +395,8 @@ get_distro_attributes() {
# For each line found in an /etc/*release file, # For each line found in an /etc/*release file,
for distro_attribute in "${distro_info[@]}"; do for distro_attribute in "${distro_info[@]}"; do
# store the key in a variable # store the key in a variable
local pretty_name_key=$(echo "${distro_attribute}" | grep "PRETTY_NAME" | cut -d '=' -f1) local pretty_name_key
pretty_name_key=$(echo "${distro_attribute}" | grep "PRETTY_NAME" | cut -d '=' -f1)
# we need just the OS PRETTY_NAME, # we need just the OS PRETTY_NAME,
if [[ "${pretty_name_key}" == "PRETTY_NAME" ]]; then if [[ "${pretty_name_key}" == "PRETTY_NAME" ]]; then
# so save in in a variable when we find it # so save in in a variable when we find it
@ -465,15 +470,15 @@ processor_check() {
else else
# Check if the architecture is currently supported for FTL # Check if the architecture is currently supported for FTL
case "${PROCESSOR}" in case "${PROCESSOR}" in
"amd64") "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}" "amd64") log_write "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
;; ;;
"armv6l") "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}" "armv6l") log_write "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
;; ;;
"armv6") "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}" "armv6") log_write "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
;; ;;
"armv7l") "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}" "armv7l") log_write "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
;; ;;
"aarch64") "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}" "aarch64") log_write "${TICK} ${COL_GREEN}${PROCESSOR}${COL_NC}"
;; ;;
# Otherwise, show the processor type # Otherwise, show the processor type
*) log_write "${INFO} ${PROCESSOR}"; *) log_write "${INFO} ${PROCESSOR}";
@ -493,13 +498,21 @@ parse_setup_vars() {
fi fi
} }
parse_locale() {
local pihole_locale
echo_current_diagnostic "Locale"
pihole_locale="$(locale)"
parse_file "${pihole_locale}"
}
does_ip_match_setup_vars() { does_ip_match_setup_vars() {
# Check for IPv4 or 6 # Check for IPv4 or 6
local protocol="${1}" local protocol="${1}"
# IP address to check for # IP address to check for
local ip_address="${2}" local ip_address="${2}"
# See what IP is in the setupVars.conf file # See what IP is in the setupVars.conf file
local setup_vars_ip=$(< ${PIHOLE_SETUP_VARS_FILE} grep IPV${protocol}_ADDRESS | cut -d '=' -f2) local setup_vars_ip
setup_vars_ip=$(< ${PIHOLE_SETUP_VARS_FILE} grep IPV"${protocol}"_ADDRESS | cut -d '=' -f2)
# If it's an IPv6 address # If it's an IPv6 address
if [[ "${protocol}" == "6" ]]; then if [[ "${protocol}" == "6" ]]; then
# Strip off the / (CIDR notation) # Strip off the / (CIDR notation)
@ -530,10 +543,10 @@ detect_ip_addresses() {
# Use ip to show the addresses for the chosen protocol # Use ip to show the addresses for the chosen protocol
# Store the values in an arry so they can be looped through # Store the values in an arry so they can be looped through
# Get the lines that are in the file(s) and store them in an array for parsing later # Get the lines that are in the file(s) and store them in an array for parsing later
declare -a ip_addr_list=( $(ip -${protocol} addr show dev ${PIHOLE_INTERFACE} | awk -F ' ' '{ for(i=1;i<=NF;i++) if ($i ~ '/^inet/') print $(i+1) }') ) mapfile -t ip_addr_list < <(ip -"${protocol}" addr show dev "${PIHOLE_INTERFACE}" | awk -F ' ' '{ for(i=1;i<=NF;i++) if ($i ~ '/^inet/') print $(i+1) }')
# If there is something in the IP address list, # If there is something in the IP address list,
if [[ -n ${ip_addr_list} ]]; then if [[ -n ${ip_addr_list[*]} ]]; then
# Local iterator # Local iterator
local i local i
# Display the protocol and interface # Display the protocol and interface
@ -547,15 +560,15 @@ detect_ip_addresses() {
log_write "" log_write ""
else else
# If there are no IPs detected, explain that the protocol is not configured # If there are no IPs detected, explain that the protocol is not configured
log_write "${CROSS} ${COL_RED}No IPv${protocol} address(es) found on the ${PIHOLE_INTERFACE}${COL_NC} interface.\n" log_write "${CROSS} ${COL_RED}No IPv${protocol} address(es) found on the ${PIHOLE_INTERFACE}${COL_NC} interface.\\n"
return 1 return 1
fi fi
# If the protocol is v6 # If the protocol is v6
if [[ "${protocol}" == "6" ]]; then if [[ "${protocol}" == "6" ]]; then
# let the user know that as long as there is one green address, things should be ok # let the user know that as long as there is one green address, things should be ok
log_write " ^ Please note that you may have more than one IP address listed." log_write " ^ Please note that you may have more than one IP address listed."
log_write " As long as one of them is green, and it matches what is in ${PIHOLE_SETUP_VARS_FILE}, there is no need for concern.\n" log_write " As long as one of them is green, and it matches what is in ${PIHOLE_SETUP_VARS_FILE}, there is no need for concern.\\n"
log_write " The link to the FAQ is for an issue that sometimes occurs when the IPv6 address changes, which is why we check for it.\n" log_write " The link to the FAQ is for an issue that sometimes occurs when the IPv6 address changes, which is why we check for it.\\n"
fi fi
} }
@ -582,7 +595,7 @@ ping_gateway() {
# Check if we are using IPv4 or IPv6 # Check if we are using IPv4 or IPv6
# Find the default gateway using IPv4 or IPv6 # Find the default gateway using IPv4 or IPv6
local gateway local gateway
gateway="$(ip -${protocol} route | grep default | cut -d ' ' -f 3)" gateway="$(ip -"${protocol}" route | grep default | cut -d ' ' -f 3)"
# If the gateway variable has a value (meaning a gateway was found), # If the gateway variable has a value (meaning a gateway was found),
if [[ -n "${gateway}" ]]; then if [[ -n "${gateway}" ]]; then
@ -592,9 +605,9 @@ ping_gateway() {
# Try to quietly ping the gateway 3 times, with a timeout of 3 seconds, using numeric output only, # Try to quietly ping the gateway 3 times, with a timeout of 3 seconds, using numeric output only,
# on the pihole interface, and tail the last three lines of the output # on the pihole interface, and tail the last three lines of the output
# If pinging the gateway is not successful, # If pinging the gateway is not successful,
if ! ${cmd} -c 3 -W 2 -n ${gateway} -I ${PIHOLE_INTERFACE} >/dev/null; then if ! ${cmd} -c 1 -W 2 -n "${gateway}" -I "${PIHOLE_INTERFACE}" >/dev/null; then
# let the user know # let the user know
log_write "${CROSS} ${COL_RED}Gateway did not respond.${COL_NC} ($FAQ_GATEWAY)\n" log_write "${CROSS} ${COL_RED}Gateway did not respond.${COL_NC} ($FAQ_GATEWAY)\\n"
# and return an error code # and return an error code
return 1 return 1
# Otherwise, # Otherwise,
@ -613,13 +626,13 @@ ping_internet() {
ping_ipv4_or_ipv6 "${protocol}" ping_ipv4_or_ipv6 "${protocol}"
log_write "* Checking Internet connectivity via IPv${protocol}..." log_write "* Checking Internet connectivity via IPv${protocol}..."
# Try to ping the address 3 times # Try to ping the address 3 times
if ! ${cmd} -W 2 -c 3 -n ${public_address} -I ${PIHOLE_INTERFACE} >/dev/null; then if ! ${cmd} -c 1 -W 2 -n ${public_address} -I "${PIHOLE_INTERFACE}" >/dev/null; then
# if it's unsuccessful, show an error # if it's unsuccessful, show an error
log_write "${CROSS} ${COL_RED}Cannot reach the Internet.${COL_NC}\n" log_write "${CROSS} ${COL_RED}Cannot reach the Internet.${COL_NC}\\n"
return 1 return 1
else else
# Otherwise, show success # Otherwise, show success
log_write "${TICK} ${COL_GREEN}Query responded.${COL_NC}\n" log_write "${TICK} ${COL_GREEN}Query responded.${COL_NC}\\n"
return 0 return 0
fi fi
} }
@ -652,15 +665,22 @@ check_required_ports() {
# Sort the addresses and remove duplicates # Sort the addresses and remove duplicates
while IFS= read -r line; do while IFS= read -r line; do
ports_in_use+=( "$line" ) ports_in_use+=( "$line" )
done < <( lsof -i -P -n | awk -F' ' '/LISTEN/ {print $9, $1}' | sort -n | uniq | cut -d':' -f2 ) done < <( lsof -iTCP -sTCP:LISTEN -P -n +c 10 )
# Now that we have the values stored, # Now that we have the values stored,
for i in "${!ports_in_use[@]}"; do for i in "${!ports_in_use[@]}"; do
# loop through them and assign some local variables # loop through them and assign some local variables
local port_number
port_number="$(echo "${ports_in_use[$i]}" | awk '{print $1}')"
local service_name local service_name
service_name=$(echo "${ports_in_use[$i]}" | awk '{print $2}') service_name=$(echo "${ports_in_use[$i]}" | awk '{print $1}')
local protocol_type
protocol_type=$(echo "${ports_in_use[$i]}" | awk '{print $5}')
local port_number
port_number="$(echo "${ports_in_use[$i]}" | awk '{print $9}')"
# Skip the line if it's the titles of the columns the lsof command produces
if [[ "${service_name}" == COMMAND ]]; then
continue
fi
# Use a case statement to determine if the right services are using the right ports # Use a case statement to determine if the right services are using the right ports
case "${port_number}" in case "${port_number}" in
53) compare_port_to_service_assigned "${resolver}" 53) compare_port_to_service_assigned "${resolver}"
@ -670,7 +690,7 @@ check_required_ports() {
4711) compare_port_to_service_assigned "${ftl}" 4711) compare_port_to_service_assigned "${ftl}"
;; ;;
# If it's not a default port that Pi-hole needs, just print it out for the user to see # If it's not a default port that Pi-hole needs, just print it out for the user to see
*) log_write "[${port_number}] is in use by ${service_name}"; *) log_write "${port_number} ${service_name} (${protocol_type})";
esac esac
done done
} }
@ -712,20 +732,20 @@ check_x_headers() {
# If the X-header found by curl matches what is should be, # If the X-header found by curl matches what is should be,
if [[ $block_page == "$block_page_working" ]]; then if [[ $block_page == "$block_page_working" ]]; then
# display a success message # display a success message
log_write "$TICK ${COL_GREEN}${block_page}${COL_NC}" log_write "$TICK Block page X-Header: ${COL_GREEN}${block_page}${COL_NC}"
else else
# Otherwise, show an error # Otherwise, show an error
log_write "$CROSS ${COL_RED}X-Header does not match or could not be retrieved.${COL_NC}" log_write "$CROSS Block page X-Header: ${COL_RED}X-Header does not match or could not be retrieved.${COL_NC}"
log_write "${COL_RED}${full_curl_output_block_page}${COL_NC}" log_write "${COL_RED}${full_curl_output_block_page}${COL_NC}"
fi fi
# Same logic applies to the dashbord as above, if the X-Header matches what a working system shoud have, # Same logic applies to the dashbord as above, if the X-Header matches what a working system shoud have,
if [[ $dashboard == "$dashboard_working" ]]; then if [[ $dashboard == "$dashboard_working" ]]; then
# then we can show a success # then we can show a success
log_write "$TICK ${COL_GREEN}${dashboard}${COL_NC}" log_write "$TICK Web interface X-Header: ${COL_GREEN}${dashboard}${COL_NC}"
else else
# Othewise, it's a failure since the X-Headers either don't exist or have been modified in some way # Othewise, it's a failure since the X-Headers either don't exist or have been modified in some way
log_write "$CROSS ${COL_RED}X-Header does not match or could not be retrieved.${COL_NC}" log_write "$CROSS Web interface X-Header: ${COL_RED}X-Header does not match or could not be retrieved.${COL_NC}"
log_write "${COL_RED}${full_curl_output_dashboard}${COL_NC}" log_write "${COL_RED}${full_curl_output_dashboard}${COL_NC}"
fi fi
} }
@ -751,14 +771,14 @@ dig_at() {
if [[ ${protocol} == "6" ]]; then if [[ ${protocol} == "6" ]]; then
# Set the IPv6 variables and record type # Set the IPv6 variables and record type
local local_address="::1" local local_address="::1"
local pihole_address="${IPV6_ADDRESS%/*}" local pihole_address="${IP}"
local remote_address="2001:4860:4860::8888" local remote_address="2001:4860:4860::8888"
local record_type="AAAA" local record_type="AAAA"
# Othwerwise, it should be 4 # Othwerwise, it should be 4
else else
# so use the IPv4 values # so use the IPv4 values
local local_address="127.0.0.1" local local_address="127.0.0.1"
local pihole_address="${IPV4_ADDRESS%/*}" local pihole_address="${IP}"
local remote_address="8.8.8.8" local remote_address="8.8.8.8"
local record_type="A" local record_type="A"
fi fi
@ -766,7 +786,8 @@ dig_at() {
# Find a random blocked url that has not been whitelisted. # Find a random blocked url that has not been whitelisted.
# This helps emulate queries to different domains that a user might query # This helps emulate queries to different domains that a user might query
# It will also give extra assurance that Pi-hole is correctly resolving and blocking domains # It will also give extra assurance that Pi-hole is correctly resolving and blocking domains
local random_url=$(shuf -n 1 "${PIHOLE_BLOCKLIST_FILE}" | awk -F ' ' '{ print $2 }') local random_url
random_url=$(shuf -n 1 "${PIHOLE_BLOCKLIST_FILE}")
# First, do a dig on localhost to see if Pi-hole can use itself to block a domain # First, do a dig on localhost to see if Pi-hole can use itself to block a domain
if local_dig=$(dig +tries=1 +time=2 -"${protocol}" "${random_url}" @${local_address} +short "${record_type}"); then if local_dig=$(dig +tries=1 +time=2 -"${protocol}" "${random_url}" @${local_address} +short "${record_type}"); then
@ -783,7 +804,7 @@ dig_at() {
# The default timeouts and tries are reduced in case the DNS server isn't working, so the user isn't waiting for too long # The default timeouts and tries are reduced in case the DNS server isn't working, so the user isn't waiting for too long
# If Pi-hole can dig itself from it's IP (not the loopback address) # If Pi-hole can dig itself from it's IP (not the loopback address)
if pihole_dig=$(dig +tries=1 +time=2 -"${protocol}" "${random_url}" @${pihole_address} +short "${record_type}"); then if pihole_dig=$(dig +tries=1 +time=2 -"${protocol}" "${random_url}" @"${pihole_address}" +short "${record_type}"); then
# show a success # show a success
log_write "${TICK} ${random_url} ${COL_GREEN}is ${pihole_dig}${COL_NC} via ${COL_CYAN}Pi-hole${COL_NC} (${pihole_address})" log_write "${TICK} ${random_url} ${COL_GREEN}is ${pihole_dig}${COL_NC} via ${COL_CYAN}Pi-hole${COL_NC} (${pihole_address})"
else else
@ -812,10 +833,12 @@ process_status(){
# If systemd # If systemd
if command -v systemctl &> /dev/null; then if command -v systemctl &> /dev/null; then
# get its status via systemctl # get its status via systemctl
local status_of_process=$(systemctl is-active "${i}") local status_of_process
status_of_process=$(systemctl is-active "${i}")
else else
# Otherwise, use the service command # Otherwise, use the service command
local status_of_process=$(service "${i}" status | awk '/Active:/ {print $2}') &> /dev/null local status_of_process
status_of_process=$(service "${i}" status | awk '/Active:/ {print $2}') &> /dev/null
fi fi
# and print it out to the user # and print it out to the user
if [[ "${status_of_process}" == "active" ]]; then if [[ "${status_of_process}" == "active" ]]; then
@ -879,15 +902,20 @@ parse_file() {
# Put the current Internal Field Separator into another variable so it can be restored later # Put the current Internal Field Separator into another variable so it can be restored later
OLD_IFS="$IFS" OLD_IFS="$IFS"
# Get the lines that are in the file(s) and store them in an array for parsing later # Get the lines that are in the file(s) and store them in an array for parsing later
local file_info
if [[ -f "$filename" ]]; then
#shellcheck disable=SC2016
IFS=$'\r\n' command eval 'file_info=( $(cat "${filename}") )' IFS=$'\r\n' command eval 'file_info=( $(cat "${filename}") )'
else
read -a file_info <<< $filename
fi
# Set a named variable for better readability # Set a named variable for better readability
local file_lines local file_lines
# For each line in the file, # For each line in the file,
for file_lines in "${file_info[@]}"; do for file_lines in "${file_info[@]}"; do
if [[ ! -z "${file_lines}" ]]; then if [[ ! -z "${file_lines}" ]]; then
# don't include the Web password hash # don't include the Web password hash
[[ "${file_linesline}" =~ ^\#.*$ || ! "${file_lines}" || "${file_lines}" == "WEBPASSWORD="* ]] && continue [[ "${file_lines}" =~ ^\#.*$ || ! "${file_lines}" || "${file_lines}" == "WEBPASSWORD="* ]] && continue
# otherwise, display the lines of the file # otherwise, display the lines of the file
log_write " ${file_lines}" log_write " ${file_lines}"
fi fi
@ -931,7 +959,7 @@ list_files_in_dir() {
# Set the first argument passed to tihs function as a named variable for better readability # Set the first argument passed to tihs function as a named variable for better readability
local dir_to_parse="${1}" local dir_to_parse="${1}"
# Store the files found in an array # Store the files found in an array
local files_found=( $(ls "${dir_to_parse}") ) mapfile -t files_found < <(ls "${dir_to_parse}")
# For each file in the array, # For each file in the array,
for each_file in "${files_found[@]}"; do for each_file in "${files_found[@]}"; do
if [[ -d "${dir_to_parse}/${each_file}" ]]; then if [[ -d "${dir_to_parse}/${each_file}" ]]; then
@ -939,26 +967,26 @@ list_files_in_dir() {
: :
elif [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_BLOCKLIST_FILE}" ]] || \ elif [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_BLOCKLIST_FILE}" ]] || \
[[ "${dir_to_parse}/${each_file}" == "${PIHOLE_DEBUG_LOG}" ]] || \ [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_DEBUG_LOG}" ]] || \
[[ ${dir_to_parse}/${each_file} == ${PIHOLE_RAW_BLOCKLIST_FILES} ]] || \ [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_RAW_BLOCKLIST_FILES}" ]] || \
[[ "${dir_to_parse}/${each_file}" == "${PIHOLE_INSTALL_LOG_FILE}" ]] || \ [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_INSTALL_LOG_FILE}" ]] || \
[[ "${dir_to_parse}/${each_file}" == "${PIHOLE_SETUP_VARS_FILE}" ]] || \ [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_SETUP_VARS_FILE}" ]] || \
[[ "${dir_to_parse}/${each_file}" == "${PIHOLE_LOG}" ]] || \ [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_LOG}" ]] || \
[[ "${dir_to_parse}/${each_file}" == "${PIHOLE_WEB_SERVER_ACCESS_LOG_FILE}" ]] || \ [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_WEB_SERVER_ACCESS_LOG_FILE}" ]] || \
[[ ${dir_to_parse}/${each_file} == ${PIHOLE_LOG_GZIPS} ]]; then [[ "${dir_to_parse}/${each_file}" == "${PIHOLE_LOG_GZIPS}" ]]; then
: :
else else
# Then, parse the file's content into an array so each line can be analyzed if need be # Then, parse the file's content into an array so each line can be analyzed if need be
for i in "${!REQUIRED_FILES[@]}"; do for i in "${!REQUIRED_FILES[@]}"; do
if [[ "${dir_to_parse}/${each_file}" == ${REQUIRED_FILES[$i]} ]]; then if [[ "${dir_to_parse}/${each_file}" == "${REQUIRED_FILES[$i]}" ]]; then
# display the filename # display the filename
log_write "\n${COL_GREEN}$(ls -ld ${dir_to_parse}/${each_file})${COL_NC}" log_write "\\n${COL_GREEN}$(ls -ld "${dir_to_parse}"/"${each_file}")${COL_NC}"
# Check if the file we want to view has a limit (because sometimes we just need a little bit of info from the file, not the entire thing) # Check if the file we want to view has a limit (because sometimes we just need a little bit of info from the file, not the entire thing)
case "${dir_to_parse}/${each_file}" in case "${dir_to_parse}/${each_file}" in
# If it's Web server error log, just give the first 25 lines # If it's Web server error log, just give the first 25 lines
"${PIHOLE_WEB_SERVER_ERROR_LOG_FILE}") make_array_from_file "${dir_to_parse}/${each_file}" 25 "${PIHOLE_WEB_SERVER_ERROR_LOG_FILE}") make_array_from_file "${dir_to_parse}/${each_file}" 25
;; ;;
# Same for the FTL log # Same for the FTL log
"${PIHOLE_FTL_LOG}") make_array_from_file "${dir_to_parse}/${each_file}" 25 "${PIHOLE_FTL_LOG}") head_tail_log "${dir_to_parse}/${each_file}" 35
;; ;;
# parse the file into an array in case we ever need to analyze it line-by-line # parse the file into an array in case we ever need to analyze it line-by-line
*) make_array_from_file "${dir_to_parse}/${each_file}"; *) make_array_from_file "${dir_to_parse}/${each_file}";
@ -991,6 +1019,34 @@ show_content_of_pihole_files() {
show_content_of_files_in_dir "${LOG_DIRECTORY}" show_content_of_files_in_dir "${LOG_DIRECTORY}"
} }
head_tail_log() {
# The file being processed
local filename="${1}"
# The number of lines to use for head and tail
local qty="${2}"
local head_line
local tail_line
# Put the current Internal Field Separator into another variable so it can be restored later
OLD_IFS="$IFS"
# Get the lines that are in the file(s) and store them in an array for parsing later
IFS=$'\r\n'
local log_head=()
mapfile -t log_head < <(head -n "${qty}" "${filename}")
log_write " ${COL_CYAN}-----head of $(basename "${filename}")------${COL_NC}"
for head_line in "${log_head[@]}"; do
log_write " ${head_line}"
done
log_write ""
local log_tail=()
mapfile -t log_tail < <(tail -n "${qty}" "${filename}")
log_write " ${COL_CYAN}-----tail of $(basename "${filename}")------${COL_NC}"
for tail_line in "${log_tail[@]}"; do
log_write " ${tail_line}"
done
# Set the IFS back to what it was
IFS="$OLD_IFS"
}
analyze_gravity_list() { analyze_gravity_list() {
echo_current_diagnostic "Gravity list" echo_current_diagnostic "Gravity list"
local head_line local head_line
@ -999,17 +1055,18 @@ analyze_gravity_list() {
OLD_IFS="$IFS" OLD_IFS="$IFS"
# Get the lines that are in the file(s) and store them in an array for parsing later # Get the lines that are in the file(s) and store them in an array for parsing later
IFS=$'\r\n' IFS=$'\r\n'
local gravity_permissions=$(ls -ld "${PIHOLE_BLOCKLIST_FILE}") local gravity_permissions
gravity_permissions=$(ls -ld "${PIHOLE_BLOCKLIST_FILE}")
log_write "${COL_GREEN}${gravity_permissions}${COL_NC}" log_write "${COL_GREEN}${gravity_permissions}${COL_NC}"
local gravity_head=() local gravity_head=()
gravity_head=( $(head -n 4 ${PIHOLE_BLOCKLIST_FILE}) ) mapfile -t gravity_head < <(head -n 4 ${PIHOLE_BLOCKLIST_FILE})
log_write " ${COL_CYAN}-----head of $(basename ${PIHOLE_BLOCKLIST_FILE})------${COL_NC}" log_write " ${COL_CYAN}-----head of $(basename ${PIHOLE_BLOCKLIST_FILE})------${COL_NC}"
for head_line in "${gravity_head[@]}"; do for head_line in "${gravity_head[@]}"; do
log_write " ${head_line}" log_write " ${head_line}"
done done
log_write "" log_write ""
local gravity_tail=() local gravity_tail=()
gravity_tail=( $(tail -n 4 ${PIHOLE_BLOCKLIST_FILE}) ) mapfile -t gravity_tail < <(tail -n 4 ${PIHOLE_BLOCKLIST_FILE})
log_write " ${COL_CYAN}-----tail of $(basename ${PIHOLE_BLOCKLIST_FILE})------${COL_NC}" log_write " ${COL_CYAN}-----tail of $(basename ${PIHOLE_BLOCKLIST_FILE})------${COL_NC}"
for tail_line in "${gravity_tail[@]}"; do for tail_line in "${gravity_tail[@]}"; do
log_write " ${tail_line}" log_write " ${tail_line}"
@ -1025,10 +1082,11 @@ analyze_pihole_log() {
OLD_IFS="$IFS" OLD_IFS="$IFS"
# Get the lines that are in the file(s) and store them in an array for parsing later # Get the lines that are in the file(s) and store them in an array for parsing later
IFS=$'\r\n' IFS=$'\r\n'
local pihole_log_permissions=$(ls -ld "${PIHOLE_LOG}") local pihole_log_permissions
pihole_log_permissions=$(ls -ld "${PIHOLE_LOG}")
log_write "${COL_GREEN}${pihole_log_permissions}${COL_NC}" log_write "${COL_GREEN}${pihole_log_permissions}${COL_NC}"
local pihole_log_head=() local pihole_log_head=()
pihole_log_head=( $(head -n 20 ${PIHOLE_LOG}) ) mapfile -t pihole_log_head < <(head -n 20 ${PIHOLE_LOG})
log_write " ${COL_CYAN}-----head of $(basename ${PIHOLE_LOG})------${COL_NC}" log_write " ${COL_CYAN}-----head of $(basename ${PIHOLE_LOG})------${COL_NC}"
local error_to_check_for local error_to_check_for
local line_to_obfuscate local line_to_obfuscate
@ -1038,10 +1096,10 @@ analyze_pihole_log() {
# that the DNS server is attempting to read. Since it's not formatted # that the DNS server is attempting to read. Since it's not formatted
# correctly, there will be an entry for "bad address at line n" # correctly, there will be an entry for "bad address at line n"
# So we can check for that here and highlight it in red so the user can see it easily # So we can check for that here and highlight it in red so the user can see it easily
error_to_check_for=$(echo ${head_line} | grep 'bad address at') error_to_check_for=$(echo "${head_line}" | grep 'bad address at')
# Some users may not want to have the domains they visit sent to us # Some users may not want to have the domains they visit sent to us
# To that end, we check for lines in the log that would contain a domain name # To that end, we check for lines in the log that would contain a domain name
line_to_obfuscate=$(echo ${head_line} | grep ': query\|: forwarded\|: reply') line_to_obfuscate=$(echo "${head_line}" | grep ': query\|: forwarded\|: reply')
# If the variable contains a value, it found an error in the log # If the variable contains a value, it found an error in the log
if [[ -n ${error_to_check_for} ]]; then if [[ -n ${error_to_check_for} ]]; then
# So we can print it in red to make it visible to the user # So we can print it in red to make it visible to the user
@ -1056,7 +1114,7 @@ analyze_pihole_log() {
if [[ -n ${line_to_obfuscate} ]]; then if [[ -n ${line_to_obfuscate} ]]; then
# If there are, we need to use awk to replace only the domain name (the 6th field in the log) # If there are, we need to use awk to replace only the domain name (the 6th field in the log)
# so we substitue the domain for the placeholder value # so we substitue the domain for the placeholder value
obfuscated_line=$(echo ${line_to_obfuscate} | awk -v placeholder="${OBFUSCATED_PLACEHOLDER}" '{sub($6,placeholder); print $0}') obfuscated_line=$(echo "${line_to_obfuscate}" | awk -v placeholder="${OBFUSCATED_PLACEHOLDER}" '{sub($6,placeholder); print $0}')
log_write " ${obfuscated_line}" log_write " ${obfuscated_line}"
else else
log_write " ${head_line}" log_write " ${head_line}"
@ -1097,7 +1155,7 @@ upload_to_tricorder() {
log_write "" log_write ""
log_write "${COL_PURPLE}********************************************${COL_NC}" log_write "${COL_PURPLE}********************************************${COL_NC}"
log_write "${COL_PURPLE}********************************************${COL_NC}" log_write "${COL_PURPLE}********************************************${COL_NC}"
log_write "${TICK} ${COL_GREEN}** FINISHED DEBUGGING! **${COL_NC}\n" log_write "${TICK} ${COL_GREEN}** FINISHED DEBUGGING! **${COL_NC}\\n"
# Provide information on what they should do with their token # Provide information on what they should do with their token
log_write " * The debug log can be uploaded to tricorder.pi-hole.net for sharing with developers only." log_write " * The debug log can be uploaded to tricorder.pi-hole.net for sharing with developers only."
@ -1144,7 +1202,7 @@ upload_to_tricorder() {
log_write " * Please try again or contact the Pi-hole team for assistance." log_write " * Please try again or contact the Pi-hole team for assistance."
fi fi
# Finally, show where the log file is no matter the outcome of the function so users can look at it # Finally, show where the log file is no matter the outcome of the function so users can look at it
log_write " * A local copy of the debug log can be found at: ${COL_CYAN}${PIHOLE_DEBUG_LOG_SANITIZED}${COL_NC}\n" log_write " * A local copy of the debug log can be found at: ${COL_CYAN}${PIHOLE_DEBUG_LOG_SANITIZED}${COL_NC}\\n"
} }
# Run through all the functions we made # Run through all the functions we made
@ -1165,6 +1223,7 @@ parse_setup_vars
check_x_headers check_x_headers
analyze_gravity_list analyze_gravity_list
show_content_of_pihole_files show_content_of_pihole_files
parse_locale
analyze_pihole_log analyze_pihole_log
copy_to_debug_log copy_to_debug_log
upload_to_tricorder upload_to_tricorder

View file

@ -16,7 +16,10 @@ source ${colfile}
# Constructed to return nothing when # Constructed to return nothing when
# a) the setting is not present in the config file, or # a) the setting is not present in the config file, or
# b) the setting is commented out (e.g. "#DBFILE=...") # b) the setting is commented out (e.g. "#DBFILE=...")
DBFILE="$(sed -n -e 's/^\s^.DBFILE\s*=\s*//p' /etc/pihole/pihole-FTL.conf)" 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. # Test for empty string. Use standard path in this case.
if [ -z "$DBFILE" ]; then if [ -z "$DBFILE" ]; then
DBFILE="/etc/pihole/pihole-FTL.db" DBFILE="/etc/pihole/pihole-FTL.db"

239
advanced/Scripts/query.sh Normal file
View file

@ -0,0 +1,239 @@
#!/usr/bin/env bash
# shellcheck disable=SC1090
# Pi-hole: A black hole for Internet advertisements
# (c) 2018 Pi-hole, LLC (https://pi-hole.net)
# Network-wide ad blocking via your own hardware.
#
# Query Domain Lists
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
# Globals
piholeDir="/etc/pihole"
adListsList="$piholeDir/adlists.list"
wildcardlist="/etc/dnsmasq.d/03-pihole-wildcard.conf"
options="$*"
adlist=""
all=""
exact=""
blockpage=""
matchType="match"
colfile="/opt/pihole/COL_TABLE"
source "${colfile}"
# Print each subdomain
# e.g: foo.bar.baz.com = "foo.bar.baz.com bar.baz.com baz.com com"
processWildcards() {
IFS="." read -r -a array <<< "${1}"
for (( i=${#array[@]}-1; i>=0; i-- )); do
ar=""
for (( j=${#array[@]}-1; j>${#array[@]}-i-2; j-- )); do
if [[ $j == $((${#array[@]}-1)) ]]; then
ar="${array[$j]}"
else
ar="${array[$j]}.${ar}"
fi
done
echo "${ar}"
done
}
# Scan an array of files for matching strings
scanList(){
# Escape full stops
local domain="${1//./\\.}" lists="${2}" type="${3:-}"
# Prevent grep from printing file path
cd "$piholeDir" || exit 1
# Prevent grep -i matching slowly: http://bit.ly/2xFXtUX
export LC_CTYPE=C
# /dev/null forces filename to be printed when only one list has been generated
# shellcheck disable=SC2086
case "${type}" in
"exact" ) grep -i -E -l "(^|\\s)${domain}($|\\s|#)" ${lists} /dev/null 2>/dev/null;;
"wc" ) grep -i -o -m 1 "/${domain}/" ${lists} 2>/dev/null;;
* ) grep -i "${domain}" ${lists} /dev/null 2>/dev/null;;
esac
}
if [[ "${options}" == "-h" ]] || [[ "${options}" == "--help" ]]; then
echo "Usage: pihole -q [option] <domain>
Example: 'pihole -q -exact domain.com'
Query the adlists for a specified domain
Options:
-adlist Print the name of the block list URL
-exact Search the block lists for exact domain matches
-all Return all query matches within a block list
-h, --help Show this help dialog"
exit 0
fi
if [[ ! -e "$adListsList" ]]; then
echo -e "${COL_LIGHT_RED}The file $adListsList was not found${COL_NC}"
exit 1
fi
# Handle valid options
if [[ "${options}" == *"-bp"* ]]; then
exact="exact"; blockpage=true
else
[[ "${options}" == *"-adlist"* ]] && adlist=true
[[ "${options}" == *"-all"* ]] && all=true
if [[ "${options}" == *"-exact"* ]]; then
exact="exact"; matchType="exact ${matchType}"
fi
fi
# Strip valid options, leaving only the domain and invalid options
# This allows users to place the options before or after the domain
options=$(sed -E 's/ ?-(bp|adlists?|all|exact) ?//g' <<< "${options}")
# Handle remaining options
# If $options contain non ASCII characters, convert to punycode
case "${options}" in
"" ) str="No domain specified";;
*" "* ) str="Unknown query option specified";;
*[![:ascii:]]* ) domainQuery=$(idn2 "${options}");;
* ) domainQuery="${options}";;
esac
if [[ -n "${str:-}" ]]; then
echo -e "${str}${COL_NC}\\nTry 'pihole -q --help' for more information."
exit 1
fi
# Scan Whitelist and Blacklist
lists="whitelist.txt blacklist.txt"
mapfile -t results <<< "$(scanList "${domainQuery}" "${lists}" "${exact}")"
if [[ -n "${results[*]}" ]]; then
wbMatch=true
# Loop through each result in order to print unique file title once
for result in "${results[@]}"; do
fileName="${result%%.*}"
if [[ -n "${blockpage}" ]]; then
echo "π ${result}"
exit 0
elif [[ -n "${exact}" ]]; then
echo " ${matchType^} found in ${COL_BOLD}${fileName^}${COL_NC}"
else
# Only print filename title once per file
if [[ ! "${fileName}" == "${fileName_prev:-}" ]]; then
echo " ${matchType^} found in ${COL_BOLD}${fileName^}${COL_NC}"
fileName_prev="${fileName}"
fi
echo " ${result#*:}"
fi
done
fi
# Scan Wildcards
if [[ -e "${wildcardlist}" ]]; then
# Determine all subdomains, domain and TLDs
mapfile -t wildcards <<< "$(processWildcards "${domainQuery}")"
for match in "${wildcards[@]}"; do
# Search wildcard list for matches
mapfile -t results <<< "$(scanList "${match}" "${wildcardlist}" "wc")"
if [[ -n "${results[*]}" ]]; then
if [[ -z "${wcMatch:-}" ]] && [[ -z "${blockpage}" ]]; then
wcMatch=true
echo " ${matchType^} found in ${COL_BOLD}Wildcards${COL_NC}:"
fi
case "${blockpage}" in
true ) echo "π ${wildcardlist##*/}"; exit 0;;
* ) echo " *.${match}";;
esac
fi
done
fi
# Get version sorted *.domains filenames (without dir path)
lists=("$(cd "$piholeDir" || exit 0; printf "%s\\n" -- *.domains | sort -V)")
# Query blocklists for occurences of domain
mapfile -t results <<< "$(scanList "${domainQuery}" "${lists[*]}" "${exact}")"
# Handle notices
if [[ -z "${wbMatch:-}" ]] && [[ -z "${wcMatch:-}" ]] && [[ -z "${results[*]}" ]]; then
echo -e " ${INFO} No ${exact/t/t }results found for ${COL_BOLD}${domainQuery}${COL_NC} within the block lists"
exit 0
elif [[ -z "${results[*]}" ]]; then
# Result found in WL/BL/Wildcards
exit 0
elif [[ -z "${all}" ]] && [[ "${#results[*]}" -ge 100 ]]; then
echo -e " ${INFO} Over 100 ${exact/t/t }results found for ${COL_BOLD}${domainQuery}${COL_NC}
This can be overridden using the -all option"
exit 0
fi
# Remove unwanted content from non-exact $results
if [[ -z "${exact}" ]]; then
# Delete lines starting with #
# Remove comments after domain
# Remove hosts format IP address
mapfile -t results <<< "$(IFS=$'\n'; sed \
-e "/:#/d" \
-e "s/[ \\t]#.*//g" \
-e "s/:.*[ \\t]/:/g" \
<<< "${results[*]}")"
# Exit if result was in a comment
[[ -z "${results[*]}" ]] && exit 0
fi
# Get adlist file content as array
if [[ -n "${adlist}" ]] || [[ -n "${blockpage}" ]]; then
for adlistUrl in $(< "${adListsList}"); do
if [[ "${adlistUrl:0:4}" =~ (http|www.) ]]; then
adlists+=("${adlistUrl}")
fi
done
fi
# Print "Exact matches for" title
if [[ -n "${exact}" ]] && [[ -z "${blockpage}" ]]; then
plural=""; [[ "${#results[*]}" -gt 1 ]] && plural="es"
echo " ${matchType^}${plural} for ${COL_BOLD}${domainQuery}${COL_NC} found in:"
fi
for result in "${results[@]}"; do
fileName="${result/:*/}"
# Determine *.domains URL using filename's number
if [[ -n "${adlist}" ]] || [[ -n "${blockpage}" ]]; then
fileNum="${fileName/list./}"; fileNum="${fileNum%%.*}"
fileName="${adlists[$fileNum]}"
# Discrepency occurs when adlists has been modified, but Gravity has not been run
if [[ -z "${fileName}" ]]; then
fileName="${COL_LIGHT_RED}(no associated adlists URL found)${COL_NC}"
fi
fi
if [[ -n "${blockpage}" ]]; then
echo "${fileNum} ${fileName}"
elif [[ -n "${exact}" ]]; then
echo " ${fileName}"
else
if [[ ! "${fileName}" == "${fileName_prev:-}" ]]; then
count=""
echo " ${matchType^} found in ${COL_BOLD}${fileName}${COL_NC}:"
fileName_prev="${fileName}"
fi
: $((count++))
# Print matching domain if $max_count has not been reached
[[ -z "${all}" ]] && max_count="50"
if [[ -z "${all}" ]] && [[ "${count}" -ge "${max_count}" ]]; then
[[ "${count}" -gt "${max_count}" ]] && continue
echo " ${COL_GRAY}Over ${count} results found, skipping rest of file${COL_NC}"
else
echo " ${result#*:}"
fi
fi
done
exit 0

View file

@ -19,6 +19,9 @@ readonly PI_HOLE_FILES_DIR="/etc/.pihole"
# shellcheck disable=SC2034 # shellcheck disable=SC2034
PH_TEST=true PH_TEST=true
# when --check-only is passed to this script, it will not perform the actual update
CHECK_ONLY=false
# shellcheck disable=SC1090 # shellcheck disable=SC1090
source "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh" source "${PI_HOLE_FILES_DIR}/automated install/basic-install.sh"
# shellcheck disable=SC1091 # shellcheck disable=SC1091
@ -28,9 +31,12 @@ source "/opt/pihole/COL_TABLE"
# make_repo() sourced from basic-install.sh # make_repo() sourced from basic-install.sh
# update_repo() source from basic-install.sh # update_repo() source from basic-install.sh
# getGitFiles() sourced from basic-install.sh # getGitFiles() sourced from basic-install.sh
# get_binary_name() sourced from basic-install.sh
# FTLcheckUpdate() sourced from basic-install.sh
GitCheckUpdateAvail() { GitCheckUpdateAvail() {
local directory="${1}" local directory
directory="${1}"
curdir=$PWD curdir=$PWD
cd "${directory}" || return cd "${directory}" || return
@ -51,14 +57,14 @@ GitCheckUpdateAvail() {
REMOTE="$(git rev-parse "@{upstream}")" REMOTE="$(git rev-parse "@{upstream}")"
if [[ "${#LOCAL}" == 0 ]]; then if [[ "${#LOCAL}" == 0 ]]; then
echo -e "\\n ${COL_LIGHT_RED}Error: Local revision could not be obtained, please contact Pi-hole Support echo -e "\\n ${COL_LIGHT_RED}Error: Local revision could not be obtained, please contact Pi-hole Support"
Additional debugging output:${COL_NC}" echo -e " Additional debugging output:${COL_NC}"
git status git status
exit exit
fi fi
if [[ "${#REMOTE}" == 0 ]]; then if [[ "${#REMOTE}" == 0 ]]; then
echo -e "\\n ${COL_LIGHT_RED}Error: Remote revision could not be obtained, please contact Pi-hole Support echo -e "\\n ${COL_LIGHT_RED}Error: Remote revision could not be obtained, please contact Pi-hole Support"
Additional debugging output:${COL_NC}" echo -e " Additional debugging output:${COL_NC}"
git status git status
exit exit
fi fi
@ -77,31 +83,23 @@ GitCheckUpdateAvail() {
fi fi
} }
FTLcheckUpdate() {
local FTLversion
FTLversion=$(/usr/bin/pihole-FTL tag)
local FTLlatesttag
FTLlatesttag=$(curl -sI https://github.com/pi-hole/FTL/releases/latest | grep 'Location' | awk -F '/' '{print $NF}' | tr -d '\r\n')
if [[ "${FTLversion}" != "${FTLlatesttag}" ]]; then
return 0
else
return 1
fi
}
main() { main() {
local pihole_version_current
local web_version_current
local basicError="\\n ${COL_LIGHT_RED}Unable to complete update, please contact Pi-hole Support${COL_NC}" local basicError="\\n ${COL_LIGHT_RED}Unable to complete update, please contact Pi-hole Support${COL_NC}"
local core_update
local web_update
local FTL_update
core_update=false
web_update=false
FTL_update=false
# shellcheck disable=1090,2154 # shellcheck disable=1090,2154
source "${setupVars}" source "${setupVars}"
# This is unlikely # This is unlikely
if ! is_repo "${PI_HOLE_FILES_DIR}" ; then if ! is_repo "${PI_HOLE_FILES_DIR}" ; then
echo -e "\\n ${COL_LIGHT_RED}Error: Core Pi-hole repo is missing from system! echo -e "\\n ${COL_LIGHT_RED}Error: Core Pi-hole repo is missing from system!"
Please re-run install script from https://pi-hole.net${COL_NC}" echo -e " Please re-run install script from https://pi-hole.net${COL_NC}"
exit 1; exit 1;
fi fi
@ -115,28 +113,10 @@ main() {
echo -e " ${INFO} Pi-hole Core:\\t${COL_LIGHT_GREEN}up to date${COL_NC}" echo -e " ${INFO} Pi-hole Core:\\t${COL_LIGHT_GREEN}up to date${COL_NC}"
fi fi
if FTLcheckUpdate ; then if [[ "${INSTALL_WEB_INTERFACE}" == true ]]; then
FTL_update=true
echo -e " ${INFO} FTL:\\t\\t${COL_YELLOW}update available${COL_NC}"
else
FTL_update=false
echo -e " ${INFO} FTL:\\t\\t${COL_LIGHT_GREEN}up to date${COL_NC}"
fi
# Logic: Don't update FTL when there is a core update available
# since the core update will run the installer which will itself
# re-install (i.e. update) FTL
if ${FTL_update} && ! ${core_update}; then
echo ""
echo -e " ${INFO} FTL out of date"
FTLdetect
echo ""
fi
if [[ "${INSTALL_WEB}" == true ]]; then
if ! is_repo "${ADMIN_INTERFACE_DIR}" ; then if ! is_repo "${ADMIN_INTERFACE_DIR}" ; then
echo -e "\\n ${COL_LIGHT_RED}Error: Web Admin repo is missing from system! echo -e "\\n ${COL_LIGHT_RED}Error: Web Admin repo is missing from system!"
Please re-run install script from https://pi-hole.net${COL_NC}" echo -e " Please re-run install script from https://pi-hole.net${COL_NC}"
exit 1; exit 1;
fi fi
@ -147,82 +127,65 @@ main() {
web_update=false web_update=false
echo -e " ${INFO} Web Interface:\\t${COL_LIGHT_GREEN}up to date${COL_NC}" echo -e " ${INFO} Web Interface:\\t${COL_LIGHT_GREEN}up to date${COL_NC}"
fi fi
fi
# Logic if FTLcheckUpdate > /dev/null; then
# If Core up to date AND web up to date: FTL_update=true
# Do nothing echo -e " ${INFO} FTL:\\t\\t${COL_YELLOW}update available${COL_NC}"
# If Core up to date AND web NOT up to date: else
# Pull web repo case $? in
# If Core NOT up to date AND web up to date: 1)
# pull pihole repo, run install --unattended -- reconfigure echo -e " ${INFO} FTL:\\t\\t${COL_LIGHT_GREEN}up to date${COL_NC}"
# if Core NOT up to date AND web NOT up to date: ;;
# pull pihole repo run install --unattended 2)
echo -e " ${INFO} FTL:\\t\\t${COL_LIGHT_RED}Branch is not available.${COL_NC}\\n\\t\\t\\tUse ${COL_LIGHT_GREEN}pihole checkout ftl [branchname]${COL_NC} to switch to a valid branch."
;;
*)
echo -e " ${INFO} FTL:\\t\\t${COL_LIGHT_RED}Something has gone wrong, contact support${COL_NC}"
esac
FTL_update=false
fi
if ! ${core_update} && ! ${web_update} ; then if [[ "${core_update}" == false && "${web_update}" == false && "${FTL_update}" == false ]]; then
if ! ${FTL_update} ; then
echo "" echo ""
echo -e " ${TICK} Everything is up to date!" echo -e " ${TICK} Everything is up to date!"
exit 0 exit 0
fi fi
elif ! ${core_update} && ${web_update} ; then
echo ""
echo -e " ${INFO} Pi-hole Web Admin files out of date"
getGitFiles "${ADMIN_INTERFACE_DIR}" "${ADMIN_INTERFACE_GIT_URL}"
elif ${core_update} && ! ${web_update} ; then
echo ""
echo -e " ${INFO} Pi-hole core files out of date"
getGitFiles "${PI_HOLE_FILES_DIR}" "${PI_HOLE_GIT_URL}"
${PI_HOLE_FILES_DIR}/automated\ install/basic-install.sh --reconfigure --unattended || \
echo -e "${basicError}" && exit 1
elif ${core_update} && ${web_update} ; then
echo ""
echo -e " ${INFO} Updating Pi-hole core and web admin files"
getGitFiles "${PI_HOLE_FILES_DIR}" "${PI_HOLE_GIT_URL}"
${PI_HOLE_FILES_DIR}/automated\ install/basic-install.sh --unattended || \
echo -e "${basicError}" && exit 1
else
echo -e " ${COL_LIGHT_RED}Update script has malfunctioned, please contact Pi-hole Support${COL_NC}"
exit 1
fi
else # Web Admin not installed, so only verify if core is up to date
if ! ${core_update}; then
if ! ${FTL_update} ; then
echo ""
echo -e " ${INFO} Everything is up to date!"
exit 0
fi
else
echo ""
echo -e " ${INFO} Pi-hole Core files out of date"
getGitFiles "${PI_HOLE_FILES_DIR}" "${PI_HOLE_GIT_URL}"
${PI_HOLE_FILES_DIR}/automated\ install/basic-install.sh --reconfigure --unattended || \
echo -e "${basicError}" && exit 1
fi
fi
if [[ "${web_update}" == true ]]; then if [[ "${CHECK_ONLY}" == true ]]; then
web_version_current="$(/usr/local/bin/pihole version --admin --current)"
echo "" echo ""
echo -e " ${INFO} Web Admin version is now at ${web_version_current/* v/v} exit 0
${INFO} If you had made any changes in '/var/www/html/admin/', they have been stashed using 'git stash'"
fi fi
if [[ "${core_update}" == true ]]; then if [[ "${core_update}" == true ]]; then
pihole_version_current="$(/usr/local/bin/pihole version --pihole --current)"
echo "" echo ""
echo -e " ${INFO} Pi-hole version is now at ${pihole_version_current/* v/v} echo -e " ${INFO} Pi-hole core files out of date, updating local repo."
${INFO} If you had made any changes in '/etc/.pihole/', they have been stashed using 'git stash'" getGitFiles "${PI_HOLE_FILES_DIR}" "${PI_HOLE_GIT_URL}"
echo -e " ${INFO} If you had made any changes in '/etc/.pihole/', they have been stashed using 'git stash'"
fi
if [[ "${web_update}" == true ]]; then
echo ""
echo -e " ${INFO} Pi-hole Web Admin files out of date, updating local repo."
getGitFiles "${ADMIN_INTERFACE_DIR}" "${ADMIN_INTERFACE_GIT_URL}"
echo -e " ${INFO} If you had made any changes in '/var/www/html/admin/', they have been stashed using 'git stash'"
fi fi
if [[ "${FTL_update}" == true ]]; then if [[ "${FTL_update}" == true ]]; then
FTL_version_current="$(/usr/bin/pihole-FTL tag)" echo ""
echo -e "\\n ${INFO} FTL version is now at ${FTL_version_current/* v/v}" echo -e " ${INFO} FTL out of date, it will be updated by the installer."
start_service pihole-FTL
enable_service pihole-FTL
fi fi
if [[ "${FTL_update}" == true || "${core_update}" == true ]]; then
${PI_HOLE_FILES_DIR}/automated\ install/basic-install.sh --reconfigure --unattended || \
echo -e "${basicError}" && exit 1
fi
echo "" echo ""
exit 0 exit 0
} }
if [[ "$1" == "--check-only" ]]; then
CHECK_ONLY=true
fi
main main

View file

@ -32,9 +32,9 @@ function get_local_branch() {
} }
function get_local_version() { function get_local_version() {
# Return active branch # Return active branch
cd "${1}" 2> /dev/null || return 1 cd "${1}" 2> /dev/null || return 1
git describe --long --dirty --tags || return 1 git describe --long --dirty --tags || return 1
} }
if [[ "$2" == "remote" ]]; then if [[ "$2" == "remote" ]]; then

View file

@ -13,6 +13,7 @@
readonly setupVars="/etc/pihole/setupVars.conf" readonly setupVars="/etc/pihole/setupVars.conf"
readonly dnsmasqconfig="/etc/dnsmasq.d/01-pihole.conf" readonly dnsmasqconfig="/etc/dnsmasq.d/01-pihole.conf"
readonly dhcpconfig="/etc/dnsmasq.d/02-pihole-dhcp.conf" readonly dhcpconfig="/etc/dnsmasq.d/02-pihole-dhcp.conf"
readonly FTLconf="/etc/pihole/pihole-FTL.conf"
# 03 -> wildcards # 03 -> wildcards
readonly dhcpstaticconfig="/etc/dnsmasq.d/04-pihole-static-dhcp.conf" readonly dhcpstaticconfig="/etc/dnsmasq.d/04-pihole-static-dhcp.conf"
@ -35,7 +36,7 @@ Options:
-e, email Set an administrative contact address for the Block Page -e, email Set an administrative contact address for the Block Page
-h, --help Show this help dialog -h, --help Show this help dialog
-i, interface Specify dnsmasq's interface listening behavior -i, interface Specify dnsmasq's interface listening behavior
Add '-h' for more info on interface usage" -l, privacylevel Set privacy level (0 = lowest, 3 = highest)"
exit 0 exit 0
} }
@ -52,6 +53,19 @@ change_setting() {
add_setting "${1}" "${2}" add_setting "${1}" "${2}"
} }
addFTLsetting() {
echo "${1}=${2}" >> "${FTLconf}"
}
deleteFTLsetting() {
sed -i "/${1}/d" "${FTLconf}"
}
changeFTLsetting() {
deleteFTLsetting "${1}"
addFTLsetting "${1}" "${2}"
}
add_dnsmasq_setting() { add_dnsmasq_setting() {
if [[ "${2}" != "" ]]; then if [[ "${2}" != "" ]]; then
echo "${1}=${2}" >> "${dnsmasqconfig}" echo "${1}=${2}" >> "${dnsmasqconfig}"
@ -135,6 +149,14 @@ ProcessDNSSettings() {
let COUNTER=COUNTER+1 let COUNTER=COUNTER+1
done done
# The option LOCAL_DNS_PORT is deprecated
# We apply it once more, and then convert it into the current format
if [ ! -z "${LOCAL_DNS_PORT}" ]; then
add_dnsmasq_setting "server" "127.0.0.1#${LOCAL_DNS_PORT}"
add_setting "PIHOLE_DNS_${COUNTER}" "127.0.0.1#${LOCAL_DNS_PORT}"
delete_setting "LOCAL_DNS_PORT"
fi
delete_dnsmasq_setting "domain-needed" delete_dnsmasq_setting "domain-needed"
if [[ "${DNS_FQDN_REQUIRED}" == true ]]; then if [[ "${DNS_FQDN_REQUIRED}" == true ]]; then
@ -182,11 +204,11 @@ trust-anchor=.,20326,8,2,E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC68345710423
add_dnsmasq_setting "interface" "${PIHOLE_INTERFACE}" add_dnsmasq_setting "interface" "${PIHOLE_INTERFACE}"
fi fi
if [[ "${CONDITIONAL_FORWARDING}" == true ]]; then if [[ "${CONDITIONAL_FORWARDING}" == true ]]; then
add_dnsmasq_setting "server=/${CONDITIONAL_FORWARDING_DOMAIN}/${CONDITIONAL_FORWARDING_IP}" add_dnsmasq_setting "server=/${CONDITIONAL_FORWARDING_DOMAIN}/${CONDITIONAL_FORWARDING_IP}"
add_dnsmasq_setting "server=/${CONDITIONAL_FORWARDING_REVERSE}/${CONDITIONAL_FORWARDING_IP}" add_dnsmasq_setting "server=/${CONDITIONAL_FORWARDING_REVERSE}/${CONDITIONAL_FORWARDING_IP}"
fi fi
} }
SetDNSServers() { SetDNSServers() {
@ -215,6 +237,7 @@ SetDNSServers() {
else else
change_setting "DNSSEC" "false" change_setting "DNSSEC" "false"
fi fi
if [[ "${args[6]}" == "conditional_forwarding" ]]; then if [[ "${args[6]}" == "conditional_forwarding" ]]; then
change_setting "CONDITIONAL_FORWARDING" "true" change_setting "CONDITIONAL_FORWARDING" "true"
change_setting "CONDITIONAL_FORWARDING_IP" "${args[7]}" change_setting "CONDITIONAL_FORWARDING_IP" "${args[7]}"
@ -361,7 +384,9 @@ CustomizeAdLists() {
elif [[ "${args[2]}" == "disable" ]]; then elif [[ "${args[2]}" == "disable" ]]; then
sed -i "\\@${args[3]}@s/^http/#http/g" "${list}" sed -i "\\@${args[3]}@s/^http/#http/g" "${list}"
elif [[ "${args[2]}" == "add" ]]; then elif [[ "${args[2]}" == "add" ]]; then
if [[ $(grep -c "^${args[3]}$" "${list}") -eq 0 ]] ; then
echo "${args[3]}" >> ${list} echo "${args[3]}" >> ${list}
fi
elif [[ "${args[2]}" == "del" ]]; then elif [[ "${args[2]}" == "del" ]]; then
var=$(echo "${args[3]}" | sed 's/\//\\\//g') var=$(echo "${args[3]}" | sed 's/\//\\\//g')
sed -i "/${var}/Id" "${list}" sed -i "/${var}/Id" "${list}"
@ -505,6 +530,13 @@ audit()
echo "${args[2]}" >> /etc/pihole/auditlog.list echo "${args[2]}" >> /etc/pihole/auditlog.list
} }
SetPrivacyLevel() {
# Set privacy level. Minimum is 0, maximum is 3
if [ "${args[2]}" -ge 0 ] && [ "${args[2]}" -le 3 ]; then
changeFTLsetting "PRIVACYLEVEL" "${args[2]}"
fi
}
main() { main() {
args=("$@") args=("$@")
@ -534,6 +566,7 @@ main() {
"-t" | "teleporter" ) Teleporter;; "-t" | "teleporter" ) Teleporter;;
"adlist" ) CustomizeAdLists;; "adlist" ) CustomizeAdLists;;
"audit" ) audit;; "audit" ) audit;;
"-l" | "privacylevel" ) SetPrivacyLevel;;
* ) helpFunc;; * ) helpFunc;;
esac esac

View file

@ -0,0 +1,28 @@
#!/bin/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.
#
# Provides an automated migration subroutine to convert Pi-hole v3.x wildcard domains to Pi-hole v4.x regex filters
#
# This file is copyright under the latest version of the EUPL.
# Please see LICENSE file for your rights under this license.
# regexFile set in gravity.sh
wildcardFile="/etc/dnsmasq.d/03-pihole-wildcard.conf"
convert_wildcard_to_regex() {
if [ ! -f "${wildcardFile}" ]; then
return
fi
local addrlines domains uniquedomains
# Obtain wildcard domains from old file
addrlines="$(grep -oE "/.*/" ${wildcardFile})"
# Strip "/" from domain names and convert "." to regex-compatible "\."
domains="$(sed 's/\///g;s/\./\\./g' <<< "${addrlines}")"
# Remove repeated domains (may have been inserted two times due to A and AAAA blocking)
uniquedomains="$(uniq <<< "${domains}")"
# Automatically generate regex filters and remove old wildcards file
awk '{print "(^|\\.)"$0"$"}' <<< "${uniquedomains}" >> "${regexFile:?}" && rm "${wildcardFile}"
}

View file

@ -0,0 +1,84 @@
### This file contains parameters for FTL behavior.
### At install, all parameters are commented out. The user can select desired options.
### Options shown are the default configuration. No modification is needed for most
### installations.
### Visit https://docs.pi-hole.net/ftldns/configfile/ for more detailed parameter explanations
## Socket Listening
## Listen only for local socket connections or permit all connections
## Options: localonly, all
#SOCKET_LISTENING=localonly
## Query Display
## Display all queries? Set to no to hide query display
## Options: yes, no
#QUERY_DISPLAY=yes
## AAA Query Analysis
## Allow FTL to analyze AAAA queries from pihole.log?
## Options: yes, no
#AAAA_QUERY_ANALYSIS=yes
## Resolve IPv6
## Should FTL try to resolve IPv6 addresses to host names?
## Options: yes, no
#RESOLVE_IPV6=yes
## Resolve IPv4
## Should FTL try to resolve IPv4 addresses to host names?
## Options: yes, no
#RESOLVE_IPV4=yes
## Max Database Days
## How long should queries be stored in the database (days)?
## Setting this to 0 disables the database
## See: https://docs.pi-hole.net/ftldns/database/
## Options: number of days
#MAXDBDAYS=365
## Database Interval
## How often do we store queries in FTL's database (minutes)?
## See: https://docs.pi-hole.net/ftldns/database/
## Options: number of minutes
#DBINTERVAL=1.0
## Database File
## Specify path and filename of FTL's SQLite3 long-term database.
## Setting this to DBFILE= disables the database altogether
## See: https://docs.pi-hole.net/ftldns/database/
## Option: path to db file
#DBFILE=/etc/pihole/pihole-FTL.db
## Max Log Age
## Up to how many hours of queries should be imported from the database and logs (hours)?
## Maximum is 744 (31 days)
## Options: number of days
#MAXLOGAGE=24.0
## FTL Port
## On which port should FTL be listening?
## Options: tcp port
#FTLPORT=4711
## Privacy Level
## Which privacy level is used?
## See: https://docs.pi-hole.net/ftldns/privacylevels/
## Options: 0, 1, 2, 3
#PRIVACYLEVEL=0
## Ignore Localhost
## Should FTL ignore queries coming from the local machine?
## Options: yes, no
#IGNORE_LOCALHOST=no
## Blocking Mode
## How should FTL reply to blocked queries?
## See: https://docs.pi-hole.net/ftldns/blockingmode/
## Options: NULL, IP-AAAA-NODATA, IP, NXDOMAIN
#BLOCKINGMODE=NULL
## Regex Debug Mode
## Controls if FTLDNS should print extended details about regex matching into pihole-FTL.log.
## See: https://docs.pi-hole.net/ftldns/regex/overview/
## Options: true, false
#REGEX_DEBUGMODE=false

View file

@ -20,6 +20,7 @@ is_running() {
ps "$(get_pid)" > /dev/null 2>&1 ps "$(get_pid)" > /dev/null 2>&1
} }
# Start the service # Start the service
start() { start() {
if is_running; then if is_running; then
@ -29,9 +30,12 @@ start() {
mkdir -p /var/run/pihole mkdir -p /var/run/pihole
mkdir -p /var/log/pihole mkdir -p /var/log/pihole
chown pihole:pihole /var/run/pihole /var/log/pihole chown pihole:pihole /var/run/pihole /var/log/pihole
rm /var/run/pihole/FTL.sock rm /var/run/pihole/FTL.sock 2> /dev/null
chown pihole:pihole /var/log/pihole-FTL.log /run/pihole-FTL.pid /run/pihole-FTL.port /etc/pihole chown pihole:pihole /var/log/pihole-FTL.log /run/pihole-FTL.pid /run/pihole-FTL.port
chown pihole:pihole /etc/pihole /etc/pihole/dhcp.leases /var/log/pihole.log
chmod 0644 /var/log/pihole-FTL.log /run/pihole-FTL.pid /run/pihole-FTL.port /var/log/pihole.log chmod 0644 /var/log/pihole-FTL.log /run/pihole-FTL.pid /run/pihole-FTL.port /var/log/pihole.log
setcap CAP_NET_BIND_SERVICE,CAP_NET_RAW,CAP_NET_ADMIN+eip "$(which pihole-FTL)"
echo "nameserver 127.0.0.1" | /sbin/resolvconf -a lo.piholeFTL
su -s /bin/sh -c "/usr/bin/pihole-FTL" "$FTLUSER" su -s /bin/sh -c "/usr/bin/pihole-FTL" "$FTLUSER"
echo echo
fi fi
@ -40,6 +44,7 @@ start() {
# Stop the service # Stop the service
stop() { stop() {
if is_running; then if is_running; then
/sbin/resolvconf -d lo.piholeFTL
kill "$(get_pid)" kill "$(get_pid)"
for i in {1..5}; do for i in {1..5}; do
if ! is_running; then if ! is_running; then
@ -64,13 +69,25 @@ stop() {
echo echo
} }
# Indicate the service status
status() {
if is_running; then
echo "[ ok ] pihole-FTL is running"
exit 0
else
echo "[ ] pihole-FTL is not running"
exit 1
fi
}
### main logic ### ### main logic ###
case "$1" in case "$1" in
stop) stop)
stop stop
;; ;;
status) status)
status pihole-FTL status
;; ;;
start|restart|reload|condrestart) start|restart|reload|condrestart)
stop stop

View file

@ -1,11 +1,79 @@
_pihole() { _pihole() {
local cur prev opts local cur prev opts opts_admin opts_checkout opts_chronometer opts_debug opts_interface opts_logging opts_privacy opts_query opts_update opts_version
COMPREPLY=() COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}" prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="admin blacklist chronometer debug disable enable flush help logging query reconfigure restartdns setupLCD status tail uninstall updateGravity updatePihole version whitelist checkout" prev2="${COMP_WORDS[COMP_CWORD-2]}"
case "${prev}" in
"pihole")
opts="admin blacklist checkout chronometer debug disable enable flush help logging query reconfigure regex restartdns status tail uninstall updateGravity updatePihole version wildcard whitelist"
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
;;
"whitelist"|"blacklist"|"wildcard"|"regex")
opts_lists="\--delmode \--noreload \--quiet \--list \--nuke"
COMPREPLY=( $(compgen -W "${opts_lists}" -- ${cur}) )
;;
"admin")
opts_admin="celsius email fahrenheit hostrecord interface kelvin password privacylevel"
COMPREPLY=( $(compgen -W "${opts_admin}" -- ${cur}) )
;;
"checkout")
opts_checkout="core ftl web master dev"
COMPREPLY=( $(compgen -W "${opts_checkout}" -- ${cur}) )
;;
"chronometer")
opts_chronometer="\--exit \--json \--refresh"
COMPREPLY=( $(compgen -W "${opts_chronometer}" -- ${cur}) )
;;
"debug")
opts_debug="-a"
COMPREPLY=( $(compgen -W "${opts_debug}" -- ${cur}) )
;;
"logging")
opts_logging="on off 'off noflush'"
COMPREPLY=( $(compgen -W "${opts_logging}" -- ${cur}) )
;;
"query")
opts_query="-adlist -all -exact"
COMPREPLY=( $(compgen -W "${opts_query}" -- ${cur}) )
;;
"updatePihole"|"-up")
opts_update="--check-only"
COMPREPLY=( $(compgen -W "${opts_update}" -- ${cur}) )
;;
"version")
opts_version="\--admin \--current \--ftl \--hash \--latest \--pihole"
COMPREPLY=( $(compgen -W "${opts_version}" -- ${cur}) )
;;
"interface")
if ( [[ "$prev2" == "admin" ]] || [[ "$prev2" == "-a" ]] ); then
opts_interface="$(cat /proc/net/dev | cut -d: -s -f1)"
COMPREPLY=( $(compgen -W "${opts_interface}" -- ${cur}) )
else
return 1
fi
;;
"privacylevel")
if ( [[ "$prev2" == "admin" ]] || [[ "$prev2" == "-a" ]] ); then
opts_privacy="0 1 2 3"
COMPREPLY=( $(compgen -W "${opts_privacy}" -- ${cur}) )
else
return 1
fi
;;
"core"|"admin"|"ftl")
if [[ "$prev2" == "checkout" ]]; then
opts_checkout="master dev"
COMPREPLY=( $(compgen -W "${opts_checkout}" -- ${cur}) )
else
return 1
fi
;;
*)
return 1
;;
esac
return 0 return 0
} }
complete -F _pihole pihole complete -F _pihole pihole

View file

@ -64,7 +64,7 @@ if ($serverName === "pi.hole") {
<html><head> <html><head>
$viewPort $viewPort
<link rel='stylesheet' href='/pihole/blockingpage.css' type='text/css'/> <link rel='stylesheet' href='/pihole/blockingpage.css' type='text/css'/>
</head><body id='splashpage'><img src='/admin/img/logo.svg'/><br/>Pi-<b>hole</b>: Your black hole for Internet advertisements</body></html> </head><body id='splashpage'><img src='/admin/img/logo.svg'/><br/>Pi-<b>hole</b>: Your black hole for Internet advertisements<br><a href='/admin'>Did you mean to go to the admin panel?</a></body></html>
"; ";
// Set splash/landing page based off presence of $landPage // Set splash/landing page based off presence of $landPage
@ -102,8 +102,10 @@ if ($serverName === "pi.hole") {
$bpAskAdmin = !empty($svEmail) ? '<a href="mailto:'.$svEmail.'?subject=Site Blocked: '.$serverName.'"></a>' : "<span/>"; $bpAskAdmin = !empty($svEmail) ? '<a href="mailto:'.$svEmail.'?subject=Site Blocked: '.$serverName.'"></a>' : "<span/>";
// Determine if at least one block list has been generated // Determine if at least one block list has been generated
if (empty(glob("/etc/pihole/list.0.*.domains"))) $blocklistglob = glob("/etc/pihole/list.0.*.domains");
if ($blocklistglob === array()) {
die("[ERROR] There are no domain lists generated lists within <code>/etc/pihole/</code>! Please update gravity by running <code>pihole -g</code>, or repair Pi-hole using <code>pihole -r</code>."); die("[ERROR] There are no domain lists generated lists within <code>/etc/pihole/</code>! Please update gravity by running <code>pihole -g</code>, or repair Pi-hole using <code>pihole -r</code>.");
}
// Set location of adlists file // Set location of adlists file
if (is_file("/etc/pihole/adlists.list")) { if (is_file("/etc/pihole/adlists.list")) {
@ -327,6 +329,7 @@ setHeader();
setTimeout(function(){window.location.reload(1);}, 10000); setTimeout(function(){window.location.reload(1);}, 10000);
$("#bpOutput").removeClass("add"); $("#bpOutput").removeClass("add");
$("#bpOutput").addClass("success"); $("#bpOutput").addClass("success");
$("#bpOutput").html("");
} else { } else {
$("#bpOutput").removeClass("add"); $("#bpOutput").removeClass("add");
$("#bpOutput").addClass("error"); $("#bpOutput").addClass("error");
@ -336,6 +339,7 @@ setHeader();
error: function(jqXHR, exception) { error: function(jqXHR, exception) {
$("#bpOutput").removeClass("add"); $("#bpOutput").removeClass("add");
$("#bpOutput").addClass("exception"); $("#bpOutput").addClass("exception");
$("#bpOutput").html("");
} }
}); });
} }

File diff suppressed because it is too large Load diff

View file

@ -14,8 +14,8 @@ while true; do
read -rp " ${QST} Are you sure you would like to remove ${COL_WHITE}Pi-hole${COL_NC}? [y/N] " yn read -rp " ${QST} Are you sure you would like to remove ${COL_WHITE}Pi-hole${COL_NC}? [y/N] " yn
case ${yn} in case ${yn} in
[Yy]* ) break;; [Yy]* ) break;;
[Nn]* ) echo -e "\n ${COL_LIGHT_GREEN}Uninstall has been cancelled${COL_NC}"; exit 0;; [Nn]* ) echo -e "${OVER} ${COL_LIGHT_GREEN}Uninstall has been cancelled${COL_NC}"; exit 0;;
* ) echo -e "\n ${COL_LIGHT_GREEN}Uninstall has been cancelled${COL_NC}"; exit 0;; * ) echo -e "${OVER} ${COL_LIGHT_GREEN}Uninstall has been cancelled${COL_NC}"; exit 0;;
esac esac
done done
@ -46,34 +46,24 @@ source "${setupVars}"
distro_check distro_check
# Install packages used by the Pi-hole # Install packages used by the Pi-hole
if [[ "${INSTALL_WEB}" == true ]]; then DEPS=("${INSTALLER_DEPS[@]}" "${PIHOLE_DEPS[@]}")
if [[ "${INSTALL_WEB_SERVER}" == true ]]; then
# Install the Web dependencies # Install the Web dependencies
DEPS=("${INSTALLER_DEPS[@]}" "${PIHOLE_DEPS[@]}" "${PIHOLE_WEB_DEPS[@]}") DEPS+=("${PIHOLE_WEB_DEPS[@]}")
# Otherwise,
else
# just install the Core dependencies
DEPS=("${INSTALLER_DEPS[@]}" "${PIHOLE_DEPS[@]}")
fi fi
# Compatability # Compatability
if [ -x "$(command -v rpm)" ]; then if [ -x "$(command -v apt-get)" ]; then
# Fedora Family
PKG_REMOVE="${PKG_MANAGER} remove -y"
package_check() {
rpm -qa | grep ^$1- > /dev/null
}
package_cleanup() {
${SUDO} ${PKG_MANAGER} -y autoremove
}
elif [ -x "$(command -v apt-get)" ]; then
# Debian Family # Debian Family
PKG_REMOVE="${PKG_MANAGER} -y remove --purge" PKG_REMOVE="${PKG_MANAGER} -y remove --purge"
package_check() { package_check() {
dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -c "ok installed" dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -c "ok installed"
} }
package_cleanup() { elif [ -x "$(command -v rpm)" ]; then
${SUDO} ${PKG_MANAGER} -y autoremove # Fedora Family
${SUDO} ${PKG_MANAGER} -y autoclean PKG_REMOVE="${PKG_MANAGER} remove -y"
package_check() {
rpm -qa | grep "^$1-" > /dev/null
} }
else else
echo -e " ${CROSS} OS distribution not supported" echo -e " ${CROSS} OS distribution not supported"
@ -84,14 +74,13 @@ removeAndPurge() {
# Purge dependencies # Purge dependencies
echo "" echo ""
for i in "${DEPS[@]}"; do for i in "${DEPS[@]}"; do
package_check ${i} > /dev/null if package_check "${i}" > /dev/null; then
if [[ "$?" -eq 0 ]]; then
while true; do while true; do
read -rp " ${QST} Do you wish to remove ${COL_WHITE}${i}${COL_NC} from your system? [Y/N] " yn read -rp " ${QST} Do you wish to remove ${COL_WHITE}${i}${COL_NC} from your system? [Y/N] " yn
case ${yn} in case ${yn} in
[Yy]* ) [Yy]* )
echo -ne " ${INFO} Removing ${i}..."; echo -ne " ${INFO} Removing ${i}...";
${SUDO} ${PKG_REMOVE} "${i}" &> /dev/null; ${SUDO} "${PKG_REMOVE} ${i}" &> /dev/null;
echo -e "${OVER} ${INFO} Removed ${i}"; echo -e "${OVER} ${INFO} Removed ${i}";
break;; break;;
[Nn]* ) echo -e " ${INFO} Skipped ${i}"; break;; [Nn]* ) echo -e " ${INFO} Skipped ${i}"; break;;
@ -103,14 +92,9 @@ removeAndPurge() {
done done
# Remove dnsmasq config files # Remove dnsmasq config files
${SUDO} rm -f /etc/dnsmasq.conf /etc/dnsmasq.conf.orig /etc/dnsmasq.d/01-pihole.conf &> /dev/null ${SUDO} rm -f /etc/dnsmasq.conf /etc/dnsmasq.conf.orig /etc/dnsmasq.d/*-pihole*.conf &> /dev/null
echo -e " ${TICK} Removing dnsmasq config files" echo -e " ${TICK} Removing dnsmasq config files"
# Take care of any additional package cleaning
echo -ne " ${INFO} Removing & cleaning remaining dependencies..."
package_cleanup &> /dev/null
echo -e "${OVER} ${TICK} Removed & cleaned up remaining dependencies"
# Call removeNoPurge to remove Pi-hole specific files # Call removeNoPurge to remove Pi-hole specific files
removeNoPurge removeNoPurge
} }
@ -168,32 +152,42 @@ removeNoPurge() {
${SUDO} rm -f /etc/sudoers.d/pihole &> /dev/null ${SUDO} rm -f /etc/sudoers.d/pihole &> /dev/null
echo -e " ${TICK} Removed config files" echo -e " ${TICK} Removed config files"
# Restore Resolved
if [[ -e /etc/systemd/resolved.conf.orig ]]; then
${SUDO} cp /etc/systemd/resolved.conf.orig /etc/systemd/resolved.conf
systemctl reload-or-restart systemd-resolved
fi
# Remove FTL # Remove FTL
if command -v pihole-FTL &> /dev/null; then if command -v pihole-FTL &> /dev/null; then
echo -ne " ${INFO} Removing pihole-FTL..." echo -ne " ${INFO} Removing pihole-FTL..."
if [[ -x "$(command -v systemctl)" ]]; then if [[ -x "$(command -v systemctl)" ]]; then
systemctl stop pihole-FTL systemctl stop pihole-FTL
else else
service pihole-FTL stop service pihole-FTL stop
fi fi
${SUDO} rm -f /etc/init.d/pihole-FTL ${SUDO} rm -f /etc/init.d/pihole-FTL
${SUDO} rm -f /usr/bin/pihole-FTL ${SUDO} rm -f /usr/bin/pihole-FTL
echo -e "${OVER} ${TICK} Removed pihole-FTL" echo -e "${OVER} ${TICK} Removed pihole-FTL"
fi fi
# If the pihole manpage exists, then delete and rebuild man-db
if [[ -f /usr/local/share/man/man8/pihole.8 ]]; then
${SUDO} rm -f /usr/local/share/man/man8/pihole.8 /usr/local/share/man/man8/pihole-FTL.8 /usr/local/share/man/man5/pihole-FTL.conf.5
${SUDO} mandb -q &>/dev/null
echo -e " ${TICK} Removed pihole man page"
fi
# If the pihole user exists, then remove # If the pihole user exists, then remove
if id "pihole" &> /dev/null; then if id "pihole" &> /dev/null; then
${SUDO} userdel -r pihole 2> /dev/null if ${SUDO} userdel -r pihole 2> /dev/null; then
if [[ "$?" -eq 0 ]]; then
echo -e " ${TICK} Removed 'pihole' user" echo -e " ${TICK} Removed 'pihole' user"
else else
echo -e " ${CROSS} Unable to remove 'pihole' user" echo -e " ${CROSS} Unable to remove 'pihole' user"
fi fi
fi fi
echo -e "\n We're sorry to see you go, but thanks for checking out Pi-hole! echo -e "\\n We're sorry to see you go, but thanks for checking out Pi-hole!
If you need help, reach out to us on Github, Discourse, Reddit or Twitter If you need help, reach out to us on Github, Discourse, Reddit or Twitter
Reinstall at any time: ${COL_WHITE}curl -sSL https://install.pi-hole.net | bash${COL_NC} Reinstall at any time: ${COL_WHITE}curl -sSL https://install.pi-hole.net | bash${COL_NC}

View file

@ -15,20 +15,20 @@ export LC_ALL=C
coltable="/opt/pihole/COL_TABLE" coltable="/opt/pihole/COL_TABLE"
source "${coltable}" source "${coltable}"
regexconverter="/opt/pihole/wildcard_regex_converter.sh"
source "${regexconverter}"
basename="pihole" basename="pihole"
PIHOLE_COMMAND="/usr/local/bin/${basename}" PIHOLE_COMMAND="/usr/local/bin/${basename}"
piholeDir="/etc/${basename}" piholeDir="/etc/${basename}"
piholeRepo="/etc/.${basename}"
adListFile="${piholeDir}/adlists.list" adListFile="${piholeDir}/adlists.list"
adListDefault="${piholeDir}/adlists.default" adListDefault="${piholeDir}/adlists.default"
adListRepoDefault="${piholeRepo}/adlists.default"
whitelistFile="${piholeDir}/whitelist.txt" whitelistFile="${piholeDir}/whitelist.txt"
blacklistFile="${piholeDir}/blacklist.txt" blacklistFile="${piholeDir}/blacklist.txt"
wildcardFile="/etc/dnsmasq.d/03-pihole-wildcard.conf" regexFile="${piholeDir}/regex.list"
adList="${piholeDir}/gravity.list" adList="${piholeDir}/gravity.list"
blackList="${piholeDir}/black.list" blackList="${piholeDir}/black.list"
@ -44,6 +44,10 @@ preEventHorizon="list.preEventHorizon"
skipDownload="false" skipDownload="false"
resolver="pihole-FTL"
haveSourceUrls=true
# Source setupVars from install script # Source setupVars from install script
setupVars="${piholeDir}/setupVars.conf" setupVars="${piholeDir}/setupVars.conf"
if [[ -f "${setupVars}" ]];then if [[ -f "${setupVars}" ]];then
@ -104,7 +108,7 @@ gravity_CheckDNSResolutionAvailable() {
fi fi
# Determine error output message # Determine error output message
if pidof dnsmasq &> /dev/null; then if pidof ${resolver} &> /dev/null; then
echo -e " ${CROSS} DNS resolution is currently unavailable" echo -e " ${CROSS} DNS resolution is currently unavailable"
else else
echo -e " ${CROSS} DNS service is not running" echo -e " ${CROSS} DNS service is not running"
@ -129,20 +133,12 @@ gravity_CheckDNSResolutionAvailable() {
gravity_GetBlocklistUrls() { gravity_GetBlocklistUrls() {
echo -e " ${INFO} ${COL_BOLD}Neutrino emissions detected${COL_NC}..." echo -e " ${INFO} ${COL_BOLD}Neutrino emissions detected${COL_NC}..."
# Determine if adlists file needs handling if [[ -f "${adListDefault}" ]] && [[ -f "${adListFile}" ]]; then
if [[ ! -f "${adListFile}" ]]; then
# Create "adlists.list" by copying "adlists.default" from internal core repo
cp "${adListRepoDefault}" "${adListFile}" 2> /dev/null || \
echo -e " ${CROSS} Unable to copy ${adListFile##*/} from ${piholeRepo}"
elif [[ -f "${adListDefault}" ]] && [[ -f "${adListFile}" ]]; then
# Remove superceded $adListDefault file # Remove superceded $adListDefault file
rm "${adListDefault}" 2> /dev/null || \ rm "${adListDefault}" 2> /dev/null || \
echo -e " ${CROSS} Unable to remove ${adListDefault}" echo -e " ${CROSS} Unable to remove ${adListDefault}"
fi fi
local str="Pulling blocklist source list into range"
echo -ne " ${INFO} ${str}..."
# Retrieve source URLs from $adListFile # Retrieve source URLs from $adListFile
# Logic: Remove comments and empty lines # Logic: Remove comments and empty lines
mapfile -t sources <<< "$(grep -v -E "^(#|$)" "${adListFile}" 2> /dev/null)" mapfile -t sources <<< "$(grep -v -E "^(#|$)" "${adListFile}" 2> /dev/null)"
@ -158,11 +154,15 @@ gravity_GetBlocklistUrls() {
}' <<< "$(printf '%s\n' "${sources[@]}")" 2> /dev/null }' <<< "$(printf '%s\n' "${sources[@]}")" 2> /dev/null
)" )"
local str="Pulling blocklist source list into range"
if [[ -n "${sources[*]}" ]] && [[ -n "${sourceDomains[*]}" ]]; then if [[ -n "${sources[*]}" ]] && [[ -n "${sourceDomains[*]}" ]]; then
echo -e "${OVER} ${TICK} ${str}" echo -e "${OVER} ${TICK} ${str}"
else else
echo -e "${OVER} ${CROSS} ${str}" echo -e "${OVER} ${CROSS} ${str}"
gravity_Cleanup "error" echo -e " ${INFO} No source list found, or it is empty"
echo ""
haveSourceUrls=false
fi fi
} }
@ -220,8 +220,15 @@ gravity_DownloadBlocklistFromUrl() {
httpCode=$(curl -s -L ${cmd_ext} ${heisenbergCompensator} -w "%{http_code}" -A "${agent}" "${url}" -o "${patternBuffer}" 2> /dev/null) httpCode=$(curl -s -L ${cmd_ext} ${heisenbergCompensator} -w "%{http_code}" -A "${agent}" "${url}" -o "${patternBuffer}" 2> /dev/null)
case $url in case $url in
# Did we "download" a local file?
"file"*)
if [[ -s "${patternBuffer}" ]]; then
echo -e "${OVER} ${TICK} ${str} Retrieval successful"; success=true
else
echo -e "${OVER} ${CROSS} ${str} Not found / empty list"
fi;;
# Did we "download" a remote file? # Did we "download" a remote file?
"http"*) *)
# Determine "Status:" output based on HTTP response # Determine "Status:" output based on HTTP response
case "${httpCode}" in case "${httpCode}" in
"200") echo -e "${OVER} ${TICK} ${str} Retrieval successful"; success=true;; "200") echo -e "${OVER} ${TICK} ${str} Retrieval successful"; success=true;;
@ -235,16 +242,8 @@ gravity_DownloadBlocklistFromUrl() {
"504") echo -e "${OVER} ${CROSS} ${str} Connection Timed Out (Gateway)";; "504") echo -e "${OVER} ${CROSS} ${str} Connection Timed Out (Gateway)";;
"521") echo -e "${OVER} ${CROSS} ${str} Web Server Is Down (Cloudflare)";; "521") echo -e "${OVER} ${CROSS} ${str} Web Server Is Down (Cloudflare)";;
"522") echo -e "${OVER} ${CROSS} ${str} Connection Timed Out (Cloudflare)";; "522") echo -e "${OVER} ${CROSS} ${str} Connection Timed Out (Cloudflare)";;
* ) echo -e "${OVER} ${CROSS} ${str} ${httpCode}";; * ) echo -e "${OVER} ${CROSS} ${str} ${url} (${httpCode})";;
esac;; esac;;
# Did we "download" a local file?
"file"*)
if [[ -s "${patternBuffer}" ]]; then
echo -e "${OVER} ${TICK} ${str} Retrieval successful"; success=true
else
echo -e "${OVER} ${CROSS} ${str} Not found / empty list"
fi;;
*) echo -e "${OVER} ${CROSS} ${str} ${url} ${httpCode}";;
esac esac
# Determine if the blocklist was downloaded and saved correctly # Determine if the blocklist was downloaded and saved correctly
@ -279,9 +278,9 @@ gravity_ParseFileIntoDomains() {
# Most of the lists downloaded are already in hosts file format but the spacing/formating is not contigious # Most of the lists downloaded are already in hosts file format but the spacing/formating is not contigious
# This helps with that and makes it easier to read # This helps with that and makes it easier to read
# It also helps with debugging so each stage of the script can be researched more in depth # It also helps with debugging so each stage of the script can be researched more in depth
#Awk -F splits on given IFS, we grab the right hand side (chops trailing #coments and /'s to grab the domain only. # Awk -F splits on given IFS, we grab the right hand side (chops trailing #coments and /'s to grab the domain only.
#Last awk command takes non-commented lines and if they have 2 fields, take the left field (the domain) and leave # Last awk command takes non-commented lines and if they have 2 fields, take the right field (the domain) and leave
#+ the right (IP address), otherwise grab the single field. # the left (IP address), otherwise grab the single field.
< ${source} awk -F '#' '{print $1}' | \ < ${source} awk -F '#' '{print $1}' | \
awk -F '/' '{print $1}' | \ awk -F '/' '{print $1}' | \
@ -345,13 +344,18 @@ gravity_ParseFileIntoDomains() {
# Scanning for "^IPv4$" is too slow with large (1M) lists on low-end hardware # Scanning for "^IPv4$" is too slow with large (1M) lists on low-end hardware
echo -ne " ${INFO} Format: URL" echo -ne " ${INFO} Format: URL"
awk '{ awk '
# Remove URL protocol, optional "username:password@", and ":?/;" # Remove URL scheme, optional "username:password@", and ":?/;"
if ($0 ~ /[:?\/;]/) { gsub(/(^.*:\/\/(.*:.*@)?|[:?\/;].*)/, "", $0) } # The scheme must be matched carefully to avoid blocking the wrong URL
# Remove lines which are only IPv4 addresses # in cases like:
if ($0 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) { $0="" } # http://www.evil.com?http://www.good.com
if ($0) { print $0 } # See RFC 3986 section 3.1 for details.
}' "${source}" 2> /dev/null > "${destination}" /[:?\/;]/ { gsub(/(^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/(.*:.*@)?|[:?\/;].*)/, "", $0) }
# Skip lines which are only IPv4 addresses
/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ { next }
# Print if nonempty
length { print }
' "${source}" 2> /dev/null > "${destination}"
echo -e "${OVER} ${TICK} Format: URL" echo -e "${OVER} ${TICK} Format: URL"
else else
@ -371,7 +375,9 @@ gravity_ConsolidateDownloadedBlocklists() {
local str lastLine local str lastLine
str="Consolidating blocklists" str="Consolidating blocklists"
if [[ "${haveSourceUrls}" == true ]]; then
echo -ne " ${INFO} ${str}..." echo -ne " ${INFO} ${str}..."
fi
# Empty $matterAndLight if it already exists, otherwise, create it # Empty $matterAndLight if it already exists, otherwise, create it
: > "${piholeDir}/${matterAndLight}" : > "${piholeDir}/${matterAndLight}"
@ -390,8 +396,9 @@ gravity_ConsolidateDownloadedBlocklists() {
fi fi
fi fi
done done
if [[ "${haveSourceUrls}" == true ]]; then
echo -e "${OVER} ${TICK} ${str}" echo -e "${OVER} ${TICK} ${str}"
fi
} }
# Parse consolidated list into (filtered, unique) domains-only format # Parse consolidated list into (filtered, unique) domains-only format
@ -399,24 +406,33 @@ gravity_SortAndFilterConsolidatedList() {
local str num local str num
str="Extracting domains from blocklists" str="Extracting domains from blocklists"
if [[ "${haveSourceUrls}" == true ]]; then
echo -ne " ${INFO} ${str}..." echo -ne " ${INFO} ${str}..."
fi
# Parse into hosts file # Parse into hosts file
gravity_ParseFileIntoDomains "${piholeDir}/${matterAndLight}" "${piholeDir}/${parsedMatter}" gravity_ParseFileIntoDomains "${piholeDir}/${matterAndLight}" "${piholeDir}/${parsedMatter}"
# Format $parsedMatter line total as currency # Format $parsedMatter line total as currency
num=$(printf "%'.0f" "$(wc -l < "${piholeDir}/${parsedMatter}")") num=$(printf "%'.0f" "$(wc -l < "${piholeDir}/${parsedMatter}")")
echo -e "${OVER} ${TICK} ${str} if [[ "${haveSourceUrls}" == true ]]; then
${INFO} Number of domains being pulled in by gravity: ${COL_BLUE}${num}${COL_NC}" echo -e "${OVER} ${TICK} ${str}"
fi
echo -e " ${INFO} Number of domains being pulled in by gravity: ${COL_BLUE}${num}${COL_NC}"
str="Removing duplicate domains" str="Removing duplicate domains"
if [[ "${haveSourceUrls}" == true ]]; then
echo -ne " ${INFO} ${str}..." echo -ne " ${INFO} ${str}..."
sort -u "${piholeDir}/${parsedMatter}" > "${piholeDir}/${preEventHorizon}" fi
echo -e "${OVER} ${TICK} ${str}"
sort -u "${piholeDir}/${parsedMatter}" > "${piholeDir}/${preEventHorizon}"
if [[ "${haveSourceUrls}" == true ]]; then
echo -e "${OVER} ${TICK} ${str}"
# Format $preEventHorizon line total as currency # Format $preEventHorizon line total as currency
num=$(printf "%'.0f" "$(wc -l < "${piholeDir}/${preEventHorizon}")") num=$(printf "%'.0f" "$(wc -l < "${piholeDir}/${preEventHorizon}")")
echo -e " ${INFO} Number of unique domains trapped in the Event Horizon: ${COL_BLUE}${num}${COL_NC}" echo -e " ${INFO} Number of unique domains trapped in the Event Horizon: ${COL_BLUE}${num}${COL_NC}"
fi
} }
# Whitelist user-defined domains # Whitelist user-defined domains
@ -438,7 +454,7 @@ gravity_Whitelist() {
echo -e "${OVER} ${INFO} ${str}" echo -e "${OVER} ${INFO} ${str}"
} }
# Output count of blacklisted domains and wildcards # Output count of blacklisted domains and regex filters
gravity_ShowBlockCount() { gravity_ShowBlockCount() {
local num local num
@ -447,13 +463,9 @@ gravity_ShowBlockCount() {
echo -e " ${INFO} Number of blacklisted domains: ${num}" echo -e " ${INFO} Number of blacklisted domains: ${num}"
fi fi
if [[ -f "${wildcardFile}" ]]; then if [[ -f "${regexFile}" ]]; then
num=$(grep -c "^" "${wildcardFile}") num=$(grep -c "^(?!#)" "${regexFile}")
# If IPv4 and IPv6 is used, divide total wildcard count by 2 echo -e " ${INFO} Number of regex filters: ${num}"
if [[ -n "${IPV4_ADDRESS}" ]] && [[ -n "${IPV6_ADDRESS}" ]];then
num=$(( num/2 ))
fi
echo -e " ${INFO} Number of wildcard blocked domains: ${num}"
fi fi
} }
@ -507,10 +519,10 @@ gravity_ParseBlacklistDomains() {
: > "${piholeDir}/${accretionDisc}" : > "${piholeDir}/${accretionDisc}"
if [[ -f "${piholeDir}/${whitelistMatter}" ]]; then if [[ -f "${piholeDir}/${whitelistMatter}" ]]; then
gravity_ParseDomainsIntoHosts "${piholeDir}/${whitelistMatter}" "${piholeDir}/${accretionDisc}" mv "${piholeDir}/${whitelistMatter}" "${piholeDir}/${accretionDisc}"
else else
# There was no whitelist file, so use preEventHorizon instead of whitelistMatter. # There was no whitelist file, so use preEventHorizon instead of whitelistMatter.
gravity_ParseDomainsIntoHosts "${piholeDir}/${preEventHorizon}" "${piholeDir}/${accretionDisc}" mv "${piholeDir}/${preEventHorizon}" "${piholeDir}/${accretionDisc}"
fi fi
# Move the file over as /etc/pihole/gravity.list so dnsmasq can use it # Move the file over as /etc/pihole/gravity.list so dnsmasq can use it
@ -528,11 +540,9 @@ gravity_ParseUserDomains() {
if [[ ! -f "${blacklistFile}" ]]; then if [[ ! -f "${blacklistFile}" ]]; then
return 0 return 0
fi fi
gravity_ParseDomainsIntoHosts "${blacklistFile}" "${blackList}.tmp"
# Copy the file over as /etc/pihole/black.list so dnsmasq can use it # Copy the file over as /etc/pihole/black.list so dnsmasq can use it
mv "${blackList}.tmp" "${blackList}" 2> /dev/null || \ cp "${blacklistFile}" "${blackList}" 2> /dev/null || \
echo -e "\\n ${CROSS} Unable to move ${blackList##*/}.tmp to ${piholeDir}" echo -e "\\n ${CROSS} Unable to move ${blacklistFile##*/} to ${piholeDir}"
} }
# Trap Ctrl-C # Trap Ctrl-C
@ -567,7 +577,7 @@ gravity_Cleanup() {
echo -e "${OVER} ${TICK} ${str}" echo -e "${OVER} ${TICK} ${str}"
# Only restart DNS service if offline # Only restart DNS service if offline
if ! pidof dnsmasq &> /dev/null; then if ! pidof ${resolver} &> /dev/null; then
"${PIHOLE_COMMAND}" restartdns "${PIHOLE_COMMAND}" restartdns
dnsWasOffline=true dnsWasOffline=true
fi fi
@ -616,7 +626,9 @@ if [[ "${skipDownload}" == false ]]; then
# Gravity needs to download blocklists # Gravity needs to download blocklists
gravity_CheckDNSResolutionAvailable gravity_CheckDNSResolutionAvailable
gravity_GetBlocklistUrls gravity_GetBlocklistUrls
if [[ "${haveSourceUrls}" == true ]]; then
gravity_SetDownloadOptions gravity_SetDownloadOptions
fi
gravity_ConsolidateDownloadedBlocklists gravity_ConsolidateDownloadedBlocklists
gravity_SortAndFilterConsolidatedList gravity_SortAndFilterConsolidatedList
else else
@ -631,6 +643,7 @@ if [[ "${skipDownload}" == false ]] || [[ "${listType}" == "whitelist" ]]; then
gravity_Whitelist gravity_Whitelist
fi fi
convert_wildcard_to_regex
gravity_ShowBlockCount gravity_ShowBlockCount
# Perform when downloading blocklists, or modifying the white/blacklist (not wildcards) # Perform when downloading blocklists, or modifying the white/blacklist (not wildcards)

112
manpages/pihole-FTL.8 Normal file
View file

@ -0,0 +1,112 @@
.TH "Pihole-FTL" "8" "pihole-FTL" "Pi-hole" "June 2018"
.SH "NAME"
pihole-FTL - Pi-hole : The Faster-Than-Light (FTL) Engine
.br
.SH "SYNOPSIS"
\fBservice pihole-FTL \fR(\fBstart\fR|\fBstop\fR|\fBrestart\fR)
.br
\fBpihole-FTL debug\fR
.br
\fBpihole-FTL test\fR
.br
\fBpihole-FTL -v\fR
.br
\fBpihole-FTL -t\fR
.br
\fBpihole-FTL -b\fR
.br
\fBpihole-FTL -f\fR
.br
\fBpihole-FTL -h\fR
.br
\fBpihole-FTL dnsmasq-test\fR
.br
\fBpihole-FTL --\fR (\fBoptions\fR)
.br
.SH "DESCRIPTION"
Pi-hole : The Faster-Than-Light (FTL) Engine is a lightweight, purpose-built daemon used to provide statistics needed for the Pi-hole Web Interface, and its API can be easily integrated into your own projects. Although it is an optional component of the Pi-hole ecosystem, it will be installed by default to provide statistics. As the name implies, FTL does its work \fIvery\fR \fIquickly\fR!
.br
Usage
.br
\fBservice pihole-FTL start\fR
.br
Start the pihole-FTL daemon
.br
\fBservice pihole-FTL stop\fR
.br
Stop the pihole-FTL daemon
.br
\fBservice pihole-FTL restart\fR
.br
If the pihole-FTP daemon is running, stop and then start, otherwise start.
.br
Command line arguments
.br
\fBdebug\fR
.br
Don't go into daemon mode (stay in foreground) + more verbose logging
.br
\fBtest\fR
.br
Start FTL and process everything, but shut down immediately afterwards
.br
\fB-v, version\fR
.br
Don't start FTL, show only version
.br
\fB-t, tag\fR
.br
Don't start FTL, show only git tag
.br
\fB-b, branch\fR
.br
Don't start FTL, show only git branch FTL was compiled from
.br
\fB-f, no-daemon\fR
.br
Don't go into background (daemon mode)
.br
\fB-h, help\fR
.br
Don't start FTL, show help
.br
\fBdnsmasq-test\fR
.br
Test resolver config file syntax
.br
\fB--\fR (options)
.br
Pass options to internal dnsmasq resolver
.br
.SH "EXAMPLE"
Command line arguments can be arbitrarily combined, e.g:
.br
\fBpihole-FTL debug test\fR
.br
Start ftl in foreground with more verbose logging, process everything and shutdown immediately
.br
.SH "SEE ALSO"
\fBpihole\fR(8), \fBpihole-FTL.conf\fR(5)
.br
.SH "COLOPHON"
Get sucked into the latest news and community activity by entering Pi-hole's orbit. Information about Pi-hole, and the latest version of the software can be found at https://pi-hole.net
.br

102
manpages/pihole-FTL.conf.5 Normal file
View file

@ -0,0 +1,102 @@
.TH "pihole-FTL.conf" "5" "pihole-FTL.conf" "pihole-FTL.conf" "June 2018"
.SH "NAME"
pihole-FTL.conf - FTL's config file
.br
.SH "DESCRIPTION"
/etc/pihole/pihole-FTL.conf will be read by \fBpihole-FTL(8)\fR on startup.
.br
\fBSOCKET_LISTENING=localonly|all\fR
.br
Listen only for local socket connections or permit all connections
.br
\fBQUERY_DISPLAY=yes|no\fR
.br
Display all queries? Set to no to hide query display
.br
\fBAAAA_QUERY_ANALYSIS=yes|no\fR
.br
Allow FTL to analyze AAAA queries from pihole.log?
.br
\fBRESOLVE_IPV6=yes|no\fR
.br
Should FTL try to resolve IPv6 addresses to host names?
.br
\fBRESOLVE_IPV4=yes|no\fR
.br
Should FTL try to resolve IPv4 addresses to host names?
.br
\fBMAXDBDAYS=365\fR
.br
How long should queries be stored in the database?
.br
Setting this to 0 disables the database
.br
\fBDBINTERVAL=1.0\fR
.br
How often do we store queries in FTL's database [minutes]?
.br
\fBDBFILE=/etc/pihole/pihole-FTL.db\fR
.br
Specify path and filename of FTL's SQLite long-term database.
.br
Setting this to DBFILE= disables the database altogether
.br
\fBMAXLOGAGE=24.0\fR
.br
Up to how many hours of queries should be imported from the database and logs?
.br
Maximum is 744 (31 days)
.br
\fBFTLPORT=4711\fR
.br
On which port should FTL be listening?
.br
\fBPRIVACYLEVEL=0|1|2|3\fR
.br
Which privacy level is used?
.br
0 - show everything
.br
1 - hide domains
.br
2 - hide domains and clients
.br
3 - paranoia mode (hide everything)
.br
\fBIGNORE_LOCALHOST=no|yes\fR
.br
Should FTL ignore queries coming from the local machine?
.br
\fBBLOCKINGMODE=IP|IP-AAAA-NODATA|NXDOMAIN|NULL\fR
.br
How should FTL reply to blocked queries?
.br
For each setting, the option shown first is the default.
.br
.SH "SEE ALSO"
\fBpihole\fR(8), \fBpihole-FTL\fR(8)
.br
.SH "COLOPHON"
Pi-hole : The Faster-Than-Light (FTL) Engine is a lightweight, purpose-built daemon used to provide statistics needed for the Pi-hole Web Interface, and its API can be easily integrated into your own projects. Although it is an optional component of the Pi-hole ecosystem, it will be installed by default to provide statistics. As the name implies, FTL does its work \fIvery quickly\fR!
.br
Get sucked into the latest news and community activity by entering Pi-hole's orbit. Information about Pi-hole, and the latest version of the software can be found at https://pi-hole.net
.br

361
manpages/pihole.8 Normal file
View file

@ -0,0 +1,361 @@
.TH "Pi-hole" "8" "Pi-hole" "Pi-hole" "May 2018"
.SH "NAME"
Pi-hole : A black-hole for internet advertisements
.br
.SH "SYNOPSIS"
\fBpihole\fR (\fB-w\fR|\fB-b\fR|\fB--wild\fR|\fB--regex\fR) [options] domain(s)
.br
\fBpihole -a\fR \fB-p\fR password
.br
\fBpihole -a\fR (\fB-c|-f|-k\fR)
.br
\fBpihole -a\fR [\fB-r\fR hostrecord]
.br
\fBpihole -a -e\fR email
.br
\fBpihole -a -i\fR interface
.br
\fBpihole -a -l\fR privacylevel
.br
\fBpihole -c\fR [-j|-r|-e]
.br
\fBpihole\fR \fB-d\fR [-a]
.br
\fBpihole -f
.br
pihole -r
.br
pihole -t
.br
pihole -g\fR
.br
\fBpihole\fR -\fBq\fR [options]
.br
\fBpihole\fR \fB-l\fR (\fBon|off|off noflush\fR)
.br
\fBpihole -up \fR[--checkonly]
.br
\fBpihole -v\fR [-p|-a|-f] [-c|-l|-hash]
.br
\fBpihole uninstall
.br
pihole status
.br
pihole restartdns\fR
.br
\fBpihole\fR (\fBenable\fR|\fBdisable\fR [time])
.br
\fBpihole\fR \fBcheckout\fR repo [branch]
.br
\fBpihole\fR \fBhelp\fR
.br
.SH "DESCRIPTION"
Available commands and options:
.br
\fB-w, whitelist\fR [options] [<domain1> <domain2 ...>]
.br
Adds or removes specified domain or domains tho the Whitelist
.br
\fB-b, blacklist\fR [options] [<domain1> <domain2 ...>]
.br
Adds or removes specified domain or domains to the blacklist
.br
\fB--wild, wildcard\fR [options] [<domain1> <domain2 ...>]
.br
Add or removes specified domain to the wildcard blacklist
.br
\fB--regex, regex\fR [options] [<regex1> <regex2 ...>]
.br
Add or removes specified regex filter to the regex blacklist
.br
(Whitelist/Blacklist manipulation options):
.br
-d, --delmode Remove domain(s) from the list
.br
-nr, --noreload Update list without refreshing dnsmasq
.br
-q, --quiet Make output less verbose
.br
-l, --list Display all your listed domains
.br
--nuke Removes all entries in a list
.br
\fB-d, debug\fR [-a]
.br
Start a debugging session
.br
-a Enable automated debugging
.br
\fB-f, flush\fR
.br
Flush the Pi-hole log
.br
\fB-r, reconfigure\fR
.br
Reconfigure or Repair Pi-hole subsystems
.br
\fB-t, tail\fR
.br
View the live output of the Pi-hole log
.br
\fB-a, admin\fR [options]
.br
(Admin options):
.br
-p, password Set Web Interface password
.br
-c, celsius Set Celsius as preferred temperature unit
.br
-f, fahrenheit Set Fahrenheit as preferred temperature unit
.br
-k, kelvin Set Kelvin as preferred temperature unit
.br
-r, hostrecord Add a name to the DNS associated to an
IPv4/IPv6 address
.br
-e, email Set an administrative contact address for the
Block Page
.br
-i, interface Specify dnsmasq's interface listening behavior
.br
-l, privacylevel <level> Set privacy level
(0 = lowest, 3 = highest)
.br
\fB-c, chronometer\fR [options]
.br
Calculates stats and displays to an LCD
.br
(Chronometer Options):
.br
-j, --json Output stats as JSON formatted string
.br
-r, --refresh Set update frequency (in seconds)
.br
-e, --exit Output stats and exit witout refreshing
.br
\fB-g, updateGravity\fR
.br
Update the list of ad-serving domains
.br
\fB-q, query\fR [option]
.br
Query the adlists for a specified domain
.br
(Query options):
.br
-adlist Print the name of the block list URL
.br
-exact Search the block lists for exact domain matches
.br
-all Return all query matches within a block list
.br
\fB-h, --help, help\fR
.br
Show a help dialog
.br
\fB-l, logging\fR [on|off|off noflush]
.br
Specify whether the Pi-hole log should be used
.br
(Logging options):
.br
on Enable the Pi-hole log at /var/log/pihole.log
.br
off Disable and flush the Pi-hole log at
/var/log/pihole.log
.br
off noflush Disable the Pi-hole log at /var/log/pihole.log
.br
\fB-up, updatePihole\fR [--check-only]
.br
Update Pi-hole subsystems
.br
--check-only Exit script before update is performed.
.br
\fB-v, version\fR [repo] [options]
.br
Show installed versions of Pi-hole, Web Interface &amp; FTL
.br
.br
(repo options):
.br
-p, --pihole Only retrieve info regarding Pi-hole repository
.br
-a, --admin Only retrieve info regarding AdminLTE
repository
.br
-f, --ftl Only retrieve info regarding FTL repository
.br
(version options):
.br
-c, --current Return the current version
.br
-l, --latest Return the latest version
.br
--hash Return the Github hash from your local
repositories
.br
\fBuninstall\fR
.br
Uninstall Pi-hole from your system
.br
\fBstatus\fR
.br
Display the running status of Pi-hole subsystems
.br
\fBenable\fR
.br
Enable Pi-hole subsystems
.br
\fBdisable\fR [time]
.br
Disable Pi-hole subsystems, optionally for a set duration
.br
(time options):
.br
#s Disable Pi-hole functionality for # second(s)
.br
#m Disable Pi-hole functionality for # minute(s)
.br
\fBrestartdns\fR
.br
Restart Pi-hole subsystems
.br
\fBcheckout\fR [repo] [branch]
.br
Switch Pi-hole subsystems to a different Github branch
.br
(repo options):
.br
core Change the branch of Pi-hole's core subsystem
.br
web Change the branch of Admin Console subsystem
.br
ftl Change the branch of Pi-hole's FTL subsystem
.br
(branch options):
.br
master Update subsystems to the latest stable release
.br
dev Update subsystems to the latest development
release
.br
branchname Update subsystems to the specified branchname
.br
.SH "EXAMPLE"
Some usage examples
.br
Whitelist/blacklist manipulation
.br
\fBpihole -w iloveads.example.com\fR
.br
Adds "iloveads.example.com" to whitelist
.br
\fBpihole -b -d noads.example.com\fR
.br
Removes "noads.example.com" from blacklist
.br
\fBpihole --wild example.com\fR
.br
Adds example.com as a wildcard - would block all subdomains of
example.com, including example.com itself.
.br
\fBpihole --regex "ad.*\\.example\\.com$"\fR
.br
Adds "ad.*\\.example\\.com$" to the regex blacklist.
Would block all subdomains of example.com which start with "ad"
.br
Changing the Web Interface password
.br
\fBpihole -a -p ExamplePassword\fR
.br
Change the password to "ExamplePassword"
.br
Updating lists from internet sources
.br
\fBpihole -g\fR
.br
Update the list of ad-serving domains
.br
Displaying version information
.br
\fBpihole -v -a -c\fR
.br
Display the current version of AdminLTE
.br
Temporarily disabling Pi-hole
.br
\fBpihole disable 5m\fR
.br
Disable Pi-hole functionality for five minutes
.br
Switching Pi-hole subsystem branches
.br
\fBpihole checkout master\fR
.br
Switch to master branch
.br
\fBpihole checkout core dev\fR
.br
Switch to core development branch
.br
.SH "SEE ALSO"
\fBlighttpd\fR(8), \fBpihole-FTL\fR(8)
.br
.SH "COLOPHON"
Get sucked into the latest news and community activity by entering Pi-hole's orbit. Information about Pi-hole, and the latest version of the software can be found at https://pi-hole.net.
.br

286
pihole
View file

@ -14,6 +14,8 @@ readonly wildcardlist="/etc/dnsmasq.d/03-pihole-wildcard.conf"
readonly colfile="${PI_HOLE_SCRIPT_DIR}/COL_TABLE" readonly colfile="${PI_HOLE_SCRIPT_DIR}/COL_TABLE"
source "${colfile}" source "${colfile}"
resolver="pihole-FTL"
# Must be root to use this tool # Must be root to use this tool
if [[ ! $EUID -eq 0 ]];then if [[ ! $EUID -eq 0 ]];then
if [[ -x "$(command -v sudo)" ]]; then if [[ -x "$(command -v sudo)" ]]; then
@ -31,17 +33,7 @@ webpageFunc() {
exit 0 exit 0
} }
whitelistFunc() { listFunc() {
"${PI_HOLE_SCRIPT_DIR}"/list.sh "$@"
exit 0
}
blacklistFunc() {
"${PI_HOLE_SCRIPT_DIR}"/list.sh "$@"
exit 0
}
wildcardFunc() {
"${PI_HOLE_SCRIPT_DIR}"/list.sh "$@" "${PI_HOLE_SCRIPT_DIR}"/list.sh "$@"
exit 0 exit 0
} }
@ -69,7 +61,8 @@ flushFunc() {
} }
updatePiholeFunc() { updatePiholeFunc() {
"${PI_HOLE_SCRIPT_DIR}"/update.sh shift
"${PI_HOLE_SCRIPT_DIR}"/update.sh "$@"
exit 0 exit 0
} }
@ -83,230 +76,9 @@ updateGravityFunc() {
exit 0 exit 0
} }
# Scan an array of files for matching strings
scanList(){
# Escape full stops
local domain="${1//./\\.}" lists="${2}" type="${3:-}"
# Prevent grep from printing file path
cd "/etc/pihole" || exit 1
# Prevent grep -i matching slowly: http://bit.ly/2xFXtUX
export LC_CTYPE=C
# /dev/null forces filename to be printed when only one list has been generated
# shellcheck disable=SC2086
case "${type}" in
"exact" ) grep -i -E -l "(^|\\s)${domain}($|\\s|#)" ${lists} /dev/null;;
"wc" ) grep -i -o -m 1 "/${domain}/" ${lists};;
* ) grep -i "${domain}" ${lists} /dev/null;;
esac
}
# Print each subdomain
# e.g: foo.bar.baz.com = "foo.bar.baz.com bar.baz.com baz.com com"
processWildcards() {
IFS="." read -r -a array <<< "${1}"
for (( i=${#array[@]}-1; i>=0; i-- )); do
ar=""
for (( j=${#array[@]}-1; j>${#array[@]}-i-2; j-- )); do
if [[ $j == $((${#array[@]}-1)) ]]; then
ar="${array[$j]}"
else
ar="${array[$j]}.${ar}"
fi
done
echo "${ar}"
done
}
queryFunc() { queryFunc() {
shift shift
local options="$*" adlist="" all="" exact="" blockpage="" matchType="match" "${PI_HOLE_SCRIPT_DIR}"/query.sh "$@"
if [[ "${options}" == "-h" ]] || [[ "${options}" == "--help" ]]; then
echo "Usage: pihole -q [option] <domain>
Example: 'pihole -q -exact domain.com'
Query the adlists for a specified domain
Options:
-adlist Print the name of the block list URL
-exact Search the block lists for exact domain matches
-all Return all query matches within a block list
-h, --help Show this help dialog"
exit 0
fi
if [[ ! -e "/etc/pihole/adlists.list" ]]; then
echo -e "${COL_LIGHT_RED}The file '/etc/pihole/adlists.list' was not found${COL_NC}"
exit 1
fi
# Handle valid options
if [[ "${options}" == *"-bp"* ]]; then
exact="exact"; blockpage=true
else
[[ "${options}" == *"-adlist"* ]] && adlist=true
[[ "${options}" == *"-all"* ]] && all=true
if [[ "${options}" == *"-exact"* ]]; then
exact="exact"; matchType="exact ${matchType}"
fi
fi
# Strip valid options, leaving only the domain and invalid options
# This allows users to place the options before or after the domain
options=$(sed -E 's/ ?-(bp|adlists?|all|exact) ?//g' <<< "${options}")
# Handle remaining options
# If $options contain non ASCII characters, convert to punycode
case "${options}" in
"" ) str="No domain specified";;
*" "* ) str="Unknown query option specified";;
*[![:ascii:]]* ) domainQuery=$(idn2 "${options}");;
* ) domainQuery="${options}";;
esac
if [[ -n "${str:-}" ]]; then
echo -e "${str}${COL_NC}\\nTry 'pihole -q --help' for more information."
exit 1
fi
# Scan Whitelist and Blacklist
lists="whitelist.txt blacklist.txt"
mapfile -t results <<< "$(scanList "${domainQuery}" "${lists}" "${exact}")"
if [[ -n "${results[*]}" ]]; then
wbMatch=true
# Loop through each result in order to print unique file title once
for result in "${results[@]}"; do
fileName="${result%%.*}"
if [[ -n "${blockpage}" ]]; then
echo "π ${result}"
exit 0
elif [[ -n "${exact}" ]]; then
echo " ${matchType^} found in ${COL_BOLD}${fileName^}${COL_NC}"
else
# Only print filename title once per file
if [[ ! "${fileName}" == "${fileName_prev:-}" ]]; then
echo " ${matchType^} found in ${COL_BOLD}${fileName^}${COL_NC}"
fileName_prev="${fileName}"
fi
echo " ${result#*:}"
fi
done
fi
# Scan Wildcards
if [[ -e "${wildcardlist}" ]]; then
# Determine all subdomains, domain and TLDs
mapfile -t wildcards <<< "$(processWildcards "${domainQuery}")"
for match in "${wildcards[@]}"; do
# Search wildcard list for matches
mapfile -t results <<< "$(scanList "${match}" "${wildcardlist}" "wc")"
if [[ -n "${results[*]}" ]]; then
if [[ -z "${wcMatch:-}" ]] && [[ -z "${blockpage}" ]]; then
wcMatch=true
echo " ${matchType^} found in ${COL_BOLD}Wildcards${COL_NC}:"
fi
case "${blockpage}" in
true ) echo "π ${wildcardlist##*/}"; exit 0;;
* ) echo " *.${match}";;
esac
fi
done
fi
# Get version sorted *.domains filenames (without dir path)
lists=("$(cd "/etc/pihole" || exit 0; printf "%s\\n" -- *.domains | sort -V)")
# Query blocklists for occurences of domain
mapfile -t results <<< "$(scanList "${domainQuery}" "${lists[*]}" "${exact}")"
# Handle notices
if [[ -z "${wbMatch:-}" ]] && [[ -z "${wcMatch:-}" ]] && [[ -z "${results[*]}" ]]; then
echo -e " ${INFO} No ${exact/t/t }results found for ${COL_BOLD}${domainQuery}${COL_NC} found within block lists"
exit 0
elif [[ -z "${results[*]}" ]]; then
# Result found in WL/BL/Wildcards
exit 0
elif [[ -z "${all}" ]] && [[ "${#results[*]}" -ge 100 ]]; then
echo -e " ${INFO} Over 100 ${exact/t/t }results found for ${COL_BOLD}${domainQuery}${COL_NC}
This can be overridden using the -all option"
exit 0
fi
# Remove unwanted content from non-exact $results
if [[ -z "${exact}" ]]; then
# Delete lines starting with #
# Remove comments after domain
# Remove hosts format IP address
mapfile -t results <<< "$(IFS=$'\n'; sed \
-e "/:#/d" \
-e "s/[ \\t]#.*//g" \
-e "s/:.*[ \\t]/:/g" \
<<< "${results[*]}")"
# Exit if result was in a comment
[[ -z "${results[*]}" ]] && exit 0
fi
# Get adlist file content as array
if [[ -n "${adlist}" ]] || [[ -n "${blockpage}" ]]; then
for adlistUrl in $(< "/etc/pihole/adlists.list"); do
if [[ "${adlistUrl:0:4}" =~ (http|www.) ]]; then
adlists+=("${adlistUrl}")
fi
done
fi
# Print "Exact matches for" title
if [[ -n "${exact}" ]] && [[ -z "${blockpage}" ]]; then
plural=""; [[ "${#results[*]}" -gt 1 ]] && plural="es"
echo " ${matchType^}${plural} for ${COL_BOLD}${domainQuery}${COL_NC} found in:"
fi
for result in "${results[@]}"; do
fileName="${result/:*/}"
# Determine *.domains URL using filename's number
if [[ -n "${adlist}" ]] || [[ -n "${blockpage}" ]]; then
fileNum="${fileName/list./}"; fileNum="${fileNum%%.*}"
fileName="${adlists[$fileNum]}"
# Discrepency occurs when adlists has been modified, but Gravity has not been run
if [[ -z "${fileName}" ]]; then
fileName="${COL_LIGHT_RED}(no associated adlists URL found)${COL_NC}"
fi
fi
if [[ -n "${blockpage}" ]]; then
echo "${fileNum} ${fileName}"
elif [[ -n "${exact}" ]]; then
echo " ${fileName}"
else
if [[ ! "${fileName}" == "${fileName_prev:-}" ]]; then
count=""
echo " ${matchType^} found in ${COL_BOLD}${fileName}${COL_NC}:"
fileName_prev="${fileName}"
fi
: $((count++))
# Print matching domain if $max_count has not been reached
[[ -z "${all}" ]] && max_count="50"
if [[ -z "${all}" ]] && [[ "${count}" -ge "${max_count}" ]]; then
[[ "${count}" -gt "${max_count}" ]] && continue
echo " ${COL_GRAY}Over ${count} results found, skipping rest of file${COL_NC}"
else
echo " ${result#*:}"
fi
fi
done
exit 0 exit 0
} }
@ -332,18 +104,18 @@ restartDNS() {
local svcOption svc str output status local svcOption svc str output status
svcOption="${1:-}" svcOption="${1:-}"
# Determine if we should reload or restart dnsmasq # Determine if we should reload or restart restart
if [[ "${svcOption}" =~ "reload" ]]; then if [[ "${svcOption}" =~ "reload" ]]; then
# Using SIGHUP will NOT re-read any *.conf files # Using SIGHUP will NOT re-read any *.conf files
svc="killall -s SIGHUP dnsmasq" svc="killall -s SIGHUP ${resolver}"
else else
# Get PID of dnsmasq to determine if it needs to start or restart # Get PID of resolver to determine if it needs to start or restart
if pidof dnsmasq &> /dev/null; then if pidof pihole-FTL &> /dev/null; then
svcOption="restart" svcOption="restart"
else else
svcOption="start" svcOption="start"
fi fi
svc="service dnsmasq ${svcOption}" svc="service ${resolver} ${svcOption}"
fi fi
# Print output to Terminal, but not to Web Admin # Print output to Terminal, but not to Web Admin
@ -359,9 +131,6 @@ restartDNS() {
[[ ! -t 1 ]] && local OVER="" [[ ! -t 1 ]] && local OVER=""
echo -e "${OVER} ${CROSS} ${output}" echo -e "${OVER} ${CROSS} ${output}"
fi fi
# Send signal to FTL to have it re-parse the gravity files
killall -s SIGHUP pihole-FTL
} }
piholeEnable() { piholeEnable() {
@ -476,7 +245,7 @@ statusFunc() {
local addnConfigs local addnConfigs
# Determine if service is running on port 53 (Cr: https://superuser.com/a/806331) # Determine if service is running on port 53 (Cr: https://superuser.com/a/806331)
if (echo > /dev/tcp/localhost/53) >/dev/null 2>&1; then if (echo > /dev/tcp/127.0.0.1/53) >/dev/null 2>&1; then
if [[ "${1}" != "web" ]]; then if [[ "${1}" != "web" ]]; then
echo -e " ${TICK} DNS service is running" echo -e " ${TICK} DNS service is running"
fi fi
@ -516,6 +285,13 @@ statusFunc() {
} }
tailFunc() { 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
echo " ${CROSS} Warning: Query logging is disabled"
fi
echo -e " ${INFO} Press Ctrl-C to exit" echo -e " ${INFO} Press Ctrl-C to exit"
# Retrieve IPv4/6 addresses # Retrieve IPv4/6 addresses
@ -541,12 +317,13 @@ Switch Pi-hole subsystems to a different Github branch
Repositories: Repositories:
core [branch] Change the branch of Pi-hole's core subsystem core [branch] Change the branch of Pi-hole's core subsystem
web [branch] Change the branch of Admin Console subsystem web [branch] Change the branch of Web Interface subsystem
ftl [branch] Change the branch of Pi-hole's FTL subsystem ftl [branch] Change the branch of Pi-hole's FTL subsystem
Branches: Branches:
master Update subsystems to the latest stable release master Update subsystems to the latest stable release
dev Update subsystems to the latest development release" dev Update subsystems to the latest development release
branchname Update subsystems to the specified branchname"
exit 0 exit 0
fi fi
@ -599,7 +376,8 @@ Add '-h' after specific commands for more information on usage
Whitelist/Blacklist Options: Whitelist/Blacklist Options:
-w, whitelist Whitelist domain(s) -w, whitelist Whitelist domain(s)
-b, blacklist Blacklist domain(s) -b, blacklist Blacklist domain(s)
-wild, wildcard Blacklist domain(s), and all its subdomains --wild, wildcard Wildcard blacklist domain(s)
--regex, regex Regex blacklist domains(s)
Add '-h' for more info on whitelist/blacklist usage Add '-h' for more info on whitelist/blacklist usage
Debugging Options: Debugging Options:
@ -610,8 +388,8 @@ Debugging Options:
-t, tail View the live output of the Pi-hole log -t, tail View the live output of the Pi-hole log
Options: Options:
-a, admin Admin Console options -a, admin Web interface options
Add '-h' for more info on admin console usage Add '-h' for more info on Web Interface usage
-c, chronometer Calculates stats and displays to an LCD -c, chronometer Calculates stats and displays to an LCD
Add '-h' for more info on chronometer usage Add '-h' for more info on chronometer usage
-g, updateGravity Update the list of ad-serving domains -g, updateGravity Update the list of ad-serving domains
@ -621,7 +399,8 @@ Options:
-q, query Query the adlists for a specified domain -q, query Query the adlists for a specified domain
Add '-h' for more info on query usage Add '-h' for more info on query usage
-up, updatePihole Update Pi-hole subsystems -up, updatePihole Update Pi-hole subsystems
-v, version Show installed versions of Pi-hole, Admin Console & FTL Add '--check-only' to exit script before update is performed.
-v, version Show installed versions of Pi-hole, Web Interface & FTL
Add '-h' for more info on version usage Add '-h' for more info on version usage
uninstall Uninstall Pi-hole from your system uninstall Uninstall Pi-hole from your system
status Display the running status of Pi-hole subsystems status Display the running status of Pi-hole subsystems
@ -640,12 +419,13 @@ fi
# Handle redirecting to specific functions based on arguments # Handle redirecting to specific functions based on arguments
case "${1}" in case "${1}" in
"-w" | "whitelist" ) whitelistFunc "$@";; "-w" | "whitelist" ) listFunc "$@";;
"-b" | "blacklist" ) blacklistFunc "$@";; "-b" | "blacklist" ) listFunc "$@";;
"-wild" | "wildcard" ) wildcardFunc "$@";; "--wild" | "wildcard" ) listFunc "$@";;
"--regex" | "regex" ) listFunc "$@";;
"-d" | "debug" ) debugFunc "$@";; "-d" | "debug" ) debugFunc "$@";;
"-f" | "flush" ) flushFunc "$@";; "-f" | "flush" ) flushFunc "$@";;
"-up" | "updatePihole" ) updatePiholeFunc;; "-up" | "updatePihole" ) updatePiholeFunc "$@";;
"-r" | "reconfigure" ) reconfigurePiholeFunc;; "-r" | "reconfigure" ) reconfigurePiholeFunc;;
"-g" | "updateGravity" ) updateGravityFunc "$@";; "-g" | "updateGravity" ) updateGravityFunc "$@";;
"-c" | "chronometer" ) chronometerFunc "$@";; "-c" | "chronometer" ) chronometerFunc "$@";;

View file

@ -3,3 +3,4 @@ pytest
pytest-xdist pytest-xdist
pytest-cov pytest-cov
testinfra testinfra
tox

6
setup.py Normal file
View file

@ -0,0 +1,6 @@
from setuptools import setup
setup(
setup_requires=['pytest-runner'],
tests_require=['pytest'],
)

25
test/README.md Normal file
View file

@ -0,0 +1,25 @@
# Recommended way to run tests
Make sure you have Docker and Python w/pip package manager.
From command line all you need to do is:
- `pip install tox`
- `tox`
Tox handles setting up a virtual environment for python dependancies, installing dependancies, building the docker images used by tests, and finally running tests. It's an easy way to have travis-ci like build behavior locally.
## Alternative py.test method of running tests
You're responsible for setting up your virtual env and dependancies in this situation.
```
py.test -vv -n auto -m "build_stage"
py.test -vv -n auto -m "not build_stage"
```
The build_stage tests have to run first to create the docker images, followed by the actual tests which utilize said images. Unless you're changing your dockerfiles you shouldn't have to run the build_stage every time - but it's a good idea to rebuild at least once a day in case the base Docker images or packages change.
# How do I debug python?
Highly recommended: Setup PyCharm on a **Docker enabled** machine. Having a python debugger like PyCharm changes your life if you've never used it :)

View file

@ -1,14 +1,30 @@
import pytest import pytest
import testinfra import testinfra
from textwrap import dedent
check_output = testinfra.get_backend( check_output = testinfra.get_backend(
"local://" "local://"
).get_module("Command").check_output ).get_module("Command").check_output
SETUPVARS = {
'PIHOLE_INTERFACE': 'eth99',
'IPV4_ADDRESS': '1.1.1.1',
'IPV6_ADDRESS': 'FE80::240:D0FF:FE48:4672',
'PIHOLE_DNS_1': '4.2.2.1',
'PIHOLE_DNS_2': '4.2.2.2'
}
tick_box = "[\x1b[1;32m\xe2\x9c\x93\x1b[0m]".decode("utf-8")
cross_box = "[\x1b[1;31m\xe2\x9c\x97\x1b[0m]".decode("utf-8")
info_box = "[i]".decode("utf-8")
@pytest.fixture @pytest.fixture
def Pihole(Docker): def Pihole(Docker):
''' used to contain some script stubbing, now pretty much an alias. '''
Also provides bash as the default run function shell ''' used to contain some script stubbing, now pretty much an alias.
Also provides bash as the default run function shell
'''
def run_bash(self, command, *args, **kwargs): def run_bash(self, command, *args, **kwargs):
cmd = self.get_command(command, *args) cmd = self.get_command(command, *args)
if self.user is not None: if self.user is not None:
@ -22,12 +38,18 @@ def Pihole(Docker):
return out return out
funcType = type(Docker.run) funcType = type(Docker.run)
Docker.run = funcType(run_bash, Docker, testinfra.backend.docker.DockerBackend) Docker.run = funcType(run_bash,
Docker,
testinfra.backend.docker.DockerBackend)
return Docker return Docker
@pytest.fixture @pytest.fixture
def Docker(request, args, image, cmd): def Docker(request, args, image, cmd):
''' combine our fixtures into a docker run command and setup finalizer to cleanup ''' '''
combine our fixtures into a docker run command and setup finalizer to
cleanup
'''
assert 'docker' in check_output('id'), "Are you in the docker group?" assert 'docker' in check_output('id'), "Are you in the docker group?"
docker_run = "docker run {} {} {}".format(args, image, cmd) docker_run = "docker run {} {} {}".format(args, image, cmd)
docker_id = check_output(docker_run) docker_id = check_output(docker_run)
@ -40,22 +62,95 @@ def Docker(request, args, image, cmd):
docker_container.id = docker_id docker_container.id = docker_id
return docker_container return docker_container
@pytest.fixture @pytest.fixture
def args(request): def args(request):
''' -t became required when tput began being used ''' '''
-t became required when tput began being used
'''
return '-t -d' return '-t -d'
@pytest.fixture(params=['debian', 'centos'])
@pytest.fixture(params=['debian', 'centos', 'fedora'])
def tag(request): def tag(request):
''' consumed by image to make the test matrix ''' '''
consumed by image to make the test matrix
'''
return request.param return request.param
@pytest.fixture() @pytest.fixture()
def image(request, tag): def image(request, tag):
''' built by test_000_build_containers.py ''' '''
built by test_000_build_containers.py
'''
return 'pytest_pihole:{}'.format(tag) return 'pytest_pihole:{}'.format(tag)
@pytest.fixture() @pytest.fixture()
def cmd(request): def cmd(request):
''' default to doing nothing by tailing null, but don't exit ''' '''
default to doing nothing by tailing null, but don't exit
'''
return 'tail -f /dev/null' return 'tail -f /dev/null'
# Helper functions
def mock_command(script, args, container):
'''
Allows for setup of commands we don't really want to have to run for real
in unit tests
'''
full_script_path = '/usr/local/bin/{}'.format(script)
mock_script = dedent('''\
#!/bin/bash -e
echo "\$0 \$@" >> /var/log/{script}
case "\$1" in'''.format(script=script))
for k, v in args.iteritems():
case = dedent('''
{arg})
echo {res}
exit {retcode}
;;'''.format(arg=k, res=v[0], retcode=v[1]))
mock_script += case
mock_script += dedent('''
esac''')
container.run('''
cat <<EOF> {script}\n{content}\nEOF
chmod +x {script}
rm -f /var/log/{scriptlog}'''.format(script=full_script_path,
content=mock_script,
scriptlog=script))
def mock_command_2(script, args, container):
'''
Allows for setup of commands we don't really want to have to run for real
in unit tests
'''
full_script_path = '/usr/local/bin/{}'.format(script)
mock_script = dedent('''\
#!/bin/bash -e
echo "\$0 \$@" >> /var/log/{script}
case "\$1 \$2" in'''.format(script=script))
for k, v in args.iteritems():
case = dedent('''
\"{arg}\")
echo \"{res}\"
exit {retcode}
;;'''.format(arg=k, res=v[0], retcode=v[1]))
mock_script += case
mock_script += dedent('''
esac''')
container.run('''
cat <<EOF> {script}\n{content}\nEOF
chmod +x {script}
rm -f /var/log/{scriptlog}'''.format(script=full_script_path,
content=mock_script,
scriptlog=script))
def run_script(Pihole, script):
result = Pihole.run(script)
assert result.rc == 0
return result

16
test/fedora.Dockerfile Normal file
View file

@ -0,0 +1,16 @@
FROM fedora:latest
ENV GITDIR /etc/.pihole
ENV SCRIPTDIR /opt/pihole
RUN mkdir -p $GITDIR $SCRIPTDIR /etc/pihole
ADD . $GITDIR
RUN cp $GITDIR/advanced/Scripts/*.sh $GITDIR/gravity.sh $GITDIR/pihole $GITDIR/automated\ install/*.sh $SCRIPTDIR/
ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$SCRIPTDIR
RUN true && \
chmod +x $SCRIPTDIR/*
ENV PH_TEST true
#sed '/# Start the installer/Q' /opt/pihole/basic-install.sh > /opt/pihole/stub_basic-install.sh && \

View file

@ -6,10 +6,15 @@ run_local = testinfra.get_backend(
"local://" "local://"
).get_module("Command").run ).get_module("Command").run
@pytest.mark.parametrize("image,tag", [ @pytest.mark.parametrize("image,tag", [
( 'test/debian.Dockerfile', 'pytest_pihole:debian' ), ('test/debian.Dockerfile', 'pytest_pihole:debian'),
( 'test/centos.Dockerfile', 'pytest_pihole:centos' ), ('test/centos.Dockerfile', 'pytest_pihole:centos'),
('test/fedora.Dockerfile', 'pytest_pihole:fedora'),
]) ])
# mark as 'build_stage' so we can ensure images are build first when tests
# are executed in parallel. (not required when tests are executed serially)
@pytest.mark.build_stage
def test_build_pihole_image(image, tag): def test_build_pihole_image(image, tag):
build_cmd = run_local('docker build -f {} -t {} .'.format(image, tag)) build_cmd = run_local('docker build -f {} -t {} .'.format(image, tag))
if build_cmd.rc != 0: if build_cmd.rc != 0:

View file

@ -1,24 +1,40 @@
import pytest
from textwrap import dedent from textwrap import dedent
import re
from conftest import (
SETUPVARS,
tick_box,
info_box,
cross_box,
mock_command,
mock_command_2,
run_script
)
SETUPVARS = {
'PIHOLE_INTERFACE' : 'eth99',
'IPV4_ADDRESS' : '1.1.1.1',
'IPV6_ADDRESS' : 'FE80::240:D0FF:FE48:4672',
'PIHOLE_DNS_1' : '4.2.2.1',
'PIHOLE_DNS_2' : '4.2.2.2'
}
tick_box="[\x1b[1;32m\xe2\x9c\x93\x1b[0m]".decode("utf-8") def test_supported_operating_system(Pihole):
cross_box="[\x1b[1;31m\xe2\x9c\x97\x1b[0m]".decode("utf-8") '''
info_box="[i]".decode("utf-8") confirm installer exists on unsupported distribution
'''
# break supported package managers to emulate an unsupported distribution
Pihole.run('rm -rf /usr/bin/apt-get')
Pihole.run('rm -rf /usr/bin/rpm')
distro_check = Pihole.run('''
source /opt/pihole/basic-install.sh
distro_check
''')
expected_stdout = cross_box + ' OS distribution not supported'
assert expected_stdout in distro_check.stdout
# assert distro_check.rc == 1
def test_setupVars_are_sourced_to_global_scope(Pihole): def test_setupVars_are_sourced_to_global_scope(Pihole):
''' currently update_dialogs sources setupVars with a dot, '''
currently update_dialogs sources setupVars with a dot,
then various other functions use the variables. then various other functions use the variables.
This confirms the sourced variables are in scope between functions ''' This confirms the sourced variables are in scope between functions
'''
setup_var_file = 'cat <<EOF> /etc/pihole/setupVars.conf\n' setup_var_file = 'cat <<EOF> /etc/pihole/setupVars.conf\n'
for k,v in SETUPVARS.iteritems(): for k, v in SETUPVARS.iteritems():
setup_var_file += "{}={}\n".format(k, v) setup_var_file += "{}={}\n".format(k, v)
setup_var_file += "EOF\n" setup_var_file += "EOF\n"
Pihole.run(setup_var_file) Pihole.run(setup_var_file)
@ -43,13 +59,17 @@ def test_setupVars_are_sourced_to_global_scope(Pihole):
output = run_script(Pihole, script).stdout output = run_script(Pihole, script).stdout
for k,v in SETUPVARS.iteritems(): for k, v in SETUPVARS.iteritems():
assert "{}={}".format(k, v) in output assert "{}={}".format(k, v) in output
def test_setupVars_saved_to_file(Pihole): def test_setupVars_saved_to_file(Pihole):
''' confirm saved settings are written to a file for future updates to re-use ''' '''
set_setup_vars = '\n' # dedent works better with this and padding matching script below confirm saved settings are written to a file for future updates to re-use
for k,v in SETUPVARS.iteritems(): '''
# dedent works better with this and padding matching script below
set_setup_vars = '\n'
for k, v in SETUPVARS.iteritems():
set_setup_vars += " {}={}\n".format(k, v) set_setup_vars += " {}={}\n".format(k, v)
Pihole.run(set_setup_vars).stdout Pihole.run(set_setup_vars).stdout
@ -67,43 +87,57 @@ def test_setupVars_saved_to_file(Pihole):
output = run_script(Pihole, script).stdout output = run_script(Pihole, script).stdout
for k,v in SETUPVARS.iteritems(): for k, v in SETUPVARS.iteritems():
assert "{}={}".format(k, v) in output assert "{}={}".format(k, v) in output
def test_configureFirewall_firewalld_running_no_errors(Pihole): def test_configureFirewall_firewalld_running_no_errors(Pihole):
''' confirms firewalld rules are applied when firewallD is running ''' '''
confirms firewalld rules are applied when firewallD is running
'''
# firewallD returns 'running' as status # firewallD returns 'running' as status
mock_command('firewall-cmd', {'*':('running', 0)}, Pihole) mock_command('firewall-cmd', {'*': ('running', 0)}, Pihole)
# Whiptail dialog returns Ok for user prompt # Whiptail dialog returns Ok for user prompt
mock_command('whiptail', {'*':('', 0)}, Pihole) mock_command('whiptail', {'*': ('', 0)}, Pihole)
configureFirewall = Pihole.run(''' configureFirewall = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
configureFirewall configureFirewall
''') ''')
expected_stdout = 'Configuring FirewallD for httpd and dnsmasq' expected_stdout = 'Configuring FirewallD for httpd and pihole-FTL'
assert expected_stdout in configureFirewall.stdout assert expected_stdout in configureFirewall.stdout
firewall_calls = Pihole.run('cat /var/log/firewall-cmd').stdout firewall_calls = Pihole.run('cat /var/log/firewall-cmd').stdout
assert 'firewall-cmd --state' in firewall_calls assert 'firewall-cmd --state' in firewall_calls
assert 'firewall-cmd --permanent --add-service=http --add-service=dns' in firewall_calls assert ('firewall-cmd '
'--permanent '
'--add-service=http '
'--add-service=dns') in firewall_calls
assert 'firewall-cmd --reload' in firewall_calls assert 'firewall-cmd --reload' in firewall_calls
def test_configureFirewall_firewalld_disabled_no_errors(Pihole): def test_configureFirewall_firewalld_disabled_no_errors(Pihole):
''' confirms firewalld rules are not applied when firewallD is not running ''' '''
confirms firewalld rules are not applied when firewallD is not running
'''
# firewallD returns non-running status # firewallD returns non-running status
mock_command('firewall-cmd', {'*':('not running', '1')}, Pihole) mock_command('firewall-cmd', {'*': ('not running', '1')}, Pihole)
configureFirewall = Pihole.run(''' configureFirewall = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
configureFirewall configureFirewall
''') ''')
expected_stdout = 'No active firewall detected.. skipping firewall configuration' expected_stdout = ('No active firewall detected.. '
'skipping firewall configuration')
assert expected_stdout in configureFirewall.stdout assert expected_stdout in configureFirewall.stdout
def test_configureFirewall_firewalld_enabled_declined_no_errors(Pihole): def test_configureFirewall_firewalld_enabled_declined_no_errors(Pihole):
''' confirms firewalld rules are not applied when firewallD is running, user declines ruleset ''' '''
confirms firewalld rules are not applied when firewallD is running, user
declines ruleset
'''
# firewallD returns running status # firewallD returns running status
mock_command('firewall-cmd', {'*':('running', 0)}, Pihole) mock_command('firewall-cmd', {'*': ('running', 0)}, Pihole)
# Whiptail dialog returns Cancel for user prompt # Whiptail dialog returns Cancel for user prompt
mock_command('whiptail', {'*':('', 1)}, Pihole) mock_command('whiptail', {'*': ('', 1)}, Pihole)
configureFirewall = Pihole.run(''' configureFirewall = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
configureFirewall configureFirewall
@ -111,6 +145,7 @@ def test_configureFirewall_firewalld_enabled_declined_no_errors(Pihole):
expected_stdout = 'Not installing firewall rulesets.' expected_stdout = 'Not installing firewall rulesets.'
assert expected_stdout in configureFirewall.stdout assert expected_stdout in configureFirewall.stdout
def test_configureFirewall_no_firewall(Pihole): def test_configureFirewall_no_firewall(Pihole):
''' confirms firewall skipped no daemon is running ''' ''' confirms firewall skipped no daemon is running '''
configureFirewall = Pihole.run(''' configureFirewall = Pihole.run('''
@ -120,14 +155,18 @@ def test_configureFirewall_no_firewall(Pihole):
expected_stdout = 'No active firewall detected' expected_stdout = 'No active firewall detected'
assert expected_stdout in configureFirewall.stdout assert expected_stdout in configureFirewall.stdout
def test_configureFirewall_IPTables_enabled_declined_no_errors(Pihole): def test_configureFirewall_IPTables_enabled_declined_no_errors(Pihole):
''' confirms IPTables rules are not applied when IPTables is running, user declines ruleset ''' '''
confirms IPTables rules are not applied when IPTables is running, user
declines ruleset
'''
# iptables command exists # iptables command exists
mock_command('iptables', {'*':('', '0')}, Pihole) mock_command('iptables', {'*': ('', '0')}, Pihole)
# modinfo returns always true (ip_tables module check) # modinfo returns always true (ip_tables module check)
mock_command('modinfo', {'*':('', '0')}, Pihole) mock_command('modinfo', {'*': ('', '0')}, Pihole)
# Whiptail dialog returns Cancel for user prompt # Whiptail dialog returns Cancel for user prompt
mock_command('whiptail', {'*':('', '1')}, Pihole) mock_command('whiptail', {'*': ('', '1')}, Pihole)
configureFirewall = Pihole.run(''' configureFirewall = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
configureFirewall configureFirewall
@ -135,14 +174,19 @@ def test_configureFirewall_IPTables_enabled_declined_no_errors(Pihole):
expected_stdout = 'Not installing firewall rulesets.' expected_stdout = 'Not installing firewall rulesets.'
assert expected_stdout in configureFirewall.stdout assert expected_stdout in configureFirewall.stdout
def test_configureFirewall_IPTables_enabled_rules_exist_no_errors(Pihole): def test_configureFirewall_IPTables_enabled_rules_exist_no_errors(Pihole):
''' confirms IPTables rules are not applied when IPTables is running and rules exist ''' '''
# iptables command exists and returns 0 on calls (should return 0 on iptables -C) confirms IPTables rules are not applied when IPTables is running and rules
mock_command('iptables', {'-S':('-P INPUT DENY', '0')}, Pihole) exist
'''
# iptables command exists and returns 0 on calls
# (should return 0 on iptables -C)
mock_command('iptables', {'-S': ('-P INPUT DENY', '0')}, Pihole)
# modinfo returns always true (ip_tables module check) # modinfo returns always true (ip_tables module check)
mock_command('modinfo', {'*':('', '0')}, Pihole) mock_command('modinfo', {'*': ('', '0')}, Pihole)
# Whiptail dialog returns Cancel for user prompt # Whiptail dialog returns Cancel for user prompt
mock_command('whiptail', {'*':('', '0')}, Pihole) mock_command('whiptail', {'*': ('', '0')}, Pihole)
configureFirewall = Pihole.run(''' configureFirewall = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
configureFirewall configureFirewall
@ -150,18 +194,46 @@ def test_configureFirewall_IPTables_enabled_rules_exist_no_errors(Pihole):
expected_stdout = 'Installing new IPTables firewall rulesets' expected_stdout = 'Installing new IPTables firewall rulesets'
assert expected_stdout in configureFirewall.stdout assert expected_stdout in configureFirewall.stdout
firewall_calls = Pihole.run('cat /var/log/iptables').stdout firewall_calls = Pihole.run('cat /var/log/iptables').stdout
assert 'iptables -I INPUT 1 -p tcp -m tcp --dport 80 -j ACCEPT' not in firewall_calls # General call type occurances
assert 'iptables -I INPUT 1 -p tcp -m tcp --dport 53 -j ACCEPT' not in firewall_calls assert len(re.findall(r'iptables -S', firewall_calls)) == 1
assert 'iptables -I INPUT 1 -p udp -m udp --dport 53 -j ACCEPT' not in firewall_calls assert len(re.findall(r'iptables -C', firewall_calls)) == 4
assert len(re.findall(r'iptables -I', firewall_calls)) == 0
# Specific port call occurances
assert len(re.findall(r'tcp --dport 80', firewall_calls)) == 1
assert len(re.findall(r'tcp --dport 53', firewall_calls)) == 1
assert len(re.findall(r'udp --dport 53', firewall_calls)) == 1
assert len(re.findall(r'tcp --dport 4711:4720', firewall_calls)) == 1
def test_configureFirewall_IPTables_enabled_not_exist_no_errors(Pihole): def test_configureFirewall_IPTables_enabled_not_exist_no_errors(Pihole):
''' confirms IPTables rules are applied when IPTables is running and rules do not exist ''' '''
confirms IPTables rules are applied when IPTables is running and rules do
not exist
'''
# iptables command and returns 0 on calls (should return 1 on iptables -C) # iptables command and returns 0 on calls (should return 1 on iptables -C)
mock_command('iptables', {'-S':('-P INPUT DENY', '0'), '-C':('', 1), '-I':('', 0)}, Pihole) mock_command(
'iptables',
{
'-S': (
'-P INPUT DENY',
'0'
),
'-C': (
'',
1
),
'-I': (
'',
0
)
},
Pihole
)
# modinfo returns always true (ip_tables module check) # modinfo returns always true (ip_tables module check)
mock_command('modinfo', {'*':('', '0')}, Pihole) mock_command('modinfo', {'*': ('', '0')}, Pihole)
# Whiptail dialog returns Cancel for user prompt # Whiptail dialog returns Cancel for user prompt
mock_command('whiptail', {'*':('', '0')}, Pihole) mock_command('whiptail', {'*': ('', '0')}, Pihole)
configureFirewall = Pihole.run(''' configureFirewall = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
configureFirewall configureFirewall
@ -169,52 +241,160 @@ def test_configureFirewall_IPTables_enabled_not_exist_no_errors(Pihole):
expected_stdout = 'Installing new IPTables firewall rulesets' expected_stdout = 'Installing new IPTables firewall rulesets'
assert expected_stdout in configureFirewall.stdout assert expected_stdout in configureFirewall.stdout
firewall_calls = Pihole.run('cat /var/log/iptables').stdout firewall_calls = Pihole.run('cat /var/log/iptables').stdout
assert 'iptables -I INPUT 1 -p tcp -m tcp --dport 80 -j ACCEPT' in firewall_calls # General call type occurances
assert 'iptables -I INPUT 1 -p tcp -m tcp --dport 53 -j ACCEPT' in firewall_calls assert len(re.findall(r'iptables -S', firewall_calls)) == 1
assert 'iptables -I INPUT 1 -p udp -m udp --dport 53 -j ACCEPT' in firewall_calls assert len(re.findall(r'iptables -C', firewall_calls)) == 4
assert len(re.findall(r'iptables -I', firewall_calls)) == 4
# Specific port call occurances
assert len(re.findall(r'tcp --dport 80', firewall_calls)) == 2
assert len(re.findall(r'tcp --dport 53', firewall_calls)) == 2
assert len(re.findall(r'udp --dport 53', firewall_calls)) == 2
assert len(re.findall(r'tcp --dport 4711:4720', firewall_calls)) == 2
def test_selinux_enforcing_default_exit(Pihole):
'''
confirms installer prompts to exit when SELinux is Enforcing by default
'''
# 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('''
source /opt/pihole/basic-install.sh
checkSelinux
''')
expected_stdout = info_box + ' SELinux mode detected: Permissive'
assert expected_stdout in check_selinux.stdout
assert check_selinux.rc == 0
def test_selinux_disabled(Pihole):
'''
confirms installer continues when SELinux is Disabled
'''
mock_command('getenforce', {'*': ('Disabled', '0')}, Pihole)
check_selinux = Pihole.run('''
source /opt/pihole/basic-install.sh
checkSelinux
''')
expected_stdout = info_box + ' SELinux mode detected: Disabled'
assert expected_stdout in check_selinux.stdout
assert check_selinux.rc == 0
def test_installPiholeWeb_fresh_install_no_errors(Pihole): def test_installPiholeWeb_fresh_install_no_errors(Pihole):
''' confirms all web page assets from Core repo are installed on a fresh build ''' '''
confirms all web page assets from Core repo are installed on a fresh build
'''
installWeb = Pihole.run(''' installWeb = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
installPiholeWeb installPiholeWeb
''') ''')
assert info_box + ' Installing blocking page...' in installWeb.stdout expected_stdout = info_box + ' Installing blocking page...'
assert tick_box + ' Creating directory for blocking page, and copying files' in installWeb.stdout assert expected_stdout in installWeb.stdout
assert cross_box + ' Backing up index.lighttpd.html' in installWeb.stdout expected_stdout = tick_box + (' Creating directory for blocking page, '
assert 'No default index.lighttpd.html file found... not backing up' in installWeb.stdout 'and copying files')
assert tick_box + ' Installing sudoer file' in installWeb.stdout assert expected_stdout in installWeb.stdout
expected_stdout = cross_box + ' Backing up index.lighttpd.html'
assert expected_stdout in installWeb.stdout
expected_stdout = ('No default index.lighttpd.html file found... '
'not backing up')
assert expected_stdout in installWeb.stdout
expected_stdout = tick_box + ' Installing sudoer file'
assert expected_stdout in installWeb.stdout
web_directory = Pihole.run('ls -r /var/www/html/pihole').stdout web_directory = Pihole.run('ls -r /var/www/html/pihole').stdout
assert 'index.php' in web_directory assert 'index.php' in web_directory
assert 'blockingpage.css' in web_directory assert 'blockingpage.css' in web_directory
def test_update_package_cache_success_no_errors(Pihole): def test_update_package_cache_success_no_errors(Pihole):
''' confirms package cache was updated without any errors''' '''
confirms package cache was updated without any errors
'''
updateCache = Pihole.run(''' updateCache = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
distro_check distro_check
update_package_cache update_package_cache
''') ''')
assert tick_box + ' Update local cache of available packages' in updateCache.stdout expected_stdout = tick_box + ' Update local cache of available packages'
assert 'Error: Unable to update package cache.' not in updateCache.stdout assert expected_stdout in updateCache.stdout
assert 'error' not in updateCache.stdout.lower()
def test_update_package_cache_failure_no_errors(Pihole): def test_update_package_cache_failure_no_errors(Pihole):
''' confirms package cache was not updated''' '''
mock_command('apt-get', {'update':('', '1')}, Pihole) confirms package cache was not updated
'''
mock_command('apt-get', {'update': ('', '1')}, Pihole)
updateCache = Pihole.run(''' updateCache = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
distro_check distro_check
update_package_cache update_package_cache
''') ''')
assert cross_box + ' Update local cache of available packages' in updateCache.stdout expected_stdout = cross_box + ' Update local cache of available packages'
assert expected_stdout in updateCache.stdout
assert 'Error: Unable to update package cache.' in updateCache.stdout assert 'Error: Unable to update package cache.' in updateCache.stdout
def test_FTL_detect_aarch64_no_errors(Pihole): def test_FTL_detect_aarch64_no_errors(Pihole):
''' confirms only aarch64 package is downloaded for FTL engine ''' '''
confirms only aarch64 package is downloaded for FTL engine
'''
# mock uname to return aarch64 platform # mock uname to return aarch64 platform
mock_command('uname', {'-m':('aarch64', '0')}, Pihole) mock_command('uname', {'-m': ('aarch64', '0')}, Pihole)
# mock ldd to respond with aarch64 shared library # mock ldd to respond with aarch64 shared library
mock_command('ldd', {'/bin/ls':('/lib/ld-linux-aarch64.so.1', '0')}, Pihole) mock_command(
'ldd',
{
'/bin/ls': (
'/lib/ld-linux-aarch64.so.1',
'0'
)
},
Pihole
)
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
FTLdetect FTLdetect
@ -226,29 +406,36 @@ def test_FTL_detect_aarch64_no_errors(Pihole):
expected_stdout = tick_box + ' Downloading and Installing FTL' expected_stdout = tick_box + ' Downloading and Installing FTL'
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
def test_FTL_detect_armv6l_no_errors(Pihole): def test_FTL_detect_armv6l_no_errors(Pihole):
''' confirms only armv6l package is downloaded for FTL engine ''' '''
confirms only armv6l package is downloaded for FTL engine
'''
# mock uname to return armv6l platform # mock uname to return armv6l platform
mock_command('uname', {'-m':('armv6l', '0')}, Pihole) mock_command('uname', {'-m': ('armv6l', '0')}, Pihole)
# mock ldd to respond with aarch64 shared library # mock ldd to respond with aarch64 shared library
mock_command('ldd', {'/bin/ls':('/lib/ld-linux-armhf.so.3', '0')}, Pihole) mock_command('ldd', {'/bin/ls': ('/lib/ld-linux-armhf.so.3', '0')}, Pihole)
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
FTLdetect FTLdetect
''') ''')
expected_stdout = info_box + ' FTL Checks...' expected_stdout = info_box + ' FTL Checks...'
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + ' Detected ARM-hf architecture (armv6 or lower)' expected_stdout = tick_box + (' Detected ARM-hf architecture '
'(armv6 or lower)')
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + ' Downloading and Installing FTL' expected_stdout = tick_box + ' Downloading and Installing FTL'
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
def test_FTL_detect_armv7l_no_errors(Pihole): def test_FTL_detect_armv7l_no_errors(Pihole):
''' confirms only armv7l package is downloaded for FTL engine ''' '''
confirms only armv7l package is downloaded for FTL engine
'''
# mock uname to return armv7l platform # mock uname to return armv7l platform
mock_command('uname', {'-m':('armv7l', '0')}, Pihole) mock_command('uname', {'-m': ('armv7l', '0')}, Pihole)
# mock ldd to respond with aarch64 shared library # mock ldd to respond with aarch64 shared library
mock_command('ldd', {'/bin/ls':('/lib/ld-linux-armhf.so.3', '0')}, Pihole) mock_command('ldd', {'/bin/ls': ('/lib/ld-linux-armhf.so.3', '0')}, Pihole)
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
FTLdetect FTLdetect
@ -260,8 +447,11 @@ def test_FTL_detect_armv7l_no_errors(Pihole):
expected_stdout = tick_box + ' Downloading and Installing FTL' expected_stdout = tick_box + ' Downloading and Installing FTL'
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
def test_FTL_detect_x86_64_no_errors(Pihole): def test_FTL_detect_x86_64_no_errors(Pihole):
''' confirms only x86_64 package is downloaded for FTL engine ''' '''
confirms only x86_64 package is downloaded for FTL engine
'''
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
FTLdetect FTLdetect
@ -273,10 +463,11 @@ def test_FTL_detect_x86_64_no_errors(Pihole):
expected_stdout = tick_box + ' Downloading and Installing FTL' expected_stdout = tick_box + ' Downloading and Installing FTL'
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
def test_FTL_detect_unknown_no_errors(Pihole): def test_FTL_detect_unknown_no_errors(Pihole):
''' confirms only generic package is downloaded for FTL engine ''' ''' confirms only generic package is downloaded for FTL engine '''
# mock uname to return generic platform # mock uname to return generic platform
mock_command('uname', {'-m':('mips', '0')}, Pihole) mock_command('uname', {'-m': ('mips', '0')}, Pihole)
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
FTLdetect FTLdetect
@ -284,8 +475,11 @@ def test_FTL_detect_unknown_no_errors(Pihole):
expected_stdout = 'Not able to detect architecture (unknown: mips)' expected_stdout = 'Not able to detect architecture (unknown: mips)'
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
def test_FTL_download_aarch64_no_errors(Pihole): def test_FTL_download_aarch64_no_errors(Pihole):
''' confirms only aarch64 package is downloaded for FTL engine ''' '''
confirms only aarch64 package is downloaded for FTL engine
'''
# mock uname to return generic platform # mock uname to return generic platform
download_binary = Pihole.run(''' download_binary = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
@ -293,13 +487,13 @@ def test_FTL_download_aarch64_no_errors(Pihole):
''') ''')
expected_stdout = tick_box + ' Downloading and Installing FTL' expected_stdout = tick_box + ' Downloading and Installing FTL'
assert expected_stdout in download_binary.stdout assert expected_stdout in download_binary.stdout
error = 'Error: Download of binary from Github failed' assert 'error' not in download_binary.stdout.lower()
assert error not in download_binary.stdout
error = 'Error: URL not found'
assert error not in download_binary.stdout
def test_FTL_download_unknown_fails_no_errors(Pihole): def test_FTL_download_unknown_fails_no_errors(Pihole):
''' confirms unknown binary is not downloaded for FTL engine ''' '''
confirms unknown binary is not downloaded for FTL engine
'''
# mock uname to return generic platform # mock uname to return generic platform
download_binary = Pihole.run(''' download_binary = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
@ -310,8 +504,11 @@ def test_FTL_download_unknown_fails_no_errors(Pihole):
error = 'Error: URL not found' error = 'Error: URL not found'
assert error in download_binary.stdout assert error in download_binary.stdout
def test_FTL_binary_installed_and_responsive_no_errors(Pihole): def test_FTL_binary_installed_and_responsive_no_errors(Pihole):
''' confirms FTL binary is copied and functional in installed location ''' '''
confirms FTL binary is copied and functional in installed location
'''
installed_binary = Pihole.run(''' installed_binary = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
FTLdetect FTLdetect
@ -320,8 +517,11 @@ def test_FTL_binary_installed_and_responsive_no_errors(Pihole):
expected_stdout = 'v' expected_stdout = 'v'
assert expected_stdout in installed_binary.stdout assert expected_stdout in installed_binary.stdout
# def test_FTL_support_files_installed(Pihole): # def test_FTL_support_files_installed(Pihole):
# ''' confirms FTL support files are installed ''' # '''
# confirms FTL support files are installed
# '''
# support_files = Pihole.run(''' # support_files = Pihole.run('''
# source /opt/pihole/basic-install.sh # source /opt/pihole/basic-install.sh
# FTLdetect # FTLdetect
@ -334,21 +534,46 @@ def test_FTL_binary_installed_and_responsive_no_errors(Pihole):
# assert '644 /run/pihole-FTL.pid' in support_files.stdout # assert '644 /run/pihole-FTL.pid' in support_files.stdout
# assert '644 /var/log/pihole-FTL.log' in support_files.stdout # assert '644 /var/log/pihole-FTL.log' in support_files.stdout
def test_IPv6_only_link_local(Pihole): def test_IPv6_only_link_local(Pihole):
''' confirms IPv6 blocking is disabled for Link-local address ''' '''
confirms IPv6 blocking is disabled for Link-local address
'''
# mock ip -6 address to return Link-local address # mock ip -6 address to return Link-local address
mock_command_2('ip', {'-6 address':('inet6 fe80::d210:52fa:fe00:7ad7/64 scope link', '0')}, Pihole) mock_command_2(
'ip',
{
'-6 address': (
'inet6 fe80::d210:52fa:fe00:7ad7/64 scope link',
'0'
)
},
Pihole
)
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
useIPv6dialog useIPv6dialog
''') ''')
expected_stdout = 'Unable to find IPv6 ULA/GUA address, IPv6 adblocking will not be enabled' expected_stdout = ('Unable to find IPv6 ULA/GUA address, '
'IPv6 adblocking will not be enabled')
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
def test_IPv6_only_ULA(Pihole): def test_IPv6_only_ULA(Pihole):
''' confirms IPv6 blocking is enabled for ULA addresses ''' '''
confirms IPv6 blocking is enabled for ULA addresses
'''
# mock ip -6 address to return ULA address # mock ip -6 address to return ULA address
mock_command_2('ip', {'-6 address':('inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global', '0')}, Pihole) mock_command_2(
'ip',
{
'-6 address': (
'inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global',
'0'
)
},
Pihole
)
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
useIPv6dialog useIPv6dialog
@ -356,10 +581,22 @@ def test_IPv6_only_ULA(Pihole):
expected_stdout = 'Found IPv6 ULA address, using it for blocking IPv6 ads' expected_stdout = 'Found IPv6 ULA address, using it for blocking IPv6 ads'
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
def test_IPv6_only_GUA(Pihole): def test_IPv6_only_GUA(Pihole):
''' confirms IPv6 blocking is enabled for GUA addresses ''' '''
confirms IPv6 blocking is enabled for GUA addresses
'''
# mock ip -6 address to return GUA address # mock ip -6 address to return GUA address
mock_command_2('ip', {'-6 address':('inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global', '0')}, Pihole) mock_command_2(
'ip',
{
'-6 address': (
'inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global',
'0'
)
},
Pihole
)
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
useIPv6dialog useIPv6dialog
@ -367,10 +604,23 @@ def test_IPv6_only_GUA(Pihole):
expected_stdout = 'Found IPv6 GUA address, using it for blocking IPv6 ads' expected_stdout = 'Found IPv6 GUA address, using it for blocking IPv6 ads'
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
def test_IPv6_GUA_ULA_test(Pihole): def test_IPv6_GUA_ULA_test(Pihole):
''' confirms IPv6 blocking is enabled for GUA and ULA addresses ''' '''
confirms IPv6 blocking is enabled for GUA and ULA addresses
'''
# mock ip -6 address to return GUA and ULA addresses # mock ip -6 address to return GUA and ULA addresses
mock_command_2('ip', {'-6 address':('inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global\ninet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global', '0')}, Pihole) mock_command_2(
'ip',
{
'-6 address': (
'inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global\n'
'inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global',
'0'
)
},
Pihole
)
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
useIPv6dialog useIPv6dialog
@ -378,61 +628,26 @@ def test_IPv6_GUA_ULA_test(Pihole):
expected_stdout = 'Found IPv6 ULA address, using it for blocking IPv6 ads' expected_stdout = 'Found IPv6 ULA address, using it for blocking IPv6 ads'
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
def test_IPv6_ULA_GUA_test(Pihole): def test_IPv6_ULA_GUA_test(Pihole):
''' confirms IPv6 blocking is enabled for GUA and ULA addresses ''' '''
confirms IPv6 blocking is enabled for GUA and ULA addresses
'''
# mock ip -6 address to return ULA and GUA addresses # mock ip -6 address to return ULA and GUA addresses
mock_command_2('ip', {'-6 address':('inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global\ninet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global', '0')}, Pihole) mock_command_2(
'ip',
{
'-6 address': (
'inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global\n'
'inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global',
'0'
)
},
Pihole
)
detectPlatform = Pihole.run(''' detectPlatform = Pihole.run('''
source /opt/pihole/basic-install.sh source /opt/pihole/basic-install.sh
useIPv6dialog useIPv6dialog
''') ''')
expected_stdout = 'Found IPv6 ULA address, using it for blocking IPv6 ads' expected_stdout = 'Found IPv6 ULA address, using it for blocking IPv6 ads'
assert expected_stdout in detectPlatform.stdout assert expected_stdout in detectPlatform.stdout
# Helper functions
def mock_command(script, args, container):
''' Allows for setup of commands we don't really want to have to run for real in unit tests '''
full_script_path = '/usr/local/bin/{}'.format(script)
mock_script = dedent('''\
#!/bin/bash -e
echo "\$0 \$@" >> /var/log/{script}
case "\$1" in'''.format(script=script))
for k, v in args.iteritems():
case = dedent('''
{arg})
echo {res}
exit {retcode}
;;'''.format(arg=k, res=v[0], retcode=v[1]))
mock_script += case
mock_script += dedent('''
esac''')
container.run('''
cat <<EOF> {script}\n{content}\nEOF
chmod +x {script}
rm -f /var/log/{scriptlog}'''.format(script=full_script_path, content=mock_script, scriptlog=script))
def mock_command_2(script, args, container):
''' Allows for setup of commands we don't really want to have to run for real in unit tests '''
full_script_path = '/usr/local/bin/{}'.format(script)
mock_script = dedent('''\
#!/bin/bash -e
echo "\$0 \$@" >> /var/log/{script}
case "\$1 \$2" in'''.format(script=script))
for k, v in args.iteritems():
case = dedent('''
\"{arg}\")
echo \"{res}\"
exit {retcode}
;;'''.format(arg=k, res=v[0], retcode=v[1]))
mock_script += case
mock_script += dedent('''
esac''')
container.run('''
cat <<EOF> {script}\n{content}\nEOF
chmod +x {script}
rm -f /var/log/{scriptlog}'''.format(script=full_script_path, content=mock_script, scriptlog=script))
def run_script(Pihole, script):
result = Pihole.run(script)
assert result.rc == 0
return result

View file

@ -0,0 +1,209 @@
import pytest
from conftest import (
tick_box,
info_box,
cross_box,
mock_command,
mock_command_2,
)
@pytest.mark.parametrize("tag", [('fedora'), ])
def test_epel_and_remi_not_installed_fedora(Pihole):
'''
confirms installer does not attempt to install EPEL/REMI repositories
on Fedora
'''
distro_check = Pihole.run('''
source /opt/pihole/basic-install.sh
distro_check
''')
assert distro_check.stdout == ''
epel_package = Pihole.package('epel-release')
assert not epel_package.is_installed
remi_package = Pihole.package('remi-release')
assert not remi_package.is_installed
@pytest.mark.parametrize("tag", [('centos'), ])
def test_release_supported_version_check_centos(Pihole):
'''
confirms installer exits on unsupported releases of CentOS
'''
# mock CentOS release < 7 (unsupported)
mock_command_2(
'rpm',
{"-q --queryformat '%{VERSION}' centos-release'": (
'5',
'0'
)},
Pihole
)
distro_check = Pihole.run('''
source /opt/pihole/basic-install.sh
distro_check
''')
expected_stdout = cross_box + (' CentOS is not suported.')
assert expected_stdout in distro_check.stdout
expected_stdout = 'Please update to CentOS release 7 or later'
assert expected_stdout in distro_check.stdout
@pytest.mark.parametrize("tag", [('centos'), ])
def test_enable_epel_repository_centos(Pihole):
'''
confirms the EPEL package repository is enabled when installed on CentOS
'''
distro_check = Pihole.run('''
source /opt/pihole/basic-install.sh
distro_check
''')
expected_stdout = info_box + (' Enabling EPEL package repository '
'(https://fedoraproject.org/wiki/EPEL)')
assert expected_stdout in distro_check.stdout
expected_stdout = tick_box + ' Installed epel-release'
assert expected_stdout in distro_check.stdout
epel_package = Pihole.package('epel-release')
assert epel_package.is_installed
@pytest.mark.parametrize("tag", [('centos'), ])
def test_php_upgrade_default_optout_centos(Pihole):
'''
confirms the default behavior to opt-out of installing PHP7 from REMI
'''
distro_check = Pihole.run('''
source /opt/pihole/basic-install.sh
distro_check
''')
expected_stdout = info_box + (' User opt-out of PHP 7 upgrade on CentOS. '
'Deprecated PHP may be in use.')
assert expected_stdout in distro_check.stdout
remi_package = Pihole.package('remi-release')
assert not remi_package.is_installed
@pytest.mark.parametrize("tag", [('centos'), ])
def test_php_upgrade_user_optout_centos(Pihole):
'''
confirms installer behavior when user opt-out of installing PHP7 from REMI
(php not currently installed)
'''
# Whiptail dialog returns Cancel for user prompt
mock_command('whiptail', {'*': ('', '1')}, Pihole)
distro_check = Pihole.run('''
source /opt/pihole/basic-install.sh
distro_check
''')
expected_stdout = info_box + (' User opt-out of PHP 7 upgrade on CentOS. '
'Deprecated PHP may be in use.')
assert expected_stdout in distro_check.stdout
remi_package = Pihole.package('remi-release')
assert not remi_package.is_installed
@pytest.mark.parametrize("tag", [('centos'), ])
def test_php_upgrade_user_optin_centos(Pihole):
'''
confirms installer behavior when user opt-in to installing PHP7 from REMI
(php not currently installed)
'''
# Whiptail dialog returns Continue for user prompt
mock_command('whiptail', {'*': ('', '0')}, Pihole)
distro_check = Pihole.run('''
source /opt/pihole/basic-install.sh
distro_check
''')
assert 'opt-out' not in distro_check.stdout
expected_stdout = info_box + (' Enabling Remi\'s RPM repository '
'(https://rpms.remirepo.net)')
assert expected_stdout in distro_check.stdout
expected_stdout = tick_box + (' Remi\'s RPM repository has '
'been enabled for PHP7')
assert expected_stdout in distro_check.stdout
remi_package = Pihole.package('remi-release')
assert remi_package.is_installed
@pytest.mark.parametrize("tag", [('centos'), ])
def test_php_version_lt_7_detected_upgrade_default_optout_centos(Pihole):
'''
confirms the default behavior to opt-out of upgrading to PHP7 from REMI
'''
# first we will install the default php version to test installer behavior
php_install = Pihole.run('yum install -y php')
assert php_install.rc == 0
php_package = Pihole.package('php')
default_centos_php_version = php_package.version.split('.')[0]
if int(default_centos_php_version) >= 7: # PHP7 is supported/recommended
pytest.skip("Test deprecated . Detected default PHP version >= 7")
distro_check = Pihole.run('''
source /opt/pihole/basic-install.sh
distro_check
''')
expected_stdout = info_box + (' User opt-out of PHP 7 upgrade on CentOS. '
'Deprecated PHP may be in use.')
assert expected_stdout in distro_check.stdout
remi_package = Pihole.package('remi-release')
assert not remi_package.is_installed
@pytest.mark.parametrize("tag", [('centos'), ])
def test_php_version_lt_7_detected_upgrade_user_optout_centos(Pihole):
'''
confirms installer behavior when user opt-out to upgrade to PHP7 via REMI
'''
# first we will install the default php version to test installer behavior
php_install = Pihole.run('yum install -y php')
assert php_install.rc == 0
php_package = Pihole.package('php')
default_centos_php_version = php_package.version.split('.')[0]
if int(default_centos_php_version) >= 7: # PHP7 is supported/recommended
pytest.skip("Test deprecated . Detected default PHP version >= 7")
# Whiptail dialog returns Cancel for user prompt
mock_command('whiptail', {'*': ('', '1')}, Pihole)
distro_check = Pihole.run('''
source /opt/pihole/basic-install.sh
distro_check
''')
expected_stdout = info_box + (' User opt-out of PHP 7 upgrade on CentOS. '
'Deprecated PHP may be in use.')
assert expected_stdout in distro_check.stdout
remi_package = Pihole.package('remi-release')
assert not remi_package.is_installed
@pytest.mark.parametrize("tag", [('centos'), ])
def test_php_version_lt_7_detected_upgrade_user_optin_centos(Pihole):
'''
confirms installer behavior when user opt-in to upgrade to PHP7 via REMI
'''
# first we will install the default php version to test installer behavior
php_install = Pihole.run('yum install -y php')
assert php_install.rc == 0
php_package = Pihole.package('php')
default_centos_php_version = php_package.version.split('.')[0]
if int(default_centos_php_version) >= 7: # PHP7 is supported/recommended
pytest.skip("Test deprecated . Detected default PHP version >= 7")
# Whiptail dialog returns Continue for user prompt
mock_command('whiptail', {'*': ('', '0')}, Pihole)
distro_check = Pihole.run('''
source /opt/pihole/basic-install.sh
distro_check
install_dependent_packages PIHOLE_WEB_DEPS[@]
''')
expected_stdout = info_box + (' User opt-out of PHP 7 upgrade on CentOS. '
'Deprecated PHP may be in use.')
assert expected_stdout not in distro_check.stdout
expected_stdout = info_box + (' Enabling Remi\'s RPM repository '
'(https://rpms.remirepo.net)')
assert expected_stdout in distro_check.stdout
expected_stdout = tick_box + (' Remi\'s RPM repository has '
'been enabled for PHP7')
assert expected_stdout in distro_check.stdout
remi_package = Pihole.package('remi-release')
assert remi_package.is_installed
updated_php_package = Pihole.package('php')
updated_php_version = updated_php_package.version.split('.')[0]
assert int(updated_php_version) == 7

View file

@ -1,13 +1,18 @@
import pytest
import testinfra import testinfra
run_local = testinfra.get_backend( run_local = testinfra.get_backend(
"local://" "local://"
).get_module("Command").run ).get_module("Command").run
def test_scripts_pass_shellcheck(): def test_scripts_pass_shellcheck():
''' Make sure shellcheck does not find anything wrong with our shell scripts ''' '''
shellcheck = "find . -type f -name 'update.sh' | while read file; do shellcheck -x \"$file\" -e SC1090,SC1091; done;" Make sure shellcheck does not find anything wrong with our shell scripts
'''
shellcheck = ("find . -type f -name 'update.sh' "
"| while read file; do "
"shellcheck -x \"$file\" -e SC1090,SC1091; "
"done;")
results = run_local(shellcheck) results = run_local(shellcheck)
print results.stdout print results.stdout
assert '' == results.stdout assert '' == results.stdout

10
tox.ini Normal file
View file

@ -0,0 +1,10 @@
[tox]
envlist = py27
[testenv]
whitelist_externals = docker
deps = -rrequirements.txt
commands = docker build -f test/debian.Dockerfile -t pytest_pihole:debian .
docker build -f test/centos.Dockerfile -t pytest_pihole:centos .
docker build -f test/fedora.Dockerfile -t pytest_pihole:fedora .
pytest {posargs:-vv -n auto} -m "not build_stage" ./test/