#!/bin/bash
#
# This script is based on KISS Linux's encryption script with MIT license.
#
# Copyright (c) 2022 Qontinuum
# Copyright (c) 2021 Muhammad Herdiansyah
# Copyright (c) 2021 Artix Linux Developers
# Copyright (c) 2020 illiliti
# Copyright (c) 2019-2020 Dylan Araps
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# To keep consistency and to enable any interested parties to take anything
# from this source code without adding another license file, this file will also
# be licensed according to the terms above.

# sourcing our rc.conf requires this to be a bash script
. /usr/lib/rc/functions

# disable globbing because we don't need it
set -f

unlock_devices() {
    exec 3<&0; while read -r name dev pass opts err; do

        # Skip comments.
        [ "${name##\#*}" ] || continue

        # Break on invalid crypttab.
        [ "$err" ] && {
            stat_fail "A valid /etc/crypttab has only 4 columns. Aborting..."
            stat_die cryptsetup
        }

        # Turn 'UUID=*', 'LABEL=*', 'PARTUUID=*' into device name.
        case "${dev%%=*}" in UUID|LABEL|PARTUUID)
            for line in $(blkid); do case "${line%%=*}" in
                /dev/*)
                    _dev="${line%:}"
                ;;
                UUID|LABEL|PARTUUID)
                    _line="${line##*=}"
                    _line="${_line%\"}"
                    _line="${_line#\"}"

                    [ "$_line" = "${dev##*=}" ] && {
                        dev="$_dev"
                        break
                    }
                ;;
            esac; done
        esac

        # Parse options by turning the list into a pseudo array.
        # shellcheck disable=2086
        { IFS=,; set -- $opts; unset IFS; }

        # Create an argument list (no other way to do this in sh).
        for opt; do case "$opt" in
            readonly|read-only) copts="$copts -r"             ;;
            tries=*)    copts="$copts -T ${opt##*=}"          ;;
            discard)    copts="$copts --allow-discards"       ;;
            noauto)     copts=; continue 2                    ;;
            plain)      copts="$copts --type plain"           ;;
            luks)       copts="$copts --type luks"            ;;
            swap)       copts="$copts --type plain"; swap="y" ;;
            cipher=*)   sopts="$sopts -c ${opt##*=}"          ;;
            size=*)     sopts="$sopts -s ${opt##*=}"          ;;
            hash=*)     sopts="$sopts -h ${opt##*=}"          ;;
            offset=*)   sopts="$sopts -o ${opt##*=}"          ;;
            skip=*)     sopts="$sopts -p ${opt##*=}"          ;;
            verify)     sopts="$sopts -y"                     ;;
            keyfile-size=*) copts="$copts --${opt}"           ;;
            keyfile-offset=*) copts="$copts --${opt}"         ;;
            header=*) copts="$copts --${opt}"                 ;;
        esac; done

        [ "$swap" = y ] && copts="$copts $sopts"

        # If password is 'none', '-' or empty ask for it.
        # shellcheck disable=2086
        case "$pass" in
            none|-|"") cryptsetup $copts open "$dev" "$name" <&3 ;;
            *) cryptsetup $copts -d "$pass" open "$dev" "$name"  ;;
        esac

        [ "$swap" = y ] && mkswap -q "/dev/mapper/$name"
    copts=; sopts=; swap=; done < /etc/crypttab; exec 3>&-
}

lock_devices() {
    for file in /sys/block/dm-*; do
        IFS=- read -r dm_type _ _ _ < "$file/dm/uuid"

        # --deferred used to prevent hang on FDE systems
        [ "$dm_type" = CRYPT ] && cryptsetup close --deferred "$(cat "$file/dm/name")"
    done
}

# restore globbing
set +f

case "$1" in
    start)
        stat_busy "Unlocking encrypted devices"
        unlock_devices
        rc=$?
        if [ -f /run/sv.d/started/lvm2 ]; then
            vgchange --sysinit -a y >/dev/null
        fi
        (( rc || $? )) && stat_die cryptsetup
        add_daemon cryptsetup
        stat_done cryptsetup
        ;;
    stop)
        stat_busy "Locking encrypted devices"
        lock_devices
        rc="$?"
        # Deactivate remaining encrypted devices that are active in LVM
        VGSCMD="$(command -v vgs)"
        if [ -n "$VGSCMD" ]; then
            vgs="$("$VGSCMD" | wc -l)"
            [ "$vgs" -gt 0 ] && vgchange -an >/dev/null
        fi
        (( rc || $? )) && stat_die cryptsetup
        rm_daemon cryptsetup
        stat_done cryptsetup
        ;;
    *)
        echo "usage: $0 {start|stop}"
        exit 1
        ;;
esac
