]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
cryptsetup: use dispatch_verb()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Sat, 18 Jan 2025 19:58:54 +0000 (04:58 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 20 Jan 2025 18:42:38 +0000 (03:42 +0900)
src/cryptsetup/cryptsetup.c

index e5f21bbce7756ff783139e3be4dd45657d20c9c2..04ff160057bd84b1c6624e21e28d87ee80f87276 100644 (file)
@@ -46,6 +46,7 @@
 #include "strv.h"
 #include "tpm2-pcr.h"
 #include "tpm2-util.h"
+#include "verbs.h"
 
 /* internal helper */
 #define ANY_LUKS "LUKS"
@@ -2388,294 +2389,296 @@ static int discover_key(const char *key_file, const char *volume, TokenType toke
         return r;
 }
 
-static int run(int argc, char *argv[]) {
+static int verb_attach(int argc, char *argv[], void *userdata) {
         _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
-        const char *verb;
+        _unused_ _cleanup_(remove_and_erasep) const char *destroy_key_file = NULL;
+        crypt_status_info status;
+        uint32_t flags = 0;
+        unsigned tries;
+        usec_t until;
+        PassphraseType passphrase_type = PASSPHRASE_NONE;
         int r;
 
-        log_setup();
+        /* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [KEY-FILE] [CONFIG] */
 
-        umask(0022);
+        assert(argc >= 3 && argc <= 5);
 
-        r = parse_argv(argc, argv);
-        if (r <= 0)
-                return r;
+        const char *volume = ASSERT_PTR(argv[1]),
+                *source = ASSERT_PTR(argv[2]),
+                *key_file = argc >= 4 ? mangle_none(argv[3]) : NULL,
+                *config = argc >= 5 ? mangle_none(argv[4]) : NULL;
 
-        cryptsetup_enable_logging(NULL);
+        if (!filename_is_valid(volume))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume);
 
-        if (argc - optind < 2)
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "This program requires at least two arguments.");
-        verb = ASSERT_PTR(argv[optind]);
-
-        if (streq(verb, "attach")) {
-                _unused_ _cleanup_(remove_and_erasep) const char *destroy_key_file = NULL;
-                crypt_status_info status;
-                uint32_t flags = 0;
-                unsigned tries;
-                usec_t until;
-                PassphraseType passphrase_type = PASSPHRASE_NONE;
-
-                /* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [KEY-FILE] [CONFIG] */
-
-                if (argc - optind < 3)
-                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach requires at least two arguments.");
-                if (argc - optind >= 6)
-                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach does not accept more than four arguments.");
-
-                const char *volume = ASSERT_PTR(argv[optind + 1]),
-                           *source = ASSERT_PTR(argv[optind + 2]),
-                           *key_file = argc - optind >= 4 ? mangle_none(argv[optind + 3]) : NULL,
-                           *config = argc - optind >= 5 ? mangle_none(argv[optind + 4]) : NULL;
-
-                if (!filename_is_valid(volume))
-                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume);
-
-                if (key_file && !path_is_absolute(key_file)) {
-                        log_warning("Password file path '%s' is not absolute. Ignoring.", key_file);
-                        key_file = NULL;
-                }
+        if (key_file && !path_is_absolute(key_file)) {
+                log_warning("Password file path '%s' is not absolute. Ignoring.", key_file);
+                key_file = NULL;
+        }
 
-                if (config) {
-                        r = parse_crypt_config(config);
-                        if (r < 0)
-                                return r;
-                }
+        if (config) {
+                r = parse_crypt_config(config);
+                if (r < 0)
+                        return r;
+        }
 
-                log_debug("%s %s ← %s type=%s cipher=%s", __func__,
-                          volume, source, strempty(arg_type), strempty(arg_cipher));
+        log_debug("%s %s ← %s type=%s cipher=%s", __func__,
+                  volume, source, strempty(arg_type), strempty(arg_cipher));
 
-                /* A delicious drop of snake oil */
-                (void) mlockall(MCL_FUTURE);
+        /* A delicious drop of snake oil */
+        (void) mlockall(MCL_FUTURE);
 
-                if (key_file && arg_keyfile_erase)
-                        destroy_key_file = key_file; /* let's get this baby erased when we leave */
+        if (key_file && arg_keyfile_erase)
+                destroy_key_file = key_file; /* let's get this baby erased when we leave */
 
-                if (arg_header) {
-                        if (streq_ptr(arg_type, CRYPT_TCRYPT)){
-                            log_debug("tcrypt header: %s", arg_header);
-                            r = crypt_init_data_device(&cd, arg_header, source);
-                        } else {
-                            log_debug("LUKS header: %s", arg_header);
-                            r = crypt_init(&cd, arg_header);
-                        }
-                } else
-                        r = crypt_init(&cd, source);
-                if (r < 0)
-                        return log_error_errno(r, "crypt_init() failed: %m");
+        if (arg_header) {
+                if (streq_ptr(arg_type, CRYPT_TCRYPT)){
+                        log_debug("tcrypt header: %s", arg_header);
+                        r = crypt_init_data_device(&cd, arg_header, source);
+                } else {
+                        log_debug("LUKS header: %s", arg_header);
+                        r = crypt_init(&cd, arg_header);
+                }
+        } else
+                r = crypt_init(&cd, source);
+        if (r < 0)
+                return log_error_errno(r, "crypt_init() failed: %m");
 
-                cryptsetup_enable_logging(cd);
+        cryptsetup_enable_logging(cd);
 
-                status = crypt_status(cd, volume);
-                if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) {
-                        log_info("Volume %s already active.", volume);
-                        return 0;
-                }
+        status = crypt_status(cd, volume);
+        if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) {
+                log_info("Volume %s already active.", volume);
+                return 0;
+        }
 
-                flags = determine_flags();
+        flags = determine_flags();
 
-                until = usec_add(now(CLOCK_MONOTONIC), arg_timeout);
-                if (until == USEC_INFINITY)
-                        until = 0;
+        until = usec_add(now(CLOCK_MONOTONIC), arg_timeout);
+        if (until == USEC_INFINITY)
+                until = 0;
 
-                if (arg_key_size == 0)
-                        arg_key_size = 256U / 8U;
+        if (arg_key_size == 0)
+                arg_key_size = 256U / 8U;
 
-                if (key_file) {
-                        struct stat st;
+        if (key_file) {
+                struct stat st;
 
-                        /* Ideally we'd do this on the open fd, but since this is just a
-                         * warning it's OK to do this in two steps. */
-                        if (stat(key_file, &st) >= 0 && S_ISREG(st.st_mode) && (st.st_mode & 0005))
-                                log_warning("Key file %s is world-readable. This is not a good idea!", key_file);
-                }
+                /* Ideally we'd do this on the open fd, but since this is just a warning it's OK to do this
+                 * in two steps. */
+                if (stat(key_file, &st) >= 0 && S_ISREG(st.st_mode) && (st.st_mode & 0005))
+                        log_warning("Key file %s is world-readable. This is not a good idea!", key_file);
+        }
 
-                if (!arg_type || STR_IN_SET(arg_type, ANY_LUKS, CRYPT_LUKS1, CRYPT_LUKS2)) {
-                        r = crypt_load(cd, !arg_type || streq(arg_type, ANY_LUKS) ? CRYPT_LUKS : arg_type, NULL);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to load LUKS superblock on device %s: %m", crypt_get_device_name(cd));
+        if (!arg_type || STR_IN_SET(arg_type, ANY_LUKS, CRYPT_LUKS1, CRYPT_LUKS2)) {
+                r = crypt_load(cd, !arg_type || streq(arg_type, ANY_LUKS) ? CRYPT_LUKS : arg_type, NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to load LUKS superblock on device %s: %m", crypt_get_device_name(cd));
 
 /* since cryptsetup 2.7.0 (Jan 2024) */
 #if HAVE_CRYPT_SET_KEYRING_TO_LINK
-                        if (arg_link_key_description) {
-                                r = crypt_set_keyring_to_link(cd, arg_link_key_description, NULL, arg_link_key_type, arg_link_keyring);
-                                if (r < 0)
-                                        log_warning_errno(r, "Failed to set keyring or key description to link volume key in, ignoring: %m");
-                        }
+                if (arg_link_key_description) {
+                        r = crypt_set_keyring_to_link(cd, arg_link_key_description, NULL, arg_link_key_type, arg_link_keyring);
+                        if (r < 0)
+                                log_warning_errno(r, "Failed to set keyring or key description to link volume key in, ignoring: %m");
+                }
 #endif
 
-                        if (arg_header) {
-                                r = crypt_set_data_device(cd, source);
-                                if (r < 0)
-                                        return log_error_errno(r, "Failed to set LUKS data device %s: %m", source);
-                        }
-
-                        /* Tokens are available in LUKS2 only, but it is ok to call (and fail) with LUKS1. */
-                        if (!key_file && use_token_plugins()) {
-                                r = crypt_activate_by_token_pin_ask_password(
-                                                cd,
-                                                volume,
-                                                /* type= */ NULL,
-                                                until,
-                                                /* userdata= */ NULL,
-                                                flags,
-                                                "Please enter LUKS2 token PIN:",
-                                                "luks2-pin",
-                                                "cryptsetup.luks2-pin");
-                                if (r >= 0) {
-                                        log_debug("Volume %s activated with a LUKS token.", volume);
-                                        return 0;
-                                }
+                if (arg_header) {
+                        r = crypt_set_data_device(cd, source);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set LUKS data device %s: %m", source);
+                }
 
-                                log_debug_errno(r, "Token activation unsuccessful for device %s: %m", crypt_get_device_name(cd));
+                /* Tokens are available in LUKS2 only, but it is ok to call (and fail) with LUKS1. */
+                if (!key_file && use_token_plugins()) {
+                        r = crypt_activate_by_token_pin_ask_password(
+                                        cd,
+                                        volume,
+                                        /* type= */ NULL,
+                                        until,
+                                        /* userdata= */ NULL,
+                                        flags,
+                                        "Please enter LUKS2 token PIN:",
+                                        "luks2-pin",
+                                        "cryptsetup.luks2-pin");
+                        if (r >= 0) {
+                                log_debug("Volume %s activated with a LUKS token.", volume);
+                                return 0;
                         }
+
+                        log_debug_errno(r, "Token activation unsuccessful for device %s: %m", crypt_get_device_name(cd));
                 }
+        }
 
 /* since cryptsetup 2.3.0 (Feb 2020) */
 #ifdef CRYPT_BITLK
-                if (streq_ptr(arg_type, CRYPT_BITLK)) {
-                        r = crypt_load(cd, CRYPT_BITLK, NULL);
+        if (streq_ptr(arg_type, CRYPT_BITLK)) {
+                r = crypt_load(cd, CRYPT_BITLK, NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to load Bitlocker superblock on device %s: %m", crypt_get_device_name(cd));
+        }
+#endif
+
+        bool use_cached_passphrase = true, try_discover_key = !key_file;
+        const char *discovered_key_fn = strjoina(volume, ".key");
+        _cleanup_strv_free_erase_ char **passwords = NULL;
+        for (tries = 0; arg_tries == 0 || tries < arg_tries; tries++) {
+                _cleanup_(iovec_done_erase) struct iovec discovered_key_data = {};
+                const struct iovec *key_data = NULL;
+                TokenType token_type = determine_token_type();
+
+                log_debug("Beginning attempt %u to unlock.", tries);
+
+                /* When we were able to acquire multiple keys, let's always process them in this order:
+                 *
+                 *    1. A key acquired via PKCS#11 or FIDO2 token, or TPM2 chip
+                 *    2. The configured or discovered key, of which both are exclusive and optional
+                 *    3. The empty password, in case arg_try_empty_password is set
+                 *    4. We enquire the user for a password
+                 */
+
+                if (try_discover_key) {
+                        r = discover_key(discovered_key_fn, volume, token_type, &discovered_key_data);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to load Bitlocker superblock on device %s: %m", crypt_get_device_name(cd));
+                                return r;
+                        if (r > 0)
+                                key_data = &discovered_key_data;
                 }
-#endif
 
-                bool use_cached_passphrase = true, try_discover_key = !key_file;
-                const char *discovered_key_fn = strjoina(volume, ".key");
-                _cleanup_strv_free_erase_ char **passwords = NULL;
-                for (tries = 0; arg_tries == 0 || tries < arg_tries; tries++) {
-                        _cleanup_(iovec_done_erase) struct iovec discovered_key_data = {};
-                        const struct iovec *key_data = NULL;
-                        TokenType token_type = determine_token_type();
-
-                        log_debug("Beginning attempt %u to unlock.", tries);
-
-                        /* When we were able to acquire multiple keys, let's always process them in this order:
-                         *
-                         *    1. A key acquired via PKCS#11 or FIDO2 token, or TPM2 chip
-                         *    2. The configured or discovered key, of which both are exclusive and optional
-                         *    3. The empty password, in case arg_try_empty_password is set
-                         *    4. We enquire the user for a password
-                         */
-
-                        if (try_discover_key) {
-                                r = discover_key(discovered_key_fn, volume, token_type, &discovered_key_data);
-                                if (r < 0)
-                                        return r;
-                                if (r > 0)
-                                        key_data = &discovered_key_data;
-                        }
+                if (token_type < 0 && !key_file && !key_data && !passwords) {
+
+                        /* If we have nothing to try anymore, then acquire a new password */
 
-                        if (token_type < 0 && !key_file && !key_data && !passwords) {
-
-                                /* If we have nothing to try anymore, then acquire a new password */
-
-                                if (arg_try_empty_password) {
-                                        /* Hmm, let's try an empty password now, but only once */
-                                        arg_try_empty_password = false;
-                                        key_data = &iovec_empty;
-                                } else {
-                                        /* Ask the user for a passphrase or recovery key only as last resort, if we have
-                                         * nothing else to check for */
-                                        if (passphrase_type == PASSPHRASE_NONE) {
-                                                passphrase_type = check_registered_passwords(cd);
-                                                if (passphrase_type == PASSPHRASE_NONE)
-                                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No passphrase or recovery key registered.");
-                                        }
-
-                                        r = get_password(
-                                                        volume,
-                                                        source,
-                                                        until,
-                                                        /* ignore_cached= */ !use_cached_passphrase || arg_verify,
-                                                        passphrase_type,
-                                                        &passwords);
-                                        use_cached_passphrase = false;
-                                        if (r == -EAGAIN)
-                                                continue;
-                                        if (r < 0)
-                                                return r;
+                        if (arg_try_empty_password) {
+                                /* Hmm, let's try an empty password now, but only once */
+                                arg_try_empty_password = false;
+                                key_data = &iovec_empty;
+                        } else {
+                                /* Ask the user for a passphrase or recovery key only as last resort, if we
+                                 * have nothing else to check for */
+                                if (passphrase_type == PASSPHRASE_NONE) {
+                                        passphrase_type = check_registered_passwords(cd);
+                                        if (passphrase_type == PASSPHRASE_NONE)
+                                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No passphrase or recovery key registered.");
                                 }
-                        }
 
-                        if (streq_ptr(arg_type, CRYPT_TCRYPT))
-                                r = attach_tcrypt(cd, volume, token_type, key_file, key_data, passwords, flags);
-                        else
-                                r = attach_luks_or_plain_or_bitlk(cd, volume, token_type, key_file, key_data, passwords, flags, until);
-                        if (r >= 0)
-                                break;
-                        if (r != -EAGAIN)
-                                return r;
+                                r = get_password(
+                                                volume,
+                                                source,
+                                                until,
+                                                /* ignore_cached= */ !use_cached_passphrase || arg_verify,
+                                                passphrase_type,
+                                                &passwords);
+                                use_cached_passphrase = false;
+                                if (r == -EAGAIN)
+                                        continue;
+                                if (r < 0)
+                                        return r;
+                        }
+                }
 
-                        /* Key not correct? Let's try again, but let's invalidate one of the passed fields,
-                         * so that we fall back to the next best thing. */
+                if (streq_ptr(arg_type, CRYPT_TCRYPT))
+                        r = attach_tcrypt(cd, volume, token_type, key_file, key_data, passwords, flags);
+                else
+                        r = attach_luks_or_plain_or_bitlk(cd, volume, token_type, key_file, key_data, passwords, flags, until);
+                if (r >= 0)
+                        break;
+                if (r != -EAGAIN)
+                        return r;
 
-                        if (token_type == TOKEN_TPM2) {
-                                arg_tpm2_device = mfree(arg_tpm2_device);
-                                arg_tpm2_device_auto = false;
-                                continue;
-                        }
+                /* Key not correct? Let's try again, but let's invalidate one of the passed fields, so that
+                 * we fall back to the next best thing. */
 
-                        if (token_type == TOKEN_FIDO2) {
-                                arg_fido2_device = mfree(arg_fido2_device);
-                                arg_fido2_device_auto = false;
-                                continue;
-                        }
+                if (token_type == TOKEN_TPM2) {
+                        arg_tpm2_device = mfree(arg_tpm2_device);
+                        arg_tpm2_device_auto = false;
+                        continue;
+                }
 
-                        if (token_type == TOKEN_PKCS11) {
-                                arg_pkcs11_uri = mfree(arg_pkcs11_uri);
-                                arg_pkcs11_uri_auto = false;
-                                continue;
-                        }
+                if (token_type == TOKEN_FIDO2) {
+                        arg_fido2_device = mfree(arg_fido2_device);
+                        arg_fido2_device_auto = false;
+                        continue;
+                }
 
-                        if (try_discover_key) {
-                                try_discover_key = false;
-                                continue;
-                        }
+                if (token_type == TOKEN_PKCS11) {
+                        arg_pkcs11_uri = mfree(arg_pkcs11_uri);
+                        arg_pkcs11_uri_auto = false;
+                        continue;
+                }
 
-                        if (key_file) {
-                                key_file = NULL;
-                                continue;
-                        }
+                if (try_discover_key) {
+                        try_discover_key = false;
+                        continue;
+                }
 
-                        if (passwords) {
-                                passwords = strv_free_erase(passwords);
-                                continue;
-                        }
+                if (key_file) {
+                        key_file = NULL;
+                        continue;
+                }
 
-                        log_debug("Prepared for next attempt to unlock.");
+                if (passwords) {
+                        passwords = strv_free_erase(passwords);
+                        continue;
                 }
 
-                if (arg_tries != 0 && tries >= arg_tries)
-                        return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Too many attempts to activate; giving up.");
+                log_debug("Prepared for next attempt to unlock.");
+        }
 
-        } else if (streq(verb, "detach")) {
-                const char *volume = ASSERT_PTR(argv[optind + 1]);
+        if (arg_tries != 0 && tries >= arg_tries)
+                return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Too many attempts to activate; giving up.");
 
-                if (argc - optind >= 3)
-                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "detach does not accept more than one argument.");
+        return 0;
+}
 
-                if (!filename_is_valid(volume))
-                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume);
+static int verb_detach(int argc, char *argv[], void *userdata) {
+        _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
+        const char *volume = ASSERT_PTR(argv[1]);
+        int r;
 
-                r = crypt_init_by_name(&cd, volume);
-                if (r == -ENODEV) {
-                        log_info("Volume %s already inactive.", volume);
-                        return 0;
-                }
-                if (r < 0)
-                        return log_error_errno(r, "crypt_init_by_name() for volume '%s' failed: %m", volume);
+        assert(argc == 2);
 
-                cryptsetup_enable_logging(cd);
+        if (!filename_is_valid(volume))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume);
 
-                r = crypt_deactivate(cd, volume);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to deactivate '%s': %m", volume);
+        r = crypt_init_by_name(&cd, volume);
+        if (r == -ENODEV) {
+                log_info("Volume %s already inactive.", volume);
+                return 0;
+        }
+        if (r < 0)
+                return log_error_errno(r, "crypt_init_by_name() for volume '%s' failed: %m", volume);
 
-        } else
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", verb);
+        cryptsetup_enable_logging(cd);
+
+        r = crypt_deactivate(cd, volume);
+        if (r < 0)
+                return log_error_errno(r, "Failed to deactivate '%s': %m", volume);
 
         return 0;
 }
 
+static int run(int argc, char *argv[]) {
+        int r;
+
+        log_setup();
+
+        umask(0022);
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r;
+
+        cryptsetup_enable_logging(NULL);
+
+        static const Verb verbs[] = {
+                { "attach", 3, 5, 0, verb_attach },
+                { "detach", 2, 2, 0, verb_detach },
+                {}
+        };
+
+        return dispatch_verb(argc, argv, verbs, NULL);
+}
+
 DEFINE_MAIN_FUNCTION(run);