#!/bin/bash
# Save these options to be the persistent default
set -e -o pipefail
. /tmp/config
. /etc/functions

TRACE_FUNC

while getopts "b:d:p:i:" arg; do
	case $arg in
	b) bootdir="$OPTARG" ;;
	d) paramsdev="$OPTARG" ;;
	p) paramsdir="$OPTARG" ;;
	i) index="$OPTARG" ;;
	esac
done

if [ -z "$bootdir" -o -z "$index" ]; then
	die "Usage: $0 -b /boot -i menu_option "
fi

if [ -z "$paramsdev" ]; then
	paramsdev="$bootdir"
fi

if [ -z "$paramsdir" ]; then
	paramsdir="$bootdir"
fi

bootdir="${bootdir%%/}"
paramsdev="${paramsdev%%/}"
paramsdir="${paramsdir%%/}"

TMP_MENU_FILE="/tmp/kexec/kexec_menu.txt"
ENTRY_FILE="$paramsdir/kexec_default.$index.txt"
HASH_FILE="$paramsdir/kexec_default_hashes.txt"
PRIMHASH_FILE="$paramsdir/kexec_primhdl_hash.txt"
KEY_DEVICES="$paramsdir/kexec_key_devices.txt"
KEY_LVM="$paramsdir/kexec_key_lvm.txt"

lvm_suggest=$(lvm vgscan | awk -F '"' {'print $1'} | tail -n +2)
num_lvm=$(echo "$lvm_suggest" | wc -l)
if [ "$num_lvm" -eq 1 ] && [ -n "$lvm_suggest" ]; then
	lvm_volume_group="$lvm_suggest"
elif [ -z "$lvm_suggest" ]; then
	num_lvm=0
fi
# $lvm_suggest is a multiline string, we need to convert it to a space separated string
lvm_suggest=$(echo $lvm_suggest | tr '\n' ' ')
DEBUG "LVM num_lvm: $num_lvm, lvm_suggest: $lvm_suggest"

# get all LUKS container devices
devices_suggest=$(blkid | cut -d ':' -f 1 | while read device; do
	if cryptsetup isLuks "$device"; then echo "$device"; fi
done | sort)
num_devices=$(echo "$devices_suggest" | wc -l)

if [ "$num_devices" -eq 1 ] && [ -s "$devices_suggest" ]; then
	key_devices=$devices_suggest
elif [ -z "$devices_suggest" ]; then
	num_devices=0
fi
# $devices_suggest is a multiline string, we need to convert it to a space separated string
devices_suggest=$(echo $devices_suggest | tr '\n' ' ')
DEBUG "LUKS num_devices: $num_devices, devices_suggest: $devices_suggest"

if [ "$num_lvm" -eq 0 ] && [ "$num_devices" -eq 0 ]; then
	#No encrypted partition found.
	no_encrypted_partition=1
fi

#Reusable function when user wants to define new TPM DUK for lvms/disks
prompt_for_existing_encrypted_lvms_or_disks() {
	TRACE_FUNC
	DEBUG "num_lvm: $num_lvm, lvm_suggest: $lvm_suggest, num_devices: $num_devices, devices_suggest: $devices_suggest"

	# Create an associative array to store the suggested LVMs and their paths
	declare -A lvms_array
	# Loop through the suggested LVMs and add them to the array
	for lvm in $lvm_suggest; do
		lvms_array[$lvm]=$lvm
	done

	# Get the number of suggested LVMs
	num_lvms=${#lvms_array[@]}

	if [ "$num_lvms" -gt 1 ]; then
		DEBUG "Multiple LVMs found: $lvm_suggest"
		selected_lvms_not_existing=1
		# Create an array to store the selected LVMs
		declare -a key_lvms_array

		while [ $selected_lvms_not_existing -ne 0 ]; do
			{
				# Read the user input and store it in a variable
				read \
					-p "Encrypted LVMs? (choose between/all: $lvm_suggest): " \
					key_lvms

				# Split the user input by spaces and add each element to the array
				IFS=' ' read -r -a key_lvms_array <<<"$key_lvms"

				# Loop through the array and check if each element is in the lvms_array
				valid=1
				for lvm in "${key_lvms_array[@]}"; do
					if [[ ! ${lvms_array[$lvm]+_} ]]; then
						# If not found, set the flag to indicate invalid input
						valid=0
						break
					fi
				done

				# If valid, set the flag to indicate valid input
				if [[ $valid -eq 1 ]]; then
					selected_lvms_not_existing=0
				fi
			}
		done
	elif [ "$num_lvms" -eq 1 ]; then
		echo "Single Encrypted LVM found at $lvm_suggest."
		key_lvms=$lvm_suggest
	else
		echo "No encrypted LVMs found."
	fi

	# Create an associative array to store the suggested devices and their paths
	declare -A devices_array
	# Loop through the suggested devices and add them to the array
	for device in $devices_suggest; do
		devices_array[$device]=$device
	done

	# Get the number of suggested devices
	num_devices=${#devices_array[@]}

	if [ "$num_devices" -gt 1 ]; then
		DEBUG "Multiple LUKS devices found: $devices_suggest"
		selected_luksdevs_not_existing=1
		# Create an array to store the selected devices
		declare -a key_devices_array

		while [ $selected_luksdevs_not_existing -ne 0 ]; do
			{
				# Read the user input and store it in a variable
				read \
					-p "Encrypted devices? (choose between/all: $devices_suggest): " \
					key_devices

				# Split the user input by spaces and add each element to the array
				IFS=' ' read -r -a key_devices_array <<<"$key_devices"

				# Loop through the array and check if each element is in the devices_array
				valid=1
				for device in "${key_devices_array[@]}"; do
					if [[ ! ${devices_array[$device]+_} ]]; then
						# If not found, set the flag to indicate invalid input
						valid=0
						break
					fi
				done

				# If valid, set the flag to indicate valid input
				if [[ $valid -eq 1 ]]; then
					selected_luksdevs_not_existing=0
				fi
			}
		done
	elif [ "$num_devices" -eq 1 ]; then
		echo "Single Encrypted Disk found at $devices_suggest."
		key_devices=$devices_suggest
	else
		echo "No encrypted devices found."
	fi

	DEBUG "Multiple LUKS devices selected: $key_devices"

}

if [ ! -r "$TMP_MENU_FILE" ]; then
	die "No menu options available, please run kexec-select-boot"
fi

entry=$(head -n $index $TMP_MENU_FILE | tail -1)
if [ -z "$entry" ]; then
	die "Invalid menu index $index"
fi

save_key="n"

if [ "$CONFIG_TPM" = "y" ] && [ "$CONFIG_TPM_NO_LUKS_DISK_UNLOCK" != "y" ] && [ "$CONFIG_BASIC" != y ]; then
	DEBUG "TPM is enabled and TPM_NO_LUKS_DISK_UNLOCK is not set"
	DEBUG "Checking if a a LUKS TPM Disk Unlock Key was previously set up from $KEY_DEVICES"
	#check if $KEY_DEVICES file exists and is not empty
	if [ -r "$KEY_DEVICES" ] && [ -s "$KEY_DEVICES" ]; then
		DEBUG "LUKS TPM Disk Unlock Key was previously set up from $KEY_DEVICES"
		read \
			-n 1 \
			-p "Do you want to reseal a Disk Unlock Key in the TPM [y/N]: " \
			change_key_confirm
		echo

		if [ "$change_key_confirm" = "y" \
			-o "$change_key_confirm" = "Y" ]; then
			old_lvm_volume_group=""
			if [ -r "$KEY_LVM" ]; then
				old_lvm_volume_group=$(cat $KEY_LVM) || true
				old_key_devices=$(cat $KEY_DEVICES |
					cut -d\  -f1 |
					grep -v "$old_lvm_volume_group" |
					xargs) || true
			else
				old_key_devices=$(cat $KEY_DEVICES |
					cut -d\  -f1 | xargs) || true
			fi

			lvm_suggest="$old_lvm_volume_group"
			devices_suggest="$old_key_devices"
			save_key="y"
		fi
	else
		DEBUG "No previous LUKS TPM Disk Unlock Key was set up, confirming to add a Disk Encryption Key to the TPM"
		read \
			-n 1 \
			-p "Do you wish to add a disk encryption to the TPM [y/N]: " \
			add_key_confirm
		echo

		if [ "$add_key_confirm" = "y" \
			-o "$add_key_confirm" = "Y" ]; then
			DEBUG "User confirmed desire to add a Disk Encryption Key to the TPM"
			save_key="y"
		fi
	fi

	if [ "$save_key" = "y" ]; then
		if [ -n "$old_key_devices" ] || [ -n "$old_lvm_volume_group" ]; then
			DEBUG "Previous LUKS TPM Disk Unlock Key was set up for $old_key_devices $old_lvm_volume_group"
			read \
				-n 1 \
				-p "Do you want to reuse configured Encrypted LVM groups/Block devices? (Y/n):" \
				reuse_past_devices
			echo
			if [ "$reuse_past_devices" = "y" ] || [ "$reuse_past_devices" = "Y" ] || [ -z "$reuse_past_devices" ]; then
				if [ -z "$key_devices" ] && [ -n "$old_key_devices" ]; then
					key_devices="$old_key_devices"
				fi
				if [ -z "$lvm_volume_group" ] && [ -n "$old_lvm_volume_group" ]; then
					lvm_volume_group="$old_lvm_volume_group"
				fi
			#User doesn't want to reuse past devices, so we need to prompt him from devices_suggest and lvm_suggest
			else
				prompt_for_existing_encrypted_lvms_or_disks
			fi
		else
			DEBUG "No previous LUKS TPM Disk Unlock Key was set up, setting up"
			prompt_for_existing_encrypted_lvms_or_disks
		fi

		save_key_params="-s -p $paramsdev"
		if [ -n "$lvm_volume_group" ]; then
			save_key_params="$save_key_params -l $lvm_volume_group $key_devices"
		else
			save_key_params="$save_key_params $key_devices"
		fi
		kexec-save-key $save_key_params ||
			die "Failed to save the LUKS TPM Disk Unlock Key"
	fi
fi

# try to switch to rw mode
mount -o rw,remount $paramsdev

if [ ! -d $paramsdir ]; then
	mkdir -p $paramsdir ||
		die "Failed to create params directory"
fi

if [ "$CONFIG_TPM2_TOOLS" = "y" ]; then
	sha256sum /tmp/secret/primary.handle >"$PRIMHASH_FILE" ||
		die "ERROR: Failed to Hash TPM2 primary key handle!"
	DEBUG "TPM2 primary key handle hash saved to $PRIMHASH_FILE"
fi

rm $paramsdir/kexec_default.*.txt 2>/dev/null || true
echo "$entry" >$ENTRY_FILE
(
	cd $bootdir && kexec-boot -b "$bootdir" -e "$entry" -f |
		xargs sha256sum >$HASH_FILE
) || die "Failed to create hashes of boot files"
if [ ! -r $ENTRY_FILE -o ! -r $HASH_FILE ]; then
	die "Failed to write default config"
fi

if [ "$save_key" = "y" ]; then
	# logic to parse OS initrd to extract crypttab, its filepaths and its OS defined options
	initrd_decompressed="/tmp/initrd_extract"
	mkdir -p "$initrd_decompressed"
	# Get initrd filename selected to be default initrd that OS could be using to configure LUKS on boot by deploying crypttab files
	current_default_initrd=$(cat /boot/kexec_default_hashes.txt | grep initr | awk -F " " {'print $NF'} | sed 's/\.\//\/boot\//g')

	echo "+++ Extracting current selected default boot's $current_default_initrd to find crypttab files..."
	unpack_initramfs.sh "$current_default_initrd" "$initrd_decompressed"
	crypttab_files=$(find "$initrd_decompressed" | grep crypttab 2>/dev/null) || true

	if [ ! -z "$crypttab_files" ]; then
		DEBUG "Found crypttab files in $current_default_initrd"
		rm -f $bootdir/kexec_initrd_crypttab_overrides.txt || true

		#Parsing each crypttab file found
		echo "$crypttab_files" | while read crypttab_file; do
			# Change crypttab file path to be relative to initrd for string manipulation
			final_initrd_filepath=${crypttab_file#/tmp/initrd_extract}
			DEBUG "Final initramfs crypttab path:$final_initrd_filepath"
			# Keep only non-commented lines for crypttab entries
			current_crypttab_entries=$(cat "$crypttab_file" | grep -v "^#")
			DEBUG "Found initrd crypttab entries $final_initrd_filepath:$current_crypttab_entries"
			# Modify each retained crypttab line for /secret.key under intramfs to be considered as a keyfile
			modified_crypttab_entries=$(echo "$current_crypttab_entries" | sed 's/none/\/secret.key/g')
			DEBUG "Modified crypttab entries $final_initrd_filepath:$modified_crypttab_entries"
			echo "$modified_crypttab_entries" | while read modified_crypttab_entry; do
				echo "$final_initrd_filepath:$modified_crypttab_entry" >>$bootdir/kexec_initrd_crypttab_overrides.txt
			done
		done

		#insert current default boot's initrd crypttab locations into tracking file to be overwritten into initramfs at kexec-inject-key
		echo "+++ The following OS crypttab file:entry were modified from default boot's initrd:"
		cat $bootdir/kexec_initrd_crypttab_overrides.txt
		echo "+++ Heads added /secret.key in those entries and saved them under $bootdir/kexec_initrd_crypttab_overrides.txt"
		echo "+++ Those overrides will be part of detached signed digests and used to prepare cpio injected at kexec of selected default boot entry."
	else
		echo "+++ No crypttab file found in extracted initrd. A generic crypttab will be generated"
		if [ -e "$bootdir/kexec_initrd_crypttab_overrides.txt" ]; then
			echo "+++ Removing $bootdir/kexec_initrd_crypttab_overrides.txt"
			rm -f "$bootdir/kexec_initrd_crypttab_overrides.txt"
		fi
	fi

	# Cleanup
	cd /
	rm -rf /tmp/initrd_extract || true
fi

# sign and auto-roll config counter
extparam=
if [ "$CONFIG_TPM" = "y" ]; then
	if [ "$CONFIG_IGNORE_ROLLBACK" != "y" ]; then
		extparam=-r
	fi
fi
if [ "$CONFIG_BASIC" != "y" ]; then
	kexec-sign-config -p $paramsdir $extparam ||
		die "Failed to sign default config"
fi
# switch back to ro mode
mount -o ro,remount $paramsdev
