Wren Linux

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