#!/usr/bin/ash
# shellcheck shell=dash

# set default values:
DBG=""
YKFDE_CONFIG_FILE="/etc/ykfde.conf"
YKFDE_NFC=""
YKFDE_DISK_UUID=""
YKFDE_LUKS_NAME=""
YKFDE_LUKS_DEV=""
YKFDE_LUKS_OPTIONS=""
YKFDE_CHALLENGE_YUBIKEY_INSERT_TIMEOUT="30"
YKFDE_CRYPTSETUP_TRIALS="5"
YKFDE_CHALLENGE_SLOT="2"
YKFDE_CHALLENGE=""
YKFDE_CHALLENGE_PASSWORD_NEEDED=""
YKFDE_SLEEP_AFTER_SUCCESSFUL_CRYPTSETUP=""
YKFDE_USE_PLYMOUTH=""

message() {
  if [ "$YKFDE_USE_PLYMOUTH" ]; then
    plymouth display-message --text="$*"
  else
    echo "$@" >&2
  fi
  return 0
}

run_hook() {
  local _tmp
  _tmp=""
  local cryptopt cryptoptions

  [ -x /bin/plymouth ] && [ "$splash" ] && plymouth --ping && YKFDE_USE_PLYMOUTH=1

  [ "$DBG" ] && message "$0:"

  [ "$DBG" ] && message " > Reading YKFDE configuration file."
  # shellcheck source=../ykfde.conf
  . "$YKFDE_CONFIG_FILE" || {
    ykfde_err 001 "Failed to read the YKFDE configuration file '$YKFDE_CONFIG_FILE'"
    return 1
  }

  # if no settings in config, try to pull it from kernel cmdline (analog to encrypt hook)
  if [ -z "$YKFDE_DISK_UUID" ] || [ -z "$YKFDE_LUKS_NAME" ]; then
    # shellcheck disable=SC2154
    if [ "$cryptdevice" ]; then
      IFS=: read -r YKFDE_LUKS_DEV YKFDE_LUKS_NAME cryptoptions <<EOF
$cryptdevice
EOF
      if [ -z "$YKFDE_LUKS_OPTIONS" ]; then
        for cryptopt in $(echo "$cryptoptions" | tr , ' '); do
          case "$cryptopt" in
            allow-discards)
              YKFDE_LUKS_OPTIONS="--allow-discards"
              ;;
            *)
              ykfde_err 002 "Encryption option '$cryptopt' not known, ignoring."
              ;;
          esac
        done
      fi
      YKFDE_LUKS_DEV="$(resolve_device "$YKFDE_LUKS_DEV")"
    else
      ykfde_err 003 "'$YKFDE_CONFIG_FILE' Please provide YKFDE_DISK_UUID /and/ YKFDE_LUKS_NAME or set cryptdevice kernel parameter."
      return 1
    fi
  fi

  # sanity checks:
  [ -z "$YKFDE_LUKS_DEV" ] && YKFDE_LUKS_DEV="/dev/disk/by-uuid/$YKFDE_DISK_UUID"
  [ "$YKFDE_CRYPTSETUP_TRIALS" -lt 1 ] && YKFDE_CRYPTSETUP_TRIALS="5"
  [ "$YKFDE_CHALLENGE_SLOT" -lt 1 ] || [ "$YKFDE_CHALLENGE_SLOT" -gt 2 ] && YKFDE_CHALLENGE_SLOT="2"

  [ -e "$YKFDE_LUKS_DEV" ] || {
    ykfde_err 004 "YKFDE cannot find LUKS device '$YKFDE_LUKS_DEV'.\\nPlease check YKFDE_DISK_UUID ($YKFDE_DISK_UUID) and/or YKFDE_LUKS_DEV variable(s) in '$YKFDE_CONFIG_FILE'."
    return 1
  }

  [ "$DBG" ] && message " > modprobing dm-crypt"
  _tmp="$(modprobe -a -q dm-crypt >/dev/null 2>&1)"

  local trial_nr
  trial_nr=1
  while [ "$trial_nr" -le "$YKFDE_CRYPTSETUP_TRIALS" ]; do
    message "Attempt #$trial_nr/$YKFDE_CRYPTSETUP_TRIALS: cryptsetup of $YKFDE_LUKS_DEV"
    ykfde_do_it && return 0
    trial_nr=$((trial_nr + 1))
  done

  # if we get here, we did NOT succeed:
  ykfde_err 000 "$0 Failed!"
  return 1
}

ykfde_err() {
  local _rc
  _rc="$?"
  local code
  code="$1"
  local msg
  msg="$2"
  [ "$msg" ] && msg="ERROR $code [rc=$_rc]: $msg" || msg="ERROR $code [rc=$_rc]"
  message "$msg" #exit 1;
}

# assemble passphrase and run 'cryptsetup luksOpen'
ykfde_do_it() {
  # key used to 'cryptsetup luksOpen'
  local _ykfde_passphrase
  _ykfde_passphrase=""
  local _tmp
  _tmp=""
  local _rc
  _rc=""

  # if we have a challenge
  [ "$YKFDE_CHALLENGE" ] || [ "$YKFDE_CHALLENGE_PASSWORD_NEEDED" ] && ykfde_challenge_response

  if [ -z "$_ykfde_passphrase" ]; then
    if [ "$YKFDE_CHALLENGE" ] || [ "$YKFDE_CHALLENGE_PASSWORD_NEEDED" ]; then
      message " > Challenge-Response failed. Falling back to manual passphrase."
      [ "$trial_nr" -le "$YKFDE_CRYPTSETUP_TRIALS" ] && message "   Press ENTER to skip and retry Challenge-Response."
    else
      message " > Passphrase needed to unlock device."
    fi

    printf "   Enter passphrase: "
    if [ "$YKFDE_USE_PLYMOUTH" ]; then
      _ykfde_passphrase="$(plymouth ask-for-password --prompt="Enter passphrase" --dont-pause-progress)"
    else
      # shellcheck disable=SC2169
      if [ "$DBG" ]; then read -r _ykfde_passphrase; else read -r -s _ykfde_passphrase; fi
    fi
    # if /NOT/ DBG, we need to output \n here.
    [ "$DBG" ] || echo
  fi

  [ "$DBG" ] && message " > Passing '$_ykfde_passphrase' to 'cryptsetup'"
  if [ "$YKFDE_RESUME" ]; then
    # shellcheck disable=SC2154
    if [ "$DBG" ]; then message " > Decrypting with 'cryptsetup luksResume $cryptname'..."; else message " > Decrypting with 'cryptsetup'..."; fi
    _tmp="$(printf %s "$_ykfde_passphrase" | cryptsetup luksResume "$cryptname" 2>&1)"
  else
    if [ "$DBG" ]; then message " > Decrypting with 'cryptsetup luksOpen $YKFDE_LUKS_DEV $YKFDE_LUKS_NAME $YKFDE_LUKS_OPTIONS'..."; else message " > Decrypting with 'cryptsetup'..."; fi
    _tmp="$(printf %s "$_ykfde_passphrase" | cryptsetup luksOpen "$YKFDE_LUKS_DEV" "$YKFDE_LUKS_NAME" "$YKFDE_LUKS_OPTIONS" 2>&1)"
  fi
  _rc=$?

  if [ "$_rc" -eq 0 ]; then
    message "   Decryption was successful."
    if [ "$YKFDE_SLEEP_AFTER_SUCCESSFUL_CRYPTSETUP" ] && [ "$YKFDE_SLEEP_AFTER_SUCCESSFUL_CRYPTSETUP" -gt 0 ]; then
      [ "$DBG" ] && message " > Making $YKFDE_SLEEP_AFTER_SUCCESSFUL_CRYPTSETUP sleep."
      sleep "$YKFDE_SLEEP_AFTER_SUCCESSFUL_CRYPTSETUP"
    fi
  else
    message "   FAILED! [$_rc] $_tmp"
  fi

  return "$_rc"
}

ykfde_challenge_response() {
  local _yubikey_timeout
  _yubikey_timeout="$YKFDE_CHALLENGE_YUBIKEY_INSERT_TIMEOUT"
  local _yubikey_timeout_str
  _yubikey_timeout_str=""
  local _yubikey_detected
  _yubikey_detected=""
  local _yubikey_nfc_detected
  _yubikey_nfc_detected=""
  local _ykfde_response
  _ykfde_response=""
  # to determine if a timeout occurred
  local _starttime
  _starttime=""
  local _endtime
  _endtime=""
  local _usedtime
  _usedtime=""
  local _tmp
  _tmp=""
  local _rc
  _rc=""

  [ "$YKFDE_CHALLENGE_YUBIKEY_INSERT_TIMEOUT" -gt 0 ] && _yubikey_timeout_str="$YKFDE_CHALLENGE_YUBIKEY_INSERT_TIMEOUT seconds"

  _starttime="$(date +%s)"
  message " > Waiting $_yubikey_timeout_str for YubiKey..."

  while [ -z "$_yubikey_detected" ] && [ -z "$_yubikey_nfc_detected" ]; do
    _endtime="$(date +%s)"
    _usedtime=$((_endtime - _starttime))
    [ "$DBG" ] && message "   (used time:$_usedtime, timeout:$_yubikey_timeout) 'ykinfo -$YKFDE_CHALLENGE_SLOT': "
    _tmp="$(ykinfo -"$YKFDE_CHALLENGE_SLOT" 2>&1)"
    _rc=$?
    [ "$DBG" ] && message "[$_rc] '$_tmp'"
    if [ "$_rc" -eq 0 ]; then
      _yubikey_detected=1
    elif [ "$YKFDE_NFC" ]; then
      [ "$DBG" ] && message "   (used time:$_usedtime, timeout:$_yubikey_timeout) 'ykchalresp-nfc -n': "
      _tmp="$(ykchalresp-nfc -n 2>&1)"
      _rc=$?
      [ "$_rc" -eq 0 ] && _yubikey_nfc_detected=1
    fi
    if [ "$_yubikey_timeout" -eq -1 ] || [ "$_usedtime" -le "$_yubikey_timeout" ]; then
      sleep 0.5
    else
      message "    Timeout - Challenge-Response aborted."
      # timeout
      return 1
    fi
  done

  [ "$YKFDE_CHALLENGE_PASSWORD_NEEDED" ] && YKFDE_CHALLENGE=""
  while [ -z "$YKFDE_CHALLENGE" ]; do
    message " > Please provide the challenge."
    printf "   Enter challenge: "
    # shellcheck disable=SC2169
    if [ "$YKFDE_USE_PLYMOUTH" ]; then
      YKFDE_CHALLENGE="$(plymouth ask-for-password --prompt="Enter challenge" --dont-pause-progress)"
    elif [ "$DBG" ]; then
      read -r YKFDE_CHALLENGE
    else
      read -r -s YKFDE_CHALLENGE
    fi
    YKFDE_CHALLENGE="$(printf %s "$YKFDE_CHALLENGE" | sha256sum | awk '{print $1}')"
    # if /NOT/ DBG, we need to output \n here.
    [ "$DBG" ] || echo
  done

  if [ "$_yubikey_detected" ]; then
    while [ -z "$_ykfde_response" ]; do
      [ "$DBG" ] && message "   Running: 'ykchalresp -$YKFDE_CHALLENGE_SLOT $YKFDE_CHALLENGE'..."
      message "   Remember to touch the device if necessary."
      _ykfde_response="$(printf %s "$YKFDE_CHALLENGE" | ykchalresp -"$YKFDE_CHALLENGE_SLOT" -i- | tr -d '\n')"
      [ "$DBG" ] && message "   Received response: '$_ykfde_response'"
    done
  fi

  if [ -n "$_yubikey_nfc_detected" ]; then
    while [ -z "$_ykfde_response" ]; do
      [ "$DBG" ] && message "   Running: 'ykchalresp-nfc -$YKFDE_CHALLENGE_SLOT $YKFDE_CHALLENGE'..."
      message "   Remember to touch the device if necessary."
      _ykfde_response="$(printf %s "$YKFDE_CHALLENGE" | ykchalresp-nfc -"$YKFDE_CHALLENGE_SLOT" | tr -d '\n')"
      [ "$DBG" ] && message "   Received response: '$_ykfde_response'"
    done
  fi

  if [ "$_ykfde_response" ]; then
    if [ "$YKFDE_CHALLENGE_PASSWORD_NEEDED" ]; then
      _ykfde_passphrase="$YKFDE_CHALLENGE$_ykfde_response"
    else
      _ykfde_passphrase="$_ykfde_response"
    fi
  fi
}
