blob: dbc2ad046c4024f72ed59fa9feef8289e9868012 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
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 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/>.
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 :
|