]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/cryptsetup/cryptsetup-generator.c
cryptsetup-generator: guess whether the keyfile argument is two items or one
[thirdparty/systemd.git] / src / cryptsetup / cryptsetup-generator.c
index 8759a26148093c45aa0c1f56df94a5b26feb870c..811a9468c1757ccfbb619cdb11c66c41b97a995b 100644 (file)
@@ -1,7 +1,9 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
 #include <errno.h>
-#include <stdio_ext.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 
 #include "alloc-util.h"
 #include "dropin.h"
@@ -35,6 +37,8 @@ typedef struct crypto_device {
 static const char *arg_dest = NULL;
 static bool arg_enabled = true;
 static bool arg_read_crypttab = true;
+static const char *arg_crypttab = NULL;
+static const char *arg_runtime_directory = NULL;
 static bool arg_whitelist = false;
 static Hashmap *arg_disks = NULL;
 static char *arg_default_options = NULL;
@@ -44,21 +48,73 @@ STATIC_DESTRUCTOR_REGISTER(arg_disks, hashmap_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_default_options, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_default_keyfile, freep);
 
-static int generate_keydev_mount(const char *name, const char *keydev, char **unit, char **mount) {
-        _cleanup_free_ char *u = NULL, *what = NULL, *where = NULL, *name_escaped = NULL;
+static int split_keyspec(const char *keyspec, char **ret_keyfile, char **ret_keydev) {
+        _cleanup_free_ char *keyfile = NULL, *keydev = NULL;
+        const char *c;
+
+        assert(ret_keyfile);
+        assert(ret_keydev);
+
+        if (!keyspec) {
+                *ret_keyfile = *ret_keydev = NULL;
+                return 0;
+        }
+
+        c = strrchr(keyspec, ':');
+        if (c) {
+                /* The keydev part has to be either an absolute path to device node (/dev/something,
+                 * /dev/foo/something, or even possibly /dev/foo/something:part), or a fstab device
+                 * specification starting with LABEL= or similar. The keyfile part has the same syntax.
+                 *
+                 * Let's try to guess if the second part looks like a keydev specification, or just part of a
+                 * filename with a colon. fstab_node_to_udev_node() will convert the fstab device syntax to
+                 * an absolute path. If we didn't get an absolute path, assume that it is just part of the
+                 * first keyfile argument. */
+
+                keydev = fstab_node_to_udev_node(c + 1);
+                if (!keydev)
+                        return log_oom();
+
+                if (path_is_absolute(keydev))
+                        keyfile = strndup(keyspec, c-keyspec);
+                else {
+                        log_debug("Keyspec argument contains a colon, but \"%s\" doesn't look like a device specification.\n"
+                                  "Assuming that \"%s\" is a single device specification.",
+                                  c + 1, keyspec);
+                        keydev = mfree(keydev);
+                        c = NULL;
+                }
+        }
+
+        if (!c)
+                /* No keydev specified */
+                keyfile = strdup(keyspec);
+
+        if (!keyfile)
+                return log_oom();
+
+        *ret_keyfile = TAKE_PTR(keyfile);
+        *ret_keydev = TAKE_PTR(keydev);
+
+        return 0;
+}
+
+static int generate_keydev_mount(const char *name, const char *keydev, const char *keydev_timeout, bool canfail, char **unit, char **mount) {
+        _cleanup_free_ char *u = NULL, *where = NULL, *name_escaped = NULL, *device_unit = NULL;
         _cleanup_fclose_ FILE *f = NULL;
         int r;
+        usec_t timeout_us;
 
         assert(name);
         assert(keydev);
         assert(unit);
         assert(mount);
 
-        r = mkdir_parents("/run/systemd/cryptsetup", 0755);
+        r = mkdir_parents(arg_runtime_directory, 0755);
         if (r < 0)
                 return r;
 
-        r = mkdir("/run/systemd/cryptsetup", 0700);
+        r = mkdir(arg_runtime_directory, 0700);
         if (r < 0 && errno != EEXIST)
                 return -errno;
 
@@ -66,7 +122,7 @@ static int generate_keydev_mount(const char *name, const char *keydev, char **un
         if (!name_escaped)
                 return -ENOMEM;
 
-        where = strjoin("/run/systemd/cryptsetup/keydev-", name_escaped);
+        where = strjoin(arg_runtime_directory, "/keydev-", name_escaped);
         if (!where)
                 return -ENOMEM;
 
@@ -82,17 +138,31 @@ static int generate_keydev_mount(const char *name, const char *keydev, char **un
         if (r < 0)
                 return r;
 
-        what = fstab_node_to_udev_node(keydev);
-        if (!what)
-                return -ENOMEM;
-
         fprintf(f,
                 "[Unit]\n"
                 "DefaultDependencies=no\n\n"
                 "[Mount]\n"
                 "What=%s\n"
                 "Where=%s\n"
-                "Options=ro\n", what, where);
+                "Options=ro%s\n", keydev, where, canfail ? ",nofail" : "");
+
+        if (keydev_timeout) {
+                r = parse_sec_fix_0(keydev_timeout, &timeout_us);
+                if (r >= 0) {
+                        r = unit_name_from_path(keydev, ".device", &device_unit);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to generate unit name: %m");
+
+                        r = write_drop_in_format(arg_dest, device_unit, 90, "device-timeout",
+                                "# Automatically generated by systemd-cryptsetup-generator \n\n"
+                                "[Unit]\nJobRunningTimeoutSec=%s", keydev_timeout);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to write device drop-in: %m");
+
+                } else
+                        log_warning_errno(r, "Failed to parse %s, ignoring: %m", keydev_timeout);
+
+        }
 
         r = fflush_and_check(f);
         if (r < 0)
@@ -104,19 +174,61 @@ static int generate_keydev_mount(const char *name, const char *keydev, char **un
         return 0;
 }
 
+static int print_dependencies(FILE *f, const char* device_path) {
+        int r;
+
+        if (STR_IN_SET(device_path, "-", "none"))
+                /* None, nothing to do */
+                return 0;
+
+        if (PATH_IN_SET(device_path, "/dev/urandom", "/dev/random", "/dev/hw_random")) {
+                /* RNG device, add random dep */
+                fputs("After=systemd-random-seed.service\n", f);
+                return 0;
+        }
+
+        _cleanup_free_ char *udev_node = fstab_node_to_udev_node(device_path);
+        if (!udev_node)
+                return log_oom();
+
+        if (path_equal(udev_node, "/dev/null"))
+                return 0;
+
+        if (path_startswith(udev_node, "/dev/")) {
+                /* We are dealing with a block device, add dependency for correspoding unit */
+                _cleanup_free_ char *unit = NULL;
+
+                r = unit_name_from_path(udev_node, ".device", &unit);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to generate unit name: %m");
+
+                fprintf(f, "After=%1$s\nRequires=%1$s\n", unit);
+        } else {
+                /* Regular file, add mount dependency */
+                _cleanup_free_ char *escaped_path = specifier_escape(device_path);
+                if (!escaped_path)
+                        return log_oom();
+
+                fprintf(f, "RequiresMountsFor=%s\n", escaped_path);
+        }
+
+        return 0;
+}
+
 static int create_disk(
                 const char *name,
                 const char *device,
-                const char *keydev,
                 const char *password,
+                const char *keydev,
                 const char *options) {
 
         _cleanup_free_ char *n = NULL, *d = NULL, *u = NULL, *e = NULL,
-                *filtered = NULL, *u_escaped = NULL, *password_escaped = NULL, *filtered_escaped = NULL, *name_escaped = NULL, *keydev_mount = NULL;
+                *keydev_mount = NULL, *keyfile_timeout_value = NULL, *password_escaped = NULL,
+                *filtered = NULL, *u_escaped = NULL, *filtered_escaped = NULL, *name_escaped = NULL, *header_path = NULL;
         _cleanup_fclose_ FILE *f = NULL;
         const char *dmname;
         bool noauto, nofail, tmp, swap, netdev;
-        int r;
+        int r, detached_header, keyfile_can_timeout;
 
         assert(name);
         assert(device);
@@ -127,6 +239,14 @@ static int create_disk(
         swap = fstab_test_option(options, "swap\0");
         netdev = fstab_test_option(options, "_netdev\0");
 
+        keyfile_can_timeout = fstab_filter_options(options, "keyfile-timeout\0", NULL, &keyfile_timeout_value, NULL);
+        if (keyfile_can_timeout < 0)
+                return log_error_errno(keyfile_can_timeout, "Failed to parse keyfile-timeout= option value: %m");
+
+        detached_header = fstab_filter_options(options, "header\0", NULL, &header_path, NULL);
+        if (detached_header < 0)
+                return log_error_errno(detached_header, "Failed to parse header= option value: %m");
+
         if (tmp && swap)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                        "Device '%s' cannot be both 'tmp' and 'swap'. Ignoring.",
@@ -156,12 +276,6 @@ static int create_disk(
         if (r < 0)
                 return log_error_errno(r, "Failed to generate unit name: %m");
 
-        if (password) {
-                password_escaped = specifier_escape(password);
-                if (!password_escaped)
-                        return log_oom();
-        }
-
         if (keydev && !password)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                        "Key device is specified, but path to the password file is missing.");
@@ -174,25 +288,38 @@ static int create_disk(
                 "[Unit]\n"
                 "Description=Cryptography Setup for %%I\n"
                 "Documentation=man:crypttab(5) man:systemd-cryptsetup-generator(8) man:systemd-cryptsetup@.service(8)\n"
-                "SourcePath=/etc/crypttab\n"
+                "SourcePath=%s\n"
                 "DefaultDependencies=no\n"
                 "Conflicts=umount.target\n"
                 "IgnoreOnIsolate=true\n"
                 "After=%s\n",
+                arg_crypttab,
                 netdev ? "remote-fs-pre.target" : "cryptsetup-pre.target");
 
+        if (password) {
+                password_escaped = specifier_escape(password);
+                if (!password_escaped)
+                        return log_oom();
+        }
+
         if (keydev) {
                 _cleanup_free_ char *unit = NULL, *p = NULL;
 
-                r = generate_keydev_mount(name, keydev, &unit, &keydev_mount);
+                r = generate_keydev_mount(name, keydev, keyfile_timeout_value, keyfile_can_timeout > 0, &unit, &keydev_mount);
                 if (r < 0)
                         return log_error_errno(r, "Failed to generate keydev mount unit: %m");
 
-                p = prefix_root(keydev_mount, password_escaped);
+                p = path_join(keydev_mount, password_escaped);
                 if (!p)
                         return log_oom();
 
                 free_and_replace(password_escaped, p);
+
+                fprintf(f, "After=%s\n", unit);
+                if (keyfile_can_timeout > 0)
+                        fprintf(f, "Wants=%s\n", unit);
+                else
+                        fprintf(f, "Requires=%s\n", unit);
         }
 
         if (!nofail)
@@ -200,30 +327,17 @@ static int create_disk(
                         "Before=%s\n",
                         netdev ? "remote-cryptsetup.target" : "cryptsetup.target");
 
-        if (password) {
-                if (PATH_IN_SET(password, "/dev/urandom", "/dev/random", "/dev/hw_random"))
-                        fputs("After=systemd-random-seed.service\n", f);
-                else if (!STR_IN_SET(password, "-", "none")) {
-                        _cleanup_free_ char *uu;
-
-                        uu = fstab_node_to_udev_node(password);
-                        if (!uu)
-                                return log_oom();
-
-                        if (!path_equal(uu, "/dev/null")) {
-
-                                if (path_startswith(uu, "/dev/")) {
-                                        _cleanup_free_ char *dd = NULL;
-
-                                        r = unit_name_from_path(uu, ".device", &dd);
-                                        if (r < 0)
-                                                return log_error_errno(r, "Failed to generate unit name: %m");
+        if (password && !keydev) {
+                r = print_dependencies(f, password);
+                if (r < 0)
+                        return r;
+        }
 
-                                        fprintf(f, "After=%1$s\nRequires=%1$s\n", dd);
-                                } else
-                                        fprintf(f, "RequiresMountsFor=%s\n", password_escaped);
-                        }
-                }
+        /* Check if a header option was specified */
+        if (detached_header > 0) {
+                r = print_dependencies(f, header_path);
+                if (r < 0)
+                        return r;
         }
 
         if (path_startswith(u, "/dev/")) {
@@ -262,6 +376,7 @@ static int create_disk(
                 "RemainAfterExit=yes\n"
                 "TimeoutSec=0\n" /* the binary handles timeouts anyway */
                 "KeyringMode=shared\n" /* make sure we can share cached keys among instances */
+                "OOMScoreAdjust=500\n" /* unlocking can allocate a lot of memory if Argon2 is used */
                 "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n"
                 "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n",
                 name_escaped, u_escaped, strempty(password_escaped), strempty(filtered_escaped),
@@ -279,7 +394,7 @@ static int create_disk(
 
         if (keydev)
                 fprintf(f,
-                        "ExecStartPost=" UMOUNT_PATH " %s\n\n",
+                        "ExecStartPost=-" UMOUNT_PATH " %s\n\n",
                         keydev_mount);
 
         r = fflush_and_check(f);
@@ -287,10 +402,6 @@ static int create_disk(
                 return log_error_errno(r, "Failed to write unit file %s: %m", n);
 
         if (!noauto) {
-                r = generator_add_symlink(arg_dest, d, "wants", n);
-                if (r < 0)
-                        return r;
-
                 r = generator_add_symlink(arg_dest,
                                           netdev ? "remote-cryptsetup.target" : "cryptsetup.target",
                                           nofail ? "wants" : "requires", n);
@@ -338,9 +449,6 @@ static crypto_device *get_crypto_device(const char *uuid) {
                 if (!d)
                         return NULL;
 
-                d->create = false;
-                d->keyfile = d->options = d->name = NULL;
-
                 d->uuid = strdup(uuid);
                 if (!d->uuid)
                         return mfree(d);
@@ -405,7 +513,6 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
         } else if (streq(key, "luks.key")) {
                 size_t n;
                 _cleanup_free_ char *keyfile = NULL, *keydev = NULL;
-                char *c;
                 const char *keyspec;
 
                 if (proc_cmdline_value_missing(key, value))
@@ -432,23 +539,13 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
                         return log_oom();
 
                 keyspec = value + n + 1;
-                c = strrchr(keyspec, ':');
-                if (c) {
-                         *c = '\0';
-                        keyfile = strdup(keyspec);
-                        keydev = strdup(c + 1);
-
-                        if (!keyfile || !keydev)
-                                return log_oom();
-                } else {
-                        /* No keydev specified */
-                        keyfile = strdup(keyspec);
-                        if (!keyfile)
-                                return log_oom();
-                }
+                r = split_keyspec(keyspec, &keyfile, &keydev);
+                if (r < 0)
+                        return r;
 
                 free_and_replace(d->keyfile, keyfile);
                 free_and_replace(d->keydev, keydev);
+
         } else if (streq(key, "luks.name")) {
 
                 if (proc_cmdline_value_missing(key, value))
@@ -479,29 +576,27 @@ static int add_crypttab_devices(void) {
         if (!arg_read_crypttab)
                 return 0;
 
-        f = fopen("/etc/crypttab", "re");
-        if (!f) {
+        r = fopen_unlocked(arg_crypttab, "re", &f);
+        if (r < 0) {
                 if (errno != ENOENT)
-                        log_error_errno(errno, "Failed to open /etc/crypttab: %m");
+                        log_error_errno(errno, "Failed to open %s: %m", arg_crypttab);
                 return 0;
         }
 
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
         if (fstat(fileno(f), &st) < 0) {
-                log_error_errno(errno, "Failed to stat /etc/crypttab: %m");
+                log_error_errno(errno, "Failed to stat %s: %m", arg_crypttab);
                 return 0;
         }
 
         for (;;) {
-                _cleanup_free_ char *line = NULL, *name = NULL, *device = NULL, *keyfile = NULL, *options = NULL;
+                _cleanup_free_ char *line = NULL, *name = NULL, *device = NULL, *keyspec = NULL, *options = NULL, *keyfile = NULL, *keydev = NULL;
                 crypto_device *d = NULL;
                 char *l, *uuid;
                 int k;
 
                 r = read_line(f, LONG_LINE_MAX, &line);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to read /etc/crypttab: %m");
+                        return log_error_errno(r, "Failed to read %s: %m", arg_crypttab);
                 if (r == 0)
                         break;
 
@@ -511,15 +606,17 @@ static int add_crypttab_devices(void) {
                 if (IN_SET(l[0], 0, '#'))
                         continue;
 
-                k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &keyfile, &options);
+                k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &keyspec, &options);
                 if (k < 2 || k > 4) {
-                        log_error("Failed to parse /etc/crypttab:%u, ignoring.", crypttab_line);
+                        log_error("Failed to parse %s:%u, ignoring.", arg_crypttab, crypttab_line);
                         continue;
                 }
 
-                uuid = STARTSWITH_SET(device, "UUID=", "luks-");
+                uuid = startswith(device, "UUID=");
                 if (!uuid)
                         uuid = path_startswith(device, "/dev/disk/by-uuid/");
+                if (!uuid)
+                        uuid = startswith(name, "luks-");
                 if (uuid)
                         d = hashmap_get(arg_disks, uuid);
 
@@ -528,7 +625,11 @@ static int add_crypttab_devices(void) {
                         continue;
                 }
 
-                r = create_disk(name, device, NULL, keyfile, (d && d->options) ? d->options : options);
+                r = split_keyspec(keyspec, &keyfile, &keydev);
+                if (r < 0)
+                        return r;
+
+                r = create_disk(name, device, keyfile, keydev, (d && d->options) ? d->options : options);
                 if (r < 0)
                         return r;
 
@@ -552,12 +653,12 @@ static int add_proc_cmdline_devices(void) {
                         continue;
 
                 if (!d->name) {
-                        d->name = strappend("luks-", d->uuid);
+                        d->name = strjoin("luks-", d->uuid);
                         if (!d->name)
                                 return log_oom();
                 }
 
-                device = strappend("UUID=", d->uuid);
+                device = strjoin("UUID=", d->uuid);
                 if (!device)
                         return log_oom();
 
@@ -568,7 +669,7 @@ static int add_proc_cmdline_devices(void) {
                 else
                         options = "timeout=0";
 
-                r = create_disk(d->name, device, d->keydev, d->keyfile ?: arg_default_keyfile, options);
+                r = create_disk(d->name, device, d->keyfile ?: arg_default_keyfile, d->keydev, options);
                 if (r < 0)
                         return r;
         }
@@ -584,6 +685,9 @@ static int run(const char *dest, const char *dest_early, const char *dest_late)
 
         assert_se(arg_dest = dest);
 
+        arg_crypttab = getenv("SYSTEMD_CRYPTTAB") ?: "/etc/crypttab";
+        arg_runtime_directory = getenv("RUNTIME_DIRECTORY") ?: "/run/systemd/cryptsetup";
+
         arg_disks = hashmap_new(&crypt_device_hash_ops);
         if (!arg_disks)
                 return log_oom();