#!/bin/sh
# Shell functions for most initialization scripts

die() {
	echo >&2 "$*";
	exit 1;
}

warn() {
	echo >&2 "$*";
}

recovery() {
	echo >&2 "!!!!! $*"

	# Remove any temporary secret files that might be hanging around
	# but recreate the directory so that new tools can use it.
	shred -n 10 -z -u /tmp/secret/* 2> /dev/null
	rm -rf /tmp/secret
	mkdir -p /tmp/secret

	# ensure /tmp/config exists for recovery scripts that depend on it
	touch /tmp/config

	if [ "$CONFIG_TPM" = y ]; then
		tpm extend -ix 4 -ic recovery
	fi
	echo >&2 "!!!!! Starting recovery shell"
	sleep 1

	if [ -x /bin/setsid ]; then
		exec /bin/setsid -c /bin/ash
	else
		exec /bin/ash
	fi
}

pause_recovery() {
	read -p 'Hit enter to proceed to recovery shell:'
	recovery $*
}

pcrs() {
	head -8 /sys/class/tpm/tpm0/pcrs
}

confirm_totp()
{
	prompt="$1"
	last_half=X
	unset totp_confirm

	while true; do

		# update the TOTP code every thirty seconds
		date=`date "+%Y-%m-%d %H:%M:%S"`
		seconds=`date "+%s"`
		half=`expr \( $seconds % 60 \) / 30`
		if [ "$CONFIG_TPM" != y ]; then
			TOTP="NO TPM"
		elif [ "$half" != "$last_half" ]; then
			last_half=$half;
			TOTP=`unseal-totp` \
			|| recovery "TOTP code generation failed"
		fi

		echo -n "$date $TOTP: "

		# read the first character, non-blocking
		read \
			-t 1 \
			-n 1 \
			-s \
			-p "$prompt" \
			totp_confirm \
		&& break

		# nothing typed, redraw the line
		echo -ne '\r'
	done

	# clean up with a newline
	echo
}

enable_usb()
{
	if [ "$CONFIG_LINUX_USB_COMPANION_CONTROLLER" = y ]; then
		if ! lsmod | grep -q uhci_hcd; then
			insmod /lib/modules/uhci-hcd.ko \
			|| die "uhci_hcd: module load failed"
		fi
		if ! lsmod | grep -q ohci_hcd; then
			insmod /lib/modules/ohci-hcd.ko \
			|| die "ohci_hcd: module load failed"
		fi
		if ! lsmod | grep -q ohci_pci; then
			insmod /lib/modules/ohci-pci.ko \
			|| die "ohci_pci: module load failed"
		fi
	fi
	if ! lsmod | grep -q ehci_hcd; then
		insmod /lib/modules/ehci-hcd.ko \
		|| die "ehci_hcd: module load failed"
	fi
	if ! lsmod | grep -q ehci_pci; then
		insmod /lib/modules/ehci-pci.ko \
		|| die "ehci_pci: module load failed"
	fi
	if ! lsmod | grep -q xhci_hcd; then
		insmod /lib/modules/xhci-hcd.ko \
		|| die "xhci_hcd: module load failed"
	fi
	if ! lsmod | grep -q xhci_pci; then
		insmod /lib/modules/xhci-pci.ko \
		|| die "xhci_pci: module load failed"
		sleep 2
	fi
}

confirm_gpg_card()
{
	read \
		-n 1 \
		-p "Please confirm that your GPG card is inserted [Y/n]: " \
		card_confirm
	echo

	if [ "$card_confirm" != "y" \
		-a "$card_confirm" != "Y" \
		-a -n "$card_confirm" ] \
	; then
		die "gpg card not confirmed"
	fi

	# setup the USB so we can reach the GPG card
	enable_usb

	echo -e "\nVerifying presence of GPG card...\n"
	# ensure we don't exit without retrying
	errexit=$(set -o | grep errexit | awk '{print $2}')
	set +e
	gpg --card-status > /dev/null
	if [ $? -ne 0 ]; then
	  # prompt for reinsertion and try a second time
	  read -n1 -r -p \
	      "Can't access GPG key; remove and reinsert, then press Enter to retry. " \
	      ignored
	  # restore prev errexit state
	  if [ "$errexit" = "on" ]; then
	    set -e
	  fi
	  # retry card status
	  gpg --card-status > /dev/null \
	  	|| die "gpg card read failed"
	fi
	# restore prev errexit state
	if [ "$errexit" = "on" ]; then
	  set -e
	fi
}


check_tpm_counter()
{
  LABEL=${2:-3135106223}
	# if the /boot.hashes file already exists, read the TPM counter ID
	# from it.
	if [ -r "$1" ]; then
		TPM_COUNTER=`grep counter- "$1" | cut -d- -f2`
	else
		warn "$1 does not exist; creating new TPM counter"
		read -s -p "TPM Owner password: " tpm_password
		echo
		tpm counter_create \
			-pwdo "$tpm_password" \
			-pwdc '' \
			-la $LABEL \
		| tee /tmp/counter \
		|| die "Unable to create TPM counter"
		TPM_COUNTER=`cut -d: -f1 < /tmp/counter`
	fi

	if [ -z "$TPM_COUNTER" ]; then
		die "$1: TPM Counter not found?"
	fi
}

read_tpm_counter()
{
	tpm counter_read -ix "$1" | tee "/tmp/counter-$1" \
	|| die "Counter read failed"
}

increment_tpm_counter()
{
	tpm counter_increment -ix "$1" -pwdc '' \
		| tee /tmp/counter-$1 \
	|| die "Counter increment failed"
}

check_config() {
	if [ ! -d /tmp/kexec ]; then
		mkdir /tmp/kexec \
		|| die 'Failed to make kexec tmp dir'
	else
		rm -rf /tmp/kexec/* \
		|| die 'Failed to empty kexec tmp dir'
	fi

	if [ ! -r $1/kexec.sig ]; then
		return
	fi

	if [ `find $1/kexec*.txt | wc -l` -eq 0 ]; then
		return
	fi

	if [ "$2" != "force" ]; then
		if ! sha256sum `find $1/kexec*.txt` | gpgv $1/kexec.sig - ; then
			die 'Invalid signature on kexec boot params'
		fi
	fi

	echo "+++ Found verified kexec boot params"
	cp $1/kexec*.txt /tmp/kexec \
	|| die "Failed to copy kexec boot params to tmp"
}

preserve_rom() {
	new_rom="$1"
	old_files=`cbfs -t 50 -l 2>/dev/null | grep "^heads/"`

	for old_file in `echo $old_files`; do
		new_file=`cbfs -o $1 -l | grep -x $old_file`
		if [ -z "$new_file" ]; then
			echo "+++ Adding $old_file to $1"
			cbfs -t 50 -r $old_file >/tmp/rom.$$ \
			|| die "Failed to read cbfs file from ROM"
			cbfs -o $1 -a $old_file -f /tmp/rom.$$ \
			|| die "Failed to write cbfs file to new ROM file"
		fi
	done
}
replace_config() {
	CONFIG_FILE=$1
	CONFIG_OPTION=$2
	NEW_SETTING=$3

	touch $CONFIG_FILE
# first pull out the existing option from the global config and place in a tmp file
	awk "gsub(\"^export ${CONFIG_OPTION}=.*\",\"export ${CONFIG_OPTION}=\\\"${NEW_SETTING}\\\"\")" /tmp/config > ${CONFIG_FILE}.tmp
	awk "gsub(\"^${CONFIG_OPTION}=.*\",\"${CONFIG_OPTION}=\\\"${NEW_SETTING}\\\"\")" /tmp/config >> ${CONFIG_FILE}.tmp

# then copy any remaining settings from the existing config file, minus the option you changed
	grep -v "^export ${CONFIG_OPTION}=" ${CONFIG_FILE} | grep -v "^${CONFIG_OPTION}=" >> ${CONFIG_FILE}.tmp || true
  sort ${CONFIG_FILE}.tmp | uniq > ${CONFIG_FILE}
	rm -f ${CONFIG_FILE}.tmp
}
combine_configs() {
	cat /etc/config* > /tmp/config
}

update_checksums()
{
	# clear screen
	printf "\033c"
	# ensure /boot mounted
	if ! grep -q /boot /proc/mounts ; then
		mount -o ro /boot \
		|| recovery "Unable to mount /boot"
	fi
	# remount RW
    	mount -o rw,remount /boot
	cd /boot
	find ./ -type f ! -name '*kexec*' | xargs sha256sum > /boot/kexec_hashes.txt
	if [ -e /boot/kexec_default_hashes.txt ]; then
	  DEFAULT_FILES=$(cat /boot/kexec_default_hashes.txt | cut -f3 -d ' ')
	  echo $DEFAULT_FILES | xargs sha256sum > /boot/kexec_default_hashes.txt
	fi
	# Remove any package trigger log files
	# We don't need them after the user decides to sign
	rm -f /boot/kexec_package_trigger*

	# sign and auto-roll config counter
	extparam=
	if [ "$CONFIG_TPM" = "y" ]; then
	extparam=-u
	fi
	kexec-sign-config -p /boot $extparam \
	|| die "Failed to sign default config"

	# switch back to ro mode
	mount -o ro,remount /boot
}

# detect and set /boot device
# mount /boot if successful
detect_boot_device()
{
	# unmount /boot to be safe
	umount /boot 2>/dev/null

	# check $CONFIG_BOOT_DEV if set/valid
	if [ -e "$CONFIG_BOOT_DEV" ]; then
		mount -o ro $CONFIG_BOOT_DEV /boot >/dev/null 2>&1
		if [[ $? && -d /boot/grub ]]; then
			# CONFIG_BOOT_DEV is valid device and contains an installed OS
			return 0
		fi
	fi

	# generate list of possible boot devices
	fdisk -l | grep "Disk" | cut -f2 -d " " | cut -f1 -d ":" > /tmp/disklist
	
	# filter out extraneous options
	> /tmp/boot_device_list
	for i in `cat /tmp/disklist`; do
		# remove block device from list if numeric partitions exist, since not bootable
		let DEV_NUM_PARTITIONS=`ls -1 $i* | wc -l`-1
		if [ ${DEV_NUM_PARTITIONS} -eq 0 ]; then
			echo $i >> /tmp/boot_device_list
		else
			ls $i* | tail -${DEV_NUM_PARTITIONS} >> /tmp/boot_device_list
		fi
	done
	
	# iterate thru possible options and check for grub dir
	for i in `cat /tmp/boot_device_list`; do
		umount /boot 2>/dev/null
		mount -o ro $i /boot >/dev/null 2>&1
		if [[ $? && -d /boot/grub ]]; then
			CONFIG_BOOT_DEV="$i"
			return 0
		fi
	done

	# no valid boot device found
	echo "Unable to locate /boot files on any mounted disk"
	umount /boot 2>/dev/null
	return 1
}