dissect: add support for encrypted images
authorLennart Poettering <lennart@poettering.net>
Mon, 5 Dec 2016 15:26:48 +0000 (16:26 +0100)
committerLennart Poettering <lennart@poettering.net>
Wed, 7 Dec 2016 17:38:41 +0000 (18:38 +0100)
This adds support to the image dissector to deal with encrypted images (only
LUKS). Given that we now have a neatly isolated image dissector codebase, let's
add a new feature to it: support for automatically dealing with encrypted
images. This is then exposed in systemd-dissect and nspawn.

It's pretty basic: only support for passphrase-based encryption.

In order to ensure that "systemd-dissect --mount" results in mount points whose
backing LUKS DM devices are cleaned up automatically we use the DM_DEV_REMOVE
ioctl() directly on the device (in DM_DEFERRED_REMOVE mode). libgcryptsetup at
the moment doesn't provide a proper API for this. Thankfully, the ioctl() API
is pretty easy to use.

Makefile.am
src/dissect/dissect.c
src/machine/image-dbus.c
src/nspawn/nspawn.c
src/shared/dissect-image.c
src/shared/dissect-image.h

index c6adf3a..1895e33 100644 (file)
@@ -1086,7 +1086,8 @@ libshared_la_CFLAGS = \
        $(ACL_CFLAGS) \
        $(LIBIDN_CFLAGS) \
        $(SECCOMP_CFLAGS) \
-       $(BLKID_CFLAGS)
+       $(BLKID_CFLAGS) \
+       $(LIBCRYPTSETUP_CFLAGS)
 
 libshared_la_LIBADD = \
        libsystemd-internal.la \
@@ -1096,7 +1097,8 @@ libshared_la_LIBADD = \
        $(ACL_LIBS) \
        $(LIBIDN_LIBS) \
        $(SECCOMP_LIBS) \
-       $(BLKID_LIBS)
+       $(BLKID_LIBS) \
+       $(LIBCRYPTSETUP_LIBS)
 
 rootlibexec_LTLIBRARIES += \
        libsystemd-shared.la
@@ -1119,6 +1121,7 @@ libsystemd_shared_la_CFLAGS = \
        $(LIBIDN_CFLAGS) \
        $(SECCOMP_CFLAGS) \
        $(BLKID_CFLAGS) \
+       $(LIBCRYPTSETUP_CFLAGS) \
        -fvisibility=default
 
 # We can't use libshared_la_LIBADD here because it would
@@ -1131,7 +1134,8 @@ libsystemd_shared_la_LIBADD = \
        $(ACL_LIBS) \
        $(LIBIDN_LIBS) \
        $(SECCOMP_LIBS) \
-       $(BLKID_LIBS)
+       $(BLKID_LIBS) \
+       $(LIBCRYPTSETUP_LIBS)
 
 libsystemd_shared_la_LDFLAGS = \
        $(AM_LDFLAGS) \
index 93ece05..5e6848a 100644 (file)
@@ -34,7 +34,7 @@ static enum {
 } arg_action = ACTION_DISSECT;
 static const char *arg_image = NULL;
 static const char *arg_path = NULL;
-static bool arg_read_only = false;
+static DissectImageFlags arg_flags = DISSECT_IMAGE_DISCARD_ON_LOOP;
 
 static void help(void) {
         printf("%s [OPTIONS...] IMAGE\n"
@@ -43,7 +43,8 @@ static void help(void) {
                "  -h --help            Show this help\n"
                "     --version         Show package version\n"
                "  -m --mount           Mount the image to the specified directory\n"
-               "  -r --read-only       Mount read-only\n",
+               "  -r --read-only       Mount read-only\n"
+               "     --discard=MODE    Choose 'discard' mode (disabled, loop, all, crypto)\n",
                program_invocation_short_name,
                program_invocation_short_name);
 }
@@ -52,6 +53,7 @@ static int parse_argv(int argc, char *argv[]) {
 
         enum {
                 ARG_VERSION = 0x100,
+                ARG_DISCARD,
         };
 
         static const struct option options[] = {
@@ -59,6 +61,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "version",   no_argument,       NULL, ARG_VERSION   },
                 { "mount",     no_argument,       NULL, 'm'           },
                 { "read-only", no_argument,       NULL, 'r'           },
+                { "discard",   required_argument, NULL, ARG_DISCARD   },
                 {}
         };
 
@@ -83,7 +86,23 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case 'r':
-                        arg_read_only = true;
+                        arg_flags |= DISSECT_IMAGE_READ_ONLY;
+                        break;
+
+                case ARG_DISCARD:
+                        if (streq(optarg, "disabled"))
+                                arg_flags &= ~(DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_DISCARD|DISSECT_IMAGE_DISCARD_ON_CRYPTO);
+                        else if (streq(optarg, "loop"))
+                                arg_flags = (arg_flags & ~(DISSECT_IMAGE_DISCARD|DISSECT_IMAGE_DISCARD_ON_CRYPTO)) | DISSECT_IMAGE_DISCARD_ON_LOOP;
+                        else if (streq(optarg, "all"))
+                                arg_flags = (arg_flags & ~(DISSECT_IMAGE_DISCARD_ON_CRYPTO)) | DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD;
+                        else if (streq(optarg, "crypt"))
+                                arg_flags |= DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD | DISSECT_IMAGE_DISCARD_ON_CRYPTO;
+                        else {
+                                log_error("Unknown --discard= parameter: %s", optarg);
+                                return -EINVAL;
+                        }
+
                         break;
 
                 case '?':
@@ -104,7 +123,7 @@ static int parse_argv(int argc, char *argv[]) {
                 }
 
                 arg_image = argv[optind];
-                arg_read_only = true;
+                arg_flags |= DISSECT_IMAGE_READ_ONLY;
                 break;
 
         case ACTION_MOUNT:
@@ -126,6 +145,7 @@ static int parse_argv(int argc, char *argv[]) {
 
 int main(int argc, char *argv[]) {
         _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
+        _cleanup_(decrypted_image_unrefp) DecryptedImage *di = NULL;
         _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
         int r;
 
@@ -136,7 +156,7 @@ int main(int argc, char *argv[]) {
         if (r <= 0)
                 goto finish;
 
-        r = loop_device_make_by_path(arg_image, arg_read_only ? O_RDONLY : O_RDWR, &d);
+        r = loop_device_make_by_path(arg_image, (arg_flags & DISSECT_IMAGE_READ_ONLY) ? O_RDONLY : O_RDWR, &d);
         if (r < 0) {
                 log_error_errno(r, "Failed to set up loopback device: %m");
                 goto finish;
@@ -186,14 +206,24 @@ int main(int argc, char *argv[]) {
         }
 
         case ACTION_MOUNT:
-                r = dissected_image_mount(m, arg_path,
-                                          (arg_read_only ? DISSECTED_IMAGE_READ_ONLY : 0) |
-                                          DISSECTED_IMAGE_DISCARD_ON_LOOP);
+                r = dissected_image_decrypt_interactively(m, NULL, arg_flags, &di);
+                if (r < 0)
+                        goto finish;
+
+                r = dissected_image_mount(m, arg_path, arg_flags);
                 if (r < 0) {
                         log_error_errno(r, "Failed to mount image: %m");
                         goto finish;
                 }
 
+                if (di) {
+                        r = decrypted_image_relinquish(di);
+                        if (r < 0) {
+                                log_error_errno(r, "Failed to relinquish DM devices: %m");
+                                goto finish;
+                        }
+                }
+
                 loop_device_relinquish(d);
                 break;
 
index 400d8ec..65953b3 100644 (file)
@@ -358,7 +358,7 @@ static int raw_image_get_os_release(Image *image, char ***ret, sd_bus_error *err
                 if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0)
                         _exit(EXIT_FAILURE);
 
-                r = dissected_image_mount(m, t, DISSECTED_IMAGE_READ_ONLY);
+                r = dissected_image_mount(m, t, DISSECT_IMAGE_READ_ONLY);
                 if (r < 0)
                         _exit(EXIT_FAILURE);
 
index 6ad20f7..035456f 100644 (file)
@@ -2365,7 +2365,7 @@ static int outer_child(
                 return r;
 
         if (dissected_image) {
-                r = dissected_image_mount(dissected_image, directory, DISSECTED_IMAGE_DISCARD_ON_LOOP|(arg_read_only ? DISSECTED_IMAGE_READ_ONLY : 0));
+                r = dissected_image_mount(dissected_image, directory, DISSECT_IMAGE_DISCARD_ON_LOOP|(arg_read_only ? DISSECT_IMAGE_READ_ONLY : 0));
                 if (r < 0)
                         return r;
         }
@@ -3410,8 +3410,9 @@ int main(int argc, char *argv[]) {
         _cleanup_release_lock_file_ LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT;
         bool interactive, veth_created = false, remove_tmprootdir = false;
         char tmprootdir[] = "/tmp/nspawn-root-XXXXXX";
-        _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
         _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
+        _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
+        _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
 
         log_parse_environment();
         log_open();
@@ -3652,6 +3653,10 @@ int main(int argc, char *argv[]) {
                         goto finish;
                 }
 
+                r = dissected_image_decrypt_interactively(dissected_image, NULL, 0, &decrypted_image);
+                if (r < 0)
+                        goto finish;
+
                 /* Now that we mounted the image, let's try to remove it again, if it is ephemeral */
                 if (remove_image && unlink(arg_image) >= 0)
                         remove_image = false;
index 7b65daa..bc4e45b 100644 (file)
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#ifdef HAVE_LIBCRYPTSETUP
+#include <libcryptsetup.h>
+#endif
+#include <linux/dm-ioctl.h>
 #include <sys/mount.h>
 
 #include "architecture.h"
+#include "ask-password-api.h"
 #include "blkid-util.h"
 #include "dissect-image.h"
+#include "fd-util.h"
 #include "gpt.h"
 #include "mount-util.h"
 #include "path-util.h"
 #include "stat-util.h"
+#include "stdio-util.h"
 #include "string-table.h"
 #include "string-util.h"
 #include "udev-util.h"
 
+static int probe_filesystem(const char *node, char **ret_fstype) {
+#ifdef HAVE_BLKID
+        _cleanup_blkid_free_probe_ blkid_probe b = NULL;
+        const char *fstype;
+        int r;
+
+        b = blkid_new_probe_from_filename(node);
+        if (!b)
+                return -ENOMEM;
+
+        blkid_probe_enable_superblocks(b, 1);
+        blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
+
+        errno = 0;
+        r = blkid_do_safeprobe(b);
+        if (r == -2 || r == 1) {
+                log_debug("Failed to identify any partition type on partition %s", node);
+                goto not_found;
+        }
+        if (r != 0) {
+                if (errno == 0)
+                        return -EIO;
+
+                return -errno;
+        }
+
+        (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
+
+        if (fstype) {
+                char *t;
+
+                t = strdup(fstype);
+                if (!t)
+                        return -ENOMEM;
+
+                *ret_fstype = t;
+                return 1;
+        }
+
+not_found:
+        *ret_fstype = NULL;
+        return 0;
+#else
+        return -EOPNOTSUPP;
+#endif
+}
+
 int dissect_image(int fd, DissectedImage **ret) {
 
 #ifdef HAVE_BLKID
@@ -96,7 +150,7 @@ int dissect_image(int fd, DissectedImage **ret) {
                 return -ENOMEM;
 
         (void) blkid_probe_lookup_value(b, "USAGE", &usage, NULL);
-        if (streq_ptr(usage, "filesystem")) {
+        if (STRPTR_IN_SET(usage, "filesystem", "crypto")) {
                 _cleanup_free_ char *t = NULL, *n = NULL;
                 const char *fstype = NULL;
 
@@ -123,6 +177,8 @@ int dissect_image(int fd, DissectedImage **ret) {
 
                 t = n = NULL;
 
+                m->encrypted = streq(fstype, "crypto_LUKS");
+
                 *ret = m;
                 m = NULL;
 
@@ -385,52 +441,24 @@ int dissect_image(int fd, DissectedImage **ret) {
                         return -ENXIO;
         }
 
+        blkid_free_probe(b);
+        b = NULL;
+
         /* Fill in file system types if we don't know them yet. */
         for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
-                const char *fstype;
-
-                if (!m->partitions[i].found) /* not found? */
-                        continue;
-
-                if (m->partitions[i].fstype) /* already know the type? */
-                        continue;
+                DissectedPartition *p = m->partitions + i;
 
-                if (!m->partitions[i].node) /* have no device node for? */
+                if (!p->found)
                         continue;
 
-                if (b)
-                        blkid_free_probe(b);
-
-                b = blkid_new_probe_from_filename(m->partitions[i].node);
-                if (!b)
-                        return -ENOMEM;
-
-                blkid_probe_enable_superblocks(b, 1);
-                blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
-
-                errno = 0;
-                r = blkid_do_safeprobe(b);
-                if (r == -2 || r == 1) {
-                        log_debug("Failed to identify any partition type on partition %i", m->partitions[i].partno);
-                        continue;
-                }
-                if (r != 0) {
-                        if (errno == 0)
-                                return -EIO;
-
-                        return -errno;
+                if (!p->fstype && p->node) {
+                        r = probe_filesystem(p->node, &p->fstype);
+                        if (r < 0)
+                                return r;
                 }
 
-                (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
-                if (fstype) {
-                        char *t;
-
-                        t = strdup(fstype);
-                        if (!t)
-                                return -ENOMEM;
-
-                        m->partitions[i].fstype = t;
-                }
+                if (streq_ptr(p->fstype, "crypto_LUKS"))
+                        m->encrypted = true;
         }
 
         *ret = m;
@@ -451,48 +479,79 @@ DissectedImage* dissected_image_unref(DissectedImage *m) {
         for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
                 free(m->partitions[i].fstype);
                 free(m->partitions[i].node);
+                free(m->partitions[i].decrypted_fstype);
+                free(m->partitions[i].decrypted_node);
         }
 
         free(m);
         return NULL;
 }
 
-static int mount_partition(DissectedPartition *m, const char *where, const char *directory, DissectedImageMountFlags flags) {
-        const char *p, *options = NULL;
+static int is_loop_device(const char *path) {
+        char s[strlen("/sys/dev/block/") + DECIMAL_STR_MAX(dev_t) + 1 + DECIMAL_STR_MAX(dev_t) + strlen("/../loop/")];
+        struct stat st;
+
+        assert(path);
+
+        if (stat(path, &st) < 0)
+                return -errno;
+
+        if (!S_ISBLK(st.st_mode))
+                return -ENOTBLK;
+
+        xsprintf(s, "/sys/dev/block/%u:%u/loop/", major(st.st_rdev), minor(st.st_rdev));
+        if (access(s, F_OK) < 0) {
+                if (errno != ENOENT)
+                        return -errno;
+
+                /* The device itself isn't a loop device, but maybe it's a partition and its parent is? */
+                xsprintf(s, "/sys/dev/block/%u:%u/../loop/", major(st.st_rdev), minor(st.st_rdev));
+                if (access(s, F_OK) < 0)
+                        return errno == ENOENT ? false : -errno;
+        }
+
+        return true;
+}
+
+static int mount_partition(
+                DissectedPartition *m,
+                const char *where,
+                const char *directory,
+                DissectImageFlags flags) {
+
+        const char *p, *options = NULL, *node, *fstype;
         bool rw;
 
         assert(m);
         assert(where);
 
-        if (!m->found || !m->node || !m->fstype)
+        node = m->decrypted_node ?: m->node;
+        fstype = m->decrypted_fstype ?: m->fstype;
+
+        if (!m->found || !node || !fstype)
                 return 0;
 
-        rw = m->rw && !(flags & DISSECTED_IMAGE_READ_ONLY);
+        /* Stacked encryption? Yuck */
+        if (streq_ptr(fstype, "crypto_LUKS"))
+                return -ELOOP;
+
+        rw = m->rw && !(flags & DISSECT_IMAGE_READ_ONLY);
 
         if (directory)
                 p = strjoina(where, directory);
         else
                 p = where;
 
-        /* Not supported for now. */
-        if (streq(m->fstype, "crypto_LUKS"))
-                return -EOPNOTSUPP;
-
-        /* If this is a loopback device then let's mount the image with discard, so that the underlying file remains
-         * sparse when possible. */
-        if ((flags & DISSECTED_IMAGE_DISCARD_ON_LOOP) &&
-            STR_IN_SET(m->fstype, "btrfs", "ext4", "vfat", "xfs")) {
-                const char *l;
-
-                l = path_startswith(m->node, "/dev");
-                if (l && startswith(l, "loop"))
-                        options = "discard";
-        }
+        /* If requested, turn on discard support. */
+        if (STR_IN_SET(fstype, "btrfs", "ext4", "vfat", "xfs") &&
+            ((flags & DISSECT_IMAGE_DISCARD) ||
+             ((flags & DISSECT_IMAGE_DISCARD_ON_LOOP) && is_loop_device(m->node))))
+                options = "discard";
 
-        return mount_verbose(LOG_DEBUG, m->node, p, m->fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), options);
+        return mount_verbose(LOG_DEBUG, node, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), options);
 }
 
-int dissected_image_mount(DissectedImage *m, const char *where, DissectedImageMountFlags flags) {
+int dissected_image_mount(DissectedImage *m, const char *where, DissectImageFlags flags) {
         int r;
 
         assert(m);
@@ -536,6 +595,284 @@ int dissected_image_mount(DissectedImage *m, const char *where, DissectedImageMo
         return 0;
 }
 
+#ifdef HAVE_LIBCRYPTSETUP
+typedef struct DecryptedPartition {
+        struct crypt_device *device;
+        char *name;
+        bool relinquished;
+} DecryptedPartition;
+
+struct DecryptedImage {
+        DecryptedPartition *decrypted;
+        size_t n_decrypted;
+        size_t n_allocated;
+};
+#endif
+
+DecryptedImage* decrypted_image_unref(DecryptedImage* d) {
+#ifdef HAVE_LIBCRYPTSETUP
+        size_t i;
+        int r;
+
+        if (!d)
+                return NULL;
+
+        for (i = 0; i < d->n_decrypted; i++) {
+                DecryptedPartition *p = d->decrypted + i;
+
+                if (p->device && p->name && !p->relinquished) {
+                        r = crypt_deactivate(p->device, p->name);
+                        if (r < 0)
+                                log_debug_errno(r, "Failed to deactivate encrypted partition %s", p->name);
+                }
+
+                if (p->device)
+                        crypt_free(p->device);
+                free(p->name);
+        }
+
+        free(d);
+#endif
+        return NULL;
+}
+
+#ifdef HAVE_LIBCRYPTSETUP
+static int decrypt_partition(
+                DissectedPartition *m,
+                const char *passphrase,
+                DissectImageFlags flags,
+                DecryptedImage *d) {
+
+        _cleanup_free_ char *node = NULL, *name = NULL;
+        struct crypt_device *cd;
+        const char *suffix;
+        int r;
+
+        assert(m);
+        assert(d);
+
+        if (!m->found || !m->node || !m->fstype)
+                return 0;
+
+        if (!streq(m->fstype, "crypto_LUKS"))
+                return 0;
+
+        suffix = strrchr(m->node, '/');
+        if (!suffix)
+                return -EINVAL;
+        suffix++;
+        if (isempty(suffix))
+                return -EINVAL;
+
+        name = strjoin(suffix, "-decrypted");
+        if (!name)
+                return -ENOMEM;
+        if (!filename_is_valid(name))
+                return -EINVAL;
+
+        node = strjoin(crypt_get_dir(), "/", name);
+        if (!node)
+                return -ENOMEM;
+
+        if (!GREEDY_REALLOC0(d->decrypted, d->n_allocated, d->n_decrypted + 1))
+                return -ENOMEM;
+
+        r = crypt_init(&cd, m->node);
+        if (r < 0)
+                return r;
+
+        r = crypt_load(cd, CRYPT_LUKS1, NULL);
+        if (r < 0)
+                goto fail;
+
+        r = crypt_activate_by_passphrase(cd, name, CRYPT_ANY_SLOT, passphrase, strlen(passphrase),
+                                         ((flags & DISSECT_IMAGE_READ_ONLY) ? CRYPT_ACTIVATE_READONLY : 0) |
+                                         ((flags & DISSECT_IMAGE_DISCARD_ON_CRYPTO) ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0));
+        if (r == -EPERM) {
+                r = -EKEYREJECTED;
+                goto fail;
+        }
+        if (r < 0)
+                goto fail;
+
+        d->decrypted[d->n_decrypted].name = name;
+        name = NULL;
+
+        d->decrypted[d->n_decrypted].device = cd;
+        d->n_decrypted++;
+
+        m->decrypted_node = node;
+        node = NULL;
+
+        return 0;
+
+fail:
+        crypt_free(cd);
+        return r;
+}
+#endif
+
+int dissected_image_decrypt(
+                DissectedImage *m,
+                const char *passphrase,
+                DissectImageFlags flags,
+                DecryptedImage **ret) {
+
+        _cleanup_(decrypted_image_unrefp) DecryptedImage *d = NULL;
+#ifdef HAVE_LIBCRYPTSETUP
+        unsigned i;
+        int r;
+#endif
+
+        assert(m);
+
+        /* Returns:
+         *
+         *      = 0           → There was nothing to decrypt
+         *      > 0           → Decrypted successfully
+         *      -ENOKEY       → There's some to decrypt but no key was supplied
+         *      -EKEYREJECTED → Passed key was not correct
+         */
+
+        if (!m->encrypted) {
+                *ret = NULL;
+                return 0;
+        }
+
+#ifdef HAVE_LIBCRYPTSETUP
+        if (!passphrase)
+                return -ENOKEY;
+
+        d = new0(DecryptedImage, 1);
+        if (!d)
+                return -ENOMEM;
+
+        for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
+                DissectedPartition *p = m->partitions + i;
+
+                if (!p->found)
+                        continue;
+
+                r = decrypt_partition(p, passphrase, flags, d);
+                if (r < 0)
+                        return r;
+
+                if (!p->decrypted_fstype && p->decrypted_node) {
+                        r = probe_filesystem(p->decrypted_node, &p->decrypted_fstype);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        *ret = d;
+        d = NULL;
+
+        return 1;
+#else
+        return -EOPNOTSUPP;
+#endif
+}
+
+int dissected_image_decrypt_interactively(
+                DissectedImage *m,
+                const char *passphrase,
+                DissectImageFlags flags,
+                DecryptedImage **ret) {
+
+        _cleanup_strv_free_erase_ char **z = NULL;
+        int n = 3, r;
+
+        if (passphrase)
+                n--;
+
+        for (;;) {
+                r = dissected_image_decrypt(m, passphrase, flags, ret);
+                if (r >= 0)
+                        return r;
+                if (r == -EKEYREJECTED)
+                        log_error_errno(r, "Incorrect passphrase, try again!");
+                else if (r != -ENOKEY) {
+                        log_error_errno(r, "Failed to decrypt image: %m");
+                        return r;
+                }
+
+                if (--n < 0) {
+                        log_error("Too many retries.");
+                        return -EKEYREJECTED;
+                }
+
+                z = strv_free(z);
+
+                r = ask_password_auto("Please enter image passphrase!", NULL, "dissect", "dissect", USEC_INFINITY, 0, &z);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to query for passphrase: %m");
+
+                passphrase = z[0];
+        }
+}
+
+#ifdef HAVE_LIBCRYPTSETUP
+static int deferred_remove(DecryptedPartition *p) {
+
+        struct dm_ioctl dm = {
+                .version = {
+                        DM_VERSION_MAJOR,
+                        DM_VERSION_MINOR,
+                        DM_VERSION_PATCHLEVEL
+                },
+                .data_size = sizeof(dm),
+                .flags = DM_DEFERRED_REMOVE,
+        };
+
+        _cleanup_close_ int fd = -1;
+
+        assert(p);
+
+        /* Unfortunately, libcryptsetup doesn't provide a proper API for this, hence call the ioctl() directly. */
+
+        fd = open("/dev/mapper/control", O_RDWR|O_CLOEXEC);
+        if (fd < 0)
+                return -errno;
+
+        strncpy(dm.name, p->name, sizeof(dm.name));
+
+        if (ioctl(fd, DM_DEV_REMOVE, &dm))
+                return -errno;
+
+        return 0;
+}
+#endif
+
+int decrypted_image_relinquish(DecryptedImage *d) {
+
+#ifdef HAVE_LIBCRYPTSETUP
+        size_t i;
+        int r;
+#endif
+
+        assert(d);
+
+        /* Turns on automatic removal after the last use ended for all DM devices of this image, and sets a boolean so
+         * that we don't clean it up ourselves either anymore */
+
+#ifdef HAVE_LIBCRYPTSETUP
+        for (i = 0; i < d->n_decrypted; i++) {
+                DecryptedPartition *p = d->decrypted + i;
+
+                if (p->relinquished)
+                        continue;
+
+                r = deferred_remove(p);
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to mark %s for auto-removal: %m", p->name);
+
+                p->relinquished = true;
+        }
+#endif
+
+        return 0;
+}
+
 static const char *const partition_designator_table[] = {
         [PARTITION_ROOT] = "root",
         [PARTITION_ROOT_SECONDARY] = "root-secondary",
index 04b19e8..69484eb 100644 (file)
@@ -25,6 +25,7 @@
 
 typedef struct DissectedImage DissectedImage;
 typedef struct DissectedPartition DissectedPartition;
+typedef struct DecryptedImage DecryptedImage;
 
 struct DissectedPartition {
         bool found:1;
@@ -33,6 +34,8 @@ struct DissectedPartition {
         int architecture;  /* Intended architecture: either native, secondary or unset (-1). */
         char *fstype;
         char *node;
+        char *decrypted_node;
+        char *decrypted_fstype;
 };
 
 enum  {
@@ -46,12 +49,15 @@ enum  {
         _PARTITION_DESIGNATOR_INVALID = -1
 };
 
-typedef enum DissectedImageMountFlags {
-        DISSECTED_IMAGE_READ_ONLY = 1,
-        DISSECTED_IMAGE_DISCARD_ON_LOOP = 2, /* Turn on "discard" if on loop device and file system supports it */
-} DissectedImageMountFlags;
+typedef enum DissectImageFlags {
+        DISSECT_IMAGE_READ_ONLY = 1,
+        DISSECT_IMAGE_DISCARD_ON_LOOP = 2,   /* Turn on "discard" if on loop device and file system supports it */
+        DISSECT_IMAGE_DISCARD = 4,           /* Turn on "discard" if file system supports it, on all block devices */
+        DISSECT_IMAGE_DISCARD_ON_CRYPTO = 8, /* Turn on "discard" also on crypto devices */
+} DissectImageFlags;
 
 struct DissectedImage {
+        bool encrypted;
         DissectedPartition partitions[_PARTITION_DESIGNATOR_MAX];
 };
 
@@ -60,7 +66,13 @@ int dissect_image(int fd, DissectedImage **ret);
 DissectedImage* dissected_image_unref(DissectedImage *m);
 DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref);
 
-int dissected_image_mount(DissectedImage *m, const char *dest, DissectedImageMountFlags flags);
+int dissected_image_decrypt(DissectedImage *m, const char *passphrase, DissectImageFlags flags, DecryptedImage **ret);
+int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, DissectImageFlags flags, DecryptedImage **ret);
+int dissected_image_mount(DissectedImage *m, const char *dest, DissectImageFlags flags);
+
+DecryptedImage* decrypted_image_unref(DecryptedImage *p);
+DEFINE_TRIVIAL_CLEANUP_FUNC(DecryptedImage*, decrypted_image_unref);
+int decrypted_image_relinquish(DecryptedImage *d);
 
 const char* partition_designator_to_string(int i) _const_;
 int partition_designator_from_string(const char *name) _pure_;