On Thu, 01 Oct 2015 at 12:24:58 +0200, Guilhem Moulin wrote:
> since I like Matthias' solution better

On second thought I take that back on second thought.  Aside from a typo
in my previous patch, init scripts such as /scripts/local-top/cryptroot
are intended to run sequentially, and running two of them in parallel
can yield some oddities such as “… not in dm-table” errors.
Furthermore, killing the existing cryptsetup prompt increases the
counter, hence the likeliness that init aborts by dropping a shell.

After some reflection, I came up with two solutions.

  1/ Replace the existing “$cryptkeyscript | $cryptopen” pipe by a named
     pipe (FIFO).  Then we can have another process dropping the
     passphrase into said FIFO.  It's a bit dirty because the reader
     ($cryptopen) will block until ALL writers are done, so upon success
     of a single writer we have to manually kill the other ones.

  2/ Patch askpass.c to make it work with Plymouth.  Actually there was
     some splashy code left (Plymouth's ancestor), although the
     changelog reads “remove usplash support from cryptroot initramfs
     script, askpass and keyscripts, add plymouth support to keyscripts.
     (closes: #620923)”.

Option 2/ was easy enough (one I made sure to load the method after the
FIFO one, and to set “no_more” to avoid starting the console method),
and the advantage of not messing around with the control flow.  Patch
attached (I also have a patch for 1/, but am not including it as I like
the other one better).

I've also included my own ‘unlock’ script.  I don't mind shipping it via
dropbear-initramfs instead (as I intended to originally), but it's
probably more suited for cryptsetup.

Cheers,
-- 
Guilhem.
diff --git a/debian/askpass.c b/debian/askpass.c
index d234879..6750385 100644
--- a/debian/askpass.c
+++ b/debian/askpass.c
@@ -38,11 +38,7 @@
 #include <sys/select.h>
 #include <sys/ioctl.h>
 #include <signal.h>
-#include <dirent.h>
-#include <linux/vt.h>
-#include <sys/socket.h>
 #include <sys/un.h>
-#include <sys/uio.h>
 
 #define DEBUG 0
 
@@ -216,65 +212,76 @@ systemd_finish(int fd)
 }
 
 /*****************************************************************************
- * splashy functions                                                         *
+ * plymouth functions                                                        *
  *****************************************************************************/
 
-/* It might be better style to just do a popen of splashy_update ? */
-
-#define SPLASHY_SOCK	"\0/splashy"
-static size_t splashyused = 0;
-static size_t splashysize = 0;
-static char *splashybuf = NULL;
+#define PLYMOUTH_PATH "/bin/plymouth"
+static pid_t plymouthpid;
+static size_t plymouthused = 0;
+static size_t plymouthsize = 0;
+static char *plymouthbuf = NULL;
 
 static int
-splashy_prepare(const char *prompt)
+plymouth_prepare(const char *prompt)
 {
-	int fd;
-	struct sockaddr addr = {AF_UNIX, SPLASHY_SOCK};
-	struct iovec iov[2];
+	int pipefds[2];
 
-	if ((fd = socket (PF_UNIX, SOCK_STREAM, 0)) == -1) {
+	if (access(PLYMOUTH_PATH, X_OK))
 		return -1;
-	}
 
-	if (connect (fd, &addr, sizeof addr) == -1) {
-		close (fd);
+	if (system(PLYMOUTH_PATH" --ping"))
 		return -1;
-	}
 
-	iov[0].iov_base = "getpass ";
-	iov[0].iov_len = strlen ("getpass ");
-	iov[1].iov_base = (char *)prompt;
-	iov[1].iov_len = strlen (prompt) + 1;
+	/* Plymouth will add a ':' if it is a non-graphical prompt */
+	int len = strlen(prompt);
+	char *prompt2 = (char *)malloc(sizeof(char) * (len+1));
+	strcpy(prompt2, prompt);
+	if (len > 1 && prompt2[len-2] == ':' && prompt2[len-1] == ' ')
+		prompt2[len-2] = '\0';
+	else if (len > 0 && prompt2[len-1] == ':')
+		prompt2[len-1] = '\0';
 
-	if (writev (fd, iov, 2) == -1) {
-		close (fd);
+	if (pipe(pipefds))
+		return -1;
+
+	plymouthpid = fork();
+	if (plymouthpid < 0) {
+		close(pipefds[0]);
+		close(pipefds[1]);
 		return -1;
 	}
 
-	/* Shutdown write? */
+	if (plymouthpid == 0) {
+		close(pipefds[0]);
+		if (dup2(pipefds[1], STDOUT_FILENO) < 0)
+			exit(EXIT_FAILURE);
+		execl(PLYMOUTH_PATH, PLYMOUTH_PATH,
+		      "ask-for-password", "--prompt", prompt2, (char*)NULL);
+		exit(EXIT_FAILURE);
+	}
 
-	return fd;
+	close(pipefds[1]);
+	return pipefds[0];
 }
 
 static bool
-splashy_read(int fd, char **buf, size_t *size)
+plymouth_read(int fd, char **buf, size_t *size)
 {
-	debug("In splashy_read\n");
-	if (fifo_common_read(fd, &splashybuf, &splashyused, &splashysize)) {
-		*buf = splashybuf;
-		*size = splashyused;
+	debug("In plymouth_read\n");
+	if (fifo_common_read(fd, &plymouthbuf, &plymouthused, &plymouthsize)) {
+		*buf = plymouthbuf;
+		*size = plymouthused;
 		return true;
 	}
 
 	return false;
 }
 
-
 static void
-splashy_finish(int fd)
+plymouth_finish(int fd)
 {
-	fifo_common_finish (fd, &splashybuf, &splashyused, &splashysize);
+	kill(plymouthpid, SIGKILL);
+	fifo_common_finish(fd, &plymouthbuf, &plymouthused, &plymouthsize);
 }
 
 /*****************************************************************************
@@ -448,8 +455,8 @@ struct method {
 
 static struct method methods[] = {
 	{ "systemd", systemd_prepare, systemd_read, systemd_finish, true, false, true, -1 },
-	{ "splashy", splashy_prepare, splashy_read, splashy_finish, false, false, true, -1 },
 	{ "fifo", fifo_prepare, fifo_read, fifo_finish, false, false, true, -1 },
+	{ "plymouth", plymouth_prepare, plymouth_read, plymouth_finish, true, false, true, -1 },
 	{ "console", console_prepare, console_read, console_finish, false, false, true, -1 }
 };
 
diff --git a/debian/cryptsetup.dirs b/debian/cryptsetup.dirs
index 94c9a56..f4663f9 100644
--- a/debian/cryptsetup.dirs
+++ b/debian/cryptsetup.dirs
@@ -10,5 +10,6 @@
 /usr/share/initramfs-tools/scripts/local-bottom
 /usr/share/initramfs-tools/scripts/local-block
 /usr/share/initramfs-tools/conf-hooks.d
+/usr/share/initramfs-tools/bin
 /usr/share/man/man5
 /usr/share/man/man8
diff --git a/debian/initramfs/cryptroot-script b/debian/initramfs/cryptroot-script
index 3e8281a..1c313c1 100644
--- a/debian/initramfs/cryptroot-script
+++ b/debian/initramfs/cryptroot-script
@@ -292,14 +292,8 @@ setup_mapping()
 				diskname="$cryptsource ($crypttarget)"
 			fi
 
-			if [ -x /bin/plymouth ] && plymouth --ping; then
-				cryptkeyscript="plymouth ask-for-password --prompt"
-				# Plymouth will add a : if it is a non-graphical prompt
-				cryptkey="Please unlock disk $diskname"
-			else
-				cryptkeyscript="/lib/cryptsetup/askpass"
-				cryptkey="Please unlock disk $diskname: "
-			fi
+			cryptkeyscript=/lib/cryptsetup/askpass
+			cryptkey="Please unlock disk $diskname: "
 		fi
 
 
diff --git a/debian/initramfs/cryptroot-unlock b/debian/initramfs/cryptroot-unlock
new file mode 100755
index 0000000..0bdfc55
--- /dev/null
+++ b/debian/initramfs/cryptroot-unlock
@@ -0,0 +1,125 @@
+#!/bin/ash
+
+# Remotely unlock encrypted volumes.
+#
+# Copyright © 2015 Guilhem Moulin <guil...@guilhem.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
+
+# Return 0 if $pid has a file descriptor pointing to $name, and 1
+# otherwise.
+in_fds() {
+    local  pid="$1" name="$2" fd
+    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() {
+    ps -eo pid,args | sed -nr "s#^\s*([0-9]+)\s+$ASKPASS\s+.*#\1#p" | while read pid; do
+        if in_fds "$pid" "$PASSFIFO"; then
+            echo "$pid"
+            break
+        fi
+    done
+}
+
+# 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=$(get_askpass_pid) timer=$(( 10 * $TIMEOUT ))
+
+    # wait for the fifo
+    until [ "$pid" ] && [ -p "$PASSFIFO" ]; do
+        sleep .1
+        pid=$(get_askpass_pid)
+        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
+    eval $(grep -Ez '^CRYPTTAB_(NAME|TRIED|SOURCE)=' "/proc/$pid/environ" | tr '\0' '\n')
+    for pid in $(ps -eo pid,args | sed -nr 's#^\s*([0-9]+)\s+/sbin/cryptsetup\s+.*#\1#p'); do
+        if grep -Fxqz "CRYPTTAB_NAME=$CRYPTTAB_NAME" "/proc/$pid/environ"; then
+            PID=$pid
+            BIRTH=$(stat -c'%Z' "/proc/$PID")
+            return 0;
+        fi
+    done
+
+    PID=
+    BIRTH=
+}
+
+# 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 ))
+    until [ ! -d "/proc/$PID" ] || [ $(stat -c'%Z' "/proc/$PID") -gt $BIRTH ]; do
+        sleep .1
+        timer=$(( $timer - 1 ))
+        if [ $timer -le 0 ]; then
+            echo "Error: Timeout reached while waiting for PID $PID." >&2
+            exit 1
+        fi
+    done
+
+    if [ -e "/dev/mapper/$CRYPTTAB_NAME" ]; then
+        echo "cryptsetup: $CRYPTTAB_NAME set up successfully" >&2
+        exit 0
+    elif [ $CRYPTTAB_TRIED -ge 2 ]; 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 -a -x "$ASKPASS" ]; then
+    # interactive mode on a TTY: keep trying until successful or
+    # maximum number of tries exceeded.
+    while :; do
+        wait_for_prompt
+        diskname="$CRYPTTAB_NAME"
+        [ "${CRYPTTAB_SOURCE#/dev/disk/by-uuid/}" != "$CRYPTTAB_SOURCE" ] || diskname="$diskname ($CRYPTTAB_SOURCE)"
+        read -rs -p "Please unlock disk $diskname: "; echo
+        echo -n "$REPLY" >"$PASSFIFO"
+        wait_for_answer || true
+    done
+else
+    # non-interactive mode: slurp the passphrase from stdin
+    wait_for_prompt
+    diskname="$CRYPTTAB_NAME"
+    [ "${CRYPTTAB_SOURCE#/dev/disk/by-uuid/}" != "$CRYPTTAB_SOURCE" ] || diskname="$diskname ($CRYPTTAB_SOURCE)"
+    echo "Please unlock disk $diskname"
+    cat >"$PASSFIFO"
+    wait_for_answer || exit 1
+fi
diff --git a/debian/initramfs/cryptroot-unlock-hook b/debian/initramfs/cryptroot-unlock-hook
new file mode 100755
index 0000000..79a21af
--- /dev/null
+++ b/debian/initramfs/cryptroot-unlock-hook
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+PREREQ=""
+
+prereqs()
+{
+	echo "$PREREQ"
+}
+
+case "$1" in
+	prereqs)
+		prereqs
+		exit 0
+	;;
+esac
+
+cp -p /usr/share/initramfs-tools/bin/cryptroot-unlock "$DESTDIR/bin/unlock"
+
+if [ -f /etc/initramfs-tools/etc/motd ]; then
+	cp /etc/initramfs-tools/etc/motd "$DESTDIR/etc/motd"
+else
+	cat >>"$DESTDIR/etc/motd" <<- EOF
+		To unlock root partition, and maybe others like swap, run \`unlock\`
+	EOF
+fi
diff --git a/debian/rules b/debian/rules
index 489ff9e..bf571fe 100755
--- a/debian/rules
+++ b/debian/rules
@@ -138,6 +138,10 @@ install-stamp: build-stamp
 		$(CURDIR)/debian/cryptsetup/usr/share/initramfs-tools/conf-hooks.d/cryptsetup
 	install -m 0755 debian/initramfs/cryptroot-hook \
 		$(CURDIR)/debian/cryptsetup/usr/share/initramfs-tools/hooks/cryptroot
+	install -m 0755 debian/initramfs/cryptroot-unlock-hook \
+		$(CURDIR)/debian/cryptsetup/usr/share/initramfs-tools/hooks/cryptroot-unlock
+	install -m 0755 debian/initramfs/cryptroot-unlock \
+		$(CURDIR)/debian/cryptsetup/usr/share/initramfs-tools/bin/cryptroot-unlock
 	install -m 0755 debian/initramfs/cryptroot-script \
 		$(CURDIR)/debian/cryptsetup/usr/share/initramfs-tools/scripts/local-top/cryptroot
 	install -m 0755 debian/initramfs/cryptroot-script-block \

Attachment: signature.asc
Description: PGP signature

Reply via email to