307 lines
8.1 KiB
Bash
Executable File
307 lines
8.1 KiB
Bash
Executable File
#!/usr/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
PATH=/usr/sbin:/usr/bin
|
|
readonly data_dir=/usr/share/plasma-fingerprint-workaround
|
|
readonly payload_config="$data_dir/payload.conf"
|
|
readonly program=${0##*/}
|
|
temporary_dir=
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage:
|
|
$program status
|
|
$program enable [--rpm PATH] [--force] [--yes]
|
|
$program disable [--yes]
|
|
|
|
Without --rpm, enable downloads the tested prebuilt package. A local package
|
|
must have been built from this tool's KScreenLocker spec. --force permits a
|
|
base-version mismatch, including an intentional downgrade.
|
|
EOF
|
|
}
|
|
|
|
die() {
|
|
printf 'Error: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
require_root() {
|
|
(( EUID == 0 )) || die "run this command as root"
|
|
}
|
|
|
|
require_fedora() {
|
|
# shellcheck source=/dev/null
|
|
source /etc/os-release
|
|
[[ ${ID:-} == fedora ]] || die "this workaround only supports Fedora Linux"
|
|
}
|
|
|
|
cleanup() {
|
|
[[ -n $temporary_dir ]] || return
|
|
rm -f -- "$temporary_dir/kscreenlocker.rpm"
|
|
rmdir -- "$temporary_dir"
|
|
}
|
|
|
|
package_value() {
|
|
local mode=$1
|
|
local format=$2
|
|
local package=$3
|
|
|
|
rpm "$mode" --qf "$format" -- "$package"
|
|
}
|
|
|
|
package_base() {
|
|
local mode=$1
|
|
local package=$2
|
|
local provides
|
|
|
|
provides=$(rpm "$mode" --provides -- "$package")
|
|
sed -n 's/^plasma-fingerprint-workaround-base = //p' <<<"$provides"
|
|
}
|
|
|
|
is_workaround_package() {
|
|
local mode=$1
|
|
local package=$2
|
|
local provides
|
|
|
|
provides=$(rpm "$mode" --provides -- "$package")
|
|
grep -qx 'plasma-fingerprint-workaround-payload = 1' <<<"$provides"
|
|
}
|
|
|
|
is_legacy_workaround() {
|
|
[[ $(installed_value '%{RELEASE}') =~ \.ajp[[:digit:]]+$ ]]
|
|
}
|
|
|
|
is_any_workaround() {
|
|
is_workaround_package -q kscreenlocker || is_legacy_workaround
|
|
}
|
|
|
|
installed_value() {
|
|
package_value -q "$1" kscreenlocker
|
|
}
|
|
|
|
installed_base() {
|
|
local base
|
|
|
|
base=$(package_base -q kscreenlocker)
|
|
if [[ -n $base ]]; then
|
|
printf '%s\n' "$base"
|
|
elif is_legacy_workaround; then
|
|
printf '%s-%s\n' \
|
|
"$(installed_value '%{VERSION}')" \
|
|
"$(installed_value '%{RELEASE}' | sed 's/\.ajp[[:digit:]]*$//')"
|
|
else
|
|
installed_value '%{EVR}'
|
|
fi
|
|
}
|
|
|
|
show_status() {
|
|
if ! rpm -q kscreenlocker >/dev/null 2>&1; then
|
|
printf 'KScreenLocker is not installed.\n'
|
|
return 1
|
|
fi
|
|
|
|
if is_workaround_package -q kscreenlocker; then
|
|
printf 'Fingerprint workaround: enabled\n'
|
|
printf 'Installed package: kscreenlocker-%s.%s\n' \
|
|
"$(installed_value '%{EVR}')" "$(installed_value '%{ARCH}')"
|
|
printf 'Based on Fedora package: kscreenlocker-%s\n' "$(installed_base)"
|
|
elif is_legacy_workaround; then
|
|
printf 'Fingerprint workaround: enabled (legacy unmanaged build)\n'
|
|
printf 'Installed package: kscreenlocker-%s.%s\n' \
|
|
"$(installed_value '%{EVR}')" "$(installed_value '%{ARCH}')"
|
|
printf 'Based on Fedora package: kscreenlocker-%s\n' "$(installed_base)"
|
|
else
|
|
printf 'Fingerprint workaround: disabled\n'
|
|
printf 'Installed package: kscreenlocker-%s.%s\n' \
|
|
"$(installed_value '%{EVR}')" "$(installed_value '%{ARCH}')"
|
|
fi
|
|
}
|
|
|
|
confirm() {
|
|
local answer
|
|
|
|
if (( assume_yes )); then
|
|
return
|
|
fi
|
|
|
|
printf 'Continue? [y/N] '
|
|
read -r answer || exit 0
|
|
[[ $answer == y || $answer == Y ]] || exit 0
|
|
}
|
|
|
|
load_payload_config() {
|
|
[[ -r $payload_config ]] || die "payload configuration is missing"
|
|
|
|
unset payload_url payload_sha256
|
|
# This root-owned file is installed by the controller RPM.
|
|
# shellcheck source=/dev/null
|
|
source "$payload_config"
|
|
|
|
[[ ${payload_url:-} == https://* ]] || die "invalid payload URL"
|
|
[[ ${payload_sha256:-} =~ ^[[:xdigit:]]{64}$ ]] ||
|
|
die "invalid payload checksum"
|
|
}
|
|
|
|
validate_candidate() {
|
|
local candidate=$1
|
|
local name arch candidate_evr candidate_base current_base current_evr
|
|
|
|
rpm -K --nosignature "$candidate" >/dev/null || die "the RPM is corrupt"
|
|
|
|
name=$(package_value -qp '%{NAME}' "$candidate")
|
|
[[ $name == kscreenlocker ]] ||
|
|
die "expected a kscreenlocker RPM, got $name"
|
|
|
|
is_workaround_package -qp "$candidate" ||
|
|
die "the RPM was not built from this workaround's KScreenLocker spec"
|
|
|
|
arch=$(package_value -qp '%{ARCH}' "$candidate")
|
|
[[ $arch == "$(installed_value '%{ARCH}')" ]] ||
|
|
die "RPM architecture $arch does not match installed architecture $(installed_value '%{ARCH}')"
|
|
|
|
candidate_base=$(package_base -qp "$candidate")
|
|
[[ -n $candidate_base ]] || die "the RPM has no base-version marker"
|
|
|
|
candidate_evr=$(package_value -qp '%{EVR}' "$candidate")
|
|
current_base=$(installed_base)
|
|
current_evr=$(installed_value '%{EVR}')
|
|
|
|
printf 'Installed: kscreenlocker-%s\n' "$current_evr"
|
|
printf 'Candidate: kscreenlocker-%s (based on %s)\n' \
|
|
"$candidate_evr" "$candidate_base"
|
|
|
|
if [[ $candidate_base != "$current_base" ]]; then
|
|
if (( ! force )); then
|
|
die "candidate base $candidate_base does not match installed base $current_base; use --force to allow the version change"
|
|
fi
|
|
printf 'Warning: forcing a KScreenLocker base-version change.\n' >&2
|
|
fi
|
|
}
|
|
|
|
enable_workaround() {
|
|
local rpm_path='' candidate
|
|
local -a dnf_args
|
|
|
|
force=0
|
|
assume_yes=0
|
|
while (( $# )); do
|
|
case $1 in
|
|
--rpm)
|
|
(( $# >= 2 )) || die "--rpm requires a path"
|
|
rpm_path=$2
|
|
shift 2
|
|
;;
|
|
--force)
|
|
force=1
|
|
shift
|
|
;;
|
|
--yes)
|
|
assume_yes=1
|
|
shift
|
|
;;
|
|
*)
|
|
die "unknown enable option: $1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
require_root
|
|
require_fedora
|
|
rpm -q kscreenlocker >/dev/null 2>&1 || die "KScreenLocker is not installed"
|
|
|
|
umask 077
|
|
temporary_dir=$(mktemp -d /var/tmp/plasma-fingerprint-workaround.XXXXXX)
|
|
trap cleanup EXIT
|
|
candidate="$temporary_dir/kscreenlocker.rpm"
|
|
|
|
if [[ -n $rpm_path ]]; then
|
|
[[ -f $rpm_path ]] || die "RPM not found: $rpm_path"
|
|
install -m 0600 -- "$rpm_path" "$candidate"
|
|
else
|
|
load_payload_config
|
|
printf 'Downloading the tested KScreenLocker build...\n'
|
|
curl --fail --location --show-error --silent \
|
|
--proto '=https' --proto-redir '=https' \
|
|
--output "$candidate" "$payload_url"
|
|
printf '%s %s\n' "$payload_sha256" "$candidate" |
|
|
sha256sum --check --status - || die "downloaded RPM checksum does not match"
|
|
fi
|
|
|
|
validate_candidate "$candidate"
|
|
printf '\nThis replaces Fedora\x27s security-sensitive KScreenLocker package.\n'
|
|
confirm
|
|
|
|
dnf_args=(install --assumeyes)
|
|
(( force )) && dnf_args+=(--allow-downgrade)
|
|
dnf5 "${dnf_args[@]}" "$candidate"
|
|
|
|
is_workaround_package -q kscreenlocker ||
|
|
die "DNF completed without enabling the workaround"
|
|
printf 'Fingerprint workaround enabled. Reboot before testing it.\n'
|
|
}
|
|
|
|
disable_workaround() {
|
|
assume_yes=0
|
|
while (( $# )); do
|
|
case $1 in
|
|
--yes)
|
|
assume_yes=1
|
|
shift
|
|
;;
|
|
*)
|
|
die "unknown disable option: $1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
require_root
|
|
require_fedora
|
|
if ! is_any_workaround; then
|
|
printf 'Fingerprint workaround is already disabled.\n'
|
|
return
|
|
fi
|
|
|
|
printf 'This will replace the workaround with Fedora\x27s current KScreenLocker package.\n'
|
|
confirm
|
|
|
|
dnf5 \
|
|
--repo=fedora,updates \
|
|
--refresh \
|
|
distro-sync \
|
|
--assumeyes \
|
|
kscreenlocker
|
|
|
|
if is_any_workaround; then
|
|
die "no suitable Fedora KScreenLocker package was installed"
|
|
fi
|
|
printf 'Fingerprint workaround disabled. Reboot before testing the lock screen.\n'
|
|
}
|
|
|
|
command=${1:-}
|
|
[[ -n $command ]] || {
|
|
usage
|
|
exit 2
|
|
}
|
|
shift
|
|
|
|
case $command in
|
|
status)
|
|
(( $# == 0 )) || die "status takes no options"
|
|
show_status
|
|
;;
|
|
enable)
|
|
enable_workaround "$@"
|
|
;;
|
|
disable)
|
|
disable_workaround "$@"
|
|
;;
|
|
-h|--help|help)
|
|
usage
|
|
;;
|
|
*)
|
|
usage >&2
|
|
exit 2
|
|
;;
|
|
esac
|