#!/bin/sh

# initramfs magic

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

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

. /usr/share/initramfs-tools/hook-functions

# Begin real processing below this line

CONFFILE="/etc/opencrypt/opencrypt.conf"

. /usr/share/opencrypt/functions.sh

cryptlog() {
	echo "$@"
}

# add own error message as copy_file do not provide it
custom_copy_file() {
	local type src target
	type="${1}"
	src="${2}"
	target="${3:-$2}"

	if [ ! -f "$src" -a ! -L "$src" ]; then
		cryptlog "E: missing file $src"
		exit 2
	fi
	copy_file "$type" "$src" "$target"
}

# add own error message as copy_exec do not provide it
custom_copy_exec() {
	local src target
	src="${1}"
    target="${2:-$1}"

	if [ ! -f "$src" ]; then
		cryptlog "E: missing file $src"
		exit 2
	fi
	copy_exec "$src" "$target"
}

package_is_installed() {
	local package="$1"
	/bin/dpkg-query --status $package >/dev/null 2>&1
	return $?
}

check_package() {
	local package="$1"
	if ! package_is_installed $package ; then
		cryptlog "E: missing package $package"
		cryptlog "E: please install it"
		exit 2
	fi
}

add_and_check_module() {
	local module
	module="$1"
	[ $VERBOSE -gt 0 ] && cryptlog "I: add module $module"
	manual_add_modules "$module"
	[ $VERBOSE -gt 0 ] && cryptlog "I: testing loading module $module"
	modprobe "$module"
}

oc_version=$(cat /usr/share/opencrypt/VERSION)

if [ ! -f "$CONFFILE" ]; then
	cryptlog "E: opencrypt $oc_version missing $CONFFILE"
	exit 1
fi

if ! load_params ; then
	cryptlog "E: opencrypt $oc_version please check $CONFFILE"
	exit 1
fi

[ $VERBOSE -gt 0 ] && cryptlog "I: starting opencrypt $oc_version"

if [ "x$WAITFOR" != "x" -a ! -L "/sys/class/net/$WAITFOR" ]; then
	cryptlog "E: network device '$WAITFOR' seems to be missing"
	cryptlog "E: fix WAITFOR in $CONFFILE"
	exit 1
fi

if [ "x$FIREWALL" != "x" -a "$FIREWALL" != "nftables" -a "$FIREWALL" != "iptables" ]; then
	cryptlog "E: invalid FIREWALL value: $FIREWALL"
	exit 1
fi

if [ "$FIREWALL" = "nftables" -a ! -f "/etc/opencrypt/nftables.conf" ]; then
	cryptlog "W: FIREWALL set to nftables but no rules found"
	cryptlog "W: you should set rules in /etc/opencrypt/nftables.conf"
fi

if [ "$FIREWALL" = "iptables" -a ! -f "/etc/opencrypt/iptables.conf" -a ! -f "/etc/opencrypt/ip6tables.conf" ]; then
	cryptlog "W: FIREWALL set to iptables but no rules found"
	cryptlog "W: you should set rules in /etc/opencrypt/iptables.conf"
	cryptlog "W: and/or /etc/opencrypt/ip6tables.conf"
fi

[ $VERBOSE -gt 0 ] && cryptlog "I: checking SEARCH parameters"
search_ids=$(get_search_ids)
if [ "x$search_ids" = "x" ]; then
	cryptlog "I: no SEARCH block set in $CONFFILE"
	cryptlog "I: only keyboard will be used for opening crypted devices"
	cryptlog "I: see man opencrypt.conf(5) for more information"
fi
for search_id in $search_ids ; do
	load_key_params $search_id
	if [ "x$REMOTEIP" != "x" ]; then
		check=$(echo "$REMOTEIP" | grep ":" | grep "\.")
		if [ "x$check" != "x" ] ; then
			cryptlog "E: $search_id: invalid REMOTEIP $REMOTEIP"
			exit 1
		fi
		oldip=$REMOTEIP
		check=$(echo "$REMOTEIP" | grep ":")
		if [ "x$check" != "x" ] ; then
			REMOTEIP=$(echo "$REMOTEIP" | sed 's/[^0-9a-fA-F:]//g')
		else
			REMOTEIP=$(echo "$REMOTEIP" | sed 's/[^0-9.]//g')
		fi
		if [ "x$oldip" != "x$REMOTEIP" ]; then
			cryptlog "W: $search_id: REMOTEIP '$oldip' changed to '$REMOTEIP'"
		fi
	fi
	if ! is_remotemode_valid "$REMOTEMODE" ; then
		cryptlog "E: invalid REMOTEMODE value: $REMOTEMODE"
		exit 1
	fi
	if [ "$REMOTEMODE" != "device" -a "x$REMOTEIP" = "x" ]; then
		cryptlog "E: $search_id: REMOTEIP must be set for REMOTEMODE $REMOTEMODE"
		cryptlog "E: see man opencrypt.conf(5) for more information"
		exit 1
	fi
	if [ "$REMOTEMODE" = "arp" ]; then
		check_ipv4=$(echo "$REMOTEIP" | sed 's/[0-9.]//g')
		if [ "x$check_ipv4" != "x" ] ; then
			cryptlog "E: $search_id: REMOTEIP $REMOTEIP must be an IPv4 for REMOTEMODE arp"
			cryptlog "E: see man opencrypt.conf(5) for more information"
			exit 1
		fi
	fi
done

[ $VERBOSE -gt 0 ] && cryptlog "I: checking network consistency"
if ! used_network_modes ; then
	if [ "x$WAITFOR" != "x" ]; then
		cryptlog "W: WAITFOR set but networking will not be used"
	fi
	if [ "x$FIREWALL" != "x" ]; then
		cryptlog "W: FIREWALL set but networking will not be used"
	fi
	if [ "$BOND" = "yes" ]; then
		cryptlog "W: BOND set but networking will not be used"
	fi
	if [ "$WPASUPPLICANT" = "yes" ]; then
		cryptlog "W: WPASUPPLICANT set but networking will not be used"
	fi
	if [ "x$MANUALWPA" != "x" ]; then
		cryptlog "W: MANUALWPA set but networking will not be used"
	fi
	if [ "x$MANUALDHCP" != "x" ]; then
		cryptlog "W: MANUALDHCP set but networking will not be used"
	fi
fi

if used_network_modes ; then
	if [ "$WPASUPPLICANT" = "no" -a "x$MANUALWPA" != "x" ]; then
		cryptlog "E: WPASUPPLICANT not set but MANUALWPA set"
		cryptlog "E: check your $CONFFILE"
		exit 1
	fi
	if [ "$WPASUPPLICANT" = "no" -a -f "/etc/network/interfaces" ]; then
		devices=$(ifquery --list --all --exclude lo 2>&1)
		for device in $devices ; do
			if ifquery $device | grep -qsw 'wpa' ; then
				cryptlog "E: WPASUPPLICANT not set but wpa stance found for $device"
				cryptlog "E: check your opencrypt and network configurations"
				exit 1
			fi
		done
	fi
fi

[ $VERBOSE -gt 0 ] && cryptlog "I: checking devices consistency"
if used_device_modes ; then
	if [ "x$DEVICELABEL" = "x" -a "x$DEVICETYPE" = "x" ]; then
		cryptlog "W: filter not set, opencrypt will search all unencrypted devices"
	fi
fi

# sync with verbose level from update-initramfs
[ $VERBOSE -lt 1 -a "x$verbose" = "xy" ] && VERBOSE=1
[ $VERBOSE -gt 2 ] && verbose="y"

if used_network_modes ; then
    if [ -f "/etc/network/interfaces" ]; then
        devices=$(ifquery --list --all --exclude lo 2>&1)
		if [ "x$devices" = "x" ]; then
			cryptlog "E: no usable network device found"
			cryptlog "E: please check your /etc/network/interfaces"
			exit 1
		else
			if [ $VERBOSE -gt 0 ]; then
			   message="I: opencrypt will use network devices:"
			   for device in $devices ; do
				   message="$message $device"
			   done
			   cryptlog "$message"
			fi
		fi
    else
		cryptlog "W: network will be set with kernel parameters"
    fi
fi

for file in /etc/opencrypt/* /usr/share/opencrypt/VERSION /usr/share/opencrypt/*.sh ; do
	custom_copy_file config "$file"
done

# busybox-static forces its own PATH
# so we need busybox package to use our /usr/local/bin
# or no busybox at all
if package_is_installed busybox-static ; then
	cryptlog "E: package busybox-static conflicts with opencrypt"
	cryptlog "E: please remove it or replace it with busybox"
	exit 1
fi

# of course we need cryptsetup
# cryptsetup-initramfs will copy cryptsetup but also some
# mandatory libs such as libgcc_s.so
# so we let him do his good job

# we need some binaries. Either via busybox or as standalone binary.
if [ "$BUSYBOX" = "n" -o "x$BUSYBOXDIR" = "x" ]; then
	for binary in "echo" "cat" "rm" "sha256sum" "grep" "cp" "cut" "mount" "umount" "rm" "expr" "sed" "ping" "awk" "timeout" "tee" ; do
		custom_copy_exec "/bin/$binary"
	done
	check_package util-linux
	custom_copy_exec /sbin/blkid
fi

if [ "$WPASUPPLICANT" = "yes" ]; then
	[ $VERBOSE -gt 0 ] && cryptlog "I: installing wpasupplicant"
	check_package wpasupplicant
	if systemctl --quiet is-active wpa_supplicant.service ; then
		if [ $VERBOSE -gt 0 ]; then
			cryptlog "W: found active service wpa_supplicant"
			cryptlog "W: it will not be installed in initramfs"
		fi
	fi
	custom_copy_exec /sbin/wpa_supplicant
	custom_copy_exec /sbin/wpa_cli
	custom_copy_exec /sbin/wpa_action
	custom_copy_exec /bin/wpa_passphrase
	if [ -f /etc/hostname ]; then
		custom_copy_file config /etc/hostname
	else
		hostname -s > "$DESTDIR/etc/hostname"
	fi
	# we need netdev group
	if ! grep -qs '^netdev:' "${DESTDIR}/etc/group"; then
		grep '^netdev:' "/etc/group" >> "${DESTDIR}/etc/group"
	fi
	custom_copy_file config /etc/dbus-1/system.d/wpa_supplicant.conf
	custom_copy_file config /usr/lib/NetworkManager/conf.d/no-mac-addr-change.conf
	for file in "/usr/share/dbus-1/system-services/fi.w1.wpa_supplicant1.service" /lib/systemd/system/wpa_supplicant* ; do
		custom_copy_file config "$file"
		if [ $VERBOSE -gt 0 ]; then
			sed 's/=\/sbin\/wpa_supplicant/=\/sbin\/wpa_supplicant -qq/' "$file" > "$DESTDIR/$file"
		fi
	done
	if [ -L "/sbin" -o ! -f "/sbin/start-stop-daemon" ]; then
		custom_copy_exec /usr/sbin/start-stop-daemon /usr/local/bin/start-stop-daemon
	else
		custom_copy_exec /sbin/start-stop-daemon /usr/local/bin/start-stop-daemon
	fi
	for file in /etc/wpa_supplicant.conf /etc/wpa_supplicant/* ; do
		custom_copy_file config "$file"
	done
	for file in /etc/network/*/wpasupplicant /etc/network/*/wireless-tools ; do
		custom_copy_file config "$file"
	done
	[ $VERBOSE -gt 0 ] && cryptlog "I: installing iw"
	check_package iw
	custom_copy_exec /sbin/iw
	[ $VERBOSE -gt 0 ] && cryptlog "I: installing iwconfig"
	check_package wireless-tools
	custom_copy_exec /sbin/iwconfig
fi

if [ "$BOND" = "yes" ]; then
	[ $VERBOSE -gt 0 ] && cryptlog "I: installing ifenslave"
	check_package coreutils
	for file in /etc/network/*/ifenslave ; do
		custom_copy_file config "$file"
	done
	# needed by ifenslave
	custom_copy_exec /bin/printenv
fi

if [ "x$OC_MODULES" != "x" ]; then
	[ $VERBOSE -gt 0 ] && cryptlog "I: installing additionnal modules"
	for module in $OC_MODULES ; do
		add_and_check_module "$module"
	done
fi

# if usr-is-merged /sbin and /bin are a link to /usr/sbin and /usr/bin
# and function custom_copy_file in hook-functions will create a link to
# ramfs /usr/sbin, so to busybox, instead of copying the real command
# so we take care to copy /usr/sbin/xxx and not /sbin/xxx
#
# get real ifup/ifdown/ifquery and run-parts
# busybox don't accept some interfaces stances such as "inet"
# and don't know how to source interfaces.d/*
# busybox run-parts don't accept --list parameter for isc-dhcp-client

if used_network_modes ; then
	[ $VERBOSE -gt 0 ] && cryptlog "I: installing ifup"
	check_package ifupdown

	if [ -L "/sbin" -o ! -f "/sbin/ifup" ]; then
		custom_copy_exec /usr/sbin/ifup /usr/local/bin/ifup
	else
		custom_copy_exec /sbin/ifup /usr/local/bin/ifup
	fi

	# we need debianutils for /bin/run-parts used by ifup
	check_package debianutils
	custom_copy_exec /usr/bin/run-parts /usr/local/bin/run-parts
	[ -f "$DESTDIR/usr/bin/run-parts" ] && rm "$DESTDIR/usr/bin/run-parts"

	[ $VERBOSE -gt 0 ] && cryptlog "I: installing network interfaces"
	for file in /etc/network/interfaces /etc/network/interfaces.d/* ; do
		[ -f "$file" ] && custom_copy_file config "$file"
	done
	for device in $(ifquery --list --all) ; do
		for file in $(ifquery $device) ; do
			if [ -f "$file" ]; then
				if [ -x "$file" ]; then
					custom_copy_exec "$file"
				else
					custom_copy_file config "$file"
				fi
			fi
		done
	done

	[ $VERBOSE -gt 0 ] && cryptlog "I: installing network dirs"
	# ifup needs some dirs even if empty
	for dir in "if-down.d" "if-post-down.d" "if-pre-up.d" "if-up.d" ; do
		mkdir -p "$DESTDIR/etc/network/$dir"
	done

	[ $VERBOSE -gt 0 ] && cryptlog "I: installing ip"
	check_package iproute2
	# get real ip as busybox doesn't list correct state
	if [ -L "/sbin" -o ! -f "/bin/ip" ]; then
		custom_copy_exec /usr/bin/ip /usr/local/bin/ip
	else
		custom_copy_exec /bin/ip /usr/local/bin/ip
	fi

	if systemctl --quiet is-active systemd-networkd.service ; then
		if [ $VERBOSE -gt 0 ]; then
			cryptlog "W: found active service systemd-networkd"
			cryptlog "W: it will not be installed in initramfs"
		fi
	fi
	if package_is_installed "dhcpcanon" ; then
		cryptlog "W: found installed package dhcpcanon"
		cryptlog "W: dhcpcanon is discontinued since bookworm"
		cryptlog "W: it will not be installed in initramfs"
	fi
	if package_is_installed "dhcpcd" ; then
		cryptlog "W: found installed package dhcpcd"
		cryptlog "W: it should NOT be installed on systems where interfaces are configured by ifupdown"
		cryptlog "W: it will not be installed in initramfs"
	fi
	if package_is_installed "network-manager" ; then
		cryptlog "W: found installed package network-manager"
		cryptlog "W: it will not be installed in initramfs"
	fi
	if package_is_installed "udhcpc" ; then
		[ $VERBOSE -gt 0 ] && cryptlog "I: installing dhcp client udhcpc"
		custom_copy_exec /etc/udhcpc/default.script
		# udhcpc is only a link to busybox and will be setup during initrd
		#custom_copy_exec /sbin/udhcpc
	fi
	if package_is_installed "isc-dhcp-client" ; then
		cryptlog "W: isc-dhcp-client is end of life and should be replaced"
		cryptlog "W: please consider using udhcpc"
		[ $VERBOSE -gt 0 ] && cryptlog "I: installing dhcp client isc-dhcp-client"
		mkdir -p "$DESTDIR/var/lib/dhcp"
		ln -rs "$DESTDIR/run" "$DESTDIR/var/run"
		custom_copy_exec /sbin/dhclient
		custom_copy_exec /sbin/dhclient-script
		for file in /etc/dhcp/* /etc/dhcp/dhclient-enter-hooks.d/* /etc/dhcp/dhclient-exit-hooks.d/* /var/lib/dhcp/dhclient.*.leases ; do
			[ -f "$file" -o -L "$file" ] && custom_copy_file config "$file"
		done
	fi
fi

if [ "$FIREWALL" = "iptables" ]; then
	# use iptables to setup a firewall during initramfs
	[ $VERBOSE -gt 0 ] && cryptlog "I: installing iptables"
	check_package iptables
	add_and_check_module "ip_tables"
	custom_copy_exec /sbin/iptables-restore
	custom_copy_exec /sbin/ip6tables-restore
elif [ "$FIREWALL" = "nftables" ]; then
	# use nft to setup a firewall during initramfs
	[ $VERBOSE -gt 0 ] && cryptlog "I: installing nft"
	check_package nftables
	add_and_check_module "nf_tables"
	custom_copy_exec /sbin/nft
	# nftables.conf could use services and protocol by name
	custom_copy_file config /etc/protocols /etc/protocols
	custom_copy_file config /etc/services /etc/services
fi

# wget size < curl size < wget2 size
if used_remote "http" ; then
	if package_is_installed "wget" ; then
		[ $VERBOSE -gt 0 ] && cryptlog "I: selecting wget for http mode"
		add_remote_mode "wget"
	elif package_is_installed "curl" ; then
		[ $VERBOSE -gt 0 ] && cryptlog "I: selecting curl for http mode"
		add_remote_mode "curl"
	elif package_is_installed "wget2" ; then
		[ $VERBOSE -gt 0 ] && cryptlog "I: selecting wget2 for http mode"
		add_remote_mode "wget2"
	else
		cryptlog "E: missing packages wget|wget2|curl"
		cryptlog "E: please install one of them"
		exit 2
	fi
fi

if used_remote "wget" ; then
	[ $VERBOSE -gt 1 ] && cryptlog "I: checking multiple network modes"
	if used_remote "curl|wget2" ; then
		cryptlog "W: found different network REMOTEMODE http|wget|wget2|curl"
		cryptlog "W: please consider using only one of them"
		cryptlog "W: for now opencrypt will use wget only"
	fi
	[ $VERBOSE -gt 0 ] && cryptlog "I: installing wget"
	check_package "wget"
	# we need real wget to use TLS
	if [ -L "/sbin" -o ! -f "/bin/wget" ]; then
		custom_copy_exec /usr/bin/wget /usr/local/bin/wget
	else
		custom_copy_exec /bin/wget /usr/local/bin/wget
	fi
elif used_remote "curl" ; then
	[ $VERBOSE -gt 1 ] && cryptlog "I: checking multiple network modes"
	if used_remote "wget2" ; then
		cryptlog "W: found different network REMOTEMODE http|wget|wget2|curl"
		cryptlog "W: please consider using only one of them"
		cryptlog "W: for now opencrypt will use curl only"
	fi
	[ $VERBOSE -gt 0 ] && cryptlog "I: installing curl"
	check_package curl
	custom_copy_exec /bin/curl
elif used_remote "wget2" ; then
	[ $VERBOSE -gt 0 ] && cryptlog "I: installing wget2"
	check_package wget2
	custom_copy_exec /bin/wget2
fi

if used_remote "ssh" ; then
	[ $VERBOSE -gt 0 ] && cryptlog "I: installing ssh"
	# we need a clean root user for ssh
	if ! grep -qs '^root:' "${DESTDIR}/etc/passwd"; then
		echo "root:x:0:0:root:/:/bin/sh" >> "${DESTDIR}/etc/passwd"
	fi
	check_package openssh-client
	custom_copy_exec /bin/ssh
fi

[ $VERBOSE -gt 0 ] && cryptlog "I: installing backup crypttab"
tmpcrypt="/tmp/$$-crypttab"
while read MAP CRYPT KEY PARAM ; do
    MAP=$(echo "$MAP" | cut -d'#' -f1)
    if [ "$KEY" = "none" -a "x$MAP" != "x#" -a "x$MAP" != "x" ]
    then
        echo "$MAP $CRYPT $KEY $PARAM" >> "$tmpcrypt"
    fi
done < "/etc/crypttab"
if [ -s $tmpcrypt ]; then
	copy_file config "$tmpcrypt" /etc/opencrypt/crypttab
	rm -f "$tmpcrypt"
else
	rm -f "$tmpcrypt"
	cryptlog "E: no usable device found in /etc/crypttab"
	cryptlog "E: opencrypt needs at least one device with 'none' in the third field"
	cryptlog "E: see man crypttab(7) for more information"
	exit 1
fi

[ $VERBOSE -gt 0 ] && cryptlog "I: ending opencrypt $oc_version"

exit 0
