On Fri, 18 Dec 2015 at 19:16:56 -0500, Richard Hansen wrote:
>  * why SIGKILL instead of SIGTERM?  seems too aggressive
>  * perhaps add a waitpid() after the kill() to ensure that a second
>    plymouth won't be run before the first one exits

Agreed, but unfortunately plymouth doesn't terminate on SIGTERM.

>  * why does cryptroot-unlock use /bin/ash instead of /bin/sh?
>  * there are lots of BusyBox ashisms in the cryptroot-unlock script,
>    many of which can be easily replaced with POSIX conformant code

POSIX's read builtin doesn't support the -s flag.  Sure we can replace
with stty with a trap to restore echo, but since busybox is a dependency
anyway I don't think it's worth it :-P

I've addressed the rest in the updated patch.  Thanks for your input!

-- 
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 */
+	char *prompt2 = strdup(prompt);
+	int len = strlen(prompt2);
+	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);
+	}
+	free(prompt2);
 
-	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 ] && [ -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
+        printf '%s' "$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