├── LICENSE ├── Manual ├── ExactDomains.txt └── RegexDomains.txt ├── README.md ├── domains └── whitelist.txt └── scripts ├── domains.sql ├── uninstall.py └── whitelist.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 TheSmashy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Manual/ExactDomains.txt: -------------------------------------------------------------------------------- 1 | account.activedirectory.windowsazure.com 2 | account.office.net 3 | accounts.accesscontrol.windows.net 4 | admin.microsoft.com 5 | admin.onedrive.com 6 | adminwebservice.microsoftonline.com 7 | ajax.aspnetcdn.com 8 | ajax.aspnetcdn.com 9 | aka.ms 10 | amp.azure.net 11 | api.microsoftstream.com 12 | api.passwordreset.microsoftonline.com 13 | attachments.office.net 14 | autologon.microsoftazuread-sso.com 15 | becws.microsoftonline.com 16 | broadcast.skype.com 17 | cdn.sharepointonline.com 18 | clientconfig.microsoftonline-p.net 19 | companymanager.microsoftonline.com 20 | compass-ssl.microsoft.com 21 | device.login.microsoftonline.com 22 | graph.microsoft.com 23 | graph.windows.net 24 | home.office.com 25 | login.microsoft.com 26 | login.microsoftonline.com 27 | login.microsoftonline-p.com 28 | login.windows.net 29 | logincdn.msauth.net 30 | logincert.microsoftonline.com 31 | loginex.microsoftonline.com 32 | login-us.microsoftonline.com 33 | main.iam.ad.ext.azure.com 34 | manage.office.com 35 | mlccdn.blob.core.windows.net 36 | mlccdn.blob.core.windows.net 37 | nexus.microsoftonline-p.com 38 | office.live.com 39 | officeclient.microsoft.com 40 | outlook.office.com 41 | outlook.office365.com 42 | passwordreset.microsoftonline.com 43 | portal.office.com 44 | privatecdn.sharepointonline.com 45 | protection.office.com 46 | provisioningapi.microsoftonline.com 47 | publiccdn.sharepointonline.com 48 | r1.res.office365.com 49 | r3.res.office365.com 50 | r3.res.outlook.com 51 | r4.res.office365.com 52 | skypemaprdsitus.trafficmanager.net 53 | smtp.office365.com 54 | spoprod-a.akamaihd.net 55 | spoprod-a.akamaihd.net 56 | static.sharepointonline.com 57 | storage.live.com 58 | suite.office.net 59 | teams.microsoft.com 60 | web.microsoftstream.com 61 | www.office.com 62 | -------------------------------------------------------------------------------- /Manual/RegexDomains.txt: -------------------------------------------------------------------------------- 1 | api.microsoftstream.com 2 | azureedge.net 3 | broadcast.skype.com 4 | keydelivery.mediaservices.windows.net 5 | lync.com 6 | mail.protection.outlook.com 7 | manage.office.com 8 | media.azure.net 9 | msecnd.net 10 | msedge.net 11 | msftidentity.com 12 | msidentity.com 13 | notification.api.microsoftstream.com 14 | office.net 15 | officeapps.live.com 16 | onenote.com 17 | online.office.com 18 | outlook.com 19 | outlook.office.com 20 | outlook.office.com 21 | outlook.office.com 22 | portal.cloudappsecurity.com 23 | protection.office.com 24 | protection.outlook.com 25 | sfbassets.com 26 | sharepointonline.com 27 | skype.com 28 | skypeforbusiness.com 29 | streaming.mediaservices.windows.net 30 | teams.microsoft.com 31 | tenor.com 32 | urlp.sfbassets.com 33 | users.storage.live.com 34 | cdn.onenote.net 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # M365 Whitelist for Pi-hole 2 | 3 | Curated allow and deny lists to keep Microsoft 365 services working (Outlook, Teams, SharePoint, OneDrive) while blocking telemetry noise. 4 | 5 | This repo is for Enterprise/Worldwide tenants. 6 | For GCC, see the fork here: [obiwantoby/O365Whitelist](https://github.com/obiwantoby/O365Whitlist). 7 | 8 | ## What it does 9 | 10 | ✅ Keeps M365 functional 11 | 12 | ✅ Blocks known telemetry endpoints 13 | 14 | ✅ Easy to apply, easy to roll back 15 | 16 | ## Quick Start 17 | 18 | **1. Back up Pi-hole first (seriously):** 19 | ``` 20 | sudo cp /etc/pihole/gravity.db /etc/pihole/gravity.db.bak.$(date +%s) 21 | ``` 22 | 23 | **2. Clone:** 24 | ``` 25 | git clone https://github.com/TheSmashy/O365Whitelist.git 26 | cd O365Whitelist 27 | ``` 28 | 29 | **3. Apply lists:** 30 | ``` 31 | python3 scripts/whitelist.py 32 | pihole -g 33 | pihole restartdns 34 | ``` 35 | 36 | **4. Roll back if you break Teams/Outlook:** 37 | ``` 38 | python3 scripts/uninstall.py 39 | sudo cp /etc/pihole/gravity.db.bak. /etc/pihole/gravity.db 40 | pihole -g 41 | pihole restartdns 42 | ```` 43 | ## Notes 44 | 45 | Tested on Pi-hole v5. Works on v6 but you’ll likely add via Adlists instead of directly editing gravity.db. 46 | 47 | Critical endpoints like login.microsoftonline.com are never denied. Don’t mess with them unless you want authentication loops. 48 | 49 | Regex rules are optional — start with exact lists first. 50 | 51 | ## Credits 52 | 53 | Original project: TheSmashy (this repo) 54 | 55 | GCC fork: obiwantoby/O365Whitelist 56 | 57 | ## Disclaimer 58 | 59 | This is community-maintained. Use at your own risk. Back up before applying. 60 | -------------------------------------------------------------------------------- /domains/whitelist.txt: -------------------------------------------------------------------------------- 1 | outlook.office.com 2 | *.outlook.office.com 3 | outlook.office365.com 4 | *.outlook.office.com 5 | smtp.office365.com 6 | r1.res.office365.com 7 | r3.res.office365.com 8 | r4.res.office365.com 9 | *.outlook.com 10 | *.outlook.office.com 11 | attachments.office.net 12 | *.protection.outlook.com 13 | *.mail.protection.outlook.com 14 | storage.live.com 15 | admin.onedrive.com 16 | officeclient.microsoft.com 17 | *.sharepointonline.com 18 | cdn.sharepointonline.com 19 | privatecdn.sharepointonline.com 20 | publiccdn.sharepointonline.com 21 | spoprod-a.akamaihd.net 22 | static.sharepointonline.com 23 | teams.microsoft.com 24 | *.lync.com 25 | *.teams.microsoft.com 26 | broadcast.skype.com 27 | *.broadcast.skype.com 28 | *.sfbassets.com 29 | *.urlp.sfbassets.com 30 | skypemaprdsitus.trafficmanager.net 31 | *.keydelivery.mediaservices.windows.net 32 | *.msecnd.net 33 | *.streaming.mediaservices.windows.net 34 | ajax.aspnetcdn.com 35 | mlccdn.blob.core.windows.net 36 | aka.ms 37 | amp.azure.net 38 | *.users.storage.live.com 39 | *.skypeforbusiness.com 40 | *.msedge.net 41 | compass-ssl.microsoft.com 42 | *.tenor.com 43 | *.skype.com 44 | ajax.aspnetcdn.com 45 | r3.res.outlook.com 46 | spoprod-a.akamaihd.net 47 | *.api.microsoftstream.com 48 | *.notification.api.microsoftstream.com 49 | api.microsoftstream.com 50 | web.microsoftstream.com 51 | *.azureedge.net 52 | *.media.azure.net 53 | *.officeapps.live.com 54 | *.online.office.com 55 | office.live.com 56 | *.onenote.com 57 | *.office.net 58 | *cdn.onenote.net 59 | *.msftidentity.com 60 | *.msidentity.com 61 | account.activedirectory.windowsazure.com 62 | accounts.accesscontrol.windows.net 63 | adminwebservice.microsoftonline.com 64 | api.passwordreset.microsoftonline.com 65 | autologon.microsoftazuread-sso.com 66 | becws.microsoftonline.com 67 | clientconfig.microsoftonline-p.net 68 | companymanager.microsoftonline.com 69 | device.login.microsoftonline.com 70 | graph.microsoft.com 71 | graph.windows.net 72 | login.microsoft.com 73 | login.microsoftonline.com 74 | login.microsoftonline-p.com 75 | login.windows.net 76 | logincert.microsoftonline.com 77 | loginex.microsoftonline.com 78 | login-us.microsoftonline.com 79 | nexus.microsoftonline-p.com 80 | passwordreset.microsoftonline.com 81 | provisioningapi.microsoftonline.com 82 | logincdn.msauth.net 83 | main.iam.ad.ext.azure.com 84 | *.manage.office.com 85 | *.protection.office.com 86 | manage.office.com 87 | protection.office.com 88 | *.portal.cloudappsecurity.com 89 | account.office.net 90 | admin.microsoft.com 91 | home.office.com 92 | portal.office.com 93 | www.office.com 94 | suite.office.net 95 | mlccdn.blob.core.windows.net 96 | -------------------------------------------------------------------------------- /scripts/domains.sql: -------------------------------------------------------------------------------- 1 | (0, 'outlook.office.com', 1, 'Exchange Online - Optimize Required - h5pYKA') 2 | (2, '(\.|^)outlook\.office\.com$', 1, 'Exchange Online - Allow Optimal - IMAP4 - h5pYKA') 3 | (0, 'outlook.office365.com', 1, 'Exchange Online - Optimize Required - h5pYKA') 4 | (2, '(\.|^)outlook\.office\.com$', 1, 'Exchange Online - Allow Optimal - IMAP4 - h5pYKA') 5 | (0, 'smtp.office365.com', 1, 'Exchange Online - Allow Required - h5pYKA') 6 | (0, 'r1.res.office365.com', 1, 'Exchange Online - Default Required - h5pYKA') 7 | (0, 'r3.res.office365.com', 1, 'Exchange Online - Default Required - h5pYKA') 8 | (0, 'r4.res.office365.com', 1, 'Exchange Online - Default Required - h5pYKA') 9 | (2, '(\.|^)outlook\.com$', 1, 'Exchange Online - Default Required - h5pYKA') 10 | (2, '(\.|^)outlook\.office\.com$', 1, 'Exchange Online - Default Required - h5pYKA') 11 | (0, 'attachments.office.net', 1, 'Exchange Online - Default Required - h5pYKA') 12 | (2, '(\.|^)protection\.outlook\.com$', 1, 'Exchange Online - Allow Required - h5pYKA') 13 | (2, '(\.|^)mail\.protection\.outlook\.com$', 1, 'Exchange Online - Allow Required - h5pYKA') 14 | (0, 'storage.live.com', 1, 'SPO and OneDrive - Supportability - h5pYKA') 15 | (0, 'admin.onedrive.com', 1, 'SPO and OneDrive - Default Required - h5pYKA') 16 | (0, 'officeclient.microsoft.com', 1, 'SPO and OneDrive - Default Required - h5pYKA') 17 | (2, '(\.|^)sharepointonline\.com$', 1, 'SPO and OneDrive - Default Required - h5pYKA') 18 | (0, 'cdn.sharepointonline.com', 1, 'SPO and OneDrive - Default Required - h5pYKA') 19 | (0, 'privatecdn.sharepointonline.com', 1, 'SPO and OneDrive - Default Required - h5pYKA') 20 | (0, 'publiccdn.sharepointonline.com', 1, 'SPO and OneDrive - Default Required - h5pYKA') 21 | (0, 'spoprod-a.akamaihd.net', 1, 'SPO and OneDrive - Default Required - h5pYKA') 22 | (0, 'static.sharepointonline.com', 1, 'SPO and OneDrive - Default Required - h5pYKA') 23 | (0, 'teams.microsoft.com', 1, 'Microsoft Teams - Allow Required - h5pYKA') 24 | (2, '(\.|^)lync\.com$', 1, 'Microsoft Teams - Allow Required - h5pYKA') 25 | (2, '(\.|^)teams\.microsoft\.com$', 1, 'Microsoft Teams - Allow Required - h5pYKA') 26 | (0, 'broadcast.skype.com', 1, 'Microsoft Teams - Allow Required - h5pYKA') 27 | (2, '(\.|^)broadcast\.skype\.com$', 1, 'Microsoft Teams - Allow Required - h5pYKA') 28 | (2, '(\.|^)sfbassets\.com$', 1, 'Microsoft Teams - Default Required - h5pYKA') 29 | (2, '(\.|^)urlp\.sfbassets\.com', 1, 'Microsoft Teams - Default Required - h5pYKA') 30 | (0, 'skypemaprdsitus.trafficmanager.net', 1, 'Microsoft Teams - Default Required - h5pYKA') 31 | (2, '(\.|^)keydelivery\.mediaservices\.windows\.net$', 1, 'Microsoft Teams - Default Required - h5pYKA') 32 | (2, '(\.|^)msecnd\.net$', 1, 'Microsoft Teams - Default Required - h5pYKA') 33 | (2, '(\.|^)streaming\.mediaservices\.windows\.net$', 1, 'Microsoft Teams - Default Required - h5pYKA') 34 | (0, 'ajax.aspnetcdn.com', 1, 'Microsoft Teams - Default Required - h5pYKA') 35 | (0, 'mlccdn.blob.core.windows.net', 1, 'Microsoft Teams - Default Required - h5pYKA') 36 | (0, 'aka.ms', 1, 'Microsoft Teams - Default Required - h5pYKA') 37 | (0, 'amp.azure.net', 1, 'Microsoft Teams - Default Required - h5pYKA') 38 | (2, '(\.|^)users\.storage\.live\.com$', 1, 'Microsoft Teams - Default Optional - Federation - h5pYKA') 39 | (2, '(\.|^)skypeforbusiness\.com$', 1, 'Microsoft Teams - Allow Optional - Skype Interop - h5pYKA') 40 | (2, '(\.|^)msedge\.net$', 1, 'Microsoft Teams - Default Required - h5pYKA') 41 | (0, 'compass-ssl.microsoft.com', 1, 'Microsoft Teams - Default Required - h5pYKA') 42 | (2, '(\.|^)tenor\.com$', 1, 'Microsoft Teams - Allow Optional - Yammer - h5pYKA') 43 | (2, '(\.|^)skype\.com$', 1, 'Microsoft Teams - Default Required - h5pYKA') 44 | (3, 'statics.teams.microsoft.com', 1, 'Microsoft Teams Telemetry - h5pYKA') 45 | (0, 'ajax.aspnetcdn.com', 1, 'Microsoft O365 Common - Video CDN - h5pYKA') 46 | (0, 'r3.res.outlook.com', 1, 'Microsoft O365 Common - Video CDN - h5pYKA') 47 | (0, 'spoprod-a.akamaihd.net', 1, 'Microsoft O365 Common - Video CDN - h5pYKA') 48 | (2, '(\.|^)api\.microsoftstream\.com$', 1, 'Microsoft O365 Common - Stream - h5pYKA') 49 | (2, '(\.|^)notification\.api\.microsoftstream\.com$', 1, 'Microsoft O365 Common - Stream - h5pYKA') 50 | (0, 'api.microsoftstream.com', 1, 'Microsoft O365 Common - Stream - h5pYKA') 51 | (0, 'web.microsoftstream.com', 1, 'Microsoft O365 Common - Stream - h5pYKA') 52 | (2, '(\.|^)azureedge\.net$', 1, 'Microsoft O365 Common - Stream - h5pYKA') 53 | (2, '(\.|^)media\.azure\.net$', 1, 'Microsoft O365 Common - Stream - h5pYKA') 54 | (2, '(\.|^)officeapps\.live\.com$', 1, 'Microsoft O365 Common - Stream - h5pYKA') 55 | (2, '(\.|^)online\.office\.com$', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 56 | (0, 'office.live.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 57 | (2, '(\.|^)onenote\.com$', 1, 'Microsoft O365 Common - Default Required - OneNote - h5pYKA') 58 | (2, '(\.|^)office\.net$', 1, 'Microsoft O365 Common - Default Required - OneNote - h5pYKA') 59 | (2, '(\.|^)cdn\.onenote\.net$', 1, 'Microsoft O365 Common - Default Required - OneNote - h5pYKA') 60 | (2, '(\.|^)msftidentity\.com$', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 61 | (2, '(\.|^)msidentity\.com$', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 62 | (0, 'account.activedirectory.windowsazure.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 63 | (0, 'accounts.accesscontrol.windows.net', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 64 | (0, 'adminwebservice.microsoftonline.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 65 | (0, 'api.passwordreset.microsoftonline.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 66 | (0, 'autologon.microsoftazuread-sso.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 67 | (0, 'becws.microsoftonline.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 68 | (0, 'clientconfig.microsoftonline-p.net', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 69 | (0, 'companymanager.microsoftonline.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 70 | (0, 'device.login.microsoftonline.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 71 | (0, 'graph.microsoft.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 72 | (0, 'graph.windows.net', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 73 | (0, 'login.microsoft.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 74 | (0, 'login.microsoftonline.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 75 | (0, 'login.microsoftonline-p.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 76 | (0, 'login.windows.net', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 77 | (0, 'logincert.microsoftonline.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 78 | (0, 'loginex.microsoftonline.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 79 | (0, 'login-us.microsoftonline.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 80 | (0, 'nexus.microsoftonline-p.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 81 | (0, 'passwordreset.microsoftonline.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 82 | (0, 'provisioningapi.microsoftonline.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 83 | (0, 'logincdn.msauth.net', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 84 | (0, 'main.iam.ad.ext.azure.com', 1, 'Azure AD Login Page that often gets blocked by regex. - h5pYKA') 85 | (2, '(\.|^)manage\.office\.com$', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 86 | (2, '(\.|^)protection\.office\.com$', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 87 | (0, 'manage.office.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 88 | (0, 'protection.office.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 89 | (2, '(\.|^)portal\.cloudappsecurity\.com$', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 90 | (0, 'account.office.net', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 91 | (0, 'admin.microsoft.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 92 | (0, 'home.office.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 93 | (0, 'portal.office.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 94 | (0, 'www.office.com', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 95 | (0, 'suite.office.net', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 96 | (0, 'mlccdn.blob.core.windows.net', 1, 'Microsoft O365 Common - Allow Required - h5pYKA') 97 | (3, '(\.|^)log\.optimizely\.com$', 1, 'Microsoft O365 Telemetry - h5pYKA') 98 | (1, 'vortex.data.microsoft.com', 1, 'Microsoft O365 Telemetry - h5pYKA') 99 | (1, 'acompli.helpshift.com', 1, 'Microsoft O365 Telemetry - h5pYKA') 100 | (1, 'analytics.localytics.com', 1, 'Microsoft O365 Telemetry - h5pYKA') 101 | (1, 'api.localytics.com', 1, 'Microsoft O365 Telemetry - h5pYKA') 102 | (1, 'sdk.hockeyapp.net', 1, 'Microsoft O365 Telemetry - h5pYKA') 103 | (1, 'web.localytics.com', 1, 'Microsoft O365 Telemetry - h5pYKA') 104 | (1, 'webanalytics.localytics.com', 1, 'Microsoft O365 Telemetry - h5pYKA') 105 | (1, 'telemetryservice.firstpartyapps.oaspapps.com', 1, 'Microsoft O365 Telemetry - h5pYKA') 106 | (1, 'watson.telemetry.microsoft.com', 1, 'Microsoft O365 Telemetry - h5pYKA') 107 | (1, 'data.flurry.com', 1, 'Microsoft O365 Telemetry - h5pYKA') 108 | (1, 'app.adjust.com', 1, 'Microsoft O365 Telemetry - h5pYKA') 109 | (1, 'activity.windows.com', 1, 'Microsoft O365 Telemetry - h5pYKA') -------------------------------------------------------------------------------- /scripts/uninstall.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Project homepage: https://github.com/TheSmashy/O365Whitlist 4 | # Licence: https://github.com/TheSmashy/O365Whitlist/blob/main/LICENSE 5 | # Created by Anudeep, modified by The Smashy 6 | # ================================================================================ 7 | import os 8 | import argparse 9 | import sqlite3 10 | import subprocess 11 | from urllib.request import Request, urlopen 12 | from urllib.error import HTTPError, URLError 13 | 14 | 15 | def fetch_whitelist_url(url): 16 | 17 | if not url: 18 | return 19 | 20 | headers = { 21 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0'} 22 | 23 | try: 24 | response = urlopen(Request(url, headers=headers)) 25 | except HTTPError as e: 26 | print('[X] HTTP Error:', e.code, 'whilst fetching', url) 27 | print('\n') 28 | print('\n') 29 | exit(1) 30 | except URLError as e: 31 | print('[X] URL Error:', e.reason, 'whilst fetching', url) 32 | print('\n') 33 | print('\n') 34 | exit(1) 35 | 36 | # Read and decode 37 | response = response.read().decode('UTF-8').replace('\r\n', '\n') 38 | 39 | # If there is data 40 | if response: 41 | # Strip leading and trailing whitespace 42 | response = '\n'.join(x.strip() for x in response.splitlines()) 43 | 44 | # Return the hosts 45 | return response 46 | 47 | 48 | def dir_path(string): 49 | if os.path.isdir(string): 50 | return string 51 | else: 52 | raise NotADirectoryError(string) 53 | 54 | 55 | def restart_pihole(docker): 56 | if docker is True: 57 | subprocess.call("docker exec -it pihole pihole restartdns reload", 58 | shell=True, stdout=subprocess.DEVNULL) 59 | else: 60 | subprocess.call(['pihole', 'restartdns', 'reload'], 61 | stdout=subprocess.DEVNULL) 62 | 63 | 64 | parser = argparse.ArgumentParser() 65 | parser.add_argument("-d", "--dir", type=dir_path, 66 | help="optional: Pi-hole etc directory") 67 | parser.add_argument( 68 | "-D", "--docker", action='store_true', help="optional: set if you're using Pi-hole in Docker environment") 69 | args = parser.parse_args() 70 | 71 | if args.dir: 72 | pihole_location = args.dir 73 | else: 74 | pihole_location = r'/etc/pihole' 75 | 76 | 77 | whitelist_remote_url = 'https://raw.githubusercontent.com/TheSmashy/O365Whitlist/main/domains/whitelist.txt' 78 | remote_sql_url = 'https://raw.githubusercontent.com/TheSmashy/O365Whitlist/main/scripts/domains.sql' 79 | gravity_whitelist_location = os.path.join(pihole_location, 'whitelist.txt') 80 | gravity_db_location = os.path.join(pihole_location, 'gravity.db') 81 | anudeep_whitelist_location = os.path.join( 82 | pihole_location, 'o365-whitelist.txt') 83 | 84 | db_exists = False 85 | sqliteConnection = None 86 | cursor = None 87 | 88 | whitelist_remote = set() 89 | whitelist_local = set() 90 | whitelist_anudeep_local = set() 91 | whitelist_old_anudeep = set() 92 | 93 | os.system('clear') 94 | print('\n') 95 | print(''' 96 | If you are using Pi-hole 5.0 or later, then this script will remove the domains which are only added by my script. 97 | Any other domains added by you will stay as it is. 98 | ''') 99 | print('\n') 100 | 101 | # Check for pihole path exsists 102 | if os.path.exists(pihole_location): 103 | print('[i] Pi-hole path exists') 104 | else: 105 | print("[X] {} was not found".format(pihole_location)) 106 | print('\n') 107 | print('\n') 108 | exit(1) 109 | 110 | # Check for write access to /etc/pihole 111 | if os.access(pihole_location, os.X_OK | os.W_OK): 112 | print("[i] Write access to {} verified" .format(pihole_location)) 113 | whitelist_str = fetch_whitelist_url(whitelist_remote_url) 114 | remote_whitelist_lines = whitelist_str.count('\n') 115 | remote_whitelist_lines += 1 116 | else: 117 | print("[X] Write access is not available for {}. Please run as root or other privileged user" .format( 118 | pihole_location)) 119 | print('\n') 120 | print('\n') 121 | exit(1) 122 | 123 | # Determine whether we are using DB or not 124 | if os.path.isfile(gravity_db_location) and os.path.getsize(gravity_db_location) > 0: 125 | db_exists = True 126 | print('[i] Pi-Hole Gravity database found') 127 | 128 | remote_sql_str = fetch_whitelist_url(remote_sql_url) 129 | remote_sql_lines = remote_sql_str.count('\n') 130 | remote_sql_lines += 1 131 | 132 | if len(remote_sql_str) > 0: 133 | print("[i] {} domains discovered" .format(remote_whitelist_lines)) 134 | else: 135 | print('[X] No remote SQL queries found') 136 | print('\n') 137 | print('\n') 138 | exit(1) 139 | else: 140 | print('[i] Legacy Pi-hole detected (Version older than 5.0)') 141 | 142 | if whitelist_str: 143 | whitelist_remote.update(x for x in map( 144 | str.strip, whitelist_str.splitlines()) if x and x[:1] != '#') 145 | else: 146 | print('[X] No remote domains were found.') 147 | print('\n') 148 | print('\n') 149 | exit(1) 150 | 151 | if db_exists: 152 | # Create a DB connection 153 | print('[i] Connecting to Gravity database') 154 | 155 | try: 156 | sqliteConnection = sqlite3.connect(gravity_db_location) 157 | cursor = sqliteConnection.cursor() 158 | print('[i] Successfully Connected to Gravity database') 159 | total_domains = cursor.execute(" SELECT * FROM domainlist WHERE type = 0 AND comment LIKE '%h5pYKA%' ") 160 | 161 | totalDomains = len(total_domains.fetchall()) 162 | print("[i] There are a total of {} domains in your whitelist which are added by my script" .format(totalDomains)) 163 | print('[i] Removing domains in the Gravity database') 164 | cursor.execute (" DELETE FROM domainlist WHERE type = 0 AND comment LIKE '%h5pYKA%' ") 165 | 166 | sqliteConnection.commit() 167 | 168 | # we only removed domains we added so use total_domains 169 | print("[i] {} domains are removed" .format(totalDomains)) 170 | remaining_domains = cursor.execute(" SELECT * FROM domainlist WHERE type = 0 OR type = 2 ") 171 | print("[i] There are a total of {} domains remaining in your whitelist" .format(len(remaining_domains.fetchall()))) 172 | 173 | cursor.close() 174 | 175 | except sqlite3.Error as error: 176 | print('[X] Failed to remove domains from Gravity database', error) 177 | print('\n') 178 | print('\n') 179 | exit(1) 180 | 181 | finally: 182 | if (sqliteConnection): 183 | sqliteConnection.close() 184 | 185 | print('[i] The database connection is closed') 186 | 187 | print('[i] Restarting Pi-hole. This could take a few seconds') 188 | restart_pihole(args.docker) 189 | print('\n') 190 | print('Done. Happy ad-blocking :)') 191 | print('\n') 192 | 193 | else: 194 | if os.path.isfile(gravity_whitelist_location) and os.path.getsize(gravity_whitelist_location) > 0: 195 | with open(gravity_whitelist_location, 'r') as fRead: 196 | whitelist_local.update(x for x in map( 197 | str.strip, fRead) if x and x[:1] != '#') 198 | 199 | if whitelist_local: 200 | print("[i] {} existing whitelisted domains identified" .format( 201 | len(whitelist_local))) 202 | 203 | if os.path.isfile(anudeep_whitelist_location) and os.path.getsize(anudeep_whitelist_location) > 0: 204 | print('[i] Existing O365-whitelist install identified') 205 | with open(anudeep_whitelist_location, 'r') as fOpen: 206 | whitelist_old_anudeep.update(x for x in map( 207 | str.strip, fOpen) if x and x[:1] != '#') 208 | 209 | if whitelist_old_anudeep: 210 | whitelist_local.difference_update(whitelist_old_anudeep) 211 | 212 | os.remove(anudeep_whitelist_location) 213 | 214 | else: 215 | print('[i] Removing domains that match the remote repo') 216 | whitelist_local.difference_update(whitelist_remote) 217 | 218 | print("[i] Adding exsisting {} domains to {}" .format( 219 | len(whitelist_local), gravity_whitelist_location)) 220 | with open(gravity_whitelist_location, 'w') as fWrite: 221 | for line in sorted(whitelist_local): 222 | fWrite.write("{}\n".format(line)) 223 | 224 | print('[i] Restarting Pi-hole. This could take a few seconds') 225 | restart_pihole(args.docker) 226 | print('[i] Done - Domains are now removed your Pi-Hole whitelist') 227 | print('\n') 228 | print('Happy ad-blocking :)') 229 | print('\n') 230 | -------------------------------------------------------------------------------- /scripts/whitelist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Project homepage: https://github.com/TheSmashy/O365Whitlist 4 | # Licence: https://github.com/TheSmashy/O365Whitlist/blob/main/LICENSE 5 | # Created by Anudeep, modified by The Smashy 6 | # ================================================================================ 7 | import os 8 | import argparse 9 | import sqlite3 10 | import subprocess 11 | from urllib.request import Request, urlopen 12 | from urllib.error import HTTPError, URLError 13 | import time 14 | 15 | today = int(time.time()) 16 | 17 | def fetch_whitelist_url(url): 18 | 19 | if not url: 20 | return 21 | 22 | headers = { 23 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0'} 24 | 25 | try: 26 | response = urlopen(Request(url, headers=headers)) 27 | except HTTPError as e: 28 | print('[X] HTTP Error:', e.code, 'whilst fetching', url) 29 | print('\n') 30 | print('\n') 31 | exit(1) 32 | except URLError as e: 33 | print('[X] URL Error:', e.reason, 'whilst fetching', url) 34 | print('\n') 35 | print('\n') 36 | exit(1) 37 | 38 | # Read and decode 39 | response = response.read().decode('UTF-8').replace('\r\n', '\n') 40 | 41 | # If there is data 42 | if response: 43 | # Strip leading and trailing whitespace 44 | response = '\n'.join(x.strip() for x in response.splitlines()) 45 | 46 | # Return the hosts 47 | return response 48 | 49 | def dir_path(string): 50 | if os.path.isdir(string): 51 | return string 52 | else: 53 | raise NotADirectoryError(string) 54 | 55 | def restart_pihole(docker): 56 | if docker is True: 57 | subprocess.call("docker exec -it pihole pihole restartdns reload", 58 | shell=True, stdout=subprocess.DEVNULL) 59 | else: 60 | subprocess.call(['pihole', '-g'], stdout=subprocess.DEVNULL) 61 | 62 | parser = argparse.ArgumentParser() 63 | parser.add_argument("-d", "--dir", type=dir_path, help="optional: Pi-hole etc directory") 64 | parser.add_argument("-D", "--docker", action='store_true', help="optional: set if you're using Pi-hole in docker environment") 65 | args = parser.parse_args() 66 | 67 | if args.dir: 68 | pihole_location = args.dir 69 | else: 70 | pihole_location = r'/etc/pihole' 71 | 72 | whitelist_remote_url = 'https://raw.githubusercontent.com/TheSmashy/O365Whitlist/main/domains/whitelist.txt' 73 | remote_sql_url = 'https://raw.githubusercontent.com/TheSmashy/O365Whitlist/main/scripts/domains.sql' 74 | gravity_whitelist_location = os.path.join(pihole_location, 'whitelist.txt') 75 | gravity_db_location = os.path.join(pihole_location, 'gravity.db') 76 | smashy_whitelist_location = os.path.join(pihole_location, 'o365-whitelist.txt') 77 | 78 | db_exists = False 79 | sqliteConnection = None 80 | cursor = None 81 | 82 | whitelist_remote = set() 83 | whitelist_local = set() 84 | whitelist_anudeep_local = set() 85 | whitelist_old_anudeep = set() 86 | 87 | os.system('clear') 88 | print('\n') 89 | print('This script will download and add domains from the repo to whitelist.') 90 | print('All the domains in this list are safe to add and does not contain any tracking or adserving domains.') 91 | print('\n') 92 | 93 | # Check for pihole path exsists 94 | if os.path.exists(pihole_location): 95 | print('[i] Pi-hole path exists') 96 | else: 97 | # print(f'[X] {pihole_location} was not found') 98 | 99 | print("[X] {} was not found".format(pihole_location)) 100 | 101 | print('\n') 102 | print('\n') 103 | exit(1) 104 | 105 | # Check for write access to /etc/pihole 106 | if os.access(pihole_location, os.X_OK | os.W_OK): 107 | print("[i] Write access to {} verified" .format(pihole_location)) 108 | whitelist_str = fetch_whitelist_url(whitelist_remote_url) 109 | remote_whitelist_lines = whitelist_str.count('\n') 110 | remote_whitelist_lines += 1 111 | else: 112 | print("[X] Write access is not available for {}. Please run as root or other privileged user" .format( 113 | pihole_location)) 114 | print('\n') 115 | print('\n') 116 | exit(1) 117 | 118 | # Determine whether we are using DB or not 119 | if os.path.isfile(gravity_db_location) and os.path.getsize(gravity_db_location) > 0: 120 | db_exists = True 121 | print('[i] Pi-Hole Gravity database found') 122 | 123 | remote_sql_str = fetch_whitelist_url(remote_sql_url) 124 | remote_sql_lines = remote_sql_str.count('\n') 125 | remote_sql_lines += 1 126 | 127 | if len(remote_sql_str) > 0: 128 | print("[i] {} domains and {} SQL queries discovered" .format( 129 | remote_whitelist_lines, remote_sql_lines)) 130 | else: 131 | print('[X] No remote SQL queries found') 132 | print('\n') 133 | print('\n') 134 | exit(1) 135 | else: 136 | print('[i] Legacy Pi-hole detected (Version older than 5.0)') 137 | 138 | # If domains were fetched, remove any comments and add to set 139 | if whitelist_str: 140 | whitelist_remote.update(x for x in map( 141 | str.strip, whitelist_str.splitlines()) if x and x[:1] != '#') 142 | else: 143 | print('[X] No remote domains were found.') 144 | print('\n') 145 | print('\n') 146 | exit(1) 147 | 148 | if db_exists: 149 | print('[i] Connecting to Gravity.') 150 | try: # Try to create a DB connection 151 | sqliteConnection = sqlite3.connect(gravity_db_location) 152 | cursor = sqliteConnection.cursor() 153 | print('[i] Successfully Connected to Gravity.') 154 | # 155 | print('[i] Checking Gravity for domains added by script.') 156 | # Check Gravity database for domains added by script 157 | gravityScript_before = cursor.execute(" SELECT * FROM domainlist WHERE type = 0 AND comment LIKE '%h5pYKA%' ") 158 | # fetch all matching entries which will create a tuple for us 159 | gravScriptBeforeTUP = gravityScript_before.fetchall() 160 | # Number of domains in database from script 161 | gravScriptBeforeTUPlen = len(gravScriptBeforeTUP) 162 | print ("[i] {} Domains from script in whitelist already." .format(gravScriptBeforeTUPlen)) 163 | # 164 | # make `remote_sql_str` a tuple so we can easily compare 165 | newWhiteTUP = remote_sql_str.split('\n') 166 | # Number of domains that would be added by script 167 | newWhiteListlen = len(newWhiteTUP) 168 | # 169 | # get domains from newWhiteTUP and make an ordered list (a list we can use predictably) 170 | nW = [None] * newWhiteListlen 171 | nwl = 0 # keep a count 172 | newWL = [None] 173 | newWhiteList = [None] * newWhiteListlen 174 | for newWhiteDomain in newWhiteTUP: # for each line found domains.sql 175 | nW[nwl] = newWhiteDomain # Add line to a controlled list 176 | removeBrace = nW[nwl].replace('(', '') # remove ( 177 | removeBraces10 = removeBrace.replace(')', '') # remove ) 178 | newWL = removeBraces10.split(', ') # split at commas to create a list 179 | newWhiteList[nwl] = newWL[1].replace('\'', '') # remove ' from domain and add to list 180 | # uncomment to see list of sql varables being imported 181 | # print (nW[nwl]) 182 | # uncomment to see list of domains being imported 183 | # print(newWhiteList[nwl]) 184 | nwl += 1 # count + 1 185 | # check database for user added exact whitelisted domains 186 | print('[i] Checking Gravity for domains added by user that are also in script.') 187 | # Check Gravity database for exact whitelisted domains added by user 188 | user_add = cursor.execute(" SELECT * FROM domainlist WHERE type = 0 AND comment NOT LIKE '%h5pYKA%' ") 189 | userAddTUP = user_add.fetchall() 190 | userAddTUPlen = len(userAddTUP) 191 | # 192 | # 193 | # check if whitelisted domains added by user are in script 194 | userAddList = [None] * userAddTUPlen # create a list that has the same size as the tuple is it compared to 195 | uA = 0 # used to count User Added domains in our script 196 | uagl = False 197 | for userAddINgravity in userAddTUP: # for every whitelisted domain we found in the database do: 198 | if userAddINgravity[2] in newWhiteList: # if the domain we found added by user IS IN our new list count it 199 | userAddList[uA] = userAddINgravity[2] # add the domain we found to the list we created 200 | uagl = True 201 | uA += 1 # bump count if domain added (starts @ 0) 202 | # 203 | uA -= 1 # subtract 1 so the count doesn't put us out of range 204 | INgravityUSERaddListCount = uA # save our count here so we know how many we have later 205 | # Make us aware of User Added domains that are also in our script 206 | if uagl == True: # if we found user added domains from our list in gravity do: 207 | print ('[i] {} domain(s) added by the user that would be added by script.\n' .format(INgravityUSERaddListCount+1)) # we have to add 1 for humans cause count starts @ 0 208 | a = 0 209 | while uA >= 0: # remember that counter now we make it go backwards to 0 210 | a += 1 # counter for number output to screen 211 | print (' {}. {}' .format(a, userAddList[uA])) # Show us what we found 212 | uA -= 1 # go backwards 213 | else: # If we don't find any 214 | INgravityUSERaddListCount = 0 # make sure this is 0 215 | print ('[i] Found {} domains added by the user that would be added by script.' .format(INgravityUSERaddListCount)) # notify of negative result 216 | # 217 | # 218 | # Check Gravity database for domains added by script that are not in new script 219 | INgravityNOTnewList = [None] * gravScriptBeforeTUPlen # create a list that has the same size as the tuple is it compared to 220 | gravScriptBeforeList = [None] * gravScriptBeforeTUPlen 221 | z = 0 222 | if uagl == True: 223 | print('\n') 224 | 225 | print('[i] Checking Gravity for domains previously added by script that are NOT in new script.') 226 | ignl = False 227 | a = 0 228 | for INgravityNOTnew in gravScriptBeforeTUP: # for every domain previously added by script 229 | gravScriptBeforeList[a] = INgravityNOTnew[2] # take domains from gravity and put them in a list for later 230 | a += 1 231 | if not INgravityNOTnew[2] in newWhiteList: # make sure it is not in new script 232 | INgravityNOTnewList[z] = INgravityNOTnew # add not found to list for later 233 | ignl = True 234 | z += 1 235 | # 236 | z -= 1 237 | INgravityNOTnewListCount = z 238 | # If In Gravity because of script but NOT in the new list Prompt for removal 239 | if ignl == True: 240 | print ('[i] {} domain(s) added previously by script that are not in new script.\n' .format(INgravityNOTnewListCount+1)) 241 | a = 0 242 | while z >= 0: 243 | a += 1 244 | print (' deleting {}' .format(INgravityNOTnewList[z][2])) 245 | # print all data retrieved from database about domain to be removed 246 | # print (INgravityNOTnewList[z]) 247 | # ability to remove old 248 | sql_delete = " DELETE FROM domainlist WHERE type = 0 AND id = '{}' " .format(INgravityNOTnewList[z][0]) 249 | cursor.executescript(sql_delete) 250 | z -= 1 251 | # If not keep going 252 | else: 253 | INgravityNOTnewListCount = 0 254 | print ('[i] Found {} domain(s) added previously by script that are not in script.' .format(INgravityNOTnewListCount)) 255 | # 256 | # 257 | # Check Gravity database for new domains to be added by script 258 | INnewNOTgravityList = [None] * newWhiteListlen 259 | w = 0 260 | if ignl == True: 261 | print('\n') 262 | # 263 | print('[i] Checking script for domains not in Gravity.') 264 | ilng = False 265 | for INnewNOTgravity in newWhiteList: # for every domain in the new script 266 | if not INnewNOTgravity in gravScriptBeforeList and not INnewNOTgravity in userAddList: # make sure it is not in gravity or added by user 267 | INnewNOTgravityList[w] = INnewNOTgravity # add domain to list we created 268 | ilng = True 269 | w += 1 270 | # 271 | w -= 1 272 | INnewNOTgravityListCount = w 273 | # If there are domains in new list that are NOT in Gravity 274 | if ilng == True: # Add domains that are missing from new script and not user additions 275 | print ('[i] {} domain(s) NOT in Gravity that are in new script.\n' .format(INnewNOTgravityListCount+1)) 276 | a = 0 277 | while w >= 0: 278 | a += 1 279 | for addNewWhiteDomain in newWhiteList: 280 | if addNewWhiteDomain in INnewNOTgravityList: 281 | print (' - Adding {}' .format(addNewWhiteDomain)) 282 | # print (addNewWhiteDomain) 283 | sql_index = newWhiteList.index(addNewWhiteDomain) 284 | # print (sql_index) 285 | # print (nW[sql_index]) 286 | # ability to add new 287 | sql_add = " INSERT OR IGNORE INTO domainlist (type, domain, enabled, comment) VALUES {} " .format(nW[sql_index]) 288 | cursor.executescript(sql_add) 289 | w -= 1 290 | # Re-Check Gravity database for domains added by script after we update it 291 | gravityScript_after = cursor.execute(" SELECT * FROM domainlist WHERE type = 0 AND comment LIKE '%h5pYKA%' ") 292 | # fetch all matching entries which will create a tuple for us 293 | gravScriptAfterTUP = gravityScript_after.fetchall() 294 | # Number of domains in database from script 295 | gravScriptAfterTUPlen = len(gravScriptAfterTUP) 296 | 297 | gsa = False 298 | ASG = INnewNOTgravityListCount 299 | ASGCOUNT = 0 300 | gravScriptAfterList = [None] * gravScriptAfterTUPlen 301 | 302 | print ('\n[i] Checking Gravity for newly added domains.\n') 303 | 304 | for gravScriptAfterDomain in gravScriptAfterTUP: 305 | gravScriptAfterList[ASGCOUNT] = gravScriptAfterTUP[ASGCOUNT][2] 306 | ASGCOUNT += 1 307 | 308 | while ASG >= 0: 309 | if INnewNOTgravityList[ASG] in gravScriptAfterList: 310 | print(' - Found {} ' .format(INnewNOTgravityList[ASG])) 311 | gsa = True 312 | ASG = ASG - 1 313 | 314 | if gsa == True: 315 | # All domains are accounted for. 316 | print("\n[i] All {} missing domain(s) to be added by script have been discovered in Gravity." .format(newWhiteListlen)) 317 | 318 | else: 319 | print ("\n[i] All {} new domain(s) have not been added to Gravity." .format(INnewNOTgravityListCount+1)) 320 | 321 | else: # We should be done now 322 | # Do Nothing and exit. All domains are accounted for. 323 | print("[i] All {} domains to be added by script have been discovered in Gravity" .format(newWhiteListlen)) 324 | # Find total whitelisted domains (regex) 325 | total_domains_R = cursor.execute(" SELECT * FROM domainlist WHERE type = 2 ") 326 | tdr = len(total_domains_R.fetchall()) 327 | # Find total whitelisted domains (exact) 328 | total_domains_E = cursor.execute(" SELECT * FROM domainlist WHERE type = 0 ") 329 | tde = len(total_domains_E.fetchall()) 330 | total_domains = tdr + tde 331 | print("[i] There are a total of {} domains in your whitelist (regex({}) & exact({}))" .format(total_domains, tdr, tde)) 332 | sqliteConnection.close() 333 | print('[i] The database connection is closed') 334 | if ilng == True: 335 | print('[i] Restarting Pi-hole. This could take a few seconds') 336 | restart_pihole(args.docker) 337 | 338 | except sqlite3.Error as error: 339 | print('[X] Failed to insert domains into Gravity database', error) 340 | print('\n') 341 | print('\n') 342 | exit(1) 343 | 344 | finally: 345 | print('\n') 346 | print('Done. Happy ad-blocking') 347 | print('\n') 348 | 349 | else: 350 | 351 | if os.path.isfile(gravity_whitelist_location) and os.path.getsize(gravity_whitelist_location) > 0: 352 | print('[i] Collecting existing entries from whitelist.txt') 353 | with open(gravity_whitelist_location, 'r') as fRead: 354 | whitelist_local.update(x for x in map( 355 | str.strip, fRead) if x and x[:1] != '#') 356 | 357 | if whitelist_local: 358 | print("[i] {} existing whitelists identified".format( 359 | len(whitelist_local))) 360 | if os.path.isfile(smashy_whitelist_location) and os.path.getsize(smashy_whitelist_location) > 0: 361 | print('[i] Existing anudeep-whitelist install identified') 362 | with open(smashy_whitelist_location, 'r') as fOpen: 363 | whitelist_old_anudeep.update(x for x in map( 364 | str.strip, fOpen) if x and x[:1] != '#') 365 | 366 | if whitelist_old_anudeep: 367 | print('[i] Removing previously installed whitelist') 368 | whitelist_local.difference_update(whitelist_old_anudeep) 369 | 370 | print("[i] Syncing with {}" .format(whitelist_remote_url)) 371 | whitelist_local.update(whitelist_remote) 372 | 373 | print("[i] Outputting {} domains to {}" .format( 374 | len(whitelist_local), gravity_whitelist_location)) 375 | with open(gravity_whitelist_location, 'w') as fWrite: 376 | for line in sorted(whitelist_local): 377 | fWrite.write("{}\n".format(line)) 378 | 379 | with open(smashy_whitelist_location, 'w') as fWrite: 380 | for line in sorted(whitelist_remote): 381 | fWrite.write("{}\n".format(line)) 382 | 383 | print('[i] Done - Domains are now added to your Pi-Hole whitelist\n') 384 | print('[i] Restarting Pi-hole. This could take a few seconds') 385 | restart_pihole(args.docker) 386 | print('[i] Done. Happy ad-blocking') 387 | --------------------------------------------------------------------------------