#!/usr/bin/ash

. "/usr/local/bin/ssh-cryptsetup-tools"

sshcs_net_start() {
  local iparg net_address ipconfig_out net_netmask net_gateway net_dns0 net_dns1

  # we must have an 'ip' setting, and a device in it
  [ -z "${ip}" ] && [ -n "${nfsaddrs}" ] && ip="${nfsaddrs}"
  [ -z "${ip}" ] && {
    dbg "No ip setting to setup network"
    return 1
  }

  net_device=$(echo ${ip} | cut -d: -f6)
  [ -z "${net_device}" ] && {
    dbg "No network device to setup"
    return 1
  }

  if [ "${sshcs_opt_net_wol:-d}" != "d" ]; then
    dbg "Setting network device=${net_device} wol=${sshcs_opt_net_wol}"
    ethtool -s "${net_device}" wol "${sshcs_opt_net_wol}"
  fi

  # Setup network and save some values
  # Note: some useful redirection means ('< <(...)' and '<<< "$(...)"') are
  # not supported in the available shell. So we have to write code in a
  # temporary file and 'source' it since '... | while read ...' spawns a
  # subshell from which outer variables cannot be altered.
  : > "${net_env}"

  echo ""
  echo "Configuring IP (timeout = ${sshcs_opt_timeout_ipconfig}s) ..."
  # ipconfig manual: https://git.kernel.org/pub/scm/libs/klibc/klibc.git/tree/usr/kinit/ipconfig/README.ipconfig
  ipconfig_out=$(ipconfig -t "${sshcs_opt_timeout_ipconfig}" "ip=${ip}")
  if [ $? -ne 0 ]; then
    err "IP configuration timeout!"
    echo "Devices probing:"
    ipconfig -n -t 5 -c none all
    return 1
  fi

  echo -n "${ipconfig_out}" | while read line; do
    [ "${line#"IP-Config:"}" != "${line}" ] && continue

    line="$(echo "${line}" | sed -e 's/ :/:/g;s/: /=/g')"

    for iparg in ${line}; do
      case "${iparg}" in
        address=*|netmask=*|gateway=*|dns0=*|dns1=*)
          echo "net_${iparg}" >> "${net_env}"
          ;;
      esac
    done
  done

  . "${net_env}"
  rm -f "${net_env}"

  echo "IP-Config: device=${net_device} ip=${net_address}/${net_netmask} gw=${net_gateway} dns0=${net_dns0} dns1=${net_dns1}"

  [ -n "${net_address}" ]
}

sshcs_net_done() {
  # we are done with the network
  if [ -n "${net_device}" ]; then
    dbg "Setting network device=${net_device} down"
    ip addr flush dev "${net_device}"
    ip link set dev "${net_device}" down
  fi
}

sshcs_trapped_timeout() {
  err "Timeout reached! Powering off."
  poweroff -f
  exit
}

sshcs_trap_timeout() {
  local pid_init=$$

  if [ ${sshcs_opt_timeout_poweroff} -gt 0 ]; then
    echo ""
    echo "WARNING! Automatic poweroff will be triggered in ${sshcs_opt_timeout_poweroff}s"
    echo "To deactivate, please unlock devices"
    trap sshcs_trapped_timeout SIGALRM
    (
      sleep ${sshcs_opt_timeout_poweroff}
      kill -SIGALRM ${pid_init}
      # Signal is not processed if cryptsetup is waiting for the password
      killall cryptsetup > /dev/null 2>&1
    ) &
    pid_timeout=$!
  fi
}

sshcs_untrap_timeout() {
  [ -z "${pid_timeout}" ] && return 0
  # Notes:
  # If there was a running SSH shell, it may also try to kill it.
  # This only kills the spawned subshell, leaving the 'sleep' command still
  # running until done (which is not an issue).
  proc_parse_stat ${pid_timeout} && kill ${pid_timeout}
  pid_timeout=
  trap - SIGALRM
  msg "Timeout cleared."
}

sshcs_shell_run() {
  sshcs_trap_timeout

  # actual script (shared with SSH login) with which we can unlock devices
  sshcs_unlocked_test=0
  . "${sshcs_shell_script}"
}

sshcs_dropbear_run() {
  local pid_timeout=
  local dev_pts_mounted=0
  local listen=

  # ensure /dev/pts is present
  if [ ! -d "/dev/pts" ]; then
    mkdir -p "/dev/pts"
    mount -t devpts devpts "/dev/pts"
    dev_pts_mounted=1
  fi

  if [ ${sshcs_opt_use_shell} -eq 0 ]; then
    sshcs_shell_script=${sshcs_cryptsetup_script}
  else
    cat <<EOF > "${sshcs_shell_script}"
#!/usr/bin/ash

. "/usr/local/bin/ssh-cryptsetup-tools"

echo ""
echo "Call ${sshcs_cryptsetup_script} to try unlocking device(s)"

# Now give the user its shell
/usr/bin/ash

# Check whether we are fully done
sshcs_check_done 1
EOF
    chmod a+x "${sshcs_shell_script}"
  fi

  # /etc/passwd file for the root user
  echo "root:x:0:0:root:/root:${sshcs_shell_script}" > "/etc/passwd"
  echo "${sshcs_shell_script}" > "/etc/shells"

  [ ! -d "/var/log" ] && mkdir -p "/var/log"
  touch "/var/log/lastlog"

  msg "Starting dropbear ..."
  dropbear -Esgjk -P "${path_dropbear_pid}" ${sshcs_opt_listen}

  # Actual unlocking shell
  sshcs_shell_run
}

sshcs_cryptpart_process() {
  local cryptdev_orig cryptopt cryptargs

  # ensure there is a device (handle 'UUID=' format)
  [ -z "${cryptdev}" ] && return 0
  [ "${cryptdev#UUID=}" != "${cryptdev}" ] && cryptdev="/dev/disk/by-uuid/${cryptdev#UUID=}"

  # get crypt options
  cryptargs=
  for cryptopt in ${cryptoptions//,/ }; do
    case ${cryptopt} in
      discard)
        cryptargs="${cryptargs} --allow-discards"
        ;;

      luks)
        ;;

      *)
        echo "Device ${cryptdev} encryption option '${cryptopt}' not known, ignoring."
        ;;
    esac
  done

  # ensure device is encrypted and handled
  cryptdev_orig=${cryptdev}
  if cryptdev=$(resolve_device "${cryptdev_orig}" ${rootdelay}); then
    if cryptsetup isLuks "${cryptdev}" >/dev/null 2>&1; then
      dbg "Adding crypt device=${cryptdev} type=${crypttype} name=${cryptname} args=<${cryptargs}> in setup script"

      # update script used to unlock device either in console or SSH
      [ -s "${sshcs_cryptsetup_script}" ] || cat <<'EOF' > "${sshcs_cryptsetup_script}"
#!/usr/bin/ash

. "/usr/local/bin/ssh-cryptsetup-tools"

cycle_or_retry() {
  local res

  read -n 1 -s -t 5 -p "Within 5s press 'P' to poweroff, 'R' to reboot or any other key to retry. " res
  echo ""
  [ "${res}" == "P" ] && poweroff -f
  [ "${res}" == "R" ] && reboot -f
}

try_unlock() {
EOF

      cat <<EOF >> "${sshcs_cryptsetup_script}"
  # loop until device is available
  while [ ! -e "/dev/mapper/${cryptname}" ]; do
    if [ \${sshcs_unlocked_test:-0} -eq 1 ]; then
      sshcs_unlocked=0
      return
    fi
    if cryptsetup open --type "${crypttype}" "${cryptdev}" "${cryptname}" ${cryptargs} "\${CSQUIET}"; then
      if poll_device "/dev/mapper/${cryptname}" ${rootdelay}; then
        killall cryptsetup > /dev/null 2>&1
        break
      fi
      err "Device still not mapped! Please wait or retry."
    elif [ ! -e "/dev/mapper/${cryptname}" ]; then
      err "cryptsetup failed! Please retry."
    else
      break
    fi

    cycle_or_retry
  done
EOF
    else
      err "Failed to manage encrypted device ${cryptdev_orig}: not a LUKS volume."
    fi
  fi
}

sshcs_cryptpart_setup() {
  local cryptdev crypttype cryptname cryptpass cryptoptions

  # check encrypted devices to handle
  cryptdev=
  crypttype=luks
  while read cryptname cryptdev cryptpass cryptoptions; do
    # skip comment lines
    [ "${cryptname:0:1}" = "#" ] && continue
    # skip devices with given password
    [ -n "${cryptpass}" ] && [ "${cryptpass}" != "none" ] && [ "${cryptpass}" != "-" ] && continue

    sshcs_cryptpart_process
  done < "${etc_crypttab}"

  # Nothing else to do if there is no device we can unlock
  [ -s "${sshcs_cryptsetup_script}" ] || return 0

  cat <<'EOF' >> "${sshcs_cryptsetup_script}"
  # No other device to unlock
}

if [ -c "/dev/mapper/control" ]; then
  CSQUIET=
  try_unlock

  [ ${sshcs_unlocked_test:-0} -eq 1 ] && return
  sshcs_check_done 0
else
  if [ \${sshcs_unlocked_test:-0} -eq 1 ]; then
    sshcs_unlocked=0
    return
  fi
  echo ""
  err "Device resources missing! Please retry."
fi
EOF
  chmod a+x "${sshcs_cryptsetup_script}"
}

run_hook() {
  local etc_crypttab="/etc/crypttab"
  local path_dropbear_pid="/.dropbear.pid"
  local sshcs_shell_script="/.sshcs_shell.sh"
  local net_env="/.sshcs_net_env.sh"
  local line net_device
  local CSQUIET

  # Note: options were loaded already

  # sanity check: crypttab should be present
  [ ! -e "${etc_crypttab}" ] && {
    dbg "No crypttab configuration to process"
    return 0
  }

  # Initialize random generator ASAP.
  # May delay first SSH login by a few seconds otherwise.
  (dd if=/dev/urandom of=/dev/null bs=4 count=1 status=none > /dev/null 2>&1) &
  (dd if=/dev/random of=/dev/null bs=4 count=1 status=none > /dev/null 2>&1) &

  modprobe -a -q dm-crypt >/dev/null 2>&1
  [ "${quiet}" = "y" ] && CSQUIET=">/dev/null"

  umask 0022

  # Setup script used to unlock device either in console or SSH
  sshcs_cryptpart_setup
  if [ ! -e "${sshcs_cryptsetup_script}" ]; then
    err "No encrypted device found! Skipping crypt remote unlocking."
    return 0
  fi

  # start and check network
  if ! sshcs_net_start; then
    err "Net interface not available! Skipping crypt remote unlocking."
    # We still allow to unlock locally with timeout
    sshcs_shell_run
    return 0
  fi

  # time to unlock (through console or dropbear)
  sshcs_dropbear_run
}
