#!/bin/sh

# initramfs magic

PREREQ=""
prereqs()
{
	echo "$PREREQ"
}

case $1 in
	prereqs)
		prereqs
		exit 0
		;;
esac

. /scripts/functions

# Begin real processing below this line

CRYPTLOG="/run/initramfs/opencrypt.log"
CONFFILE="/etc/opencrypt/opencrypt.conf"
LOCALPREFIX="/run/opencrypt$$"

. /usr/share/opencrypt/functions.sh
. /usr/share/opencrypt/script-functions.sh

try_open() {
	local slot checksum mode passwd
	mode=$1
	shift
	cryptlog "try open $MAP with $mode"
	[ $VERBOSE -gt 1 ] && cryptlog "parameters: $@"
	if [ "$mode" = "keyboard" ]; then
		/sbin/cryptsetup open "$@" --debug "$CRYPT" "$MAP" >> "$CRYPTLOG.tmp.$$"
	else
		/sbin/cryptsetup open "$@" --debug "$CRYPT" "$MAP" >> "$CRYPTLOG.tmp.$$" 2>> "$CRYPTLOG"
	fi
	if [ "$?" = "0" ]; then
		cryptlog "$MAP successfully opened with $mode"
		OPEN="true"
	else
		cryptlog "open $MAP failed with parameters: open $@ $CRYPT $MAP"
	fi
	if [ -s "$CRYPTLOG.tmp.$$" ]; then
		slot=$(grep unlocked "$CRYPTLOG.tmp.$$")
		if [ $VERBOSE -gt 1 ]; then
			copy_tmp_log
		else
			rm -f "$CRYPTLOG.tmp.$$"
		fi
	fi
	if [ "x$slot" != "x" ]; then
		cryptlog "$slot"
	else
		cryptlog "No key slot unlocked."
	fi
	if [ -s "$HASH" ]; then
		checksum=$(sha256sum "$HASH" 2>&1 | cut -d' ' -f1)
		cryptlog "Used key checksum $checksum"
	fi
}

setup_firewall() {
	cryptlog "setup firewall"

	# setup a minimal firewall with almost all dropped
	# accept igmp, icmp and established/related
	if [ "$FIREWALL" = "iptables" -a -f "/etc/opencrypt/iptables.conf" ]; then
		cryptlog "configuring iptables"
		modprobe --quiet ip_tables
		execute "/usr/sbin/iptables-restore /etc/opencrypt/iptables.conf"
	fi
	if [ "$FIREWALL" = "iptables" -a -f "/etc/opencrypt/ip6tables.conf" ]; then
		cryptlog "configuring ip6tables"
		modprobe --quiet ip_tables
		execute "/usr/sbin/ip6tables-restore /etc/opencrypt/ip6tables.conf"
	fi
	if [ "$FIREWALL" = "nftables" -a -f "/etc/opencrypt/nftables.conf" ]; then
		cryptlog "configuring nftables"
		modprobe --quiet nf_tables
		execute "/usr/sbin/nft -f /etc/opencrypt/nftables.conf"
	fi
}

setup_interface() {
	local interface="$1"
	if [ ! -L "/sys/class/net/$interface" ]; then
		[ $VERBOSE -gt 1 ] && cryptlog "waiting for udev setting $interface"
		udevadm settle --timeout=30 >/dev/null 2>&1
	fi
	if ! ip link show "$interface" >/dev/null 2>&1 ; then
		[ $VERBOSE -gt 1 ] && cryptlog "setting $interface up"
		ip link set dev "$interface" up  >/dev/null 2>&1
	fi
	if [ -L "/sys/class/net/$interface" ]; then
		if ip link show "$interface" >/dev/null 2>&1 ; then
			return 0
		fi
	fi
	sleep 2
	return 1
}

set_wpa_dir() {
	local line param value
	OC_WPADIR="/run/wpa_supplicant"
	if [ -f "/etc/wpa_supplicant.conf" ]; then
		for p in `grep -s "ctrl_interface=" /etc/wpa_supplicant.conf | sed 's/ctrl_interface=//'` ; do
			param=$(echo "$p" | cut -d'=' -f1)
			value=$(echo "$p" | cut -d'=' -f2)
			if [ "x$param" = "xDIR" -a "x$value" != "x" ]; then
				[ -d "$value" ] || mkdir -p "$value"
				OC_WPADIR="$value"
			fi
		done
	fi
}

wpa_completed() {
	local interface check
	interface="$1"
	if [ -e "$OC_WPADIR/$interface" ]; then
		check=$(wpa_cli -p "$OC_WPADIR" -i "$interface" status 2>&1 | grep "wpa_state")
		[ "x$check" = "xwpa_state=COMPLETED" ] && return 0
	fi
	return 1
}

address_set() {
	local interface="$1"
	ip address show "$interface" | grep -qs "inet "
	return $?
}

udhcpc_running() {
	local interface pid check
	interface="$1"
	[ ! -f "/run/udhcpc.$interface.pid" ] && return 1
	pid=$(cat "/run/udhcpc.$interface.pid")
	[ "x$pid" = "x" ] && return 1
	check=$(ps aux | grep udhcpc | grep $pid)
	return $?
}

run_post_up() {
	local interface stances stance
	interface="$1"
	[ $VERBOSE -gt 1 ] && cryptlog "run post up for $interface"
	stances=$(ifquery "$interface" | grep ^up: | sed -r 's/^up: //')
	local IFS=
	echo $stances | while read -r stance; do
		[ $VERBOSE -gt 1 ] && cryptlog "evaluating $stance"
		execute "$stance"
		if [ $? != 0 ]; then
			cryptlog "error evaluating: $stance"
		fi
	done
}

setup_dhcp() {
	local try interface options script
	interface="$1"
	[ $VERBOSE -gt 0 ] && cryptlog "setting dhcp for $interface"
	[ -f "/etc/hostname" ] && hostname -F "/etc/hostname"
	options="-t 10 -A 20 -n"
	if [ -f "/etc/opencrypt/udhcpc.script" ]; then
		[ $VERBOSE -gt 1 ] && cryptlog "using /etc/opencrypt/udhcpc.script"
		options="$options -s /etc/opencrypt/udhcpc.script"
	elif [ -f "/etc/udhcpc/default.script" ]; then
		[ $VERBOSE -gt 1 ] && cryptlog "using /etc/udhcpc/default.script"
		options="$options -s /etc/udhcpc/default.script"
	fi
	for try in $(seq 1 5) ; do
		if [ -x "/sbin/dhclient" -a "x$USEUDHCPC" != "xyes" ]; then
			[ $VERBOSE -gt 1 ] && cryptlog "using dhclient for $interface try $try"
			/sbin/dhclient -v -4 "$interface" >> "/run/dhclient.$interface.log" 2>&1
		else
			if ! udhcpc_running "$interface" ; then
				[ $VERBOSE -gt 1 ] && cryptlog "using udhcpc for $interface try $try"
				/sbin/udhcpc $options -p "/run/udhcpc.$interface.pid" -i "$interface" >> "/run/udhcpc.$interface.log" 2>&1
			else
				[ $VERBOSE -gt 1 ] && cryptlog "udhcpc still running for $interface try $try"
			fi
		fi
		if [ $VERBOSE -gt 1 ]; then
			execute "ip address show"
		fi
		if address_set "$interface" ; then
			run_post_up "$interface"
			break
		fi
		sleep 2
	done
}

setup_wireless() {
	local interface try status options
	interface="$1"

	[ $VERBOSE -gt 0 ] && cryptlog "starting $interface connection"
	options="-B -D nl80211,wext -P /run/wpa_supplicant.$interface.pid -i $interface"
	[ -f "/etc/wpa_supplicant.conf" ] && options="$options -c /etc/wpa_supplicant.conf"
	if [ $VERBOSE -gt 0 ]; then
		wpa_supplicant -t -d $options -f "/run/wpa_supplicant.$interface.log"
	else
		wpa_supplicant -s $options
	fi

	if [ $? != 0 ]; then
		cryptlog "wpa failed on $interface"
		return 1
	fi

	[ $VERBOSE -gt 0 ] && cryptlog "waiting for wpa completion on $interface"
	status="down"
	for try in $(seq 1 15) ; do
		[ $VERBOSE -gt 1 ] && cryptlog "waiting pass $try"
		if wpa_completed "$interface" ; then
			status="up"
			break
		fi
		sleep 2
	done

	if [ "$status" = "up" ] ; then
		[ $VERBOSE -gt 0 ] && cryptlog "wpa completed on $interface"
		echo "$interface" > "/run/network/ifstate.$interface"
		echo "$interface=$interface" >> "/run/network/ifstate"
		return 0
	else
		cryptlog "wpa not completed on $interface"
		return 1
	fi
}

manual_wpa() {
	local interface i
	interface=$1
	for i in $MANUALWPA ; do
		if [ "x$i" = "x$interface" ]; then
			return 0
		fi
	done
	return 1
}

manual_dhcp() {
	local interface i
	interface=$1
	for i in $MANUALDHCP ; do
		if [ "x$i" = "x$interface" ]; then
			return 0
		fi
	done
	return 1
}

check_device() {
	local interface checkdev try checklink
	interface=$1
	for try in $(seq 1 5) ; do
		[ $VERBOSE -gt 1 ] && cryptlog "setup interface $interface try $try"
		setup_interface $interface && break
	done
	if ! setup_interface $interface ; then
		cryptlog "E: interface $interface not set"
		if [ $VERBOSE -gt 1 ]; then
			execute "ip link"
		fi
		return 1
	fi
	checkdev=$(ifquery --state $interface 2>&1)
	if [ "x$checkdev" = "x" -o "$checkdev" != "$interface=$interface" ]; then
		if manual_wpa "$interface" ; then
			setup_wireless $interface
		else
			[ $VERBOSE -gt 0 ] && cryptlog "configuring $interface using ifup"
			if [ $VERBOSE -gt 1 ]; then
				timeout $IFUPTIMEOUT ifup $interface 2>&1 | tee -a "$CRYPTLOG.tmp.$$"
			else
				timeout $IFUPTIMEOUT ifup $interface >> "$CRYPTLOG.tmp.$$" 2>&1
			fi
			copy_tmp_log
		fi
		checklink=$(ip link show $interface 2>&1 | grep 'state UP')
		if [ "x$checklink" != "x" ]; then
			if ! address_set "$interface" || manual_dhcp "$interface" ; then
				if dhcp_used "$interface" || manual_dhcp "$interface" ; then
					setup_dhcp "$interface"
				else
					cryptlog "E: address not set for $interface"
				fi
			fi
		fi
	else
		[ $VERBOSE -gt 0 ] && cryptlog "$interface already configured"
	fi
}

open_network() {
	local devices device checklink

	[ "$NETWORK" = "up" ] && return 0

	if [ -f "/etc/network/interfaces" ]; then
		devices=$(ifquery --list --all 2>/dev/null)
		for device in $devices ; do
			check_device $device
		done
	else
		# configure_networking will use kernel parameter ip
		cryptlog "configuring networking with kernel parameters"
		execute "configure_networking"
	fi

	# we check state set to UP, i.e. when a connection is actually established
	# ifquery checks only if the device is configured
	if [ "x$WAITFOR" != "x" ]; then
		checklink=$(ip link show $WAITFOR 2>&1 | grep 'state UP')
		if [ "x$checklink" != "x" ]; then
			NETWORK="up"
			cryptlog "interface $WAITFOR up"
		fi
	else
		checklink=$(ip link show 2>&1 | grep 'state UP')
		if [ "x$checklink" != "x" ]; then
			NETWORK="up"
			cryptlog "network up"
		fi
	fi
	[ "$NETWORK" != "up" ] && sleep 2
}

device_exist() {
	local device
	device="$1"
	device=$(echo "$device" | sed 's/^.*=//')
	blkid | grep -qs "$device" && return 0
	return 1
}

open_map() {
	local status options
	cryptlog "opening $MAP $CRYPT"
	status=$(cryptsetup status "$MAP" | grep 'is active')
	if [ "x$status" != "x" ]; then
		[ $VERBOSE -gt 0 ] && cryptlog "$MAP already open, skipping"
		return 0
	fi
	if ! device_exist $CRYPT ; then
		cryptlog "$CRYPT not found, skipping"
		return 0
	fi

	OPEN="false"
	while [ "$OPEN" = "false" ]; do
		scan_devices
		create_hash
		if [ ! -s "$HASH" ]; then
			scan_network
			create_hash
		fi
		if [ -s "$HASH" ]; then
			options="--type=$CRYPTYPE --key-file=$HASH"
			if [ "$CRYPTYPE" = "luks" -a "x$REMOTEKEYSLOT" != "x" ]; then
				options="$options --key-slot=$REMOTEKEYSLOT"
			fi
			try_open "remote key" $options
		fi
		if [ "$OPEN" = "false" ]; then
			options="--type=$CRYPTYPE --tries=1"
			if [ "$CRYPTYPE" = "luks" -a "x$KEYBOARDKEYSLOT" != "x" ]; then
				options="$options --key-slot=$KEYBOARDKEYSLOT"
			fi
			if [ "$CRYPTYPE" != "loopaes" -a "x$KEYBOARDTIMEOUT" != "x" ]; then
				options="$options --timeout=$KEYBOARDTIMEOUT"
			fi
			try_open "keyboard" $options
		fi
	done
}

relink_exec() {
	local file=$1
	if [ $VERBOSE -gt 2 ]; then
		execute "ls -l /usr/bin/$file /bin/$file /usr/sbin/$file /sbin/$file /usr/local/bin/$file"
	fi
	if [ -f /usr/sbin/$file -a -f /usr/local/bin/$file ]; then
		[ $VERBOSE -gt 1 ] && cryptlog "relink /usr/sbin/$file"
		execute "rm -f /usr/sbin/$file /sbin/$file"
		execute "ln /usr/local/bin/$file /usr/sbin/$file"
	fi
	if [ -f /usr/bin/$file -a -f /usr/local/bin/$file ]; then
		[ $VERBOSE -gt 1 ] && cryptlog "relink /usr/bin/$file"
		execute "rm -f /usr/bin/$file /bin/$file"
		execute "ln /usr/local/bin/$file /usr/bin/$file"
	fi
	if [ $VERBOSE -gt 2 ]; then
		execute "ls -l /usr/bin/$file /bin/$file /usr/sbin/$file /sbin/$file /usr/local/bin/$file"
	fi
}

oc_version=$(cat /usr/share/opencrypt/VERSION)
cryptlog "starting opencrypt $oc_version"

# /cryptroot/crypttab could be empty but must exists
if [ ! -f "/cryptroot/crypttab" ]; then
	cryptlog "no crypttab found"
	exit 1
fi

# same for configuration file
if [ ! -f "$CONFFILE" ]; then
	cryptlog "missing $CONFFILE"
	exit 1
fi

if ! load_params ; then
	cryptlog "please check $CONFFILE"
	exit 1
fi
[ $VERBOSE -gt 2 ] && echo "ending load_params"

NETWORK="down"
HASH="/run/opencrypt$$.hash"

# cannot do mount in /run/initramfs so stay in current dir
TMP2="tmp2$$"

if  [ "$WPASUPPLICANT" = "yes" -o "x$MANUALWPA" != "x" ]; then
	set_wpa_dir
	[ ! -d "/run/wpa_supplicant" ] && mkdir -p "/run/wpa_supplicant"
	[ ! -d "/run/wpa_action" ] && mkdir -p "/run/wpa_action"
	[ ! -d "/run/sendsigs.omit.d" ] && mkdir -p "/run/sendsigs.omit.d"
	if [ $VERBOSE -gt 2 ]; then
		execute "ls -al /run"
	fi
fi

if [ $VERBOSE -gt 2 ]; then
	execute "env"
fi

[ $VERBOSE -gt 2 ] && echo "load_modules"
load_modules

# we need 2 steps because cryptsetup fails if we are reading a file

# first we load mapping with system crypttab mapping
# which contains only filesystems needed for booting
# (usually only root) as found by cryptsetup-initramfs
cryptlog "loading system crypttab"
[ $VERBOSE -gt 2 ] && echo "load_crypttab system"
load_crypttab "/cryptroot/crypttab"

# next we use our custom crypttab to ease device name changes
# in which case /cryptroot/crypttab will be empty
# as initramfs uses the mounted root when creating initrd
cryptlog "loading custom crypttab"
[ $VERBOSE -gt 2 ] && echo "load_crypttab custom"
load_crypttab "/etc/opencrypt/crypttab"

# busybox wget do not handle TLS handshake on https
# wget2 and curl are not part of busybox
# busybox ip do not show state UP on links
# busybox ifup fumble on some regular stances
# run-parts, ifdown, and ifquery are needed for ifup
cryptlog "installing real binaries"
relink_exec "wget"
relink_exec "ip"
relink_exec "ifup"
relink_exec "run-parts"
if [ -f /sbin/ifup ]; then
	rm -rf /sbin/ifdown /usr/sbin/ifdown /sbin/ifquery /usr/sbin/ifquery
	ln -s /sbin/ifup /sbin/ifdown
	ln -s /sbin/ifup /sbin/ifquery
fi

if [ "x$FIREWALL" != "x" ]; then
	setup_firewall
fi

[ $VERBOSE -gt 2 ] && sleep 5

if used_network_modes ; then
	cryptlog "opening network"
	for try in $(seq 1 10) ; do
		[ "$NETWORK" = "up" ] && break
		[ $VERBOSE -gt 0 ] && cryptlog "opening network try $try"
		open_network
	done
	if [ "$NETWORK" != "up" ]; then
		cryptlog "network down"
	fi
	if [ -f "/etc/opencrypt/resolv.conf" ]; then
		[ $VERBOSE -gt 0 ] && cryptlog "installing resolv.conf"
		if [ -f "/etc/resolv.conf" ]; then
			cp "/etc/resolv.conf" "/etc/resolv.conf.opencrypt"
		fi
		cp "/etc/opencrypt/resolv.conf" "/etc/resolv.conf"
	fi
	route=$(ip route 2>&1)
	if [ "x$route" = "x" ]; then
		cryptlog "no route found"
	fi
	if [ $VERBOSE -gt 2 ]; then
		execute "ip addr"
		execute "ip route"
	fi
fi

# now we can try to search the keys and open the partitions
open_crypt_devices $cryptdevices

# do not close network as it is correctly configured
# and other networking tools could be disabled

umount_devices

rm -f "$HASH" ${LOCALPREFIX}.*

cryptlog "ending opencrypt $oc_version"
exit 0

