2039 lines
92 KiB
Bash
2039 lines
92 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
# Source-only helpers for the non-secret local recovery hardware contract.
|
|
# The contract is parsed as data; it must never be sourced as shell code.
|
|
|
|
if [[ "${K3SLR_KEEPASS_ENTRY+x}" == x ]]; then
|
|
[[ "$K3SLR_KEEPASS_ENTRY" == 'K3s Recovery LUKS' ]] || return 1
|
|
readonly K3SLR_KEEPASS_ENTRY
|
|
else
|
|
readonly K3SLR_KEEPASS_ENTRY='K3s Recovery LUKS'
|
|
fi
|
|
if [[ "${K3SLR_KEEPASS_HEADER_ATTACHMENT+x}" == x ]]; then
|
|
[[ "$K3SLR_KEEPASS_HEADER_ATTACHMENT" == 'luks-header-backup.bin' ]] || return 1
|
|
readonly K3SLR_KEEPASS_HEADER_ATTACHMENT
|
|
else
|
|
readonly K3SLR_KEEPASS_HEADER_ATTACHMENT='luks-header-backup.bin'
|
|
fi
|
|
if [[ "${_K3SLR_INT64_MAX+x}" == x ]]; then
|
|
[[ "$_K3SLR_INT64_MAX" == 9223372036854775807 ]] || return 1
|
|
readonly _K3SLR_INT64_MAX
|
|
else
|
|
readonly _K3SLR_INT64_MAX=9223372036854775807
|
|
fi
|
|
if [[ "${_K3SLR_REQUIRED_MINIMUM_FREE_BYTES+x}" == x ]]; then
|
|
[[ "$_K3SLR_REQUIRED_MINIMUM_FREE_BYTES" == 10737418240 ]] || return 1
|
|
readonly _K3SLR_REQUIRED_MINIMUM_FREE_BYTES
|
|
else
|
|
readonly _K3SLR_REQUIRED_MINIMUM_FREE_BYTES=10737418240
|
|
fi
|
|
if [[ "${_K3SLR_KEEPASSXC_VERSION+x}" == x ]]; then
|
|
[[ "$_K3SLR_KEEPASSXC_VERSION" == '2.7.6+dfsg.1-1build3' ]] || return 1
|
|
readonly _K3SLR_KEEPASSXC_VERSION
|
|
else
|
|
readonly _K3SLR_KEEPASSXC_VERSION='2.7.6+dfsg.1-1build3'
|
|
fi
|
|
if [[ "${_K3SLR_CRYPTSETUP_VERSION+x}" == x ]]; then
|
|
[[ "$_K3SLR_CRYPTSETUP_VERSION" == '2:2.7.0-1ubuntu4.2' ]] || return 1
|
|
readonly _K3SLR_CRYPTSETUP_VERSION
|
|
else
|
|
readonly _K3SLR_CRYPTSETUP_VERSION='2:2.7.0-1ubuntu4.2'
|
|
fi
|
|
|
|
_k3slr_uint() {
|
|
local value="${1-}"
|
|
[[ "$value" =~ ^(0|[1-9][0-9]*)$ ]] || return 1
|
|
(( ${#value} < 19 )) && return 0
|
|
(( ${#value} == 19 )) &&
|
|
[[ "$value" < '9223372036854775807' || "$value" == '9223372036854775807' ]] && return 0
|
|
return 1
|
|
}
|
|
|
|
_k3slr_contract_value_is_valid() {
|
|
local key="$1" value="$2"
|
|
|
|
case "$key" in
|
|
K3SLR_SCHEMA_VERSION)
|
|
[[ "$value" == 1 ]]
|
|
;;
|
|
K3SLR_RECOVERY_DISK_BY_ID|K3SLR_K3S_DISK_BY_ID)
|
|
[[ "$value" =~ ^/dev/disk/by-id/wwn-0x[0-9a-f]{16}$ ]]
|
|
;;
|
|
K3SLR_RECOVERY_PARTITION_BY_ID|K3SLR_K3S_PARTITION_BY_ID)
|
|
[[ "$value" =~ ^/dev/disk/by-id/wwn-0x[0-9a-f]{16}-part[1-9][0-9]*$ ]]
|
|
;;
|
|
K3SLR_RECOVERY_FS_UUID)
|
|
[[ "$value" =~ ^[0-9A-F]{16}$ ]]
|
|
;;
|
|
K3SLR_K3S_FS_UUID|K3SLR_RECOVERY_PARTUUID|K3SLR_K3S_PARTUUID)
|
|
[[ "$value" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]]
|
|
;;
|
|
K3SLR_RECOVERY_MODEL|K3SLR_K3S_MODEL)
|
|
[[ "$value" =~ ^[A-Za-z0-9._-]+$ ]]
|
|
;;
|
|
K3SLR_RECOVERY_SERIAL|K3SLR_K3S_SERIAL)
|
|
[[ "$value" =~ ^[A-Za-z0-9]+$ ]]
|
|
;;
|
|
K3SLR_RECOVERY_WWN|K3SLR_K3S_WWN)
|
|
[[ "$value" =~ ^0x[0-9a-f]{16}$ ]]
|
|
;;
|
|
K3SLR_OWNER_UID|K3SLR_OWNER_GID)
|
|
_k3slr_uint "$value"
|
|
;;
|
|
K3SLR_OUTER_MOUNT|K3SLR_INNER_MOUNT)
|
|
[[ "$value" =~ ^/[A-Za-z0-9._/-]+$ && "$value" != *'//'* && "$value" != *'/./'* && "$value" != *'/../'* && "$value" != */. && "$value" != */.. && "$value" != */ ]]
|
|
;;
|
|
K3SLR_ROOT_RELATIVE|K3SLR_DATABASE_RELATIVE|K3SLR_CONTAINER_RELATIVE|K3SLR_RUNTIME_METADATA_RELATIVE)
|
|
[[ "$value" =~ ^[A-Za-z0-9][A-Za-z0-9._/-]*$ && "$value" != *'//'* && "$value" != *'/./'* && "$value" != *'/../'* && "$value" != */. && "$value" != */.. && "$value" != */ ]]
|
|
;;
|
|
K3SLR_MAPPING_NAME|K3SLR_PROOF_MAPPING_NAME)
|
|
[[ "$value" =~ ^[a-z][a-z0-9-]*$ ]]
|
|
;;
|
|
K3SLR_INNER_LABEL)
|
|
[[ "$value" =~ ^[A-Z][A-Z0-9_]*$ ]]
|
|
;;
|
|
K3SLR_CONTAINER_SIZE_BYTES|K3SLR_MINIMUM_FREE_BYTES)
|
|
_k3slr_uint "$value" && [[ "$value" != 0 ]]
|
|
;;
|
|
K3SLR_OUTER_MIN_REMAINING_PERCENT)
|
|
_k3slr_uint "$value" && [[ "$value" != 0 ]] && (( 10#$value <= 99 ))
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_k3slr_parse_contract() {
|
|
local file="$1" result_name="$2" line key value
|
|
local -n result="$result_name"
|
|
local -A allowed=(
|
|
[K3SLR_SCHEMA_VERSION]=1
|
|
[K3SLR_RECOVERY_DISK_BY_ID]=1 [K3SLR_RECOVERY_PARTITION_BY_ID]=1
|
|
[K3SLR_RECOVERY_FS_UUID]=1 [K3SLR_RECOVERY_PARTUUID]=1
|
|
[K3SLR_RECOVERY_MODEL]=1 [K3SLR_RECOVERY_SERIAL]=1 [K3SLR_RECOVERY_WWN]=1
|
|
[K3SLR_K3S_DISK_BY_ID]=1 [K3SLR_K3S_PARTITION_BY_ID]=1
|
|
[K3SLR_K3S_FS_UUID]=1 [K3SLR_K3S_PARTUUID]=1
|
|
[K3SLR_K3S_MODEL]=1 [K3SLR_K3S_SERIAL]=1 [K3SLR_K3S_WWN]=1
|
|
[K3SLR_OWNER_UID]=1 [K3SLR_OWNER_GID]=1
|
|
[K3SLR_OUTER_MOUNT]=1 [K3SLR_INNER_MOUNT]=1
|
|
[K3SLR_ROOT_RELATIVE]=1 [K3SLR_DATABASE_RELATIVE]=1
|
|
[K3SLR_CONTAINER_RELATIVE]=1 [K3SLR_RUNTIME_METADATA_RELATIVE]=1
|
|
[K3SLR_MAPPING_NAME]=1 [K3SLR_PROOF_MAPPING_NAME]=1 [K3SLR_INNER_LABEL]=1
|
|
[K3SLR_CONTAINER_SIZE_BYTES]=1 [K3SLR_MINIMUM_FREE_BYTES]=1
|
|
[K3SLR_OUTER_MIN_REMAINING_PERCENT]=1
|
|
)
|
|
local -A seen=()
|
|
|
|
[[ -f "$file" && ! -L "$file" && -r "$file" ]] || return 1
|
|
result=()
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
[[ "$line" != *$'\r'* && "$line" =~ ^([A-Z][A-Z0-9_]*)=(.*)$ ]] || return 1
|
|
key="${BASH_REMATCH[1]}"
|
|
value="${BASH_REMATCH[2]}"
|
|
[[ "${allowed[$key]+present}" == present && "${seen[$key]+present}" != present ]] || return 1
|
|
_k3slr_contract_value_is_valid "$key" "$value" || return 1
|
|
seen["$key"]=1
|
|
result["$key"]="$value"
|
|
done <"$file"
|
|
|
|
for key in "${!allowed[@]}"; do
|
|
[[ "${seen[$key]+present}" == present ]] || return 1
|
|
done
|
|
}
|
|
|
|
_k3slr_config_value() {
|
|
local file="$1" key="$2"
|
|
local -A values=()
|
|
|
|
_k3slr_parse_contract "$file" values || return 1
|
|
[[ "${values[$key]+present}" == present ]] || return 1
|
|
printf '%s\n' "${values[$key]}"
|
|
}
|
|
|
|
_k3slr_load_contract() {
|
|
local file="$1" key
|
|
local -A values=()
|
|
|
|
_k3slr_parse_contract "$file" values || return 1
|
|
for key in "${!values[@]}"; do
|
|
printf -v "$key" '%s' "${values[$key]}"
|
|
done
|
|
}
|
|
|
|
_k3slr_required_bytes() {
|
|
local phase_bytes="$1" used_bytes="$2" padded maximum_used
|
|
|
|
_k3slr_uint "$phase_bytes" || return 1
|
|
_k3slr_uint "$used_bytes" || return 1
|
|
(( phase_bytes <= 1844674407370955160 )) || return 1
|
|
padded=$(((phase_bytes * 5 + 3) / 4))
|
|
(( padded <= (_K3SLR_INT64_MAX - _K3SLR_REQUIRED_MINIMUM_FREE_BYTES) / 2 )) || return 1
|
|
maximum_used=$((_K3SLR_INT64_MAX - 2 * padded - _K3SLR_REQUIRED_MINIMUM_FREE_BYTES))
|
|
(( used_bytes <= maximum_used )) || return 1
|
|
printf '%s\n' "$((used_bytes + 2 * padded + _K3SLR_REQUIRED_MINIMUM_FREE_BYTES))"
|
|
}
|
|
|
|
_k3slr_inner_capacity_fits() {
|
|
local total_bytes="$1" used_bytes="$2" phase_bytes="$3" required
|
|
|
|
_k3slr_uint "$total_bytes" || return 1
|
|
required="$(_k3slr_required_bytes "$phase_bytes" "$used_bytes")" || return 1
|
|
(( total_bytes >= required ))
|
|
}
|
|
|
|
_k3slr_outer_capacity_fits() {
|
|
local total_bytes="$1" free_bytes="$2" container_bytes="$3"
|
|
local minimum_free_bytes="${K3SLR_MINIMUM_FREE_BYTES:-}"
|
|
local remaining_percent="${K3SLR_OUTER_MIN_REMAINING_PERCENT:-}"
|
|
local percent_reserve required
|
|
|
|
_k3slr_uint "$total_bytes" || return 1
|
|
_k3slr_uint "$free_bytes" || return 1
|
|
_k3slr_uint "$container_bytes" || return 1
|
|
_k3slr_uint "$minimum_free_bytes" || return 1
|
|
_k3slr_uint "$remaining_percent" || return 1
|
|
[[ "$container_bytes" != 0 && "$minimum_free_bytes" != 0 && "$remaining_percent" != 0 ]] || return 1
|
|
(( remaining_percent <= 99 && free_bytes <= total_bytes )) || return 1
|
|
|
|
(( total_bytes <= (_K3SLR_INT64_MAX - 99) / remaining_percent )) || return 1
|
|
percent_reserve=$(((total_bytes * remaining_percent + 99) / 100))
|
|
(( container_bytes <= (_K3SLR_INT64_MAX - minimum_free_bytes) / 2 )) || return 1
|
|
required=$((minimum_free_bytes + 2 * container_bytes))
|
|
(( required <= _K3SLR_INT64_MAX - percent_reserve )) || return 1
|
|
required=$((required + percent_reserve))
|
|
(( free_bytes >= required ))
|
|
}
|
|
|
|
_k3slr_xtrace_is_disabled() {
|
|
[[ "$-" != *x* ]]
|
|
}
|
|
|
|
_k3slr_parse_lifecycle_cli() {
|
|
local destination_name="${1-}" mode
|
|
shift || return 1
|
|
_k3slr_output_name_is_safe "$destination_name" _k3slr_cli_ || return 1
|
|
case "$#" in
|
|
0) mode=dry-run ;;
|
|
1)
|
|
[[ "$1" == --execute ]] || return 1
|
|
mode=execute
|
|
;;
|
|
*) return 1 ;;
|
|
esac
|
|
printf -v "$destination_name" '%s' "$mode"
|
|
}
|
|
|
|
_k3slr_tty_capability() {
|
|
local tty_fd
|
|
(( $# == 0 )) || return 1
|
|
[[ -t 0 ]] || return 1
|
|
exec {tty_fd}<>/dev/tty || return 1
|
|
if [[ ! -t "$tty_fd" ]]; then
|
|
exec {tty_fd}>&-
|
|
return 1
|
|
fi
|
|
exec {tty_fd}>&-
|
|
}
|
|
|
|
_k3slr_require_execute_tty() {
|
|
(( $# == 0 )) || return 1
|
|
_k3slr_tty_capability
|
|
}
|
|
|
|
_k3slr_open_close_capability_gate() {
|
|
local keepass_state='' cryptsetup_state='' binary
|
|
local -a required_binaries=(
|
|
/usr/bin/keepassxc-cli
|
|
/usr/sbin/cryptsetup
|
|
/usr/sbin/losetup
|
|
/usr/sbin/blkid
|
|
/usr/sbin/smartctl
|
|
/usr/bin/mount
|
|
/usr/bin/umount
|
|
/usr/bin/findmnt
|
|
/usr/bin/lsblk
|
|
/usr/bin/readlink
|
|
/usr/bin/stat
|
|
/usr/bin/ntfsinfo
|
|
/usr/bin/ntfs-3g.probe
|
|
/usr/bin/fuser
|
|
/usr/bin/sync
|
|
/usr/bin/cmp
|
|
/usr/bin/cat
|
|
/usr/bin/sha256sum
|
|
/usr/bin/mawk
|
|
/usr/bin/od
|
|
/usr/bin/bash
|
|
/usr/bin/du
|
|
/usr/bin/test
|
|
/usr/bin/dd
|
|
/usr/bin/id
|
|
/usr/bin/ps
|
|
/usr/bin/sleep
|
|
/usr/bin/mkdir
|
|
/usr/bin/dpkg-query
|
|
/usr/bin/mktemp
|
|
/usr/bin/rm
|
|
/usr/bin/rmdir
|
|
/usr/bin/sudo
|
|
)
|
|
local keepass_rc=0 cryptsetup_rc=0
|
|
|
|
_k3slr_query_package_state keepass_state keepassxc "$_K3SLR_KEEPASSXC_VERSION" || keepass_rc=$?
|
|
_k3slr_query_package_state cryptsetup_state cryptsetup-bin "$_K3SLR_CRYPTSETUP_VERSION" || cryptsetup_rc=$?
|
|
(( keepass_rc == 0 && cryptsetup_rc == 0 )) || return 1
|
|
[[ "$keepass_state" == installed && "$cryptsetup_state" == installed ]] || return 1
|
|
for binary in "${required_binaries[@]}"; do
|
|
_k3slr_trusted_root_executable "$binary" || return 1
|
|
done
|
|
}
|
|
|
|
_k3slr_lifecycle_step() {
|
|
(( $# == 1 ))
|
|
}
|
|
|
|
_k3slr_lifecycle_action() {
|
|
local lifecycle="${1-}" action="${2-}"
|
|
case "$lifecycle" in
|
|
prepare)
|
|
(( $# == 3 )) || return 1
|
|
;;
|
|
open|close)
|
|
(( $# == 2 )) || return 1
|
|
;;
|
|
*) return 1 ;;
|
|
esac
|
|
case "$lifecycle/$action" in
|
|
prepare/sudo)
|
|
[[ "${3-}" == _k3slr_prepare_invocation ]] || return 1
|
|
_k3slr_command /usr/bin/sudo --validate
|
|
;;
|
|
prepare/*|open/*|close/*)
|
|
return 1
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
_k3slr_lifecycle_dispatch() {
|
|
local lifecycle="${1-}" header_provider="${2-}" step
|
|
local -a steps=() action_arguments=()
|
|
(( $# == 2 )) || return 1
|
|
case "$lifecycle" in
|
|
prepare)
|
|
local -A _k3slr_prepare_invocation=()
|
|
steps=(
|
|
sudo
|
|
context
|
|
device-validator
|
|
estimate-phase
|
|
capacity-preflight
|
|
confirm-prepare
|
|
install-packages
|
|
feasibility
|
|
revalidate-device
|
|
mount-outer
|
|
revalidate-capacity
|
|
create-layout
|
|
create-kdbx
|
|
create-container
|
|
validate-allocation
|
|
create-loop
|
|
luks-format
|
|
luks-open
|
|
mkfs-ext4
|
|
mount-inner
|
|
write-marker-metadata
|
|
header-proof
|
|
close-inner
|
|
close-mapping
|
|
detach-loop
|
|
unmount-outer
|
|
closed-validator
|
|
)
|
|
;;
|
|
open)
|
|
steps=(
|
|
sudo
|
|
context
|
|
confirm-open
|
|
'device-validator(--expect-device-ready)'
|
|
revalidate-context-device
|
|
mount-outer
|
|
validate-layout-kdbx-container
|
|
validate-allocation-and-ntfs-attributes
|
|
validate-outer-metadata
|
|
collision-check
|
|
create-loop
|
|
luks-open
|
|
mount-inner
|
|
validate-inner-metadata-marker-capacity
|
|
'open-validator(--expect-open)'
|
|
)
|
|
;;
|
|
close)
|
|
steps=(
|
|
sudo
|
|
context
|
|
confirm-close
|
|
'open-validator(--expect-open)'
|
|
validate-metadata-and-full-lineage
|
|
busy-process-query-pass-1
|
|
busy-process-query-pass-2
|
|
sync-inner-filesystem
|
|
unmount-inner
|
|
close-main-mapping
|
|
detach-original-loop
|
|
unmount-outer
|
|
'closed-validator(--expect-closed)'
|
|
)
|
|
;;
|
|
*) return 1 ;;
|
|
esac
|
|
|
|
for step in "${steps[@]}"; do
|
|
_k3slr_lifecycle_step "$step" || return 1
|
|
action_arguments=("$lifecycle" "$step")
|
|
if [[ "$lifecycle" == prepare ]]; then
|
|
action_arguments+=(_k3slr_prepare_invocation)
|
|
fi
|
|
_k3slr_lifecycle_action "${action_arguments[@]}" || return 1
|
|
done
|
|
}
|
|
|
|
_k3slr_lifecycle_main() {
|
|
local lifecycle="${1-}" execution_mode="${2-}"
|
|
(( $# == 2 )) || return 1
|
|
[[ "$lifecycle" == prepare || "$lifecycle" == open || "$lifecycle" == close ]] || return 1
|
|
[[ -n "${K3SLR_WRAPPER_CONTRACT:-}" ]] || return 1
|
|
_k3slr_load_contract "$K3SLR_WRAPPER_CONTRACT" || return 1
|
|
case "$execution_mode" in
|
|
dry-run)
|
|
_k3slr_prepackage_device_preflight || return 1
|
|
printf 'DRY RUN: %s lifecycle; no changes made.\n' "$lifecycle"
|
|
;;
|
|
execute)
|
|
if [[ "$lifecycle" == prepare ]]; then
|
|
declare -F _k3slr_prove_header_restore >/dev/null || return 1
|
|
else
|
|
_k3slr_open_close_capability_gate || return 1
|
|
fi
|
|
_k3slr_require_execute_tty || return 1
|
|
_k3slr_lifecycle_dispatch "$lifecycle" _k3slr_prove_header_restore
|
|
;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
_k3slr_command() {
|
|
"$@"
|
|
}
|
|
|
|
_k3slr_output_name_is_safe() {
|
|
[[ "$1" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ && "$1" != "$2"* ]]
|
|
}
|
|
|
|
_k3slr_capture_directory_is_safe() {
|
|
[[ "${1-}" =~ ^/tmp/k3slr-capture\.[A-Za-z0-9]{8,}$ ]]
|
|
}
|
|
|
|
_k3slr_remove_capture_directory() {
|
|
local _k3slr_process_capture_directory="${1-}" _k3slr_process_capture_rc=0
|
|
_k3slr_capture_directory_is_safe "$_k3slr_process_capture_directory" || return 1
|
|
_k3slr_command /usr/bin/rm -- \
|
|
"${_k3slr_process_capture_directory}/stdout" \
|
|
"${_k3slr_process_capture_directory}/stderr" >/dev/null 2>&1 || _k3slr_process_capture_rc=1
|
|
_k3slr_command /usr/bin/rmdir -- "$_k3slr_process_capture_directory" \
|
|
>/dev/null 2>&1 || _k3slr_process_capture_rc=1
|
|
return "$_k3slr_process_capture_rc"
|
|
}
|
|
|
|
_k3slr_capture_process() {
|
|
local _k3slr_process_capture_stdout_name="${1-}"
|
|
local _k3slr_process_capture_stderr_name="${2-}"
|
|
local _k3slr_process_capture_rc_name="${3-}"
|
|
local _k3slr_process_capture_directory='' _k3slr_process_capture_stdout=''
|
|
local _k3slr_process_capture_stderr='' _k3slr_process_capture_status=0
|
|
shift 3 || return 1
|
|
(( $# > 0 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_process_capture_stdout_name" _k3slr_process_capture_ || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_process_capture_stderr_name" _k3slr_process_capture_ || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_process_capture_rc_name" _k3slr_process_capture_ || return 1
|
|
[[ "$_k3slr_process_capture_stdout_name" != "$_k3slr_process_capture_stderr_name" &&
|
|
"$_k3slr_process_capture_stdout_name" != "$_k3slr_process_capture_rc_name" &&
|
|
"$_k3slr_process_capture_stderr_name" != "$_k3slr_process_capture_rc_name" ]] || return 1
|
|
|
|
_k3slr_capture_one_line _k3slr_process_capture_directory \
|
|
/usr/bin/mktemp --directory --tmpdir=/tmp 'k3slr-capture.XXXXXXXX' || return 1
|
|
_k3slr_capture_directory_is_safe "$_k3slr_process_capture_directory" || return 1
|
|
if _k3slr_command "$@" \
|
|
>"${_k3slr_process_capture_directory}/stdout" \
|
|
2>"${_k3slr_process_capture_directory}/stderr"; then
|
|
_k3slr_process_capture_status=0
|
|
else
|
|
_k3slr_process_capture_status=$?
|
|
fi
|
|
|
|
if IFS= read -r -d '' _k3slr_process_capture_stdout \
|
|
<"${_k3slr_process_capture_directory}/stdout"; then
|
|
_k3slr_remove_capture_directory "$_k3slr_process_capture_directory" || :
|
|
return 1
|
|
fi
|
|
if IFS= read -r -d '' _k3slr_process_capture_stderr \
|
|
<"${_k3slr_process_capture_directory}/stderr"; then
|
|
_k3slr_remove_capture_directory "$_k3slr_process_capture_directory" || :
|
|
return 1
|
|
fi
|
|
_k3slr_remove_capture_directory "$_k3slr_process_capture_directory" || return 1
|
|
printf -v "$_k3slr_process_capture_stdout_name" '%s' "$_k3slr_process_capture_stdout"
|
|
printf -v "$_k3slr_process_capture_stderr_name" '%s' "$_k3slr_process_capture_stderr"
|
|
printf -v "$_k3slr_process_capture_rc_name" '%s' "$_k3slr_process_capture_status"
|
|
}
|
|
|
|
_k3slr_parse_current_context_record() {
|
|
local _k3slr_context_record="${1-}" _k3slr_context_destination="${2-}"
|
|
(( $# == 2 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_context_destination" _k3slr_context_ || return 1
|
|
[[ "$_k3slr_context_record" == $'default\n' ]] || return 1
|
|
printf -v "$_k3slr_context_destination" '%s' default
|
|
}
|
|
|
|
_k3slr_current_context() {
|
|
local _k3slr_context_destination="${1-}" _k3slr_context_stdout=''
|
|
local _k3slr_context_stderr='' _k3slr_context_rc=''
|
|
(( $# == 1 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_context_destination" _k3slr_context_ || return 1
|
|
_k3slr_capture_process _k3slr_context_stdout _k3slr_context_stderr _k3slr_context_rc \
|
|
/usr/local/bin/k3s kubectl config current-context || return 1
|
|
[[ "$_k3slr_context_rc" == 0 && -z "$_k3slr_context_stderr" ]] || return 1
|
|
_k3slr_parse_current_context_record "$_k3slr_context_stdout" "$_k3slr_context_destination"
|
|
}
|
|
|
|
_k3slr_parse_package_query_record() {
|
|
local _k3slr_package_destination="${1-}" _k3slr_package_name="${2-}"
|
|
local _k3slr_package_expected="${3-}" _k3slr_package_rc="${4-}"
|
|
local _k3slr_package_stdout="${5-}" _k3slr_package_stderr="${6-}"
|
|
local _k3slr_package_state=malformed _k3slr_package_prefix _k3slr_package_version
|
|
(( $# == 6 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_package_destination" _k3slr_package_ || return 1
|
|
case "${_k3slr_package_name}|${_k3slr_package_expected}" in
|
|
"keepassxc|${_K3SLR_KEEPASSXC_VERSION}"|"cryptsetup-bin|${_K3SLR_CRYPTSETUP_VERSION}") ;;
|
|
*) return 1 ;;
|
|
esac
|
|
_k3slr_uint "$_k3slr_package_rc" || return 1
|
|
(( _k3slr_package_rc <= 255 )) || return 1
|
|
|
|
if [[ "$_k3slr_package_rc" == 0 && -z "$_k3slr_package_stderr" ]]; then
|
|
if [[ "$_k3slr_package_stdout" == "install ok installed|${_k3slr_package_expected}"$'\n' ]]; then
|
|
_k3slr_package_state=installed
|
|
else
|
|
_k3slr_package_prefix='install ok installed|'
|
|
if [[ "$_k3slr_package_stdout" == "$_k3slr_package_prefix"*$'\n' ]]; then
|
|
_k3slr_package_version="${_k3slr_package_stdout#"$_k3slr_package_prefix"}"
|
|
_k3slr_package_version="${_k3slr_package_version%$'\n'}"
|
|
if [[ -n "$_k3slr_package_version" && "$_k3slr_package_version" != *$'\n'* &&
|
|
"$_k3slr_package_version" != *$'\r'* &&
|
|
"$_k3slr_package_stdout" == "${_k3slr_package_prefix}${_k3slr_package_version}"$'\n' ]]; then
|
|
_k3slr_package_state=wrong-version
|
|
fi
|
|
fi
|
|
fi
|
|
elif [[ "$_k3slr_package_rc" == 1 && -z "$_k3slr_package_stdout" &&
|
|
"$_k3slr_package_stderr" == "dpkg-query: no packages found matching ${_k3slr_package_name}"$'\n' ]]; then
|
|
_k3slr_package_state=absent
|
|
fi
|
|
printf -v "$_k3slr_package_destination" '%s' "$_k3slr_package_state"
|
|
}
|
|
|
|
_k3slr_query_package_state() {
|
|
local _k3slr_package_destination="${1-}" _k3slr_package_name="${2-}"
|
|
local _k3slr_package_expected="${3-}" _k3slr_package_stdout=''
|
|
local _k3slr_package_stderr='' _k3slr_package_rc='' _k3slr_query_result=''
|
|
(( $# == 3 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_package_destination" _k3slr_package_ || return 1
|
|
_k3slr_capture_process _k3slr_package_stdout _k3slr_package_stderr _k3slr_package_rc \
|
|
/usr/bin/dpkg-query --show '--showformat=${Status}|${Version}\n' "$_k3slr_package_name" || return 1
|
|
_k3slr_parse_package_query_record _k3slr_query_result "$_k3slr_package_name" \
|
|
"$_k3slr_package_expected" "$_k3slr_package_rc" \
|
|
"$_k3slr_package_stdout" "$_k3slr_package_stderr" || return 1
|
|
printf -v "$_k3slr_package_destination" '%s' "$_k3slr_query_result"
|
|
}
|
|
|
|
_k3slr_parse_phase_bytes_record() {
|
|
local _k3slr_phase_record="${1-}" _k3slr_phase_destination="${2-}"
|
|
local _k3slr_phase_value
|
|
(( $# == 2 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_phase_destination" _k3slr_phase_ || return 1
|
|
[[ "$_k3slr_phase_record" == phase_bytes=*$'\n' ]] || return 1
|
|
_k3slr_phase_value="${_k3slr_phase_record#phase_bytes=}"
|
|
_k3slr_phase_value="${_k3slr_phase_value%$'\n'}"
|
|
[[ "$_k3slr_phase_record" == "phase_bytes=${_k3slr_phase_value}"$'\n' ]] || return 1
|
|
_k3slr_uint "$_k3slr_phase_value" || return 1
|
|
printf -v "$_k3slr_phase_destination" '%s' "$_k3slr_phase_value"
|
|
}
|
|
|
|
_k3slr_max_phase_bytes() {
|
|
local _k3slr_phase_destination="${1-}" _k3slr_phase_value _k3slr_phase_max=0
|
|
(( $# == 4 )) || return 1
|
|
shift
|
|
_k3slr_output_name_is_safe "$_k3slr_phase_destination" _k3slr_phase_ || return 1
|
|
for _k3slr_phase_value in "$@"; do
|
|
_k3slr_uint "$_k3slr_phase_value" || return 1
|
|
if (( _k3slr_phase_value > _k3slr_phase_max )); then
|
|
_k3slr_phase_max="$_k3slr_phase_value"
|
|
fi
|
|
done
|
|
printf -v "$_k3slr_phase_destination" '%s' "$_k3slr_phase_max"
|
|
}
|
|
|
|
_k3slr_parse_statfs_record() {
|
|
local _k3slr_statfs_record="${1-}" _k3slr_statfs_total_name="${2-}"
|
|
local _k3slr_statfs_free_name="${3-}" _k3slr_statfs_used_name="${4-}"
|
|
local _k3slr_statfs_block_size _k3slr_statfs_blocks _k3slr_statfs_available
|
|
local _k3slr_statfs_total _k3slr_statfs_free _k3slr_statfs_used
|
|
(( $# == 4 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_statfs_total_name" _k3slr_statfs_ || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_statfs_free_name" _k3slr_statfs_ || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_statfs_used_name" _k3slr_statfs_ || return 1
|
|
[[ "$_k3slr_statfs_total_name" != "$_k3slr_statfs_free_name" &&
|
|
"$_k3slr_statfs_total_name" != "$_k3slr_statfs_used_name" &&
|
|
"$_k3slr_statfs_free_name" != "$_k3slr_statfs_used_name" ]] || return 1
|
|
[[ "$_k3slr_statfs_record" =~ ^(0|[1-9][0-9]*)\|(0|[1-9][0-9]*)\|(0|[1-9][0-9]*)$'\n'$ ]] || return 1
|
|
_k3slr_statfs_block_size="${BASH_REMATCH[1]}"
|
|
_k3slr_statfs_blocks="${BASH_REMATCH[2]}"
|
|
_k3slr_statfs_available="${BASH_REMATCH[3]}"
|
|
_k3slr_uint "$_k3slr_statfs_block_size" || return 1
|
|
_k3slr_uint "$_k3slr_statfs_blocks" || return 1
|
|
_k3slr_uint "$_k3slr_statfs_available" || return 1
|
|
(( _k3slr_statfs_block_size > 0 && _k3slr_statfs_blocks > 0 &&
|
|
_k3slr_statfs_available <= _k3slr_statfs_blocks )) || return 1
|
|
(( _k3slr_statfs_blocks <= _K3SLR_INT64_MAX / _k3slr_statfs_block_size )) || return 1
|
|
_k3slr_statfs_total=$((_k3slr_statfs_block_size * _k3slr_statfs_blocks))
|
|
_k3slr_statfs_free=$((_k3slr_statfs_block_size * _k3slr_statfs_available))
|
|
_k3slr_statfs_used=$((_k3slr_statfs_total - _k3slr_statfs_free))
|
|
printf -v "$_k3slr_statfs_total_name" '%s' "$_k3slr_statfs_total"
|
|
printf -v "$_k3slr_statfs_free_name" '%s' "$_k3slr_statfs_free"
|
|
printf -v "$_k3slr_statfs_used_name" '%s' "$_k3slr_statfs_used"
|
|
}
|
|
|
|
_k3slr_conservative_inner_capacity_fits() {
|
|
local _k3slr_inner_container="${1-}" _k3slr_inner_phase="${2-}"
|
|
local _k3slr_inner_used
|
|
(( $# == 2 )) || return 1
|
|
_k3slr_uint "$_k3slr_inner_container" || return 1
|
|
_k3slr_uint "$_k3slr_inner_phase" || return 1
|
|
(( _k3slr_inner_container > 0 )) || return 1
|
|
_k3slr_inner_used=$((_k3slr_inner_container / 2 + _k3slr_inner_container % 2))
|
|
_k3slr_inner_capacity_fits "$_k3slr_inner_container" "$_k3slr_inner_used" "$_k3slr_inner_phase"
|
|
}
|
|
|
|
_k3slr_marker_sha256_is_valid() {
|
|
[[ "${1-}" =~ ^[0-9a-f]{64}$ ]]
|
|
}
|
|
|
|
_k3slr_encoded_uuid_is_valid() {
|
|
local _k3slr_uuid_hex="${1-}" _k3slr_uuid_kind="${2-}"
|
|
local _k3slr_uuid_index _k3slr_uuid_byte
|
|
(( $# == 2 && ${#_k3slr_uuid_hex} == 72 )) || return 1
|
|
[[ "$_k3slr_uuid_kind" == canonical || "$_k3slr_uuid_kind" == v4 ]] || return 1
|
|
for (( _k3slr_uuid_index=0; _k3slr_uuid_index<36; _k3slr_uuid_index++ )); do
|
|
_k3slr_uuid_byte="${_k3slr_uuid_hex:_k3slr_uuid_index * 2:2}"
|
|
case "$_k3slr_uuid_index" in
|
|
8|13|18|23)
|
|
[[ "$_k3slr_uuid_byte" == 2d ]] || return 1
|
|
;;
|
|
14)
|
|
if [[ "$_k3slr_uuid_kind" == v4 ]]; then
|
|
[[ "$_k3slr_uuid_byte" == 34 ]] || return 1
|
|
else
|
|
[[ "$_k3slr_uuid_byte" =~ ^(3[0-9]|6[1-6])$ ]] || return 1
|
|
fi
|
|
;;
|
|
19)
|
|
if [[ "$_k3slr_uuid_kind" == v4 ]]; then
|
|
[[ "$_k3slr_uuid_byte" =~ ^(38|39|61|62)$ ]] || return 1
|
|
else
|
|
[[ "$_k3slr_uuid_byte" =~ ^(3[0-9]|6[1-6])$ ]] || return 1
|
|
fi
|
|
;;
|
|
*)
|
|
[[ "$_k3slr_uuid_byte" =~ ^(3[0-9]|6[1-6])$ ]] || return 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
_k3slr_marker_hex_is_valid() {
|
|
local _k3slr_marker_hex_record="${1-}" _k3slr_marker_hex
|
|
(( $# == 1 )) || return 1
|
|
[[ "$_k3slr_marker_hex_record" =~ ^[0-9a-f]+$'\n'$ ]] || return 1
|
|
_k3slr_marker_hex="${_k3slr_marker_hex_record%$'\n'}"
|
|
[[ ${#_k3slr_marker_hex} == 74 && "${_k3slr_marker_hex:72:2}" == 0a ]] || return 1
|
|
_k3slr_encoded_uuid_is_valid "${_k3slr_marker_hex:0:72}" v4
|
|
}
|
|
|
|
_k3slr_parse_runtime_metadata_hex() {
|
|
local _k3slr_metadata_hex_record="${1-}" _k3slr_metadata_hash_name="${2-}"
|
|
local _k3slr_metadata_hex _k3slr_metadata_prefix _k3slr_metadata_remainder
|
|
local _k3slr_metadata_uuid_hex _k3slr_metadata_tail_prefix _k3slr_metadata_hash_hex
|
|
local _k3slr_metadata_contract_hex='' _k3slr_metadata_label_hex=''
|
|
local _k3slr_metadata_hash='' _k3slr_metadata_character _k3slr_metadata_byte
|
|
local _k3slr_metadata_index
|
|
(( $# == 2 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_metadata_hash_name" _k3slr_metadata_ || return 1
|
|
[[ -n "${K3SLR_CONTAINER_SIZE_BYTES:-}" && -n "${K3SLR_INNER_LABEL:-}" ]] || return 1
|
|
_k3slr_uint "$K3SLR_CONTAINER_SIZE_BYTES" || return 1
|
|
[[ "$K3SLR_INNER_LABEL" =~ ^[A-Z][A-Z0-9_]*$ ]] || return 1
|
|
[[ "$_k3slr_metadata_hex_record" =~ ^[0-9a-f]+$'\n'$ ]] || return 1
|
|
_k3slr_metadata_hex="${_k3slr_metadata_hex_record%$'\n'}"
|
|
(( ${#_k3slr_metadata_hex} <= 4096 && ${#_k3slr_metadata_hex} % 2 == 0 )) || return 1
|
|
|
|
for (( _k3slr_metadata_index=0;
|
|
_k3slr_metadata_index<${#K3SLR_CONTAINER_SIZE_BYTES};
|
|
_k3slr_metadata_index++ )); do
|
|
_k3slr_metadata_character="${K3SLR_CONTAINER_SIZE_BYTES:_k3slr_metadata_index:1}"
|
|
printf -v _k3slr_metadata_byte '%02x' "'$_k3slr_metadata_character"
|
|
_k3slr_metadata_contract_hex+="$_k3slr_metadata_byte"
|
|
done
|
|
for (( _k3slr_metadata_index=0;
|
|
_k3slr_metadata_index<${#K3SLR_INNER_LABEL};
|
|
_k3slr_metadata_index++ )); do
|
|
_k3slr_metadata_character="${K3SLR_INNER_LABEL:_k3slr_metadata_index:1}"
|
|
printf -v _k3slr_metadata_byte '%02x' "'$_k3slr_metadata_character"
|
|
_k3slr_metadata_label_hex+="$_k3slr_metadata_byte"
|
|
done
|
|
|
|
_k3slr_metadata_prefix='736368656d613d6b33736c722d72756e74696d652d76310a6c756b735f757569643d'
|
|
[[ "$_k3slr_metadata_hex" == "$_k3slr_metadata_prefix"* ]] || return 1
|
|
_k3slr_metadata_remainder="${_k3slr_metadata_hex#"$_k3slr_metadata_prefix"}"
|
|
(( ${#_k3slr_metadata_remainder} >= 72 )) || return 1
|
|
_k3slr_metadata_uuid_hex="${_k3slr_metadata_remainder:0:72}"
|
|
_k3slr_encoded_uuid_is_valid "$_k3slr_metadata_uuid_hex" canonical || return 1
|
|
_k3slr_metadata_remainder="${_k3slr_metadata_remainder:72}"
|
|
_k3slr_metadata_tail_prefix="0a636f6e7461696e65725f62797465733d${_k3slr_metadata_contract_hex}0a696e6e65725f6c6162656c3d${_k3slr_metadata_label_hex}0a6d61726b65725f7368613235363d"
|
|
[[ "$_k3slr_metadata_remainder" == "$_k3slr_metadata_tail_prefix"* ]] || return 1
|
|
_k3slr_metadata_hash_hex="${_k3slr_metadata_remainder#"$_k3slr_metadata_tail_prefix"}"
|
|
[[ ${#_k3slr_metadata_hash_hex} == 130 &&
|
|
"${_k3slr_metadata_hash_hex:128:2}" == 0a ]] || return 1
|
|
_k3slr_metadata_hash_hex="${_k3slr_metadata_hash_hex:0:128}"
|
|
for (( _k3slr_metadata_index=0; _k3slr_metadata_index<128; _k3slr_metadata_index+=2 )); do
|
|
_k3slr_metadata_byte="${_k3slr_metadata_hash_hex:_k3slr_metadata_index:2}"
|
|
case "$_k3slr_metadata_byte" in
|
|
30) _k3slr_metadata_hash+=0 ;; 31) _k3slr_metadata_hash+=1 ;;
|
|
32) _k3slr_metadata_hash+=2 ;; 33) _k3slr_metadata_hash+=3 ;;
|
|
34) _k3slr_metadata_hash+=4 ;; 35) _k3slr_metadata_hash+=5 ;;
|
|
36) _k3slr_metadata_hash+=6 ;; 37) _k3slr_metadata_hash+=7 ;;
|
|
38) _k3slr_metadata_hash+=8 ;; 39) _k3slr_metadata_hash+=9 ;;
|
|
61) _k3slr_metadata_hash+=a ;; 62) _k3slr_metadata_hash+=b ;;
|
|
63) _k3slr_metadata_hash+=c ;; 64) _k3slr_metadata_hash+=d ;;
|
|
65) _k3slr_metadata_hash+=e ;; 66) _k3slr_metadata_hash+=f ;;
|
|
*) return 1 ;;
|
|
esac
|
|
done
|
|
_k3slr_marker_sha256_is_valid "$_k3slr_metadata_hash" || return 1
|
|
printf -v "$_k3slr_metadata_hash_name" '%s' "$_k3slr_metadata_hash"
|
|
}
|
|
|
|
_k3slr_normalize_od_hex_record() {
|
|
local _k3slr_od_record="${1-}" _k3slr_od_destination="${2-}"
|
|
local _k3slr_od_remaining _k3slr_od_line _k3slr_od_token
|
|
local _k3slr_od_hex='' _k3slr_od_bytes=0
|
|
local -a _k3slr_od_tokens=()
|
|
(( $# == 2 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_od_destination" _k3slr_od_ || return 1
|
|
[[ -n "$_k3slr_od_record" && "$_k3slr_od_record" == *$'\n' &&
|
|
"$_k3slr_od_record" != *$'\r'* ]] || return 1
|
|
_k3slr_od_remaining="$_k3slr_od_record"
|
|
while [[ -n "$_k3slr_od_remaining" ]]; do
|
|
[[ "$_k3slr_od_remaining" == *$'\n'* ]] || return 1
|
|
_k3slr_od_line="${_k3slr_od_remaining%%$'\n'*}"
|
|
_k3slr_od_remaining="${_k3slr_od_remaining#*$'\n'}"
|
|
read -r -a _k3slr_od_tokens <<<"$_k3slr_od_line"
|
|
for _k3slr_od_token in "${_k3slr_od_tokens[@]}"; do
|
|
[[ "$_k3slr_od_token" =~ ^[0-9a-f]{2}$ ]] || return 1
|
|
_k3slr_od_bytes=$((_k3slr_od_bytes + 1))
|
|
(( _k3slr_od_bytes <= 65536 )) || return 1
|
|
_k3slr_od_hex+="$_k3slr_od_token"
|
|
done
|
|
done
|
|
(( _k3slr_od_bytes > 0 )) || return 1
|
|
printf -v "$_k3slr_od_destination" '%s\n' "$_k3slr_od_hex"
|
|
}
|
|
|
|
_k3slr_parse_unmounted_ntfs_capacity_hex() {
|
|
local _k3slr_ntfs_record="${1-}" _k3slr_ntfs_total_name="${2-}"
|
|
local _k3slr_ntfs_free_name="${3-}" _k3slr_ntfs_hex _k3slr_ntfs_text=''
|
|
local _k3slr_ntfs_byte _k3slr_ntfs_character _k3slr_ntfs_remaining _k3slr_ntfs_line
|
|
local _k3slr_ntfs_trimmed _k3slr_ntfs_cluster_size='' _k3slr_ntfs_volume=''
|
|
local _k3slr_ntfs_allocated='' _k3slr_ntfs_free_clusters=''
|
|
local _k3slr_ntfs_allocated_percent='' _k3slr_ntfs_free_percent=''
|
|
local _k3slr_ntfs_integer _k3slr_ntfs_fraction _k3slr_ntfs_allocated_tenths
|
|
local _k3slr_ntfs_free_tenths _k3slr_ntfs_total _k3slr_ntfs_free
|
|
local _k3slr_ntfs_left _k3slr_ntfs_right _k3slr_ntfs_difference
|
|
local _k3slr_ntfs_index
|
|
(( $# == 3 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_ntfs_total_name" _k3slr_ntfs_ || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_ntfs_free_name" _k3slr_ntfs_ || return 1
|
|
[[ "$_k3slr_ntfs_total_name" != "$_k3slr_ntfs_free_name" ]] || return 1
|
|
[[ "$_k3slr_ntfs_record" =~ ^[0-9a-f]+$'\n'$ ]] || return 1
|
|
_k3slr_ntfs_hex="${_k3slr_ntfs_record%$'\n'}"
|
|
(( ${#_k3slr_ntfs_hex} > 0 && ${#_k3slr_ntfs_hex} <= 131072 &&
|
|
${#_k3slr_ntfs_hex} % 2 == 0 )) || return 1
|
|
[[ "${_k3slr_ntfs_hex: -2}" == 0a ]] || return 1
|
|
|
|
for (( _k3slr_ntfs_index=0;
|
|
_k3slr_ntfs_index<${#_k3slr_ntfs_hex};
|
|
_k3slr_ntfs_index+=2 )); do
|
|
_k3slr_ntfs_byte="${_k3slr_ntfs_hex:_k3slr_ntfs_index:2}"
|
|
[[ "$_k3slr_ntfs_byte" != 00 && "$_k3slr_ntfs_byte" != 0d ]] || return 1
|
|
if [[ "$_k3slr_ntfs_byte" == 09 ]]; then
|
|
_k3slr_ntfs_character=$'\t'
|
|
elif [[ "$_k3slr_ntfs_byte" == 0a ]]; then
|
|
_k3slr_ntfs_character=$'\n'
|
|
elif (( 16#$_k3slr_ntfs_byte >= 16#20 && 16#$_k3slr_ntfs_byte <= 16#7e )); then
|
|
printf -v _k3slr_ntfs_character '%b' "\\x${_k3slr_ntfs_byte}"
|
|
else
|
|
return 1
|
|
fi
|
|
_k3slr_ntfs_text+="$_k3slr_ntfs_character"
|
|
done
|
|
[[ "$_k3slr_ntfs_text" == *$'\n' ]] || return 1
|
|
|
|
_k3slr_ntfs_remaining="$_k3slr_ntfs_text"
|
|
while [[ -n "$_k3slr_ntfs_remaining" ]]; do
|
|
[[ "$_k3slr_ntfs_remaining" == *$'\n'* ]] || return 1
|
|
_k3slr_ntfs_line="${_k3slr_ntfs_remaining%%$'\n'*}"
|
|
_k3slr_ntfs_remaining="${_k3slr_ntfs_remaining#*$'\n'}"
|
|
_k3slr_ntfs_trimmed="$_k3slr_ntfs_line"
|
|
while [[ "$_k3slr_ntfs_trimmed" == ' '* || "$_k3slr_ntfs_trimmed" == $'\t'* ]]; do
|
|
_k3slr_ntfs_trimmed="${_k3slr_ntfs_trimmed:1}"
|
|
done
|
|
if [[ "$_k3slr_ntfs_trimmed" =~ ^Cluster\ Size:\ (0|[1-9][0-9]*)$ ]]; then
|
|
[[ -z "$_k3slr_ntfs_cluster_size" ]] || return 1
|
|
_k3slr_ntfs_cluster_size="${BASH_REMATCH[1]}"
|
|
elif [[ "$_k3slr_ntfs_trimmed" =~ ^Volume\ Size\ in\ Clusters:\ (0|[1-9][0-9]*)$ ]]; then
|
|
[[ -z "$_k3slr_ntfs_volume" ]] || return 1
|
|
_k3slr_ntfs_volume="${BASH_REMATCH[1]}"
|
|
elif [[ "$_k3slr_ntfs_trimmed" =~ ^Allocated\ clusters\ (0|[1-9][0-9]*)\ \(((0|[1-9][0-9]{0,2})\.([0-9]))%\)$ ]]; then
|
|
[[ -z "$_k3slr_ntfs_allocated" ]] || return 1
|
|
_k3slr_ntfs_allocated="${BASH_REMATCH[1]}"
|
|
_k3slr_ntfs_allocated_percent="${BASH_REMATCH[2]}"
|
|
elif [[ "$_k3slr_ntfs_trimmed" =~ ^Free\ Clusters:\ (0|[1-9][0-9]*)\ \(((0|[1-9][0-9]{0,2})\.([0-9]))%\)$ ]]; then
|
|
[[ -z "$_k3slr_ntfs_free_clusters" ]] || return 1
|
|
_k3slr_ntfs_free_clusters="${BASH_REMATCH[1]}"
|
|
_k3slr_ntfs_free_percent="${BASH_REMATCH[2]}"
|
|
elif [[ "$_k3slr_ntfs_trimmed" == 'Cluster Size:'* ||
|
|
"$_k3slr_ntfs_trimmed" == 'Volume Size in Clusters:'* ||
|
|
"$_k3slr_ntfs_trimmed" == 'Allocated clusters'* ||
|
|
"$_k3slr_ntfs_trimmed" == 'Free Clusters:'* ]]; then
|
|
return 1
|
|
fi
|
|
done
|
|
[[ -n "$_k3slr_ntfs_cluster_size" && -n "$_k3slr_ntfs_volume" &&
|
|
-n "$_k3slr_ntfs_allocated" && -n "$_k3slr_ntfs_free_clusters" ]] || return 1
|
|
_k3slr_uint "$_k3slr_ntfs_cluster_size" || return 1
|
|
_k3slr_uint "$_k3slr_ntfs_volume" || return 1
|
|
_k3slr_uint "$_k3slr_ntfs_allocated" || return 1
|
|
_k3slr_uint "$_k3slr_ntfs_free_clusters" || return 1
|
|
(( _k3slr_ntfs_cluster_size > 0 && _k3slr_ntfs_volume > 0 )) || return 1
|
|
(( _k3slr_ntfs_allocated <= _K3SLR_INT64_MAX - _k3slr_ntfs_free_clusters &&
|
|
_k3slr_ntfs_allocated + _k3slr_ntfs_free_clusters == _k3slr_ntfs_volume )) || return 1
|
|
(( _k3slr_ntfs_volume <= _K3SLR_INT64_MAX / _k3slr_ntfs_cluster_size )) || return 1
|
|
_k3slr_ntfs_total=$((_k3slr_ntfs_cluster_size * _k3slr_ntfs_volume))
|
|
_k3slr_ntfs_free=$((_k3slr_ntfs_cluster_size * _k3slr_ntfs_free_clusters))
|
|
|
|
_k3slr_ntfs_integer="${_k3slr_ntfs_allocated_percent%.*}"
|
|
_k3slr_ntfs_fraction="${_k3slr_ntfs_allocated_percent#*.}"
|
|
_k3slr_ntfs_allocated_tenths=$((10#$_k3slr_ntfs_integer * 10 + 10#$_k3slr_ntfs_fraction))
|
|
_k3slr_ntfs_integer="${_k3slr_ntfs_free_percent%.*}"
|
|
_k3slr_ntfs_fraction="${_k3slr_ntfs_free_percent#*.}"
|
|
_k3slr_ntfs_free_tenths=$((10#$_k3slr_ntfs_integer * 10 + 10#$_k3slr_ntfs_fraction))
|
|
(( _k3slr_ntfs_allocated_tenths <= 1000 && _k3slr_ntfs_free_tenths <= 1000 )) || return 1
|
|
(( _k3slr_ntfs_volume <= _K3SLR_INT64_MAX / 1000 &&
|
|
_k3slr_ntfs_allocated <= _K3SLR_INT64_MAX / 1000 &&
|
|
_k3slr_ntfs_free_clusters <= _K3SLR_INT64_MAX / 1000 )) || return 1
|
|
|
|
_k3slr_ntfs_left=$((_k3slr_ntfs_allocated_tenths * _k3slr_ntfs_volume))
|
|
_k3slr_ntfs_right=$((_k3slr_ntfs_allocated * 1000))
|
|
if (( _k3slr_ntfs_left >= _k3slr_ntfs_right )); then
|
|
_k3slr_ntfs_difference=$((_k3slr_ntfs_left - _k3slr_ntfs_right))
|
|
else
|
|
_k3slr_ntfs_difference=$((_k3slr_ntfs_right - _k3slr_ntfs_left))
|
|
fi
|
|
(( _k3slr_ntfs_difference <= _K3SLR_INT64_MAX / 2 &&
|
|
2 * _k3slr_ntfs_difference <= _k3slr_ntfs_volume )) || return 1
|
|
_k3slr_ntfs_left=$((_k3slr_ntfs_free_tenths * _k3slr_ntfs_volume))
|
|
_k3slr_ntfs_right=$((_k3slr_ntfs_free_clusters * 1000))
|
|
if (( _k3slr_ntfs_left >= _k3slr_ntfs_right )); then
|
|
_k3slr_ntfs_difference=$((_k3slr_ntfs_left - _k3slr_ntfs_right))
|
|
else
|
|
_k3slr_ntfs_difference=$((_k3slr_ntfs_right - _k3slr_ntfs_left))
|
|
fi
|
|
(( _k3slr_ntfs_difference <= _K3SLR_INT64_MAX / 2 &&
|
|
2 * _k3slr_ntfs_difference <= _k3slr_ntfs_volume )) || return 1
|
|
printf -v "$_k3slr_ntfs_total_name" '%s' "$_k3slr_ntfs_total"
|
|
printf -v "$_k3slr_ntfs_free_name" '%s' "$_k3slr_ntfs_free"
|
|
}
|
|
|
|
_k3slr_parse_prepackage_snapshot_fields() {
|
|
local _k3slr_snapshot_record="${1-}" _k3slr_snapshot_result_name="${2-}"
|
|
local _k3slr_snapshot_remaining _k3slr_snapshot_line _k3slr_snapshot_key _k3slr_snapshot_value
|
|
local -A _k3slr_snapshot_allowed=(
|
|
[canonical_partition]=1 [partition_major_minor]=1
|
|
[canonical_parent_disk]=1 [disk_major_minor]=1
|
|
[filesystem_uuid]=1 [partuuid]=1 [filesystem_type]=1
|
|
[model]=1 [serial]=1 [wwn]=1
|
|
[canonical_k3s_partition]=1 [k3s_partition_major_minor]=1
|
|
[canonical_k3s_disk]=1 [k3s_disk_major_minor]=1
|
|
[k3s_filesystem_uuid]=1 [k3s_partuuid]=1 [k3s_filesystem_type]=1
|
|
[k3s_model]=1 [k3s_serial]=1 [k3s_wwn]=1
|
|
[smart_health]=1 [smart_reallocated]=1 [smart_pending]=1 [smart_uncorrectable]=1
|
|
[outer_mount]=1 [inner_mount]=1 [source_mounts]=1 [loops]=1
|
|
[mapper_directory]=1 [mapping_path]=1 [proof_mapping_path]=1 [mapping_names]=1
|
|
[ntfs_probe]=1
|
|
)
|
|
(( $# == 2 )) || return 1
|
|
[[ "$_k3slr_snapshot_result_name" =~ ^[A-Za-z_][A-Za-z0-9_]*$ &&
|
|
"$_k3slr_snapshot_result_name" != _k3slr_snapshot_* ]] || return 1
|
|
local -n _k3slr_snapshot_result="$_k3slr_snapshot_result_name"
|
|
[[ -n "$_k3slr_snapshot_record" && "$_k3slr_snapshot_record" == *$'\n' &&
|
|
"$_k3slr_snapshot_record" != *$'\r'* ]] || return 1
|
|
_k3slr_snapshot_result=()
|
|
_k3slr_snapshot_remaining="$_k3slr_snapshot_record"
|
|
while [[ -n "$_k3slr_snapshot_remaining" ]]; do
|
|
[[ "$_k3slr_snapshot_remaining" == *$'\n'* ]] || return 1
|
|
_k3slr_snapshot_line="${_k3slr_snapshot_remaining%%$'\n'*}"
|
|
_k3slr_snapshot_remaining="${_k3slr_snapshot_remaining#*$'\n'}"
|
|
[[ "$_k3slr_snapshot_line" =~ ^([a-z][a-z0-9_]*)=([^[:space:]]+)$ ]] || return 1
|
|
_k3slr_snapshot_key="${BASH_REMATCH[1]}"
|
|
_k3slr_snapshot_value="${BASH_REMATCH[2]}"
|
|
[[ "${_k3slr_snapshot_allowed[$_k3slr_snapshot_key]+present}" == present &&
|
|
"${_k3slr_snapshot_result[$_k3slr_snapshot_key]+present}" != present ]] || return 1
|
|
_k3slr_snapshot_result["$_k3slr_snapshot_key"]="$_k3slr_snapshot_value"
|
|
done
|
|
for _k3slr_snapshot_key in "${!_k3slr_snapshot_allowed[@]}"; do
|
|
[[ "${_k3slr_snapshot_result[$_k3slr_snapshot_key]+present}" == present ]] || return 1
|
|
done
|
|
}
|
|
|
|
_k3slr_validate_prepackage_snapshot() {
|
|
local _k3slr_snapshot_record="${1-}"
|
|
local -A snapshot_fields=()
|
|
(( $# == 1 )) || return 1
|
|
_k3slr_parse_prepackage_snapshot_fields "$_k3slr_snapshot_record" snapshot_fields || return 1
|
|
[[ "${snapshot_fields[canonical_partition]}" =~ ^/dev/[A-Za-z0-9._/-]+$ &&
|
|
"${snapshot_fields[canonical_parent_disk]}" =~ ^/dev/[A-Za-z0-9._/-]+$ &&
|
|
"${snapshot_fields[canonical_k3s_partition]}" =~ ^/dev/[A-Za-z0-9._/-]+$ &&
|
|
"${snapshot_fields[canonical_k3s_disk]}" =~ ^/dev/[A-Za-z0-9._/-]+$ ]] || return 1
|
|
[[ "${snapshot_fields[partition_major_minor]}" =~ ^[0-9]+:[0-9]+$ &&
|
|
"${snapshot_fields[disk_major_minor]}" =~ ^[0-9]+:[0-9]+$ &&
|
|
"${snapshot_fields[k3s_partition_major_minor]}" =~ ^[0-9]+:[0-9]+$ &&
|
|
"${snapshot_fields[k3s_disk_major_minor]}" =~ ^[0-9]+:[0-9]+$ ]] || return 1
|
|
[[ "${snapshot_fields[canonical_parent_disk]}" != "${snapshot_fields[canonical_k3s_disk]}" &&
|
|
"${snapshot_fields[disk_major_minor]}" != "${snapshot_fields[k3s_disk_major_minor]}" ]] || return 1
|
|
[[ "${snapshot_fields[filesystem_uuid]}" == "$K3SLR_RECOVERY_FS_UUID" &&
|
|
"${snapshot_fields[partuuid]}" == "$K3SLR_RECOVERY_PARTUUID" &&
|
|
"${snapshot_fields[filesystem_type]}" == ntfs &&
|
|
"${snapshot_fields[model]}" == "$K3SLR_RECOVERY_MODEL" &&
|
|
"${snapshot_fields[serial]}" == "$K3SLR_RECOVERY_SERIAL" &&
|
|
"${snapshot_fields[wwn]}" == "$K3SLR_RECOVERY_WWN" ]] || return 1
|
|
[[ "${snapshot_fields[k3s_filesystem_uuid]}" == "$K3SLR_K3S_FS_UUID" &&
|
|
"${snapshot_fields[k3s_partuuid]}" == "$K3SLR_K3S_PARTUUID" &&
|
|
"${snapshot_fields[k3s_filesystem_type]}" == ext4 &&
|
|
"${snapshot_fields[k3s_model]}" == "$K3SLR_K3S_MODEL" &&
|
|
"${snapshot_fields[k3s_serial]}" == "$K3SLR_K3S_SERIAL" &&
|
|
"${snapshot_fields[k3s_wwn]}" == "$K3SLR_K3S_WWN" ]] || return 1
|
|
[[ "${snapshot_fields[smart_health]}" == PASSED &&
|
|
"${snapshot_fields[smart_reallocated]}" == 0 &&
|
|
"${snapshot_fields[smart_pending]}" == 0 &&
|
|
"${snapshot_fields[smart_uncorrectable]}" == 0 &&
|
|
"${snapshot_fields[outer_mount]}" == absent &&
|
|
"${snapshot_fields[inner_mount]}" == absent &&
|
|
"${snapshot_fields[source_mounts]}" == absent &&
|
|
"${snapshot_fields[loops]}" == absent &&
|
|
"${snapshot_fields[mapper_directory]}" == physical-root-safe &&
|
|
"${snapshot_fields[mapping_path]}" == absent &&
|
|
"${snapshot_fields[proof_mapping_path]}" == absent &&
|
|
"${snapshot_fields[mapping_names]}" == absent &&
|
|
"${snapshot_fields[ntfs_probe]}" == pass ]] || return 1
|
|
}
|
|
|
|
_k3slr_prepackage_snapshot_identity() {
|
|
local _k3slr_snapshot_record="${1-}" _k3slr_snapshot_identity_name="${2-}"
|
|
local _k3slr_snapshot_identity
|
|
local -A snapshot_fields=()
|
|
(( $# == 2 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_snapshot_identity_name" _k3slr_snapshot_ || return 1
|
|
_k3slr_validate_prepackage_snapshot "$_k3slr_snapshot_record" || return 1
|
|
_k3slr_parse_prepackage_snapshot_fields "$_k3slr_snapshot_record" snapshot_fields || return 1
|
|
printf -v _k3slr_snapshot_identity '%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s' \
|
|
"${snapshot_fields[canonical_partition]}" "${snapshot_fields[partition_major_minor]}" \
|
|
"${snapshot_fields[canonical_parent_disk]}" "${snapshot_fields[disk_major_minor]}" \
|
|
"${snapshot_fields[filesystem_uuid]}" "${snapshot_fields[partuuid]}" \
|
|
"${snapshot_fields[filesystem_type]}" "${snapshot_fields[model]}" \
|
|
"${snapshot_fields[serial]}" "${snapshot_fields[wwn]}" \
|
|
"${snapshot_fields[k3s_disk_major_minor]}"
|
|
printf -v "$_k3slr_snapshot_identity_name" '%s' "$_k3slr_snapshot_identity"
|
|
}
|
|
|
|
_k3slr_prepackage_device_preflight() {
|
|
local _k3slr_preflight_before='' _k3slr_preflight_after=''
|
|
(( $# == 0 )) || return 1
|
|
_k3slr_collect_prepackage_snapshot _k3slr_preflight_before || return 1
|
|
_k3slr_validate_prepackage_snapshot "$_k3slr_preflight_before" || return 1
|
|
_k3slr_collect_prepackage_snapshot _k3slr_preflight_after || return 1
|
|
_k3slr_validate_prepackage_snapshot "$_k3slr_preflight_after" || return 1
|
|
[[ "$_k3slr_preflight_before" == "$_k3slr_preflight_after" ]]
|
|
}
|
|
|
|
_k3slr_collect_unmounted_ntfs_capacity() {
|
|
local _k3slr_capacity_hex_name="${1-}" _k3slr_capacity_identity_name="${2-}"
|
|
local _k3slr_capacity_before='' _k3slr_capacity_after=''
|
|
local _k3slr_capacity_hex='' _k3slr_capacity_identity=''
|
|
local -A snapshot_fields=()
|
|
(( $# == 2 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_capacity_hex_name" _k3slr_capacity_ || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_capacity_identity_name" _k3slr_capacity_ || return 1
|
|
[[ "$_k3slr_capacity_hex_name" != "$_k3slr_capacity_identity_name" ]] || return 1
|
|
_k3slr_collect_prepackage_snapshot _k3slr_capacity_before || return 1
|
|
_k3slr_validate_prepackage_snapshot "$_k3slr_capacity_before" || return 1
|
|
_k3slr_parse_prepackage_snapshot_fields "$_k3slr_capacity_before" snapshot_fields || return 1
|
|
_k3slr_capture_ntfsinfo_hex _k3slr_capacity_hex "${snapshot_fields[canonical_partition]}" || return 1
|
|
_k3slr_collect_prepackage_snapshot _k3slr_capacity_after || return 1
|
|
_k3slr_validate_prepackage_snapshot "$_k3slr_capacity_after" || return 1
|
|
[[ "$_k3slr_capacity_before" == "$_k3slr_capacity_after" ]] || return 1
|
|
_k3slr_prepackage_snapshot_identity "$_k3slr_capacity_before" _k3slr_capacity_identity || return 1
|
|
printf -v "$_k3slr_capacity_hex_name" '%s' "$_k3slr_capacity_hex"
|
|
printf -v "$_k3slr_capacity_identity_name" '%s' "$_k3slr_capacity_identity"
|
|
}
|
|
|
|
_k3slr_capture_exact_one_line_record() {
|
|
local _k3slr_record_destination="${1-}" _k3slr_record_stdout=''
|
|
local _k3slr_record_stderr='' _k3slr_record_rc='' _k3slr_record_value
|
|
shift || return 1
|
|
(( $# > 0 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_record_destination" _k3slr_record_ || return 1
|
|
_k3slr_capture_process _k3slr_record_stdout _k3slr_record_stderr _k3slr_record_rc "$@" || return 1
|
|
[[ "$_k3slr_record_rc" == 0 && -z "$_k3slr_record_stderr" &&
|
|
"$_k3slr_record_stdout" == *$'\n' ]] || return 1
|
|
_k3slr_record_value="${_k3slr_record_stdout%$'\n'}"
|
|
[[ -n "$_k3slr_record_value" && "$_k3slr_record_value" != *$'\n'* &&
|
|
"$_k3slr_record_value" != *$'\r'* &&
|
|
"$_k3slr_record_stdout" == "${_k3slr_record_value}"$'\n' ]] || return 1
|
|
printf -v "$_k3slr_record_destination" '%s' "$_k3slr_record_value"
|
|
}
|
|
|
|
_k3slr_capture_absent_record() {
|
|
local _k3slr_absent_stdout='' _k3slr_absent_stderr='' _k3slr_absent_rc=''
|
|
(( $# > 0 )) || return 1
|
|
_k3slr_capture_process _k3slr_absent_stdout _k3slr_absent_stderr _k3slr_absent_rc "$@" || return 1
|
|
[[ "$_k3slr_absent_rc" == 1 && -z "$_k3slr_absent_stdout" && -z "$_k3slr_absent_stderr" ]]
|
|
}
|
|
|
|
_k3slr_capture_empty_success() {
|
|
local _k3slr_empty_stdout='' _k3slr_empty_stderr='' _k3slr_empty_rc=''
|
|
(( $# > 0 )) || return 1
|
|
_k3slr_capture_process _k3slr_empty_stdout _k3slr_empty_stderr _k3slr_empty_rc "$@" || return 1
|
|
[[ "$_k3slr_empty_rc" == 0 && -z "$_k3slr_empty_stdout" && -z "$_k3slr_empty_stderr" ]]
|
|
}
|
|
|
|
_k3slr_capture_raw_success() {
|
|
local _k3slr_raw_destination="${1-}" _k3slr_raw_stdout=''
|
|
local _k3slr_raw_stderr='' _k3slr_raw_rc=''
|
|
shift || return 1
|
|
(( $# > 0 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_raw_destination" _k3slr_raw_ || return 1
|
|
_k3slr_capture_process _k3slr_raw_stdout _k3slr_raw_stderr _k3slr_raw_rc "$@" || return 1
|
|
[[ "$_k3slr_raw_rc" == 0 && -z "$_k3slr_raw_stderr" ]] || return 1
|
|
printf -v "$_k3slr_raw_destination" '%s' "$_k3slr_raw_stdout"
|
|
}
|
|
|
|
_k3slr_parse_smart_evidence() {
|
|
local _k3slr_smart_record="${1-}" _k3slr_smart_destination="${2-}"
|
|
local _k3slr_smart_remaining _k3slr_smart_line _k3slr_smart_id _k3slr_smart_raw
|
|
local _k3slr_smart_health='' _k3slr_smart_reallocated=''
|
|
local _k3slr_smart_pending='' _k3slr_smart_uncorrectable=''
|
|
local -a _k3slr_smart_fields=()
|
|
(( $# == 2 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_smart_destination" _k3slr_smart_ || return 1
|
|
[[ -n "$_k3slr_smart_record" && "$_k3slr_smart_record" == *$'\n' &&
|
|
"$_k3slr_smart_record" != *$'\r'* ]] || return 1
|
|
_k3slr_smart_remaining="$_k3slr_smart_record"
|
|
while [[ -n "$_k3slr_smart_remaining" ]]; do
|
|
_k3slr_smart_line="${_k3slr_smart_remaining%%$'\n'*}"
|
|
_k3slr_smart_remaining="${_k3slr_smart_remaining#*$'\n'}"
|
|
if [[ "$_k3slr_smart_line" =~ ^SMART[[:space:]]overall-health[[:space:]]self-assessment[[:space:]]test[[:space:]]result:[[:space:]]*PASSED[[:space:]]*$ ]]; then
|
|
[[ -z "$_k3slr_smart_health" ]] || return 1
|
|
_k3slr_smart_health=PASSED
|
|
continue
|
|
fi
|
|
[[ "$_k3slr_smart_line" =~ ^[[:space:]]*(5|197|198)[[:space:]] ]] || continue
|
|
_k3slr_smart_id="${BASH_REMATCH[1]}"
|
|
read -r -a _k3slr_smart_fields <<<"$_k3slr_smart_line"
|
|
(( ${#_k3slr_smart_fields[@]} >= 2 )) || return 1
|
|
_k3slr_smart_raw="${_k3slr_smart_fields[${#_k3slr_smart_fields[@]} - 1]}"
|
|
_k3slr_uint "$_k3slr_smart_raw" || return 1
|
|
case "$_k3slr_smart_id" in
|
|
5)
|
|
[[ -z "$_k3slr_smart_reallocated" ]] || return 1
|
|
_k3slr_smart_reallocated="$_k3slr_smart_raw"
|
|
;;
|
|
197)
|
|
[[ -z "$_k3slr_smart_pending" ]] || return 1
|
|
_k3slr_smart_pending="$_k3slr_smart_raw"
|
|
;;
|
|
198)
|
|
[[ -z "$_k3slr_smart_uncorrectable" ]] || return 1
|
|
_k3slr_smart_uncorrectable="$_k3slr_smart_raw"
|
|
;;
|
|
esac
|
|
done
|
|
[[ "$_k3slr_smart_health" == PASSED && "$_k3slr_smart_reallocated" == 0 &&
|
|
"$_k3slr_smart_pending" == 0 && "$_k3slr_smart_uncorrectable" == 0 ]] || return 1
|
|
printf -v "$_k3slr_smart_destination" '%s' 'PASSED|0|0|0'
|
|
}
|
|
|
|
_k3slr_all_loops_exclude_backing_device() {
|
|
local _k3slr_loops_record="${1-}" _k3slr_loops_backing_major_minor="${2-}"
|
|
local _k3slr_loops_remaining _k3slr_loops_line
|
|
local -a _k3slr_loops_fields=()
|
|
(( $# == 2 )) || return 1
|
|
[[ "$_k3slr_loops_backing_major_minor" =~ ^[0-9]+:[0-9]+$ ]] || return 1
|
|
[[ "$_k3slr_loops_record" != *$'\r'* ]] || return 1
|
|
[[ -z "$_k3slr_loops_record" || "$_k3slr_loops_record" == *$'\n' ]] || return 1
|
|
_k3slr_loops_remaining="$_k3slr_loops_record"
|
|
while [[ -n "$_k3slr_loops_remaining" ]]; do
|
|
_k3slr_loops_line="${_k3slr_loops_remaining%%$'\n'*}"
|
|
_k3slr_loops_remaining="${_k3slr_loops_remaining#*$'\n'}"
|
|
read -r -a _k3slr_loops_fields <<<"$_k3slr_loops_line"
|
|
(( ${#_k3slr_loops_fields[@]} == 7 )) || return 1
|
|
[[ "${_k3slr_loops_fields[0]}" =~ ^/dev/loop[0-9]+$ &&
|
|
"${_k3slr_loops_fields[1]}" =~ ^/[^[:space:]]+$ &&
|
|
"${_k3slr_loops_fields[2]}" =~ ^(0|[1-9][0-9]*)$ &&
|
|
"${_k3slr_loops_fields[3]}" =~ ^[0-9]+:[0-9]+$ &&
|
|
"${_k3slr_loops_fields[4]}" =~ ^[0-9]+:[0-9]+$ &&
|
|
"${_k3slr_loops_fields[5]}" =~ ^(0|[1-9][0-9]*)$ &&
|
|
"${_k3slr_loops_fields[6]}" =~ ^(0|[1-9][0-9]*)$ ]] || return 1
|
|
[[ "${_k3slr_loops_fields[3]}" != "$_k3slr_loops_backing_major_minor" ]] || return 1
|
|
done
|
|
}
|
|
|
|
_k3slr_mapper_name_scan_is_clear() {
|
|
local _k3slr_names_record="${1-}" _k3slr_names_remaining _k3slr_names_line
|
|
local -a _k3slr_names_fields=()
|
|
(( $# == 1 )) || return 1
|
|
[[ -n "$_k3slr_names_record" && "$_k3slr_names_record" == *$'\n' &&
|
|
"$_k3slr_names_record" != *$'\r'* ]] || return 1
|
|
_k3slr_names_remaining="$_k3slr_names_record"
|
|
while [[ -n "$_k3slr_names_remaining" ]]; do
|
|
_k3slr_names_line="${_k3slr_names_remaining%%$'\n'*}"
|
|
_k3slr_names_remaining="${_k3slr_names_remaining#*$'\n'}"
|
|
read -r -a _k3slr_names_fields <<<"$_k3slr_names_line"
|
|
(( ${#_k3slr_names_fields[@]} == 2 )) || return 1
|
|
[[ "${_k3slr_names_fields[0]}" =~ ^[A-Za-z0-9._/-]+$ &&
|
|
"${_k3slr_names_fields[1]}" =~ ^[a-z][a-z0-9_-]*$ ]] || return 1
|
|
[[ "${_k3slr_names_fields[0]}" != "$K3SLR_MAPPING_NAME" &&
|
|
"${_k3slr_names_fields[0]}" != "$K3SLR_PROOF_MAPPING_NAME" ]] || return 1
|
|
done
|
|
}
|
|
|
|
_k3slr_mapper_directory_is_safe() {
|
|
local _k3slr_mapper_record="${1-}" _k3slr_mapper_mode _k3slr_mapper_group _k3slr_mapper_other
|
|
(( $# == 1 )) || return 1
|
|
[[ "$_k3slr_mapper_record" =~ ^directory\|0\|0\|([0-7]{3,4})$'\n'$ ]] || return 1
|
|
_k3slr_mapper_mode="${BASH_REMATCH[1]}"
|
|
_k3slr_mapper_group="${_k3slr_mapper_mode: -2:1}"
|
|
_k3slr_mapper_other="${_k3slr_mapper_mode: -1}"
|
|
(( (10#$_k3slr_mapper_group & 2) == 0 && (10#$_k3slr_mapper_other & 2) == 0 ))
|
|
}
|
|
|
|
_k3slr_collect_prepackage_snapshot() {
|
|
local _k3slr_system_destination="${1-}"
|
|
local _k3slr_system_recovery_partition _k3slr_system_recovery_disk
|
|
local _k3slr_system_k3s_partition _k3slr_system_k3s_disk
|
|
local _k3slr_system_recovery_parent _k3slr_system_partition_mm
|
|
local _k3slr_system_recovery_model _k3slr_system_recovery_serial _k3slr_system_recovery_wwn
|
|
local _k3slr_system_recovery_mm _k3slr_system_k3s_parent _k3slr_system_k3s_partition_mm
|
|
local _k3slr_system_k3s_model _k3slr_system_k3s_serial _k3slr_system_k3s_wwn _k3slr_system_k3s_mm
|
|
local _k3slr_system_recovery_uuid _k3slr_system_recovery_partuuid _k3slr_system_recovery_type
|
|
local _k3slr_system_k3s_uuid _k3slr_system_k3s_partuuid _k3slr_system_k3s_type
|
|
local _k3slr_system_smart_raw _k3slr_system_smart _k3slr_system_loops
|
|
local _k3slr_system_mapper_metadata _k3slr_system_names _k3slr_system_snapshot
|
|
(( $# == 1 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_system_destination" _k3slr_system_ || return 1
|
|
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_partition \
|
|
/usr/bin/readlink -f -- "$K3SLR_RECOVERY_PARTITION_BY_ID" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_disk \
|
|
/usr/bin/readlink -f -- "$K3SLR_RECOVERY_DISK_BY_ID" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_partition \
|
|
/usr/bin/readlink -f -- "$K3SLR_K3S_PARTITION_BY_ID" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_disk \
|
|
/usr/bin/readlink -f -- "$K3SLR_K3S_DISK_BY_ID" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_parent \
|
|
/usr/bin/lsblk --noheadings --paths --output PKNAME -- "$_k3slr_system_recovery_partition" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_partition_mm \
|
|
/usr/bin/lsblk --noheadings --paths --output MAJ:MIN -- "$_k3slr_system_recovery_partition" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_model \
|
|
/usr/bin/lsblk --noheadings --paths --output MODEL -- "$_k3slr_system_recovery_disk" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_serial \
|
|
/usr/bin/lsblk --noheadings --paths --output SERIAL -- "$_k3slr_system_recovery_disk" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_wwn \
|
|
/usr/bin/lsblk --noheadings --paths --output WWN -- "$_k3slr_system_recovery_disk" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_mm \
|
|
/usr/bin/lsblk --noheadings --paths --output MAJ:MIN -- "$_k3slr_system_recovery_disk" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_parent \
|
|
/usr/bin/lsblk --noheadings --paths --output PKNAME -- "$_k3slr_system_k3s_partition" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_partition_mm \
|
|
/usr/bin/lsblk --noheadings --paths --output MAJ:MIN -- "$_k3slr_system_k3s_partition" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_model \
|
|
/usr/bin/lsblk --noheadings --paths --output MODEL -- "$_k3slr_system_k3s_disk" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_serial \
|
|
/usr/bin/lsblk --noheadings --paths --output SERIAL -- "$_k3slr_system_k3s_disk" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_wwn \
|
|
/usr/bin/lsblk --noheadings --paths --output WWN -- "$_k3slr_system_k3s_disk" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_mm \
|
|
/usr/bin/lsblk --noheadings --paths --output MAJ:MIN -- "$_k3slr_system_k3s_disk" || return 1
|
|
[[ "$_k3slr_system_recovery_parent" == "$_k3slr_system_recovery_disk" &&
|
|
"$_k3slr_system_k3s_parent" == "$_k3slr_system_k3s_disk" ]] || return 1
|
|
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_uuid \
|
|
/usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value --match-tag UUID -- "$_k3slr_system_recovery_partition" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_partuuid \
|
|
/usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value --match-tag PARTUUID -- "$_k3slr_system_recovery_partition" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_recovery_type \
|
|
/usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value --match-tag TYPE -- "$_k3slr_system_recovery_partition" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_uuid \
|
|
/usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value --match-tag UUID -- "$_k3slr_system_k3s_partition" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_partuuid \
|
|
/usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value --match-tag PARTUUID -- "$_k3slr_system_k3s_partition" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_k3s_type \
|
|
/usr/bin/sudo --non-interactive -- /usr/sbin/blkid --output value --match-tag TYPE -- "$_k3slr_system_k3s_partition" || return 1
|
|
_k3slr_capture_raw_success _k3slr_system_smart_raw \
|
|
/usr/bin/sudo --non-interactive -- /usr/sbin/smartctl -H -A "$_k3slr_system_recovery_disk" || return 1
|
|
_k3slr_parse_smart_evidence "$_k3slr_system_smart_raw" _k3slr_system_smart || return 1
|
|
|
|
_k3slr_capture_absent_record /usr/bin/sudo --non-interactive -- /usr/bin/findmnt \
|
|
--noheadings --raw --output SOURCE,FSTYPE,OPTIONS,ID,MAJ:MIN --mountpoint "$K3SLR_OUTER_MOUNT" || return 1
|
|
_k3slr_capture_absent_record /usr/bin/sudo --non-interactive -- /usr/bin/findmnt \
|
|
--noheadings --raw --output SOURCE,FSTYPE,OPTIONS,ID,MAJ:MIN --mountpoint "$K3SLR_INNER_MOUNT" || return 1
|
|
_k3slr_capture_absent_record /usr/bin/sudo --non-interactive -- /usr/bin/findmnt \
|
|
--noheadings --raw --output TARGET --source "$_k3slr_system_recovery_partition" || return 1
|
|
_k3slr_capture_raw_success _k3slr_system_loops /usr/bin/sudo --non-interactive -- \
|
|
/usr/sbin/losetup --list --noheadings --raw \
|
|
--output NAME,BACK-FILE,BACK-INO,BACK-MAJ:MIN,MAJ:MIN,OFFSET,SIZELIMIT || return 1
|
|
_k3slr_all_loops_exclude_backing_device "$_k3slr_system_loops" "$_k3slr_system_partition_mm" || return 1
|
|
_k3slr_capture_exact_one_line_record _k3slr_system_mapper_metadata \
|
|
/usr/bin/stat '--format=%F|%u|%g|%a' -- /dev/mapper || return 1
|
|
_k3slr_mapper_directory_is_safe "${_k3slr_system_mapper_metadata}"$'\n' || return 1
|
|
_k3slr_capture_empty_success /usr/bin/test ! -L /dev/mapper || return 1
|
|
_k3slr_capture_empty_success /usr/bin/test ! -e "/dev/mapper/${K3SLR_MAPPING_NAME}" || return 1
|
|
_k3slr_capture_empty_success /usr/bin/test ! -L "/dev/mapper/${K3SLR_MAPPING_NAME}" || return 1
|
|
_k3slr_capture_empty_success /usr/bin/test ! -e "/dev/mapper/${K3SLR_PROOF_MAPPING_NAME}" || return 1
|
|
_k3slr_capture_empty_success /usr/bin/test ! -L "/dev/mapper/${K3SLR_PROOF_MAPPING_NAME}" || return 1
|
|
_k3slr_capture_raw_success _k3slr_system_names \
|
|
/usr/bin/lsblk --noheadings --raw --output NAME,TYPE || return 1
|
|
_k3slr_mapper_name_scan_is_clear "$_k3slr_system_names" || return 1
|
|
_k3slr_capture_empty_success /usr/bin/sudo --non-interactive -- \
|
|
/usr/bin/ntfs-3g.probe --readwrite "$_k3slr_system_recovery_partition" || return 1
|
|
|
|
printf -v _k3slr_system_snapshot \
|
|
'canonical_partition=%s\npartition_major_minor=%s\ncanonical_parent_disk=%s\ndisk_major_minor=%s\nfilesystem_uuid=%s\npartuuid=%s\nfilesystem_type=%s\nmodel=%s\nserial=%s\nwwn=%s\ncanonical_k3s_partition=%s\nk3s_partition_major_minor=%s\ncanonical_k3s_disk=%s\nk3s_disk_major_minor=%s\nk3s_filesystem_uuid=%s\nk3s_partuuid=%s\nk3s_filesystem_type=%s\nk3s_model=%s\nk3s_serial=%s\nk3s_wwn=%s\nsmart_health=PASSED\nsmart_reallocated=0\nsmart_pending=0\nsmart_uncorrectable=0\nouter_mount=absent\ninner_mount=absent\nsource_mounts=absent\nloops=absent\nmapper_directory=physical-root-safe\nmapping_path=absent\nproof_mapping_path=absent\nmapping_names=absent\nntfs_probe=pass\n' \
|
|
"$_k3slr_system_recovery_partition" "$_k3slr_system_partition_mm" \
|
|
"$_k3slr_system_recovery_disk" "$_k3slr_system_recovery_mm" \
|
|
"$_k3slr_system_recovery_uuid" "$_k3slr_system_recovery_partuuid" \
|
|
"$_k3slr_system_recovery_type" "$_k3slr_system_recovery_model" \
|
|
"$_k3slr_system_recovery_serial" "$_k3slr_system_recovery_wwn" \
|
|
"$_k3slr_system_k3s_partition" "$_k3slr_system_k3s_partition_mm" \
|
|
"$_k3slr_system_k3s_disk" "$_k3slr_system_k3s_mm" \
|
|
"$_k3slr_system_k3s_uuid" "$_k3slr_system_k3s_partuuid" \
|
|
"$_k3slr_system_k3s_type" "$_k3slr_system_k3s_model" \
|
|
"$_k3slr_system_k3s_serial" "$_k3slr_system_k3s_wwn"
|
|
_k3slr_validate_prepackage_snapshot "$_k3slr_system_snapshot" || return 1
|
|
printf -v "$_k3slr_system_destination" '%s' "$_k3slr_system_snapshot"
|
|
}
|
|
|
|
_k3slr_capture_shell_function() {
|
|
local _k3slr_function_capture_stdout_name="${1-}"
|
|
local _k3slr_function_capture_stderr_name="${2-}"
|
|
local _k3slr_function_capture_rc_name="${3-}"
|
|
local _k3slr_function_capture_directory='' _k3slr_function_capture_stdout=''
|
|
local _k3slr_function_capture_stderr='' _k3slr_function_capture_status=0
|
|
shift 3 || return 1
|
|
(( $# > 0 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_function_capture_stdout_name" _k3slr_function_capture_ || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_function_capture_stderr_name" _k3slr_function_capture_ || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_function_capture_rc_name" _k3slr_function_capture_ || return 1
|
|
[[ "$_k3slr_function_capture_stdout_name" != "$_k3slr_function_capture_stderr_name" &&
|
|
"$_k3slr_function_capture_stdout_name" != "$_k3slr_function_capture_rc_name" &&
|
|
"$_k3slr_function_capture_stderr_name" != "$_k3slr_function_capture_rc_name" ]] || return 1
|
|
declare -F "$1" >/dev/null || return 1
|
|
_k3slr_capture_one_line _k3slr_function_capture_directory \
|
|
/usr/bin/mktemp --directory --tmpdir=/tmp 'k3slr-capture.XXXXXXXX' || return 1
|
|
_k3slr_capture_directory_is_safe "$_k3slr_function_capture_directory" || return 1
|
|
if "$@" >"${_k3slr_function_capture_directory}/stdout" \
|
|
2>"${_k3slr_function_capture_directory}/stderr"; then
|
|
_k3slr_function_capture_status=0
|
|
else
|
|
_k3slr_function_capture_status=$?
|
|
fi
|
|
if IFS= read -r -d '' _k3slr_function_capture_stdout \
|
|
<"${_k3slr_function_capture_directory}/stdout"; then
|
|
_k3slr_remove_capture_directory "$_k3slr_function_capture_directory" || :
|
|
return 1
|
|
fi
|
|
if IFS= read -r -d '' _k3slr_function_capture_stderr \
|
|
<"${_k3slr_function_capture_directory}/stderr"; then
|
|
_k3slr_remove_capture_directory "$_k3slr_function_capture_directory" || :
|
|
return 1
|
|
fi
|
|
_k3slr_remove_capture_directory "$_k3slr_function_capture_directory" || return 1
|
|
printf -v "$_k3slr_function_capture_stdout_name" '%s' "$_k3slr_function_capture_stdout"
|
|
printf -v "$_k3slr_function_capture_stderr_name" '%s' "$_k3slr_function_capture_stderr"
|
|
printf -v "$_k3slr_function_capture_rc_name" '%s' "$_k3slr_function_capture_status"
|
|
}
|
|
|
|
_k3slr_run_ntfsinfo_hex_pipeline() {
|
|
local _k3slr_pipeline_partition="${1-}" _k3slr_pipeline_program
|
|
local -a _k3slr_pipeline_statuses=()
|
|
(( $# == 1 )) || return 1
|
|
[[ "$_k3slr_pipeline_partition" =~ ^/dev/[A-Za-z0-9._/-]+$ ]] || return 1
|
|
_k3slr_pipeline_program=$'{\n for (i = 1; i <= NF; i++) {\n if ($i !~ /^[0-9a-f]{2}$/ || ++bytes > 65536) {\n bad = 1\n exit\n }\n hex = hex $i\n }\n}\nEND {\n if (bad || bytes == 0) exit 1\n print hex\n}'
|
|
_k3slr_command /usr/bin/sudo --non-interactive -- \
|
|
/usr/bin/ntfsinfo --mft --notime "$_k3slr_pipeline_partition" |
|
|
_k3slr_command /usr/bin/od -An -v -tx1 |
|
|
_k3slr_command /usr/bin/mawk "$_k3slr_pipeline_program"
|
|
_k3slr_pipeline_statuses=("${PIPESTATUS[@]}")
|
|
(( ${#_k3slr_pipeline_statuses[@]} == 3 &&
|
|
_k3slr_pipeline_statuses[0] == 0 &&
|
|
_k3slr_pipeline_statuses[1] == 0 &&
|
|
_k3slr_pipeline_statuses[2] == 0 ))
|
|
}
|
|
|
|
_k3slr_capture_ntfsinfo_hex() {
|
|
local _k3slr_ntfs_capture_destination="${1-}" _k3slr_ntfs_capture_partition="${2-}"
|
|
local _k3slr_ntfs_capture_stdout='' _k3slr_ntfs_capture_stderr='' _k3slr_ntfs_capture_rc=''
|
|
(( $# == 2 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_ntfs_capture_destination" _k3slr_ntfs_capture_ || return 1
|
|
_k3slr_capture_shell_function _k3slr_ntfs_capture_stdout _k3slr_ntfs_capture_stderr \
|
|
_k3slr_ntfs_capture_rc _k3slr_run_ntfsinfo_hex_pipeline "$_k3slr_ntfs_capture_partition" || return 1
|
|
[[ "$_k3slr_ntfs_capture_rc" == 0 && -z "$_k3slr_ntfs_capture_stderr" &&
|
|
"$_k3slr_ntfs_capture_stdout" =~ ^[0-9a-f]+$'\n'$ ]] || return 1
|
|
(( ${#_k3slr_ntfs_capture_stdout} > 1 && ${#_k3slr_ntfs_capture_stdout} <= 131073 &&
|
|
(${#_k3slr_ntfs_capture_stdout} - 1) % 2 == 0 )) || return 1
|
|
printf -v "$_k3slr_ntfs_capture_destination" '%s' "$_k3slr_ntfs_capture_stdout"
|
|
}
|
|
|
|
_k3slr_capture_one_line() {
|
|
local _k3slr_capture_framed _k3slr_capture_record _k3slr_capture_value
|
|
_k3slr_output_name_is_safe "$1" _k3slr_capture_ || return 1
|
|
_k3slr_capture_framed="$(
|
|
_k3slr_command "${@:2}"
|
|
_k3slr_capture_command_rc=$?
|
|
printf '\034%s' "$_k3slr_capture_command_rc"
|
|
)" || return 1
|
|
[[ "$_k3slr_capture_framed" == *$'\0340' ]] || return 1
|
|
_k3slr_capture_record="${_k3slr_capture_framed%$'\0340'}"
|
|
[[ "$_k3slr_capture_record" == *$'\n' ]] || return 1
|
|
_k3slr_capture_value="${_k3slr_capture_record%$'\n'}"
|
|
[[ -n "$_k3slr_capture_value" && "$_k3slr_capture_value" != *$'\n'* &&
|
|
"$_k3slr_capture_value" != *$'\r'* && "$_k3slr_capture_value" != *$'\034'* ]] || return 1
|
|
printf -v "$1" '%s' "$_k3slr_capture_value"
|
|
}
|
|
|
|
_k3slr_trusted_root_executable() {
|
|
local binary="$1" metadata uid mode kind group_digit other_digit
|
|
[[ "$binary" == /* ]] || return 1
|
|
_k3slr_command /usr/bin/test -f "$binary" >/dev/null 2>&1 || return 1
|
|
_k3slr_command /usr/bin/test ! -L "$binary" >/dev/null 2>&1 || return 1
|
|
_k3slr_command /usr/bin/test -x "$binary" >/dev/null 2>&1 || return 1
|
|
_k3slr_capture_one_line metadata /usr/bin/stat --format='%u|%a|%F' -- "$binary" 2>/dev/null || return 1
|
|
IFS='|' read -r uid mode kind <<<"$metadata"
|
|
[[ "$uid" == 0 && "$mode" =~ ^[0-7]{3,4}$ && "$kind" == 'regular file' ]] || return 1
|
|
group_digit="${mode: -2:1}"
|
|
other_digit="${mode: -1}"
|
|
(( (10#$group_digit & 2) == 0 && (10#$other_digit & 2) == 0 ))
|
|
}
|
|
|
|
_k3slr_pin_keepass_database() {
|
|
local database="$1" destination_name="$2" expected current canonical metadata uid
|
|
local component index=0 lineage=''
|
|
local -a components=()
|
|
[[ -n "${K3SLR_OUTER_MOUNT:-}" && -n "${K3SLR_DATABASE_RELATIVE:-}" ]] || return 1
|
|
expected="${K3SLR_OUTER_MOUNT}/${K3SLR_DATABASE_RELATIVE}"
|
|
[[ "$database" == "$expected" ]] || return 1
|
|
_k3slr_capture_one_line uid /usr/bin/id -u || return 1
|
|
[[ "$uid" =~ ^[0-9]+$ ]] || return 1
|
|
current="$K3SLR_OUTER_MOUNT"
|
|
IFS='/' read -r -a components <<<"$K3SLR_DATABASE_RELATIVE"
|
|
for component in '' "${components[@]}"; do
|
|
if (( index > 0 )); then current="${current}/${component}"; fi
|
|
_k3slr_command /usr/bin/test ! -L "$current" >/dev/null 2>&1 || return 1
|
|
_k3slr_capture_one_line canonical /usr/bin/readlink -e -- "$current" 2>/dev/null || return 1
|
|
[[ "$canonical" == "$current" ]] || return 1
|
|
_k3slr_capture_one_line metadata /usr/bin/stat --format='%d:%i|%F|%u|%a' -- "$current" 2>/dev/null || return 1
|
|
if (( index < ${#components[@]} )); then
|
|
[[ "$metadata" == *'|directory|'* ]] || return 1
|
|
else
|
|
[[ "$current" == "$database" && "$metadata" == *'|regular file|'* &&
|
|
"$metadata" == *"|${uid}|600" ]] || return 1
|
|
fi
|
|
lineage+="${metadata%|*|*},"
|
|
index=$((index + 1))
|
|
done
|
|
printf -v "$destination_name" '%s' "${lineage%,}"
|
|
}
|
|
|
|
_k3slr_keepass_database_lineage_matches() {
|
|
local database="$1" expected_snapshot="$2" current_snapshot=''
|
|
_k3slr_pin_keepass_database "$database" current_snapshot || return 1
|
|
[[ "$current_snapshot" == "$expected_snapshot" ]]
|
|
}
|
|
|
|
_k3slr_parse_keepass_attachment_listing() {
|
|
local _k3slr_attachment_record="${1-}" _k3slr_attachment_name="${2-}"
|
|
local _k3slr_attachment_destination="${3-}" _k3slr_attachment_prefix=''
|
|
local _k3slr_attachment_size=''
|
|
(( $# == 3 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_attachment_destination" _k3slr_attachment_ || return 1
|
|
[[ "$_k3slr_attachment_name" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,254}$ ]] || return 1
|
|
(( ${#_k3slr_attachment_record} >= 1 && ${#_k3slr_attachment_record} <= 8192 )) || return 1
|
|
[[ "$_k3slr_attachment_record" != *$'\r'* &&
|
|
"$_k3slr_attachment_record" == *$'\n' ]] || return 1
|
|
|
|
if [[ "$_k3slr_attachment_record" == "${K3SLR_KEEPASS_ENTRY}"$'\n\nNo attachments present.\n' ]]; then
|
|
printf -v "$_k3slr_attachment_destination" '%s' absent
|
|
return 0
|
|
fi
|
|
|
|
_k3slr_attachment_prefix="${K3SLR_KEEPASS_ENTRY}"$'\n\nAttachments:\n '"${_k3slr_attachment_name} ("
|
|
[[ "$_k3slr_attachment_record" == "$_k3slr_attachment_prefix"* ]] || return 1
|
|
_k3slr_attachment_size="${_k3slr_attachment_record#"$_k3slr_attachment_prefix"}"
|
|
[[ "$_k3slr_attachment_size" == *$'\n' ]] || return 1
|
|
_k3slr_attachment_size="${_k3slr_attachment_size%$'\n'}"
|
|
[[ "$_k3slr_attachment_size" =~ ^[0-9]+\.[0-9]\ (B|KiB|MiB|GiB)\)$ ]] || return 1
|
|
printf -v "$_k3slr_attachment_destination" '%s' present
|
|
}
|
|
|
|
_k3slr_capture_keepass_attachment_listing() {
|
|
local _k3slr_keepass_capture_destination="${1-}"
|
|
local _k3slr_keepass_capture_stdout_path="${2-}"
|
|
local _k3slr_keepass_capture_stderr_path="${3-}"
|
|
local _k3slr_keepass_capture_stdout_fd='' _k3slr_keepass_capture_stderr_fd=''
|
|
local _k3slr_keepass_capture_size='' _k3slr_keepass_capture_record=''
|
|
local _k3slr_keepass_capture_command_rc=0 _k3slr_keepass_capture_cleanup_rc=0
|
|
local _k3slr_keepass_capture_had_noclobber=false
|
|
shift 3 || return 1
|
|
(( $# > 0 )) || return 1
|
|
_k3slr_output_name_is_safe "$_k3slr_keepass_capture_destination" \
|
|
_k3slr_keepass_capture_ || return 1
|
|
declare -F "$1" >/dev/null || return 1
|
|
[[ "$_k3slr_keepass_capture_stdout_path" =~ ^/[A-Za-z0-9._/-]+$ &&
|
|
"$_k3slr_keepass_capture_stderr_path" =~ ^/[A-Za-z0-9._/-]+$ &&
|
|
"$_k3slr_keepass_capture_stdout_path" != *'//'* &&
|
|
"$_k3slr_keepass_capture_stderr_path" != *'//'* &&
|
|
"$_k3slr_keepass_capture_stdout_path" != *'/../'* &&
|
|
"$_k3slr_keepass_capture_stderr_path" != *'/../'* &&
|
|
"$_k3slr_keepass_capture_stdout_path" != "$_k3slr_keepass_capture_stderr_path" &&
|
|
! -e "$_k3slr_keepass_capture_stdout_path" && ! -L "$_k3slr_keepass_capture_stdout_path" &&
|
|
! -e "$_k3slr_keepass_capture_stderr_path" && ! -L "$_k3slr_keepass_capture_stderr_path" ]] || return 1
|
|
|
|
[[ -o noclobber ]] && _k3slr_keepass_capture_had_noclobber=true
|
|
set -o noclobber
|
|
if ! exec {_k3slr_keepass_capture_stdout_fd}>"$_k3slr_keepass_capture_stdout_path"; then
|
|
"$_k3slr_keepass_capture_had_noclobber" || set +o noclobber
|
|
return 1
|
|
fi
|
|
if ! exec {_k3slr_keepass_capture_stderr_fd}>"$_k3slr_keepass_capture_stderr_path"; then
|
|
exec {_k3slr_keepass_capture_stdout_fd}>&-
|
|
"$_k3slr_keepass_capture_had_noclobber" || set +o noclobber
|
|
_k3slr_command /usr/bin/rm -- "$_k3slr_keepass_capture_stdout_path" >/dev/null 2>&1 || :
|
|
return 1
|
|
fi
|
|
"$_k3slr_keepass_capture_had_noclobber" || set +o noclobber
|
|
|
|
if "$@" >&"$_k3slr_keepass_capture_stdout_fd" 2>&"$_k3slr_keepass_capture_stderr_fd"; then
|
|
_k3slr_keepass_capture_command_rc=0
|
|
else
|
|
_k3slr_keepass_capture_command_rc=$?
|
|
fi
|
|
exec {_k3slr_keepass_capture_stdout_fd}>&-
|
|
exec {_k3slr_keepass_capture_stderr_fd}>&-
|
|
|
|
if (( _k3slr_keepass_capture_command_rc == 0 )) &&
|
|
[[ -f "$_k3slr_keepass_capture_stdout_path" && ! -L "$_k3slr_keepass_capture_stdout_path" &&
|
|
-f "$_k3slr_keepass_capture_stderr_path" && ! -L "$_k3slr_keepass_capture_stderr_path" &&
|
|
! -s "$_k3slr_keepass_capture_stderr_path" ]] &&
|
|
_k3slr_capture_one_line _k3slr_keepass_capture_size /usr/bin/stat \
|
|
--format='%s' -- "$_k3slr_keepass_capture_stdout_path" &&
|
|
_k3slr_uint "$_k3slr_keepass_capture_size" &&
|
|
(( 10#$_k3slr_keepass_capture_size >= 1 && 10#$_k3slr_keepass_capture_size <= 8192 )); then
|
|
if IFS= read -r -d '' _k3slr_keepass_capture_record \
|
|
<"$_k3slr_keepass_capture_stdout_path"; then
|
|
_k3slr_keepass_capture_command_rc=1
|
|
fi
|
|
else
|
|
_k3slr_keepass_capture_command_rc=1
|
|
fi
|
|
|
|
_k3slr_command /usr/bin/rm -- \
|
|
"$_k3slr_keepass_capture_stdout_path" \
|
|
"$_k3slr_keepass_capture_stderr_path" >/dev/null 2>&1 ||
|
|
_k3slr_keepass_capture_cleanup_rc=1
|
|
(( _k3slr_keepass_capture_command_rc == 0 && _k3slr_keepass_capture_cleanup_rc == 0 )) || return 1
|
|
printf -v "$_k3slr_keepass_capture_destination" '%s' "$_k3slr_keepass_capture_record"
|
|
}
|
|
|
|
_k3slr_keepass_password_stdout() {
|
|
local database="${1-}" entry="${2-}"
|
|
(( $# == 2 )) || return 1
|
|
_k3slr_xtrace_is_disabled || return 1
|
|
[[ "$entry" == "$K3SLR_KEEPASS_ENTRY" ]] || return 1
|
|
_k3slr_command /usr/bin/test -t 0 >/dev/null 2>&1 || return 1
|
|
_k3slr_command /usr/bin/keepassxc-cli show --show-protected \
|
|
--attributes Password "$database" "$entry"
|
|
}
|
|
|
|
_k3slr_validated_keepass_stdout() {
|
|
local database="$1" entry="$2" stage_fd='' stage_pid='' stage_rc
|
|
exec {stage_fd}< <(
|
|
set -o pipefail
|
|
_k3slr_keepass_password_stdout "$database" "$entry" |
|
|
_k3slr_command /usr/bin/od -An -v -tx1 |
|
|
_k3slr_command /usr/bin/mawk '
|
|
function digit(c) { return index("0123456789abcdef", c) - 1 }
|
|
function byte_value(h) { return 16 * digit(substr(h, 1, 1)) + digit(substr(h, 2, 1)) }
|
|
{
|
|
for (i=1; i<=NF; i++) {
|
|
n++; byte=$i
|
|
if (n <= 40) {
|
|
if (byte ~ /^3[0-9]$/) numeric=1
|
|
else if (byte ~ /^(4[1-9a-f]|5[0-9a])$/) uppercase=1
|
|
else if (byte ~ /^(6[1-9a-f]|7[0-9a])$/) lowercase=1
|
|
else invalid=1
|
|
if (!invalid) value=value sprintf("%c", byte_value(byte))
|
|
} else if (n == 41) {
|
|
if (byte != "0a") invalid=1
|
|
} else invalid=1
|
|
}
|
|
}
|
|
END {
|
|
if (n == 41 && !invalid && lowercase && uppercase && numeric) printf "%s\n", value
|
|
else exit 1
|
|
}
|
|
'
|
|
)
|
|
stage_pid=$!
|
|
_k3slr_lifecycle_boundary validator-stage-published "$stage_pid"
|
|
if wait "$stage_pid"; then stage_rc=0; else stage_rc=$?; fi
|
|
if (( stage_rc == 0 )); then
|
|
_k3slr_command /usr/bin/dd iflag=fullblock bs=41 count=1 status=none <&"$stage_fd" || stage_rc=$?
|
|
fi
|
|
exec {stage_fd}<&-
|
|
return "$stage_rc"
|
|
}
|
|
|
|
_k3slr_cached_sudo_is_available() {
|
|
_k3slr_trusted_root_executable /usr/bin/sudo || return 1
|
|
_k3slr_command /usr/bin/sudo --non-interactive --validate >/dev/null 2>&1
|
|
}
|
|
|
|
_k3slr_loop_device_is_valid() {
|
|
local loop_device="$1" canonical kind
|
|
[[ "$loop_device" =~ ^/dev/loop(0|[1-9][0-9]*)$ ]] || return 1
|
|
_k3slr_command /usr/bin/test -b "$loop_device" >/dev/null 2>&1 || return 1
|
|
_k3slr_command /usr/bin/test ! -L "$loop_device" >/dev/null 2>&1 || return 1
|
|
_k3slr_capture_one_line canonical /usr/bin/readlink -e -- "$loop_device" 2>/dev/null || return 1
|
|
[[ "$canonical" == "$loop_device" ]] || return 1
|
|
_k3slr_capture_one_line kind /usr/bin/stat --format='%F' -- "$loop_device" 2>/dev/null || return 1
|
|
[[ "$kind" == 'block special file' ]]
|
|
}
|
|
|
|
_k3slr_mapping_name_is_allowed() {
|
|
local mapping_name="$1"
|
|
[[ -n "${K3SLR_MAPPING_NAME:-}" && -n "${K3SLR_PROOF_MAPPING_NAME:-}" ]] || return 1
|
|
[[ "$mapping_name" == "$K3SLR_MAPPING_NAME" || "$mapping_name" == "$K3SLR_PROOF_MAPPING_NAME" ]]
|
|
}
|
|
|
|
_k3slr_cryptsetup_format_stdin() {
|
|
local loop_device="${1-}"
|
|
(( $# == 1 )) || return 1
|
|
_k3slr_xtrace_is_disabled || return 1
|
|
_k3slr_command /usr/bin/sudo --non-interactive -- /usr/sbin/cryptsetup luksFormat \
|
|
--batch-mode --type luks2 --key-file=- "$loop_device"
|
|
}
|
|
|
|
_k3slr_cryptsetup_open_stdin() {
|
|
local loop_device="${1-}" mapping_name="${2-}"
|
|
(( $# == 2 )) || return 1
|
|
_k3slr_xtrace_is_disabled || return 1
|
|
_k3slr_command /usr/bin/sudo --non-interactive -- /usr/sbin/cryptsetup open \
|
|
--type luks2 --key-file=- "$loop_device" "$mapping_name"
|
|
}
|
|
|
|
_k3slr_cryptsetup_open_readonly_stdin() {
|
|
local loop_device="${1-}" mapping_name="${2-}"
|
|
(( $# == 2 )) || return 1
|
|
_k3slr_xtrace_is_disabled || return 1
|
|
_k3slr_command /usr/bin/sudo --non-interactive -- /usr/sbin/cryptsetup open \
|
|
--readonly --type luks2 --key-file=- "$loop_device" "$mapping_name"
|
|
}
|
|
|
|
_k3slr_password_pipe_preflight() {
|
|
local destination_name="$1" database="$2" entry="$3" loop_device="$4" mapping_name="${5-}"
|
|
local snapshot='' binary
|
|
_k3slr_xtrace_is_disabled || return 1
|
|
_k3slr_command /usr/bin/test -t 0 >/dev/null 2>&1 || return 1
|
|
[[ "$entry" == "$K3SLR_KEEPASS_ENTRY" ]] || return 1
|
|
for binary in /usr/bin/test /usr/bin/stat /usr/bin/readlink /usr/bin/id \
|
|
/usr/bin/od /usr/bin/mawk /usr/bin/dd /usr/bin/ps /usr/bin/sleep /usr/bin/sudo \
|
|
/usr/bin/keepassxc-cli /usr/sbin/cryptsetup; do
|
|
_k3slr_trusted_root_executable "$binary" || return 1
|
|
done
|
|
_k3slr_pin_keepass_database "$database" snapshot || return 1
|
|
_k3slr_loop_device_is_valid "$loop_device" || return 1
|
|
if [[ -n "$mapping_name" ]]; then _k3slr_mapping_name_is_allowed "$mapping_name" || return 1; fi
|
|
_k3slr_cached_sudo_is_available || return 1
|
|
printf -v "$destination_name" '%s' "$snapshot"
|
|
}
|
|
|
|
_k3slr_process_group_for_pid() {
|
|
local _k3slr_group_capture='' _k3slr_group_resolved=''
|
|
_k3slr_output_name_is_safe "$1" _k3slr_group_ || return 1
|
|
[[ "$2" =~ ^[1-9][0-9]*$ ]] || return 1
|
|
_k3slr_capture_one_line _k3slr_group_capture /usr/bin/ps -o pgid= -p "$2" || return 1
|
|
_k3slr_group_resolved="${_k3slr_group_capture//[[:space:]]/}"
|
|
[[ "$_k3slr_group_resolved" =~ ^[1-9][0-9]*$ ]] || return 1
|
|
printf -v "$1" '%s' "$_k3slr_group_resolved"
|
|
}
|
|
|
|
_k3slr_read_exact_record() {
|
|
local _k3slr_exact_value='' _k3slr_exact_trailing='' _k3slr_exact_fd=''
|
|
_k3slr_output_name_is_safe "$1" _k3slr_exact_ || return 1
|
|
exec {_k3slr_exact_fd}<"$2" || return 1
|
|
if ! IFS= read -r _k3slr_exact_value <&"$_k3slr_exact_fd"; then
|
|
exec {_k3slr_exact_fd}<&-
|
|
return 1
|
|
fi
|
|
if IFS= read -r _k3slr_exact_trailing <&"$_k3slr_exact_fd" || [[ -n "$_k3slr_exact_trailing" ]]; then
|
|
exec {_k3slr_exact_fd}<&-
|
|
return 1
|
|
fi
|
|
exec {_k3slr_exact_fd}<&-
|
|
[[ -n "$_k3slr_exact_value" && "$_k3slr_exact_value" != *$'\r'* &&
|
|
"$_k3slr_exact_value" != *$'\n'* ]] || return 1
|
|
printf -v "$1" '%s' "$_k3slr_exact_value"
|
|
}
|
|
|
|
_k3slr_proc_stat_record() {
|
|
local _k3slr_stat_value=''
|
|
_k3slr_output_name_is_safe "$1" _k3slr_stat_ || return 1
|
|
[[ "$2" =~ ^[1-9][0-9]*$ ]] || return 1
|
|
_k3slr_read_exact_record _k3slr_stat_value "/proc/${2}/stat" || return 1
|
|
printf -v "$1" '%s' "$_k3slr_stat_value"
|
|
}
|
|
|
|
_k3slr_parse_proc_stat_record() {
|
|
local _k3slr_parse_suffix='' _k3slr_parse_state='' _k3slr_parse_parent=''
|
|
local _k3slr_parse_group='' _k3slr_parse_start='' _k3slr_parse_prefix=''
|
|
local _k3slr_parse_after_comm=''
|
|
local -a _k3slr_parse_fields=()
|
|
[[ "$1" != "$2" ]] &&
|
|
_k3slr_output_name_is_safe "$1" _k3slr_parse_ &&
|
|
_k3slr_output_name_is_safe "$2" _k3slr_parse_ || return 1
|
|
[[ "$3" =~ ^[1-9][0-9]*$ ]] || return 1
|
|
[[ -n "$4" && "$4" != *$'\r'* && "$4" != *$'\n'* ]] || return 1
|
|
_k3slr_parse_prefix="${4%)*}"
|
|
_k3slr_parse_after_comm="${4##*)}"
|
|
[[ "$_k3slr_parse_prefix" != "$4" &&
|
|
"$_k3slr_parse_prefix" == "${3} ("* &&
|
|
"$_k3slr_parse_after_comm" == ' '* ]] || return 1
|
|
_k3slr_parse_suffix="${_k3slr_parse_after_comm:1}"
|
|
[[ -n "$_k3slr_parse_suffix" && "$_k3slr_parse_suffix" != [[:space:]]* ]] || return 1
|
|
read -r -a _k3slr_parse_fields <<<"$_k3slr_parse_suffix"
|
|
(( ${#_k3slr_parse_fields[@]} >= 20 )) || return 1
|
|
_k3slr_parse_state="${_k3slr_parse_fields[0]}"
|
|
_k3slr_parse_parent="${_k3slr_parse_fields[1]}"
|
|
_k3slr_parse_group="${_k3slr_parse_fields[2]}"
|
|
_k3slr_parse_start="${_k3slr_parse_fields[19]}"
|
|
[[ "$_k3slr_parse_state" =~ ^[RSDZTWtXxKPI]$ &&
|
|
"$_k3slr_parse_parent" =~ ^(0|[1-9][0-9]*)$ &&
|
|
"$_k3slr_parse_group" =~ ^[1-9][0-9]*$ &&
|
|
"$_k3slr_parse_start" =~ ^[1-9][0-9]*$ ]] || return 1
|
|
printf -v "$1" '%s' "${3}|${_k3slr_parse_start}|${_k3slr_parse_parent}|${_k3slr_parse_group}"
|
|
printf -v "$2" '%s' "$_k3slr_parse_state"
|
|
}
|
|
|
|
_k3slr_process_record() {
|
|
local _k3slr_record_value='' _k3slr_record_identity='' _k3slr_record_state=''
|
|
[[ "$1" != "$2" ]] &&
|
|
_k3slr_output_name_is_safe "$1" _k3slr_record_ &&
|
|
_k3slr_output_name_is_safe "$2" _k3slr_record_ || return 1
|
|
_k3slr_proc_stat_record _k3slr_record_value "$3" || return 1
|
|
_k3slr_parse_proc_stat_record _k3slr_record_identity _k3slr_record_state \
|
|
"$3" "$_k3slr_record_value" || return 1
|
|
printf -v "$1" '%s' "$_k3slr_record_identity"
|
|
printf -v "$2" '%s' "$_k3slr_record_state"
|
|
}
|
|
|
|
_k3slr_process_identity() {
|
|
local _k3slr_identity_value='' _k3slr_identity_state=''
|
|
_k3slr_output_name_is_safe "$1" _k3slr_identity_ || return 1
|
|
_k3slr_process_record _k3slr_identity_value _k3slr_identity_state "$2" || return 1
|
|
printf -v "$1" '%s' "$_k3slr_identity_value"
|
|
}
|
|
|
|
_k3slr_wait_for_stopped_group() {
|
|
local _k3slr_stopped_current_identity='' _k3slr_stopped_state='' _k3slr_stopped_attempt
|
|
local _k3slr_stopped_group_id='' _k3slr_stopped_identity_pid='' _k3slr_stopped_start_time=''
|
|
local _k3slr_stopped_parent_pid='' _k3slr_stopped_identity_group=''
|
|
[[ "$1" != "$2" ]] &&
|
|
_k3slr_output_name_is_safe "$1" _k3slr_stopped_ &&
|
|
_k3slr_output_name_is_safe "$2" _k3slr_stopped_ || return 1
|
|
for ((_k3slr_stopped_attempt=0; _k3slr_stopped_attempt<200; _k3slr_stopped_attempt++)); do
|
|
if _k3slr_process_record _k3slr_stopped_current_identity _k3slr_stopped_state "$3"; then
|
|
IFS='|' read -r _k3slr_stopped_identity_pid _k3slr_stopped_start_time \
|
|
_k3slr_stopped_parent_pid _k3slr_stopped_identity_group <<<"$_k3slr_stopped_current_identity"
|
|
if [[ "$_k3slr_stopped_state" =~ ^[Tt]$ && "$_k3slr_stopped_parent_pid" == "$BASHPID" ]]; then
|
|
printf -v "$2" '%s' "$_k3slr_stopped_current_identity"
|
|
_k3slr_process_group_for_pid _k3slr_stopped_group_id "$3" || return 1
|
|
[[ "$_k3slr_stopped_group_id" == "$_k3slr_stopped_identity_group" ]] || return 1
|
|
printf -v "$1" '%s' "$_k3slr_stopped_group_id"
|
|
return 0
|
|
fi
|
|
fi
|
|
_k3slr_command /usr/bin/sleep 0.01 || return 1
|
|
done
|
|
return 1
|
|
}
|
|
|
|
_k3slr_group_exists() {
|
|
local group_id="$1"
|
|
[[ "$group_id" =~ ^[1-9][0-9]*$ ]] || return 2
|
|
kill -0 -- "-${group_id}" >/dev/null 2>&1
|
|
}
|
|
|
|
_k3slr_wait_for_group_exit() {
|
|
local group_id="$1" maximum_attempts="${2:-100}" attempt
|
|
[[ "$maximum_attempts" =~ ^[1-9][0-9]*$ ]] || return 1
|
|
for ((attempt=0; attempt<maximum_attempts; attempt++)); do
|
|
_k3slr_group_exists "$group_id" || return 0
|
|
_k3slr_command /usr/bin/sleep 0.01 || return 1
|
|
done
|
|
return 1
|
|
}
|
|
|
|
_k3slr_group_authority_is_current() {
|
|
local child_pid="$1" group_id="$2" expected_identity="$3"
|
|
local current_identity='' state='' caller_group='' observed_group=''
|
|
local identity_pid='' start_time='' parent_pid='' identity_group=''
|
|
_k3slr_process_record current_identity state "$child_pid" || return 1
|
|
[[ "$current_identity" == "$expected_identity" ]] || return 1
|
|
IFS='|' read -r identity_pid start_time parent_pid identity_group <<<"$current_identity"
|
|
[[ "$identity_pid" == "$child_pid" && "$parent_pid" == "$BASHPID" &&
|
|
"$identity_group" == "$group_id" ]] || return 1
|
|
_k3slr_process_group_for_pid caller_group "$BASHPID" 2>/dev/null || return 1
|
|
_k3slr_process_group_for_pid observed_group "$child_pid" 2>/dev/null || return 1
|
|
[[ "$observed_group" == "$group_id" && "$group_id" != "$caller_group" ]]
|
|
}
|
|
|
|
_k3slr_signal_pid() {
|
|
local signal_name="$1" child_pid="$2" expected_identity="$3" expected_parent="$4" expected_group="${5-}"
|
|
local current_identity='' state='' identity_pid='' start_time='' parent_pid='' identity_group=''
|
|
local caller_group='' observed_group=''
|
|
[[ "$signal_name" =~ ^(TERM|CONT|KILL)$ ]] || return 1
|
|
_k3slr_process_record current_identity state "$child_pid" || return 1
|
|
[[ "$current_identity" == "$expected_identity" ]] || return 1
|
|
IFS='|' read -r identity_pid start_time parent_pid identity_group <<<"$current_identity"
|
|
[[ "$identity_pid" == "$child_pid" && "$parent_pid" == "$expected_parent" ]] || return 1
|
|
if [[ -n "$expected_group" ]]; then
|
|
[[ "$identity_group" == "$expected_group" ]] || return 1
|
|
_k3slr_process_group_for_pid caller_group "$BASHPID" 2>/dev/null || return 1
|
|
_k3slr_process_group_for_pid observed_group "$child_pid" 2>/dev/null || return 1
|
|
[[ "$observed_group" == "$expected_group" && "$expected_group" != "$caller_group" ]] || return 1
|
|
fi
|
|
kill -"$signal_name" "$child_pid" >/dev/null 2>&1
|
|
}
|
|
|
|
_k3slr_signal_group() {
|
|
local signal_name="$1" child_pid="$2" group_id="$3" expected_identity="$4"
|
|
[[ "$signal_name" =~ ^(TERM|CONT|KILL)$ ]] || return 1
|
|
_k3slr_group_authority_is_current "$child_pid" "$group_id" "$expected_identity" || return 1
|
|
kill -"$signal_name" -- "-${group_id}" >/dev/null 2>&1
|
|
}
|
|
|
|
_k3slr_signal_published_pid() {
|
|
local signal_name="$1" child_pid="$2"
|
|
[[ "$signal_name" == KILL && "$child_pid" =~ ^[1-9][0-9]*$ ]] || return 1
|
|
kill -KILL "$child_pid" >/dev/null 2>&1
|
|
}
|
|
|
|
_k3slr_wait_child() {
|
|
wait "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
_k3slr_terminate_published_child() {
|
|
local child_pid="$1" wait_rc
|
|
[[ "$child_pid" =~ ^[1-9][0-9]*$ ]] || return 1
|
|
_k3slr_signal_published_pid KILL "$child_pid" || {
|
|
kill -0 "$child_pid" >/dev/null 2>&1 && return 1
|
|
}
|
|
if _k3slr_wait_child "$child_pid"; then wait_rc=0; else wait_rc=$?; fi
|
|
(( wait_rc != 127 )) || return 1
|
|
! kill -0 "$child_pid" >/dev/null 2>&1
|
|
}
|
|
|
|
_k3slr_continue_owned_child() {
|
|
local child_pid="$1" group_id="$2" expected_identity="$3"
|
|
_k3slr_signal_pid CONT "$child_pid" "$expected_identity" "$BASHPID" "$group_id"
|
|
}
|
|
|
|
_k3slr_terminate_direct_stopped_child() {
|
|
local child_pid="$1" expected_identity="$2" current_identity='' state='' wait_rc
|
|
_k3slr_process_record current_identity state "$child_pid" || return 1
|
|
local identity_pid='' start_time='' parent_pid='' identity_group=''
|
|
IFS='|' read -r identity_pid start_time parent_pid identity_group <<<"$current_identity"
|
|
[[ "$current_identity" == "$expected_identity" && "$state" =~ ^[Tt]$ &&
|
|
"$identity_pid" == "$child_pid" && "$parent_pid" == "$BASHPID" ]] || return 1
|
|
_k3slr_signal_pid TERM "$child_pid" "$expected_identity" "$BASHPID" || return 1
|
|
if kill -0 "$child_pid" >/dev/null 2>&1; then
|
|
_k3slr_process_record current_identity state "$child_pid" || return 1
|
|
IFS='|' read -r identity_pid start_time parent_pid identity_group <<<"$current_identity"
|
|
[[ "$current_identity" == "$expected_identity" && "$identity_pid" == "$child_pid" &&
|
|
"$parent_pid" == "$BASHPID" ]] || return 1
|
|
_k3slr_signal_pid CONT "$child_pid" "$expected_identity" "$BASHPID" || return 1
|
|
fi
|
|
for ((wait_rc=0; wait_rc<100; wait_rc++)); do
|
|
if ! kill -0 "$child_pid" >/dev/null 2>&1; then break; fi
|
|
_k3slr_command /usr/bin/sleep 0.01 || return 1
|
|
done
|
|
if kill -0 "$child_pid" >/dev/null 2>&1; then
|
|
_k3slr_process_record current_identity state "$child_pid" || return 1
|
|
IFS='|' read -r identity_pid start_time parent_pid identity_group <<<"$current_identity"
|
|
[[ "$current_identity" == "$expected_identity" && "$identity_pid" == "$child_pid" &&
|
|
"$parent_pid" == "$BASHPID" ]] || return 1
|
|
_k3slr_signal_pid KILL "$child_pid" "$expected_identity" "$BASHPID" || return 1
|
|
fi
|
|
if _k3slr_wait_child "$child_pid"; then wait_rc=0; else wait_rc=$?; fi
|
|
(( wait_rc != 127 )) || return 1
|
|
! _k3slr_process_identity current_identity "$child_pid" 2>/dev/null
|
|
}
|
|
|
|
_k3slr_terminate_and_reap() {
|
|
local child_pid="$1" group_id="${2-}" expected_identity="${3-}"
|
|
local caller_group='' observed_group='' current_identity='' state='' wait_rc
|
|
local identity_pid='' start_time='' parent_pid='' identity_group=''
|
|
[[ "$child_pid" =~ ^[1-9][0-9]*$ &&
|
|
( -z "$group_id" || "$group_id" =~ ^[1-9][0-9]*$ ) &&
|
|
-n "$expected_identity" ]] || return 1
|
|
_k3slr_process_record current_identity state "$child_pid" || return 1
|
|
[[ "$current_identity" == "$expected_identity" ]] || return 1
|
|
IFS='|' read -r identity_pid start_time parent_pid identity_group <<<"$current_identity"
|
|
[[ "$identity_pid" == "$child_pid" && "$parent_pid" == "$BASHPID" ]] || return 1
|
|
|
|
if [[ -z "$group_id" ]] ||
|
|
! _k3slr_process_group_for_pid caller_group "$BASHPID" 2>/dev/null ||
|
|
! _k3slr_process_group_for_pid observed_group "$child_pid" 2>/dev/null ||
|
|
[[ "$observed_group" != "$identity_group" || "$observed_group" != "$group_id" ||
|
|
"$group_id" == "$caller_group" ]]; then
|
|
_k3slr_terminate_direct_stopped_child "$child_pid" "$expected_identity"
|
|
return
|
|
fi
|
|
|
|
_k3slr_signal_group TERM "$child_pid" "$group_id" "$expected_identity" || {
|
|
_k3slr_group_exists "$group_id" && return 1
|
|
}
|
|
if _k3slr_group_exists "$group_id"; then
|
|
if _k3slr_group_authority_is_current "$child_pid" "$group_id" "$expected_identity"; then
|
|
_k3slr_signal_group CONT "$child_pid" "$group_id" "$expected_identity" || {
|
|
_k3slr_group_exists "$group_id" && return 1
|
|
}
|
|
else
|
|
_k3slr_wait_for_group_exit "$group_id" || return 1
|
|
fi
|
|
fi
|
|
if ! _k3slr_wait_for_group_exit "$group_id" 25; then
|
|
if _k3slr_group_authority_is_current "$child_pid" "$group_id" "$expected_identity"; then
|
|
_k3slr_signal_group KILL "$child_pid" "$group_id" "$expected_identity" || {
|
|
_k3slr_group_exists "$group_id" && return 1
|
|
}
|
|
else
|
|
_k3slr_wait_for_group_exit "$group_id" || return 1
|
|
fi
|
|
fi
|
|
if _k3slr_wait_child "$child_pid"; then wait_rc=0; else wait_rc=$?; fi
|
|
(( wait_rc != 127 )) || return 1
|
|
_k3slr_wait_for_group_exit "$group_id"
|
|
}
|
|
|
|
_k3slr_lifecycle_boundary() {
|
|
:
|
|
}
|
|
|
|
_k3slr_run_password_pipe() (
|
|
local operation="$1" database="$2" entry="$3" loop_device="$4" mapping_name="${5-}"
|
|
local database_snapshot='' producer_pid='' producer_pgid='' producer_source_fd='' pipe_fd='' tty_fd=''
|
|
local producer_identity='' stopped_identity='' current_identity='' caller_pgid='' monitor_enabled=false producer_rc consumer_rc
|
|
local lifecycle_state='empty' pending_signal='' pending_status=''
|
|
_k3slr_password_pipe_preflight database_snapshot "$database" "$entry" "$loop_device" "$mapping_name" || return 1
|
|
_k3slr_process_group_for_pid caller_pgid "$BASHPID" || return 1
|
|
|
|
_k3slr_password_pipe_cleanup() {
|
|
local cleanup_rc=0
|
|
if "$monitor_enabled"; then set +m; monitor_enabled=false; fi
|
|
if [[ -n "$tty_fd" ]]; then exec {tty_fd}<&- 2>/dev/null || true; tty_fd=''; fi
|
|
if [[ -n "$pipe_fd" ]]; then exec {pipe_fd}<&- 2>/dev/null || true; pipe_fd=''; fi
|
|
if [[ -n "$producer_source_fd" ]]; then exec {producer_source_fd}<&- 2>/dev/null || true; producer_source_fd=''; fi
|
|
if [[ -n "$producer_pid" ]]; then
|
|
if [[ "$lifecycle_state" == published ]]; then
|
|
_k3slr_terminate_published_child "$producer_pid" || cleanup_rc=$?
|
|
elif [[ -n "$producer_identity" ]]; then
|
|
_k3slr_terminate_and_reap "$producer_pid" "$producer_pgid" "$producer_identity" || cleanup_rc=$?
|
|
else
|
|
cleanup_rc=1
|
|
fi
|
|
if (( cleanup_rc == 0 )); then
|
|
producer_pid=''
|
|
producer_pgid=''
|
|
producer_identity=''
|
|
lifecycle_state='reaped'
|
|
fi
|
|
fi
|
|
return "$cleanup_rc"
|
|
}
|
|
_k3slr_password_pipe_signal() {
|
|
if [[ "$1" == TERM || "$pending_signal" != TERM ]]; then
|
|
pending_signal="$1"
|
|
pending_status="$2"
|
|
fi
|
|
}
|
|
_k3slr_password_pipe_pending_checkpoint() {
|
|
local signal_status cleanup_rc=0
|
|
[[ -n "$pending_signal" ]] || return 0
|
|
signal_status="$pending_status"
|
|
if [[ -n "$producer_pid" ]]; then
|
|
[[ -n "$producer_identity" ]] || return 1
|
|
_k3slr_password_pipe_cleanup || cleanup_rc=$?
|
|
fi
|
|
(( cleanup_rc == 0 )) || return 1
|
|
return "$signal_status"
|
|
}
|
|
_k3slr_password_pipe_exit_cleanup() {
|
|
local original_status="$1" cleanup_rc=0
|
|
trap - EXIT
|
|
_k3slr_password_pipe_cleanup || cleanup_rc=$?
|
|
(( cleanup_rc == 0 )) || exit 1
|
|
exit "$original_status"
|
|
}
|
|
trap '_k3slr_password_pipe_signal INT 130' INT
|
|
trap '_k3slr_password_pipe_signal TERM 143' TERM
|
|
trap '_k3slr_password_pipe_exit_cleanup "$?"' EXIT
|
|
|
|
exec {tty_fd}<&0 || return 1
|
|
set -m
|
|
monitor_enabled=true
|
|
lifecycle_state='launching'
|
|
_k3slr_lifecycle_boundary coproc-launch-before
|
|
_k3slr_password_pipe_pending_checkpoint || return $?
|
|
coproc K3SLR_PASSWORD_PRODUCER {
|
|
producer_cancel=''
|
|
trap '[[ "$producer_cancel" == TERM ]] || producer_cancel=INT' INT
|
|
trap 'producer_cancel=TERM' TERM
|
|
kill -STOP "$BASHPID"
|
|
producer_worker_rc=143
|
|
if [[ -z "$producer_cancel" ]]; then
|
|
if _k3slr_validated_keepass_stdout "$database" "$entry" <&"$tty_fd"; then
|
|
producer_worker_rc=0
|
|
else
|
|
producer_worker_rc=$?
|
|
fi
|
|
fi
|
|
while [[ -n "$producer_cancel" ]]; do
|
|
_k3slr_command /usr/bin/sleep 0.01 || :
|
|
done
|
|
exit "$producer_worker_rc"
|
|
}
|
|
producer_pid="$K3SLR_PASSWORD_PRODUCER_PID"
|
|
lifecycle_state='published'
|
|
_k3slr_lifecycle_boundary pid-published "$producer_pid"
|
|
if ! _k3slr_process_identity producer_identity "$producer_pid"; then
|
|
_k3slr_password_pipe_cleanup || return 1
|
|
if [[ -n "$pending_signal" ]]; then return "$pending_status"; fi
|
|
return 1
|
|
fi
|
|
if ! _k3slr_wait_for_stopped_group producer_pgid stopped_identity "$producer_pid" ||
|
|
[[ "$stopped_identity" != "$producer_identity" ]]; then
|
|
_k3slr_password_pipe_cleanup || return 1
|
|
if [[ -n "$pending_signal" ]]; then return "$pending_status"; fi
|
|
return 1
|
|
fi
|
|
lifecycle_state='stopped_pinned'
|
|
_k3slr_lifecycle_boundary stop-query-complete "$producer_pid"
|
|
_k3slr_password_pipe_pending_checkpoint || return $?
|
|
if [[ "$producer_pgid" == "$caller_pgid" ]]; then
|
|
_k3slr_password_pipe_cleanup || return 1
|
|
return 1
|
|
fi
|
|
set +m
|
|
monitor_enabled=false
|
|
_k3slr_lifecycle_boundary cont-before
|
|
_k3slr_password_pipe_pending_checkpoint || return $?
|
|
_k3slr_continue_owned_child "$producer_pid" "$producer_pgid" "$producer_identity" || return 1
|
|
lifecycle_state='continued'
|
|
_k3slr_lifecycle_boundary cont-after
|
|
_k3slr_password_pipe_pending_checkpoint || return $?
|
|
producer_source_fd="${K3SLR_PASSWORD_PRODUCER[0]}"
|
|
exec {pipe_fd}<&"$producer_source_fd" || return 1
|
|
exec {producer_source_fd}<&-
|
|
exec {tty_fd}<&-
|
|
tty_fd=''
|
|
|
|
lifecycle_state='waiting'
|
|
if _k3slr_wait_child "$producer_pid"; then producer_rc=0; else producer_rc=$?; fi
|
|
_k3slr_lifecycle_boundary wait-reaped-before-clear
|
|
if _k3slr_process_identity current_identity "$producer_pid" 2>/dev/null &&
|
|
[[ "$current_identity" == "$producer_identity" ]]; then
|
|
_k3slr_password_pipe_cleanup || return 1
|
|
fi
|
|
lifecycle_state='reaped' producer_pid='' producer_pgid='' producer_identity=''
|
|
_k3slr_lifecycle_boundary state-cleared
|
|
if [[ -n "$pending_signal" ]]; then
|
|
return "$pending_status"
|
|
fi
|
|
(( producer_rc == 0 )) || return "$producer_rc"
|
|
_k3slr_keepass_database_lineage_matches "$database" "$database_snapshot" || return 1
|
|
_k3slr_lifecycle_boundary pre-consumer
|
|
_k3slr_password_pipe_pending_checkpoint || return $?
|
|
|
|
if [[ "$operation" == format ]]; then
|
|
if _k3slr_cryptsetup_format_stdin "$loop_device" <&"$pipe_fd"; then consumer_rc=0; else consumer_rc=$?; fi
|
|
elif [[ "$operation" == open ]]; then
|
|
if _k3slr_cryptsetup_open_stdin "$loop_device" "$mapping_name" <&"$pipe_fd"; then consumer_rc=0; else consumer_rc=$?; fi
|
|
elif [[ "$operation" == open-readonly ]]; then
|
|
if _k3slr_cryptsetup_open_readonly_stdin "$loop_device" "$mapping_name" <&"$pipe_fd"; then consumer_rc=0; else consumer_rc=$?; fi
|
|
else
|
|
return 1
|
|
fi
|
|
exec {pipe_fd}<&-
|
|
pipe_fd=''
|
|
return "$consumer_rc"
|
|
)
|
|
|
|
_k3slr_luks_format_from_keepass() {
|
|
(( $# == 3 )) || return 1
|
|
_k3slr_run_password_pipe format "$1" "$2" "$3"
|
|
}
|
|
|
|
_k3slr_luks_open_from_keepass() {
|
|
(( $# == 4 )) || return 1
|
|
_k3slr_run_password_pipe open "$1" "$2" "$3" "$4"
|
|
}
|
|
|
|
_k3slr_luks_open_readonly_from_keepass() {
|
|
(( $# == 4 )) || return 1
|
|
_k3slr_run_password_pipe open-readonly "$1" "$2" "$3" "$4"
|
|
}
|