diff options
Diffstat (limited to 'debian/initramfs/cryptroot-unlock')
-rw-r--r-- | debian/initramfs/cryptroot-unlock | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/debian/initramfs/cryptroot-unlock b/debian/initramfs/cryptroot-unlock new file mode 100644 index 0000000..d31b6f4 --- /dev/null +++ b/debian/initramfs/cryptroot-unlock @@ -0,0 +1,196 @@ +#!/bin/busybox ash + +# Remotely unlock encrypted volumes. +# +# Copyright © 2015-2018 Guilhem Moulin <guilhem@debian.org> +# +# 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, see <http://www.gnu.org/licenses/>. + +set -ue +PATH=/sbin:/bin + +TIMEOUT=10 +PASSFIFO=/lib/cryptsetup/passfifo +ASKPASS=/lib/cryptsetup/askpass +UNLOCK_ALL=n + +[ -f /lib/cryptsetup/functions ] || return 0 +. /lib/cryptsetup/functions +TABFILE="/cryptroot/crypttab" +unset -v IFS + +if [ ! -f "$TABFILE" ] || [ "$TABFILE" -ot "/proc/1" ]; then + # Too early, init-top/cryptroot hasn't finished yet + echo "Try again later" >&2 + exit 1 +fi + +# Print the list of PIDs the executed command of which is $exe. +pgrep_exe() { + local exe pid + exe="$(readlink -f -- "$1" 2>/dev/null)" && [ -f "$exe" ] || return 0 + ps -eo pid= | while read pid; do + [ "$(readlink -f "/proc/$pid/exe")" != "$exe" ] || printf '%d\n' "$pid" + done +} + +# Return 0 if $pid has a file descriptor pointing to $name, and 1 +# otherwise. +in_fds() { + local pid="$1" name fd + name="$(readlink -f -- "$2" 2>/dev/null)" && [ -e "$name" ] || return 1 + for fd in $(find "/proc/$pid/fd" -type l); do + [ "$(readlink -f "$fd")" != "$name" ] || return 0 + done + return 1 +} + +# Print the PID of the askpass process with a file descriptor opened to +# /lib/cryptsetup/passfifo. +get_askpass_pid() { + local pid + for pid in $(pgrep_exe "$ASKPASS"); do + if in_fds "$pid" "$PASSFIFO"; then + echo "$pid" + return 0 + fi + done + return 1 +} + +# Print the number of configured crypt devices that have not been unlocked yet. +count_locked_devices() { + local COUNT=0 + crypttab_foreach_entry count_locked_devices_callback + printf '%d\n' "$COUNT" +} +count_locked_devices_callback() { + dm_blkdevname "$CRYPTTAB_NAME" >/dev/null || COUNT=$(( $COUNT + 1 )) +} + +# Wait for askpass, then set $PID (resp. $BIRTH) to the PID (resp. +# birth date) of the cryptsetup process with same $CRYPTTAB_NAME. +wait_for_prompt() { + local pid timer num_locked_devices=-1 n + + # wait for the fifo + while :; do + n=$(count_locked_devices) + if [ $n -eq 0 ]; then + # all configured devices have been unlocked, we're done + exit 0 + elif [ $num_locked_devices -lt 0 ] || [ $n -lt $num_locked_devices ]; then + # reset $timer if a device was unlocked (for instance using + # a keyscript) while we were waiting + timer=$(( 10 * $TIMEOUT )) + fi + num_locked_devices=$n + + if pid=$(get_askpass_pid) && [ -p "$PASSFIFO" ]; then + break + fi + + usleep 100000 + timer=$(( $timer - 1 )) + if [ $timer -le 0 ]; then + echo "Error: Timeout reached while waiting for askpass." >&2 + exit 1 + fi + done + + # find the cryptsetup process with same $CRYPTTAB_NAME + local o v + for o in NAME TRIED OPTION_tries; do + if v="$(grep -z -m1 "^CRYPTTAB_$o=" "/proc/$pid/environ")"; then + eval "CRYPTTAB_$o"="\${v#CRYPTTAB_$o=}" + else + eval unset -v "CRYPTTAB_$o" + fi + done + if [ -z "${CRYPTTAB_NAME:+x}" ] || [ -z "${CRYPTTAB_TRIED:+x}" ]; then + return 1 + fi + if ( ! crypttab_find_entry --quiet "$CRYPTTAB_NAME" ); then + # use a subshell to avoid polluting our enironment + echo "Error: Refusing to process unknown device $CRYPTTAB_NAME" >&2 + exit 1 + fi + + for pid in $(pgrep_exe "/sbin/cryptsetup"); do + if grep -Fxqz "CRYPTTAB_NAME=$CRYPTTAB_NAME" "/proc/$pid/environ"; then + PID=$pid + BIRTH=$(stat -c"%Z" "/proc/$PID" 2>/dev/null) || break + return 0 + fi + done + + PID= + BIRTH= + return 1 +} + +# Wait until $PID no longer exists or has a birth date greater that +# $BIRTH (ie was reallocated). Then return with exit value 0 if +# /dev/mapper/$CRYPTTAB_NAME exists, and with exit value 1 if the +# maximum number of tries exceeded. Otherwise (if the unlocking +# failed), return with value 1. +wait_for_answer() { + local timer=$(( 10 * $TIMEOUT )) b + while [ -d "/proc/$PID" ] && b=$(stat -c"%Z" "/proc/$PID" 2>/dev/null) && [ $b -le $BIRTH ]; do + usleep 100000 + timer=$(( $timer - 1 )) + if [ $timer -le 0 ]; then + echo "Error: Timeout reached while waiting for PID $PID." >&2 + exit 1 + fi + done + + if dm_blkdevname "$CRYPTTAB_NAME" >/dev/null; then + echo "cryptsetup: $CRYPTTAB_NAME set up successfully" >&2 + [ "$UNLOCK_ALL" = y ] && return 0 || exit 0 + elif [ $(( ${CRYPTTAB_TRIED:-0} + 1 )) -ge ${CRYPTTAB_OPTION_tries:-3} ] && + [ ${CRYPTTAB_OPTION_tries:-3} -gt 0 ]; then + echo "cryptsetup: maximum number of tries exceeded for $CRYPTTAB_NAME" >&2 + exit 1 + else + echo "cryptsetup: cryptsetup failed, bad password or options?" >&2 + return 1 + fi +} + + +if [ -t 0 ] && [ -x "$ASKPASS" ]; then + # interactive mode on a TTY: keep trying until all configured devices have + # been unlocked or the maximum number of tries exceeded + UNLOCK_ALL=y + while :; do + # note: if the script is not killed before pivot_root it should + # exit on its own once $TIMEOUT is reached + if ! wait_for_prompt; then + usleep 100000 + continue + fi + read -rs -p "Please unlock disk $CRYPTTAB_NAME: "; echo + printf '%s' "$REPLY" >"$PASSFIFO" + wait_for_answer || true + done +else + # non-interactive mode: slurp the passphrase from stdin and exit + wait_for_prompt || exit 1 + echo "Please unlock disk $CRYPTTAB_NAME" + cat >"$PASSFIFO" + wait_for_answer || exit 1 +fi + +# vim: set filetype=sh : |