#!/bin/sh -efu
#
# Configures the system bootloader to load the kernel in
# IMA fix mode and reboots the system. It then is used to
# run the signing process and to configure the system bootloader
# to load the kernel in IMA enforce mode and to reboot the system.
#
# Copyright (C) 2023  Denis Medvedev.
# Copyright (C) 2024  Paul Wolneykien.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
. shell-error
. shell-make-config

REBOOT_DELAY=5
STATEDIR=/var/lib/integrity_update

[ ! -e /etc/integrity/config ] || . /etc/integrity/config
[ ! -e /etc/sysconfig/integrity ] || . /etc/sysconfig/integrity

PROG="${0##*/}"
VERSION="0.7.7"

HASH_ALGO="${HASH_ALGO:-sha512}"
DEFAULT_EVM_MODE='0x80000002'

usage()
{
    [ "$1" = 0 ] || exec >&2
    echo "Usage: $PROG [ -v | --verbose ] [ --hash=HASH ] [ -E | --with-evm ] [ --without-evm ] [ -R | --no-reboot ]"
    echo
    echo "# This is the 1st step:"
    echo "       $PROG -i | --init [ -G | --disable-graphics ] [ -A | --auto ]"
    echo
    echo "# This is the 2nd step:"
    echo "       $PROG -s | --sign [ -I | --skip-initrd ] [ --log FILE ]"
    echo
    echo "# Other:"
    echo "       $PROG --initrd-only"
    echo "       $PROG -h | --help"
    echo "       $PROG -V | --version"
    exit "${1:-0}"
}

TEMP="$(getopt -n "$PROG" -o a:GvsiRIAhVE -l hash:,disable-graphics,with-evm,without-evm,init,sign,no-reboot,skip-initrd,initrd-only,verbose,log:,log-stderr,auto,help,version -- "$@")" || usage 1
eval set -- "$TEMP"

mode=
disable_graphics=
no_reboot=
skip_initrd=
log_file=
auto=
override_hash=
with_evm=
without_evm=
while :; do
    case "$1" in
	-i|--init)
	    mode=init
	    ;;
	-s|--sign)
	    mode=sign
	    ;;
	-R|--no-reboot)
	    no_reboot=1
	    ;;
	-I|--skip-initrd)
	    skip_initrd=1
	    ;;
	--initrd-only)
	    mode=initrd_only
	    ;;
	-A|--auto)
	    auto=1
	    ;;
	-v|--verbose)
	    #shellcheck disable=SC2034
	    verbose=y
	    ;;
        -a|--hash)
	    shift; HASH_ALGO="$1"
	    override_hash=1
            ;;
	-G|--disable-graphics)
	    disable_graphics=y
	    ;;
        --log)
	    shift; log_file="$1"
            ;;
        --log-stderr)
	    log_file="-"
            ;;
	-E|--with-evm)
	    with_evm=1
	    ;;
	--without-evm)
	    without_evm=1
	    ;;
        -h|--help)
	    usage 0
            ;;
	-V|--version)
	    cat <<EOF
$VERSION 2024
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
EOF
	    exit 0
	    ;;
        --)
	    shift
	    break
            ;;
        *)
	    message "$PROG: unrecognized option: $1" >&2
	    usage 1
            ;;
    esac
    shift
done

get_ima_hash() {
    sed -e 's/^.*[[:space:]]ima_hash=\([^[:space:]]\+\).*$/\1/' </proc/cmdline
}

get_ima_appraise() {
    sed -e 's/^.*[[:space:]]ima_appraise=\([^[:space:]]\+\).*$/\1/' </proc/cmdline
}

current_appraise="$(get_ima_appraise)"
current_hash="$(get_ima_hash)"

if [ -z "$HASH_ALGO" -a -z "$current_hash" ]; then
    fatal "Hash algo isn't set (config or cmdline)"
elif [ -z "$HASH_ALGO" ]; then
    HASH_ALGO="$current_hash"
fi

if [ -z "$mode" ]; then
    case "$current_appraise" in
	fix)
	    message "Current IMA mode is 'fix'. Select signing mode."
	    mode=sign
	    ;;
	*)
	    message "Current IMA mode is not 'fix'. Select initialization mode."
	    mode=init
	    ;;
    esac
fi

if [ "$mode" != 'init' -a -z "$override_hash" ]; then
    if [ -n "$current_hash" ]; then
	HASH_ALGO="$current_hash"
    fi
fi

case "$mode" in
    init)
	mkdir -p "$STATEDIR"

	systemctl is-enabled integalert.service \
		  >"$STATEDIR"/integalert.status ||:

	case "$(cat "$STATEDIR"/integalert.status)" in
	    enabled)
		message "Disabling the integalert.service..."
		systemctl disable integalert.service
		;;
	esac

	if [ -z "$auto" ]; then
	    if [ -n "$disable_graphics" ]; then
		message "Disabling graphical.target..."
		systemctl get-default >"$STATEDIR"/default
		systemctl set-default multi-user.target
	    else
		rm -f "$STATEDIR"/default
	    fi
	else
	    systemctl get-default >"$STATEDIR"/default
	    systemctl set-default ima-signing.target
	    systemctl enable ima-signing.service
	    message "The special ima-signing.target is set for the next boot."
	fi

	if [ -n "$with_evm" ]; then
	    echo 1 >"$STATEDIR"/evm
	elif [ -n "$without_evm" ]; then
	    echo 0 >"$STATEDIR"/evm
	else
	    rm -f "$STATEDIR"/evm
	fi

	message "Reconfiguring the bootloader..."

	SKIP_APPLY=1 control ima_hash "$HASH_ALGO"
	control ima_appraise fix

	message "Finished stage I."

	if [ -z "$no_reboot" ]; then
	    if [ -z "$auto" ]; then
		message "Rebooting to Stage II... After reboot please run '$PROG --sign'."
	    else
		message "Rebooting to Stage II... After reboot it will be run automatically."
	    fi
	    [ -z "$REBOOT_DELAY" ] || sleep "$REBOOT_DELAY"
	    reboot -fp
	else
	    message "Now please reboot. After reboot run '$PROG --sign'."
	fi
	;;
    sign|initrd_only)
	export TMPDIR=/var/tmp

	if [ -z "$log_file" -a -n "$LOG_FILE" ]; then
	    log_file="$LOG_FILE"
	    [ "$log_file" = "-" ] || \
		message "Write file signing log to $LOG_FILE as configured..."
	fi

	if [ -z "$with_evm$without_evm" ]; then
	    if [ -e "$STATEDIR"/evm ]; then
		if [ "$(cat "$STATEDIR"/evm)" = '1' ]; then
		    with_evm=1
		else
		    without_evm=1
		fi
	    fi
	fi

	case "$mode" in
	    sign)
	        systemd-run -p KeyringMode=inherit \
			    -p PrivateTmp=false \
	                    --service-type=oneshot \
	                    -P -- \
	            /usr/sbin/integrity-sign ${verbose:+-v} --sign \
		                   ${log_file:+--log "$log_file"} \
				   ${with_evm:+--with-evm} \
				   ${without_evm:+--without-evm} \
		                   ${override_hash:+--hash="$HASH_ALGO"} || \
	                fatal "Error while signing the files!"
	        ;;
	esac

	if [ -z "$skip_initrd" ]; then
	    message "Regenerating initrd image..."
	    add_make_value /etc/initrd.mk FEATURES integrity

	    case "$HASH_ALGO" in
		streebog*)
		    add_make_value /etc/initrd.mk \
				   INTEGRITY_FEATURES \
				   gost
		    ;;
		*)
		    del_make_value /etc/initrd.mk \
				   INTEGRITY_FEATURES \
				   gost
		    ;;
	    esac

	    echo "$DEFAULT_EVM_MODE" >/etc/integrity/evm_mode
	    if [ -n "${WITH_EVM:-}" ]; then
		case "$WITH_EVM" in
		    0x*)
			"$WITH_EVM" >/etc/integrity/evm_mode
			;;
		esac
	    fi

	    make-initrd || \
		fatal "Error regenerating initrd image!"

	    case "$mode" in
		initrd_only)
		    exit 0
		    ;;
	    esac
	fi

	if [ -e "$STATEDIR"/integalert.status ]; then
	    case "$(cat "$STATEDIR"/integalert.status)" in
		enabled)
		    message "Updating integalert data..."
		    integalert="$(which integalert 2>/dev/null)" ||:
		    if [ -n "$integalert" ]; then
			"$integalert" fix
		    fi

		    message "Re-enabling the integalert.service..."
		    systemctl enable integalert.service
		    ;;
	    esac
	fi

	if [ -e "$STATEDIR"/default ]; then
	    message "Re-enabling the default target..."
	    systemctl set-default "$(cat "$STATEDIR"/default)"
	fi

	message "Reconfiguring the bootloader..."

	SKIP_APPLY=1 control ima_hash "$HASH_ALGO"
	control ima_appraise enforce

	message "Stage II is done."

	[ -z "$skip_initrd" ] || \
	    message "WARNING! You may also need to regenerate initrd image (you can use --initrd-only option for that)."

	if [ -z "$no_reboot" ]; then
	    message "Rebooting to normal mode..."
	    [ -z "$REBOOT_DELAY" ] || sleep "$REBOOT_DELAY"
	    reboot -fp
	else
	    message "Now please reboot. After reboot the system will be ready to use${skip_initrd:+ (if initrd image is okay)}."
	fi
	;;
esac
