wren-linux / bin / ublock-allow
#!/bin/bash
# ublock-allow: sites that get uBlock's standard mode while the rest stay in medium.
# Written by Wren Linux parts/firefox-hardening.sh.
#
# uBlock Origin runs in one of two modes for every Firefox profile, chosen
# when the sandboxed Firefox is installed (extras/firefox-sandbox.sh):
# standard its filter lists block ads, trackers and known malware sites;
# the rest of a site runs, so sites work
# medium besides, scripts and frames from other sites (ad networks,
# trackers, most malicious ads) are blocked everywhere; when a
# site breaks because of it, give that site standard mode here.
# That isn't "everything allowed": the filter lists still block
# ads, trackers and known malware sites on it.
# uBlock reads these rules at every start, so clicks in its popup only last
# until Firefox restarts; this list stays.
#
# ublock-allow example.com standard mode for example.com (and its subdomains)
# ublock-allow --remove example.com back to medium mode for it
# ublock-allow --list the mode, and the sites in standard mode
# ublock-allow --mode standard|medium
# ublock-allow --refresh write it again (as it is)
# Restart Firefox afterwards.
set -euo pipefail
ETC=${WREN_FIREFOX_ETC:-/etc/firefox-esr}
LIST="$ETC/wren-ublock-allow"
# standard or medium; no file: medium, as Wren Linux was before the choice
MODE_FILE="$ETC/wren-ublock-mode"
# The "Learn (Wren Linux)" bookmarks (data/firefox-bookmarks.json), put there by
# parts/firefox-hardening.sh; none if it isn't there
BOOKMARKS="$ETC/wren-bookmarks.json"
# Debian's Firefox only finds policies.json in its install folder (it looks
# in /etc/firefox-esr/policies too, but the Debian build never reads it)
DIST=${WREN_FIREFOX_DIST:-/usr/share/firefox-esr/distribution}
POLICY="$DIST/policies.json"
OURS="$DIST/.wren-written"
OLD="$ETC/policies" # where an earlier Wren Linux put it, which Firefox ignored
if [ "$(id -u)" -eq 0 ] || [ -n "${WREN_FIREFOX_ETC:-}" ]; then SUDO=; else SUDO=sudo; fi
die() { printf 'ublock-allow: %s\n' "$*" >&2; exit 1; }
# A bare host name: no scheme, path, port or capitals
site_of() {
local s=${1,,}
s=${s#*://}
s=${s%%/*}
s=${s%%:*}
s=${s#www.}
[[ $s =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$ ]] ||
die "\"$1\" doesn't look like a site name, e.g. example.com"
echo "$s"
}
sites() { [ -f "$LIST" ] && grep -v '^#' "$LIST" | grep . || true; }
mode() { if [ "$(head -c 16 "$MODE_FILE" 2>/dev/null)" = standard ]; then echo standard; else echo medium; fi; }
# The policy: uBlock's own default rules, block other sites' scripts and
# frames everywhere, then the allowed sites. uBlock wants it as a JSON
# string inside the JSON, hence the double escaping.
write_policy() {
local rules=() s inner sum
rules=('behind-the-scene * * noop' 'behind-the-scene * image noop'
'behind-the-scene * 3p noop' 'behind-the-scene * inline-script noop'
'behind-the-scene * 1p-script noop' 'behind-the-scene * 3p-script noop'
'behind-the-scene * 3p-frame noop')
# medium mode: other sites' scripts and frames blocked everywhere
[ "$(mode)" != medium ] || rules+=('* * 3p-script block' '* * 3p-frame block')
while read -r s; do
[ -n "$s" ] && rules+=("$s * 3p-script noop" "$s * 3p-frame noop")
done < <(sites)
inner=$(printf '%s\\n' "${rules[@]}")
inner="{\"userSettings\":{\"advancedUserEnabled\":true},\"dynamicFilteringString\":\"${inner%\\n}\"}"
inner=$(sed 's/\\/\\\\/g; s/"/\\"/g' <<<"$inner")
$SUDO mkdir -p "$DIST"
# Firefox reads one policies.json, so Wren Linux's other Firefox policies live
# here too (parts/firefox-hardening.sh has no separate file to put them in):
# SearchEngines.Default - DuckDuckGo is the default search engine (Firefox
# locks the default engine to enterprise policy).
# SearchEngines.DefaultPrivate - the same for private windows, which have
# their own default (incognito is one all the time).
# ExtensionSettings - uBlock runs in private windows (off by default
# for every add-on; incognito is private all the time).
# Override*Page - no Mozilla first-run or what's-new tabs; Firefox
# opens straight to the home page.
# OfferToSaveLogins - Firefox never asks to save a password: KeePassXC
# keeps them, encrypted with your own passphrase and
# apart from the browser pages run in. Logins saved
# before stay in Firefox's list, to move over.
# ManagedBookmarks - a read-only folder of learning links (Linux, Bash,
# sed and awk, security, programming, the web),
# kept apart from your own bookmarks.
# (Firefox's own DNS isn't set here: each Firefox Wren Linux starts sets
# it at its start, firefox-session and incognito-session, so the Wi-Fi
# sign-in window can differ from the rest. A policy would lock it for all.)
local bookmarks='[]'
[ ! -s "$BOOKMARKS" ] || bookmarks=$(tr -d '\n' <"$BOOKMARKS")
printf '{\n "policies": {\n "SearchEngines": { "Default": "DuckDuckGo", "DefaultPrivate": "DuckDuckGo" },\n "OverrideFirstRunPage": "",\n "OverridePostUpdatePage": "",\n "OfferToSaveLogins": false,\n "ManagedBookmarks": %s,\n "ExtensionSettings": {\n "uBlock0@raymondhill.net": { "private_browsing": true }\n },\n "3rdparty": {\n "Extensions": {\n "uBlock0@raymondhill.net": {\n "adminSettings": "%s"\n }\n }\n }\n }\n}\n' "$bookmarks" "$inner" |
$SUDO tee "$POLICY" >/dev/null
sum=$(sha256sum "$POLICY")
echo "$sum" | $SUDO tee "$OURS" >/dev/null
# The earlier, unread copy, if it is still ours
if [ -f "$OLD/.wren-written" ] && sha256sum --status -c "$OLD/.wren-written" 2>/dev/null; then
$SUDO rm -f "$OLD/policies.json" "$OLD/.wren-written"
$SUDO rmdir "$OLD" 2>/dev/null || true
fi
}
# Someone else's policies.json is left alone, before anything changes
policy_is_ours() {
if [ -f "$POLICY" ] && ! { [ -f "$OURS" ] && sha256sum --status -c "$OURS" 2>/dev/null; }; then
die "$POLICY exists and wasn't written by Wren Linux. Add uBlock's settings to it by hand."
fi
}
case "${1:-}" in
--remove | --refresh | --mode) policy_is_ours ;;
--list | "" | -*) ;;
*) policy_is_ours ;;
esac
case "${1:-}" in
--list | "")
echo "uBlock Origin: $(mode) mode"
if [ "$(mode)" = medium ]; then
if [ -n "$(sites)" ]; then
echo "In standard mode (its filter lists still block ads, trackers and malware sites there):"
sites | sed 's/^/ /'
else
echo "(no site in standard mode yet: ublock-allow SITE, for one that breaks)"
fi
elif [ -n "$(sites)" ]; then
echo "Kept for medium mode (they would be in standard mode there):"
sites | sed 's/^/ /'
fi
[ -n "${1:-}" ] || echo "Usage: ublock-allow SITE | --remove SITE | --list | --mode standard|medium"
;;
--mode)
case ${2:-} in standard | medium) ;; *) die "Usage: ublock-allow --mode standard|medium" ;; esac
echo "$2" | $SUDO tee "$MODE_FILE" >/dev/null
write_policy
echo "uBlock Origin: $2 mode. Restart Firefox for it to take effect."
;;
--remove)
[ $# -eq 2 ] || die "Usage: ublock-allow --remove SITE"
site=$(site_of "$2")
{ echo "# Written by ublock-allow; one site per line"; sites | grep -vxF "$site" || true; } |
$SUDO tee "$LIST.new" >/dev/null
$SUDO mv "$LIST.new" "$LIST"
write_policy
echo "$site is back in medium mode. Restart Firefox for it to take effect."
;;
--refresh)
write_policy
;;
-*)
die "Usage: ublock-allow SITE | --remove SITE | --list | --mode standard|medium"
;;
*)
site=$(site_of "$1")
{ echo "# Written by ublock-allow; one site per line"; sites | grep -vxF "$site" || true; echo "$site"; } |
$SUDO tee "$LIST.new" >/dev/null
$SUDO mv "$LIST.new" "$LIST"
write_policy
echo "$site now gets uBlock's standard mode: its filter lists still block ads, trackers and"
echo "malware sites there; only other sites' scripts and frames are no longer blocked on it."
echo "Restart Firefox for it to take effect."
[ "$(mode)" = medium ] || echo "(uBlock is in standard mode everywhere already; this counts if you go back to medium.)"
;;
esac