#!/bin/sh

#/usr/local/sbin/bootkeyscript
#https://doc.ubuntu-fr.org/tutoriel/securiser_ubuntu_avec_peripherique_externe#etape_3parametrer_le_demarrage


# Swami Petaramesh, 2007/11/28
# Modified by Swami Petaramesh 2010/03/23
# Modified by malabarth 2013/09/26
#
# Version 3.0
#
# Copyright Swami Petaramesh 2007-2010 <swami AT petaramesh DOT org>

# Parts copied from "cryptroot" script from the "cryptsetup"
# package, copyright cryptsetup authors. (Christophe Saout,
# Clemens Fruhwirth).

# This script allows using a key file stored on an external
# device, which can be LUKS-encrypted or not. It can fallback
# to using a manually input passphrase if the external device
# is missing.

# 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 3 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, see <http://www.gnu.org/licenses/>


# Additional boot params : cryptopts=key=<device>:<keyfile>
# Example : cryptopts=key=mmcblk0p1:mykeyfile
#       Will mount /dev/mmcblk0p1 and use "mykeyfile" on this device
#
# UUID=something:mykeyfile and LABEL=something:mykeyfile are also
# supported.

#
# Helper functions
#

. /scripts/functions

# debug=1 will print debug messages and echo typed password
# debug=0 will be much more silent
# It can be set using boot command line, i.e. cryptopts=target=,key=/dev/sdb1/.key,debug=1
debug=0

message()
{
        if [ -x /bin/plymouth ] && plymouth --ping; then
                plymouth message --text="$@"
        elif [ -p /dev/.initramfs/usplash_outfifo ] && [ -x /sbin/usplash_write ]; then
                /sbin/usplash_write "TEXT-URGENT $@"
        else
                echo "$@" > /dev/console
        fi
}


parse_options()
{
        local cryptopts
        cryptopts="$@"

        [ "${debug}" == "1" ] && message "(parse_options)"

        if [ -z "$cryptopts" ]; then
                message "(parse_options): Called without parameters. Nothing to do..."
                return 1
        fi

        # Defaults
        crypttarget="filesystem"
        cryptkeydev=""
        dispkeydev=""
        cryptkey=""

        local IFS=" ,"
        for x in $cryptopts; do
                case $x in
                target=*)
                        crypttarget=${x#target=}
                        ;;
                key=*)
                        if [ "${x#key=}" != "none" ]; then
                                cryptkey=${x#key=}
                        fi
                        ;;
                debug=*)
                        if [ "${x#debug=}" == "1" ]; then
                                debug=1
                        fi
                        ;;
                esac
        done

        case ${cryptkey} in
           *:*)
                case ${cryptkey} in
                [Uu][Uu][Ii][Dd]=*)
                        cryptkey="${cryptkey#[Uu][Uu][Ii][Dd]=}"
                        cryptkeydev="/dev/disk/by-uuid/${cryptkey%%:*}"
                        dispkeydev="${cryptkey%%:*}"
                        dispkeydev="UUID:${dispkeydev##*-}"
                        cryptkey="${cryptkey#*:}"
                        ;;
                [Ll][Aa][Bb][Ee][Ll]=*)
                        cryptkey="${cryptkey#[Ll][Aa][Bb][Ee][Ll]=}"
                        cryptkeydev="/dev/disk/by-label/${cryptkey%%:*}"
                        dispkeydev="${cryptkey%%:*}"
                        cryptkey="${cryptkey#*:}"
                        ;;
                *)
                        cryptkeydev="/dev/${cryptkey%%:*}"
                        dispkeydev="${cryptkey%%:*}"
                        cryptkey="${cryptkey#*:}"
                        ;;
                esac
                ;;
           *)
                cryptkeydev="/dev/${cryptkey%%/*}"
                dispkeydev="${cryptkey%%/*}"
                cryptkey="${cryptkey#*/}"
                ;;
        esac

        if [ "${debug}" == "1" ]; then
                 message "target: ${crypttarget}; keydev: ${dispkeydev}; key: ${cryptkey}"
        fi
}

trymount() {
        local mtmsg mtresult
        [ "${debug}" == "1" ] && message "(trymount)"

        [ -d "/mnt/rootKey" ] || mkdir -p /mnt/rootKey >/dev/null 2>&1

        FSTYPE=''
        eval $(fstype < "${cryptkeydev}")
        [ "${debug}" == "1" ] && message "${dispkeydev} filesystem is ${FSTYPE}"
        mtmsg=""
        mtresult=1

        if [ -n "${FSTYPE}" -a "${FSTYPE}" != "unknown" -a "${FSTYPE}" != "Unknown" ]; then
                [ "${debug}" == "1" ] && message "Trying to mount $1 as ${FSTYPE}..."
                mtmsg="`mount -t ${FSTYPE} -o ro $1 /mnt/rootKey 2>&1`"
                mtresult="$?"
        fi

        if [ ${mtresult} -ne 0 ]; then
                [ "${debug}" == "1" ] && message "Trying to mount $1 as ext2..."
                mtmsg="`mount -t ext2 -o ro $1 /mnt/rootKey 2>&1`"
                mtresult="$?"
        fi

        if [ ${mtresult} -ne 0 ]; then
                [ "${debug}" == "1" ] && message "Trying to mount $1 as vfat..."
                mtmsg="`mount -t vfat -o ro $1 /mnt/rootKey 2>&1`"
                mtresult="$?"
        fi

        if [ ${mtresult} -ne 0 ]; then
                [ "${debug}" == "1" ] && message "Trying to mount $1 as ntfs..."
                mtmsg="`mount -t ntfs-3g -o ro $1 /mnt/rootKey 2>&1`"
                mtresult="$?"
        fi

        if [ "${debug}" == "1" ]; then
                if [ ${mtresult} -eq 0 ]; then
                        message "Success !"
                else
                        message "FAILED with message:"
                        message "${mtmsg}"
                fi
        fi

        return ${mtresult}
}

type_key() {
        local PASS
        [ "${debug}" == "1" ] && {
                sleep 5
                message "(type_key)"
        }

        if [ -x /bin/plymouth ] && plymouth --ping; then
                PASS="$(plymouth ask-for-password --prompt="${1:-Enter password:}")"
        elif [ -p /dev/.initramfs/usplash_outfifo ] && [ -x /sbin/usplash_write ]; then
                /sbin/usplash_write "TIMEOUT 90" || true
                if [ "${debug}" == "1" ]; then
                        /sbin/usplash_write "INPUT ${1:-Enter password:} "
                else
                        /sbin/usplash_write "INPUTQUIET ${1:-Enter password:} "
                fi
                PASS="$(cat /dev/.initramfs/usplash_outfifo)"
        else
                [ "${debug}" != "1" ] && /bin/stty -F /dev/console -echo
                read -p "${1:-Enter password:} " PASS < /dev/console 2> /dev/console
                [ "${debug}" != "1" ] && /bin/stty -F /dev/console echo
        fi

        [ "${debug}" == "1" ] && message "Pwd: ${PASS}"

        echo -n "${PASS}"

        if [ -p /dev/.initramfs/usplash_outfifo ] && [ -x /sbin/usplash_write ]; then
                # clean the text
                /sbin/usplash_write "TIMEOUT 15" || true
                [ "${debug}" != "1" ] && /sbin/usplash_write "CLEAR"
        fi
}

fetch_key()
{
        local opts count
        opts="$@"

        [ "${debug}" == "1" ] && message "(fetch_key)"

        if [ -z "$opts" ]; then
                message "(fetch_key): Called without parameters. Nothing to do..."
                return 1
        fi

        parse_options "$opts" || {
                message "(fetch_key): ERROR: Could not parse options."
                return 1
        }

        # If no key device and file specified, fallback to typing the passphrase
        if [ -z "${cryptkeydev}" -o -z "${cryptkey}" ]; then
                type_key "Enter password for ${crypttarget}:"
        else
                # Wait for udev to be ready, see https://launchpad.net/bugs/85640
                if [ -x /sbin/udevsettle ]; then
                        /sbin/udevsettle --timeout=30
                fi

                # If the encrypted source device hasn't shown up yet, give it a little while
                # to deal with removable devices
                if [ ! -b "${cryptkeydev}" ] || ! /lib/udev/vol_id "${cryptkeydev}" >/dev/null 2>&1; then
                        [ "${debug}" == "1" ] && message "Waiting for ${dispkeydev}..."

                        # We'll wait for a maximum of 20 seconds : External key devices
                        # such as solid-state USB keys or SD/MMC shouldn't take longer
                        # to show up...
                        slumber=20
                        if [ -x /sbin/usplash_write ]; then
                                /sbin/usplash_write "TIMEOUT 25" || true
                        fi

                        slumber=$(( ${slumber} * 10 ))
                        while [ ! -b "${cryptkeydev}" ] || ! /lib/udev/vol_id "${cryptkeydev}" >/dev/null 2>&1; do
                                /bin/sleep 0.1
                                slumber=$(( ${slumber} - 1 ))
                                [ ${slumber} -gt 0 ] || break
                        done

                        if [ -x /sbin/usplash_write ]; then
                                /sbin/usplash_write "TIMEOUT 15" || true
                        fi
                fi

                # Fallback to typing the passphrase
                if [ ! -b "${cryptkeydev}" ]; then
                        type_key "Missing boot device ${dispkeydev}!"
                        exit 0
                fi

                [ "${debug}" == "1" ] && message "${dispkeydev} is available."

                # Now that we've found the key device, let's check if it is
                # itself LUKS-encrypted
                if /sbin/cryptsetup isLuks ${cryptkeydev} > /dev/null 2>&1; then
                        [ "${debug}" == "1" ] && message "${dispkeydev} is LUKS encrypted."

                        # Try to get a satisfactory password three times
                        count=0
                        while [ $count -lt 3 ]; do
                                count=$(( $count + 1 ))
                                type_key "Enter password for ${dispkeydev}:" | /sbin/cryptsetup --readonly luksOpen ${cryptkeydev} rootKeyDev > /dev/null 2>&1

                                if [ $? -ne 0 ]; then
                                        echo "cryptsetup: cryptsetup failed, bad password ?" > /dev/console
                                        sleep 2
                                        continue
                                elif [ ! -b "/dev/mapper/rootKeyDev" ]; then
                                        echo "cryptsetup: unknown error setting up device mapping" > /dev/console
                                        # Fallback to typing the passphrase
                                        type_key "Setup of ${dispkeydev} failed!"
                                        exit 0
                                fi
                                break
                        done

                        if [ $count -ge 3 ]; then
                                echo "cryptsetup: maximum number of tries exceeded" > /dev/console
                                type_key "Failed opening ${dispkeydev}!"
                                exit 0
                        fi

                        # Now try to mount the encrypted key container
                        trymount /dev/mapper/rootKeyDev
                else
                        # Or try to mount the device
                        trymount ${cryptkeydev}
                fi

                if [ $? -ne 0 ]; then
                        type_key "Failed mounting ${dispkeydev}!"

                # echo the keyfile contents
                elif [ -r "/mnt/rootKey/${cryptkey}" -a -s "/mnt/rootKey/${cryptkey}" ]; then
                        cat /mnt/rootKey/${cryptkey}
                else
                        type_key "Could not find ${cryptkey} on ${dispkeydev}!"
                fi

                # Unmount the key device
                umount /mnt/rootKey > /dev/null 2>&1
                # Wait for udev to be ready, see https://launchpad.net/bugs/85640
                if [ -x /sbin/udevsettle ]; then
                        /sbin/udevsettle --timeout=30
                fi
                sleep 1

                # Close the encrypted device if it has been used
                [ -b "/dev/mapper/rootKeyDev" ] && /sbin/cryptsetup luksClose rootKeyDev > /dev/null 2>&1
        fi
        exit 0
}

#
# Begin real processing
#

# Do we have any kernel boot arguments?
found=''
for opt in $(cat /proc/cmdline); do
        case $opt in
        cryptdebug*)
                debug=1
                ;;
        cryptopts=*)
                found=yes
                fetch_key "${opt#cryptopts=}"
                ;;
        esac
done

if [ -n "$found" ]; then
        if [ -p /dev/.initramfs/usplash_outfifo ] && [ -x /sbin/usplash_write ]; then
                /sbin/usplash_write "TIMEOUT 60" || true
        fi
        exit 0
fi

# Do we have any settings from the /conf/conf.d/cryptroot file?
if [ -r /conf/conf.d/cryptroot ]; then
        while read mapping; do
                fetch_key "$mapping"
        done < /conf/conf.d/cryptroot
fi

if [ -p /dev/.initramfs/usplash_outfifo ] && [ -x /sbin/usplash_write ]; then
        /sbin/usplash_write "TIMEOUT 60" || true
fi

exit 0
