Files
cryptsetup/src/utils_tools.c
2018-07-19 14:44:54 +02:00

677 lines
15 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-2018, Red Hat, Inc. All rights reserved.
* Copyright (C) 2009-2018, 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_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) {
if (level >= 0) {
/* All verbose and error messages in tools end with EOL. */
if (level == CRYPT_LOG_VERBOSE || level == CRYPT_LOG_ERROR)
strncat(target, "\n", LOG_MAX_LEN);
crypt_log(cd, level, target);
#ifdef CRYPT_DEBUG
} else if (opt_debug)
printf("# %s:%d %s\n", file ?: "?", line, target);
#else
} else if (opt_debug)
printf("# %s\n", target);
#endif
}
va_end(argp);
}
void tool_log(int level, const char *msg, void *usrptr __attribute__((unused)))
{
switch(level) {
case CRYPT_LOG_NORMAL:
fputs(msg, stdout);
break;
case CRYPT_LOG_VERBOSE:
if (opt_verbose)
fputs(msg, stdout);
break;
case CRYPT_LOG_ERROR:
fputs(msg, stderr);
break;
case CRYPT_LOG_DEBUG:
if (opt_debug)
printf("# %s\n", msg);
break;
default:
fprintf(stderr, "Internal error on logging class for msg: %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);
}
int yesDialog(const char *msg, void *usrptr)
{
const char *fail_msg = (const char *)usrptr;
char *answer = NULL;
size_t size = 0;
int r = 1, 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 uppercase yes): ", 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 if (strcmp(answer, "YES\n")) {
r = 0;
if (fail_msg)
log_err("%s", fail_msg);
}
}
if (block && !quit)
set_int_block(1);
free(answer);
return r;
}
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);
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;
}
/*
* 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");
}
void tools_time_progress(uint64_t device_size, uint64_t bytes,
struct timeval *start_time, struct timeval *end_time)
{
struct timeval now_time;
unsigned long long mbytes, eta;
double tdiff, mib, frequency;
int final = (bytes == device_size);
const char *eol;
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;
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;
mib = (double)(mbytes) / tdiff;
if (!mib)
return;
/* FIXME: calculate this from last minute only and remaining space */
eta = (unsigned long long)(device_size / 1024 / 1024 / mib - tdiff);
tools_clear_line();
if (final)
log_std("Finished, time %02llu:%02llu.%03llu, "
"%4llu MiB written, speed %5.1f MiB/s\n",
(unsigned long long)tdiff / 60,
(unsigned long long)tdiff % 60,
(unsigned long long)((tdiff - floor(tdiff)) * 1000.0),
mbytes, mib);
else
log_std("Progress: %5.1f%%, ETA %02llu:%02llu, "
"%4llu MiB written, speed %5.1f MiB/s%s",
(double)bytes / device_size * 100,
eta / 60, eta % 60, mbytes, mib, eol);
fflush(stdout);
}
int tools_wipe_progress(uint64_t size, uint64_t offset, void *usrptr)
{
static struct timeval start_time = {}, end_time = {};
int r = 0;
tools_time_progress(size, 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 on device %s will be wiped.",
blk_get_partition_type(h), path);
if (blk_is_superblock(h))
log_verbose("Existing '%s' superblock signature on device %s will be wiped.",
blk_get_superblock_type(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;
}
/*
* FIXME: 4MiBs is max LUKS2 mda length (including binary header).
* In future, read max allowed JSON size from config section.
*/
#define LUKS2_MAX_MDA_SIZE 0x400000
int tools_read_json_file(struct crypt_device *cd, const char *file, char **json, size_t *json_size)
{
ssize_t ret;
int fd, block, r;
void *buf = NULL;
block = tools_signals_blocked();
if (block)
set_int_block(0);
if (tools_is_stdin(file)) {
fd = STDIN_FILENO;
log_dbg("STDIN descriptor JSON read requested.");
} else {
log_dbg("File descriptor JSON read requested.");
fd = open(file, O_RDONLY);
if (fd < 0) {
log_err(_("Failed to open file %s in read-only mode."), file);
r = -EINVAL;
goto out;
}
}
buf = malloc(LUKS2_MAX_MDA_SIZE);
if (!buf) {
r = -ENOMEM;
goto out;
}
if (isatty(fd) && !opt_batch_mode)
log_std(_("Provide valid LUKS2 token JSON:\n"));
/* we expect JSON (string) */
r = 0;
ret = read_buffer_intr(fd, buf, LUKS2_MAX_MDA_SIZE - 1, &quit);
if (ret < 0) {
r = -EIO;
log_err(_("Failed to read JSON file."));
goto out;
}
check_signal(&r);
if (r) {
log_err(_("\nRead interrupted."));
goto out;
}
*json_size = (size_t)ret;
*json = buf;
*(*json + ret) = '\0';
out:
if (block && !quit)
set_int_block(1);
if (fd != STDIN_FILENO)
close(fd);
if (r && buf) {
memset(buf, 0, LUKS2_MAX_MDA_SIZE);
free(buf);
}
return r;
}
int tools_write_json_file(struct crypt_device *cd, const char *file, const char *json)
{
int block, fd, r;
size_t json_len;
ssize_t ret;
if (!json || !(json_len = strlen(json)) || json_len >= LUKS2_MAX_MDA_SIZE)
return -EINVAL;
block = tools_signals_blocked();
if (block)
set_int_block(0);
if (tools_is_stdin(file)) {
fd = STDOUT_FILENO;
log_dbg("STDOUT descriptor JSON write requested.");
} else {
log_dbg("File descriptor JSON write requested.");
fd = open(file, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
}
if (fd < 0) {
log_err(_("Failed to open file %s in write mode."), file ?: "");
r = -EINVAL;
goto out;
}
r = 0;
ret = write_buffer_intr(fd, json, json_len, &quit);
check_signal(&r);
if (r) {
log_err(_("\nWrite interrupted."));
goto out;
}
if (ret < 0 || (size_t)ret != json_len) {
log_err(_("Failed to write JSON file."));
r = -EIO;
goto out;
}
if (isatty(fd))
(void) write_buffer_intr(fd, "\n", 1, &quit);
out:
if (block && !quit)
set_int_block(1);
if (fd != STDOUT_FILENO)
close(fd);
return r;
}