mirror of
https://gitlab.com/cryptsetup/cryptsetup.git
synced 2025-12-11 19:00:02 +01:00
635 lines
14 KiB
C
635 lines
14 KiB
C
/*
|
|
* cryptsetup - setup cryptographic volumes for dm-crypt
|
|
*
|
|
* Copyright (C) 2004 Jana Saout <jana@saout.de>
|
|
* Copyright (C) 2004-2007 Clemens Fruhwirth <clemens@endorphin.org>
|
|
* Copyright (C) 2009-2021 Red Hat, Inc. All rights reserved.
|
|
* Copyright (C) 2009-2021 Milan Broz
|
|
*
|
|
* 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 2
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include "cryptsetup.h"
|
|
#include <math.h>
|
|
#include <signal.h>
|
|
|
|
int opt_verbose = 0;
|
|
int opt_debug = 0;
|
|
int opt_debug_json = 0;
|
|
int opt_batch_mode = 0;
|
|
int opt_progress_frequency = 0;
|
|
|
|
/* interrupt handling */
|
|
volatile int quit = 0;
|
|
static int signals_blocked = 0;
|
|
|
|
static void int_handler(int sig __attribute__((__unused__)))
|
|
{
|
|
quit++;
|
|
}
|
|
|
|
int tools_signals_blocked(void)
|
|
{
|
|
return signals_blocked;
|
|
}
|
|
|
|
void set_int_block(int block)
|
|
{
|
|
sigset_t signals_open;
|
|
|
|
log_dbg("%slocking interruption on signal.", block ? "B" : "Unb");
|
|
|
|
sigemptyset(&signals_open);
|
|
sigaddset(&signals_open, SIGINT);
|
|
sigaddset(&signals_open, SIGTERM);
|
|
sigprocmask(block ? SIG_SETMASK : SIG_UNBLOCK, &signals_open, NULL);
|
|
signals_blocked = block;
|
|
quit = 0;
|
|
}
|
|
|
|
void set_int_handler(int block)
|
|
{
|
|
struct sigaction sigaction_open;
|
|
|
|
log_dbg("Installing SIGINT/SIGTERM handler.");
|
|
memset(&sigaction_open, 0, sizeof(struct sigaction));
|
|
sigaction_open.sa_handler = int_handler;
|
|
sigaction(SIGINT, &sigaction_open, 0);
|
|
sigaction(SIGTERM, &sigaction_open, 0);
|
|
set_int_block(block);
|
|
}
|
|
|
|
void check_signal(int *r)
|
|
{
|
|
if (quit && !*r)
|
|
*r = -EINTR;
|
|
}
|
|
|
|
#define LOG_MAX_LEN 4096
|
|
|
|
__attribute__((format(printf, 5, 6)))
|
|
void clogger(struct crypt_device *cd, int level, const char *file, int line,
|
|
const char *format, ...)
|
|
{
|
|
va_list argp;
|
|
char target[LOG_MAX_LEN + 2];
|
|
|
|
va_start(argp, format);
|
|
|
|
if (vsnprintf(&target[0], LOG_MAX_LEN, format, argp) > 0) {
|
|
/* All verbose and error messages in tools end with EOL. */
|
|
if (level == CRYPT_LOG_VERBOSE || level == CRYPT_LOG_ERROR ||
|
|
level == CRYPT_LOG_DEBUG || level == CRYPT_LOG_DEBUG_JSON)
|
|
strncat(target, "\n", LOG_MAX_LEN);
|
|
|
|
crypt_log(cd, level, target);
|
|
}
|
|
|
|
va_end(argp);
|
|
}
|
|
|
|
void tool_log(int level, const char *msg, void *usrptr __attribute__((unused)))
|
|
{
|
|
switch(level) {
|
|
|
|
case CRYPT_LOG_NORMAL:
|
|
fprintf(stdout, "%s", msg);
|
|
break;
|
|
case CRYPT_LOG_VERBOSE:
|
|
if (opt_verbose)
|
|
fprintf(stdout, "%s", msg);
|
|
break;
|
|
case CRYPT_LOG_ERROR:
|
|
fprintf(stderr, "%s", msg);
|
|
break;
|
|
case CRYPT_LOG_DEBUG_JSON:
|
|
case CRYPT_LOG_DEBUG:
|
|
if (opt_debug)
|
|
fprintf(stdout, "# %s", msg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void quiet_log(int level, const char *msg, void *usrptr)
|
|
{
|
|
if (!opt_verbose && (level == CRYPT_LOG_ERROR || level == CRYPT_LOG_NORMAL))
|
|
level = CRYPT_LOG_VERBOSE;
|
|
tool_log(level, msg, usrptr);
|
|
}
|
|
|
|
static int _dialog(const char *msg, void *usrptr, int default_answer)
|
|
{
|
|
const char *fail_msg = (const char *)usrptr;
|
|
char *answer = NULL;
|
|
size_t size = 0;
|
|
int r = default_answer, block;
|
|
|
|
block = tools_signals_blocked();
|
|
if (block)
|
|
set_int_block(0);
|
|
|
|
if (isatty(STDIN_FILENO) && !opt_batch_mode) {
|
|
log_std("\nWARNING!\n========\n");
|
|
log_std("%s\n\nAre you sure? (Type 'yes' in capital letters): ", msg);
|
|
fflush(stdout);
|
|
if(getline(&answer, &size, stdin) == -1) {
|
|
r = 0;
|
|
/* Aborted by signal */
|
|
if (!quit)
|
|
log_err(_("Error reading response from terminal."));
|
|
else
|
|
log_dbg("Query interrupted on signal.");
|
|
} else {
|
|
r = !strcmp(answer, "YES\n");
|
|
if (!r && fail_msg)
|
|
log_err("%s", fail_msg);
|
|
}
|
|
}
|
|
|
|
if (block && !quit)
|
|
set_int_block(1);
|
|
|
|
free(answer);
|
|
return r;
|
|
}
|
|
|
|
int yesDialog(const char *msg, void *usrptr)
|
|
{
|
|
return _dialog(msg, usrptr, 1);
|
|
}
|
|
|
|
int noDialog(const char *msg, void *usrptr)
|
|
{
|
|
return _dialog(msg, usrptr, 0);
|
|
}
|
|
|
|
void show_status(int errcode)
|
|
{
|
|
char *crypt_error;
|
|
|
|
if(!opt_verbose)
|
|
return;
|
|
|
|
if(!errcode) {
|
|
log_std(_("Command successful.\n"));
|
|
return;
|
|
}
|
|
|
|
if (errcode < 0)
|
|
errcode = translate_errno(errcode);
|
|
|
|
if (errcode == 1)
|
|
crypt_error = _("wrong or missing parameters");
|
|
else if (errcode == 2)
|
|
crypt_error = _("no permission or bad passphrase");
|
|
else if (errcode == 3)
|
|
crypt_error = _("out of memory");
|
|
else if (errcode == 4)
|
|
crypt_error = _("wrong device or file specified");
|
|
else if (errcode == 5)
|
|
crypt_error = _("device already exists or device is busy");
|
|
else
|
|
crypt_error = _("unknown error");
|
|
|
|
log_std(_("Command failed with code %i (%s).\n"), -errcode, crypt_error);
|
|
}
|
|
|
|
const char *uuid_or_device(const char *spec)
|
|
{
|
|
static char device[PATH_MAX];
|
|
char s, *ptr;
|
|
int i = 0, uuid_len = 5;
|
|
|
|
/* Check if it is correct UUID=<LUKS_UUID> format */
|
|
if (spec && !strncmp(spec, "UUID=", uuid_len)) {
|
|
strcpy(device, "/dev/disk/by-uuid/");
|
|
ptr = &device[strlen(device)];
|
|
i = uuid_len;
|
|
while ((s = spec[i++]) && i < (PATH_MAX - 13)) {
|
|
if (!isxdigit(s) && s != '-')
|
|
return spec; /* Bail it out */
|
|
if (isalpha(s))
|
|
s = tolower(s);
|
|
*ptr++ = s;
|
|
}
|
|
*ptr = '\0';
|
|
return device;
|
|
}
|
|
|
|
return spec;
|
|
}
|
|
|
|
__attribute__ ((noreturn)) void usage(poptContext popt_context,
|
|
int exitcode, const char *error,
|
|
const char *more)
|
|
{
|
|
poptPrintUsage(popt_context, stderr, 0);
|
|
if (error)
|
|
log_err("%s: %s", more, error);
|
|
tools_cleanup();
|
|
poptFreeContext(popt_context);
|
|
exit(exitcode);
|
|
}
|
|
|
|
void dbg_version_and_cmd(int argc, const char **argv)
|
|
{
|
|
int i;
|
|
|
|
log_std("# %s %s processing \"", PACKAGE_NAME, PACKAGE_VERSION);
|
|
for (i = 0; i < argc; i++) {
|
|
if (i)
|
|
log_std(" ");
|
|
log_std("%s", argv[i]);
|
|
}
|
|
log_std("\"\n");
|
|
}
|
|
|
|
/* Translate exit code to simple codes */
|
|
int translate_errno(int r)
|
|
{
|
|
switch (r) {
|
|
case 0: r = EXIT_SUCCESS; break;
|
|
case -EEXIST:
|
|
case -EBUSY: r = 5; break;
|
|
case -ENOTBLK:
|
|
case -ENODEV: r = 4; break;
|
|
case -ENOMEM: r = 3; break;
|
|
case -EPERM: r = 2; break;
|
|
case -EINVAL:
|
|
case -ENOENT:
|
|
case -ENOSYS:
|
|
default: r = EXIT_FAILURE;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void tools_keyslot_msg(int keyslot, crypt_object_op op)
|
|
{
|
|
if (keyslot < 0)
|
|
return;
|
|
|
|
if (op == CREATED)
|
|
log_verbose(_("Key slot %i created."), keyslot);
|
|
else if (op == UNLOCKED)
|
|
log_verbose(_("Key slot %i unlocked."), keyslot);
|
|
else if (op == REMOVED)
|
|
log_verbose(_("Key slot %i removed."), keyslot);
|
|
}
|
|
|
|
void tools_token_msg(int token, crypt_object_op op)
|
|
{
|
|
if (token < 0)
|
|
return;
|
|
|
|
if (op == CREATED)
|
|
log_verbose(_("Token %i created."), token);
|
|
else if (op == REMOVED)
|
|
log_verbose(_("Token %i removed."), token);
|
|
}
|
|
|
|
/*
|
|
* Device size string parsing, suffixes:
|
|
* s|S - 512 bytes sectors
|
|
* k |K |m |M |g |G |t |T - 1024 base
|
|
* kiB|KiB|miB|MiB|giB|GiB|tiB|TiB - 1024 base
|
|
* kb |KB |mM |MB |gB |GB |tB |TB - 1000 base
|
|
*/
|
|
int tools_string_to_size(struct crypt_device *cd, const char *s, uint64_t *size)
|
|
{
|
|
char *endp = NULL;
|
|
size_t len;
|
|
uint64_t mult_base, mult, tmp;
|
|
|
|
*size = strtoull(s, &endp, 10);
|
|
if (!isdigit(s[0]) ||
|
|
(errno == ERANGE && *size == ULLONG_MAX) ||
|
|
(errno != 0 && *size == 0))
|
|
return -EINVAL;
|
|
|
|
if (!endp || !*endp)
|
|
return 0;
|
|
|
|
len = strlen(endp);
|
|
/* Allow "B" and "iB" suffixes */
|
|
if (len > 3 ||
|
|
(len == 3 && (endp[1] != 'i' || endp[2] != 'B')) ||
|
|
(len == 2 && endp[1] != 'B'))
|
|
return -EINVAL;
|
|
|
|
if (len == 1 || len == 3)
|
|
mult_base = 1024;
|
|
else
|
|
mult_base = 1000;
|
|
|
|
mult = 1;
|
|
switch (endp[0]) {
|
|
case 's':
|
|
case 'S': mult = 512;
|
|
break;
|
|
case 't':
|
|
case 'T': mult *= mult_base;
|
|
/* Fall through */
|
|
case 'g':
|
|
case 'G': mult *= mult_base;
|
|
/* Fall through */
|
|
case 'm':
|
|
case 'M': mult *= mult_base;
|
|
/* Fall through */
|
|
case 'k':
|
|
case 'K': mult *= mult_base;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
tmp = *size * mult;
|
|
if (*size && (tmp / *size) != mult) {
|
|
log_dbg("Device size overflow.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
*size = tmp;
|
|
return 0;
|
|
}
|
|
|
|
/* Time progress helper */
|
|
|
|
/* The difference in seconds between two times in "timeval" format. */
|
|
static double time_diff(struct timeval *start, struct timeval *end)
|
|
{
|
|
return (end->tv_sec - start->tv_sec)
|
|
+ (end->tv_usec - start->tv_usec) / 1E6;
|
|
}
|
|
|
|
void tools_clear_line(void)
|
|
{
|
|
if (opt_progress_frequency)
|
|
return;
|
|
/* vt100 code clear line */
|
|
log_std("\33[2K\r");
|
|
}
|
|
|
|
static void tools_time_progress(uint64_t device_size, uint64_t bytes, uint64_t *start_bytes,
|
|
struct timeval *start_time, struct timeval *end_time)
|
|
{
|
|
struct timeval now_time;
|
|
unsigned long long mbytes, eta;
|
|
double tdiff, uib, frequency;
|
|
int final = (bytes == device_size);
|
|
const char *eol, *ustr = "";
|
|
|
|
if (opt_batch_mode)
|
|
return;
|
|
|
|
gettimeofday(&now_time, NULL);
|
|
if (start_time->tv_sec == 0 && start_time->tv_usec == 0) {
|
|
*start_time = now_time;
|
|
*end_time = now_time;
|
|
*start_bytes = bytes;
|
|
return;
|
|
}
|
|
|
|
if (opt_progress_frequency) {
|
|
frequency = (double)opt_progress_frequency;
|
|
eol = "\n";
|
|
} else {
|
|
frequency = 0.5;
|
|
eol = "";
|
|
}
|
|
|
|
if (!final && time_diff(end_time, &now_time) < frequency)
|
|
return;
|
|
|
|
*end_time = now_time;
|
|
|
|
tdiff = time_diff(start_time, end_time);
|
|
if (!tdiff)
|
|
return;
|
|
|
|
mbytes = bytes / 1024 / 1024;
|
|
uib = (double)(bytes - *start_bytes) / tdiff;
|
|
|
|
/* FIXME: calculate this from last minute only. */
|
|
eta = (unsigned long long)(device_size / uib - tdiff);
|
|
|
|
if (uib > 1073741824.0f) {
|
|
uib /= 1073741824.0f;
|
|
ustr = "Gi";
|
|
} else if (uib > 1048576.0f) {
|
|
uib /= 1048576.0f;
|
|
ustr = "Mi";
|
|
} else if (uib > 1024.0f) {
|
|
uib /= 1024.0f;
|
|
ustr = "Ki";
|
|
}
|
|
|
|
tools_clear_line();
|
|
if (final)
|
|
log_std("Finished, time %02llu:%02llu.%03llu, "
|
|
"%4llu MiB written, speed %5.1f %sB/s\n",
|
|
(unsigned long long)tdiff / 60,
|
|
(unsigned long long)tdiff % 60,
|
|
(unsigned long long)((tdiff - floor(tdiff)) * 1000.0),
|
|
mbytes, uib, ustr);
|
|
else
|
|
log_std("Progress: %5.1f%%, ETA %02llu:%02llu, "
|
|
"%4llu MiB written, speed %5.1f %sB/s%s",
|
|
(double)bytes / device_size * 100,
|
|
eta / 60, eta % 60, mbytes, uib, ustr, eol);
|
|
fflush(stdout);
|
|
}
|
|
|
|
int tools_wipe_progress(uint64_t size, uint64_t offset, void *usrptr)
|
|
{
|
|
static struct timeval start_time = {}, end_time = {};
|
|
static uint64_t start_offset = 0;
|
|
int r = 0;
|
|
|
|
tools_time_progress(size, offset, &start_offset, &start_time, &end_time);
|
|
|
|
check_signal(&r);
|
|
if (r) {
|
|
tools_clear_line();
|
|
log_err(_("\nWipe interrupted."));
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static void report_partition(const char *value, const char *device)
|
|
{
|
|
if (opt_batch_mode)
|
|
log_dbg("Device %s already contains a '%s' partition signature.", device, value);
|
|
else
|
|
log_std(_("WARNING: Device %s already contains a '%s' partition signature.\n"), device, value);
|
|
}
|
|
|
|
static void report_superblock(const char *value, const char *device)
|
|
{
|
|
if (opt_batch_mode)
|
|
log_dbg("Device %s already contains a '%s' superblock signature.", device, value);
|
|
else
|
|
log_std(_("WARNING: Device %s already contains a '%s' superblock signature.\n"), device, value);
|
|
}
|
|
|
|
int tools_detect_signatures(const char *device, int ignore_luks, size_t *count)
|
|
{
|
|
int r;
|
|
size_t tmp_count;
|
|
struct blkid_handle *h;
|
|
blk_probe_status pr;
|
|
|
|
if (!count)
|
|
count = &tmp_count;
|
|
|
|
*count = 0;
|
|
|
|
if (!blk_supported()) {
|
|
log_dbg("Blkid support disabled.");
|
|
return 0;
|
|
}
|
|
|
|
if ((r = blk_init_by_path(&h, device))) {
|
|
log_err(_("Failed to initialize device signature probes."));
|
|
return -EINVAL;
|
|
}
|
|
|
|
blk_set_chains_for_full_print(h);
|
|
|
|
if (ignore_luks && blk_superblocks_filter_luks(h)) {
|
|
r = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
while ((pr = blk_probe(h)) < PRB_EMPTY) {
|
|
if (blk_is_partition(h))
|
|
report_partition(blk_get_partition_type(h), device);
|
|
else if (blk_is_superblock(h))
|
|
report_superblock(blk_get_superblock_type(h), device);
|
|
else {
|
|
log_dbg("Internal tools_detect_signatures() error.");
|
|
r = -EINVAL;
|
|
goto out;
|
|
}
|
|
(*count)++;
|
|
}
|
|
|
|
if (pr == PRB_FAIL)
|
|
r = -EINVAL;
|
|
out:
|
|
blk_free(h);
|
|
return r;
|
|
}
|
|
|
|
int tools_wipe_all_signatures(const char *path)
|
|
{
|
|
int fd, flags, r;
|
|
blk_probe_status pr;
|
|
struct stat st;
|
|
struct blkid_handle *h = NULL;
|
|
|
|
if (!blk_supported()) {
|
|
log_dbg("Blkid support disabled.");
|
|
return 0;
|
|
}
|
|
|
|
if (stat(path, &st)) {
|
|
log_err(_("Failed to stat device %s."), path);
|
|
return -EINVAL;
|
|
}
|
|
|
|
flags = O_RDWR;
|
|
if (S_ISBLK(st.st_mode))
|
|
flags |= O_EXCL;
|
|
|
|
/* better than opening regular file with O_EXCL (undefined) */
|
|
/* coverity[toctou] */
|
|
fd = open(path, flags);
|
|
if (fd < 0) {
|
|
if (errno == EBUSY)
|
|
log_err(_("Device %s is in use. Can not proceed with format operation."), path);
|
|
else
|
|
log_err(_("Failed to open file %s in read/write mode."), path);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((r = blk_init_by_fd(&h, fd))) {
|
|
log_err(_("Failed to initialize device signature probes."));
|
|
r = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
blk_set_chains_for_wipes(h);
|
|
|
|
while ((pr = blk_probe(h)) < PRB_EMPTY) {
|
|
if (blk_is_partition(h))
|
|
log_verbose(_("Existing '%s' partition signature (offset: %" PRIi64 " bytes) on device %s will be wiped."),
|
|
blk_get_partition_type(h), blk_get_offset(h), path);
|
|
if (blk_is_superblock(h))
|
|
log_verbose(_("Existing '%s' superblock signature (offset: %" PRIi64 " bytes) on device %s will be wiped."),
|
|
blk_get_superblock_type(h), blk_get_offset(h), path);
|
|
if (blk_do_wipe(h)) {
|
|
log_err(_("Failed to wipe device signature."));
|
|
r = -EINVAL;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (pr != PRB_EMPTY) {
|
|
log_err(_("Failed to probe device %s for a signature."), path);
|
|
r = -EINVAL;
|
|
}
|
|
out:
|
|
close(fd);
|
|
blk_free(h);
|
|
return r;
|
|
}
|
|
|
|
int tools_is_cipher_null(const char *cipher)
|
|
{
|
|
if (!cipher)
|
|
return 0;
|
|
|
|
return !strcmp(cipher, "cipher_null") ? 1 : 0;
|
|
}
|
|
|
|
/*
|
|
* Keyfile - is standard input treated as a binary file (no EOL handling).
|
|
*/
|
|
int tools_is_stdin(const char *key_file)
|
|
{
|
|
if (!key_file)
|
|
return 1;
|
|
|
|
return strcmp(key_file, "-") ? 0 : 1;
|
|
}
|
|
|
|
int tools_reencrypt_progress(uint64_t size, uint64_t offset, void *usrptr)
|
|
{
|
|
static struct timeval start_time = {}, end_time = {};
|
|
static uint64_t start_offset = 0;
|
|
int r = 0;
|
|
|
|
tools_time_progress(size, offset, &start_offset, &start_time, &end_time);
|
|
|
|
check_signal(&r);
|
|
if (r) {
|
|
tools_clear_line();
|
|
log_err(_("\nReencryption interrupted."));
|
|
}
|
|
|
|
return r;
|
|
}
|