update tcplay to 2.0
authorAlex Hornung <alex@alexhornung.com>
Sun, 16 Mar 2014 07:37:01 +0000 (07:37 +0000)
committerAlex Hornung <alex@alexhornung.com>
Sun, 16 Mar 2014 14:12:55 +0000 (14:12 +0000)
16 files changed:
lib/libtcplay/Makefile
lib/libtcplay/crypto.c
lib/libtcplay/hdr.c
lib/libtcplay/io.c
lib/libtcplay/safe_mem.c
lib/libtcplay/tcplay.3
lib/libtcplay/tcplay.c
lib/libtcplay/tcplay.h
lib/libtcplay/tcplay.map
lib/libtcplay/tcplay_api.c
lib/libtcplay/tcplay_api.h
lib/libtcplay/tcplay_api_internal.h [copied from lib/libtcplay/tcplay_api.h with 61% similarity]
lib/libtcplay/tcplay_api_test.c [deleted file]
sbin/tcplay/Makefile
sbin/tcplay/main.c
sbin/tcplay/tcplay.8

index 67240b3..1a6bf64 100644 (file)
@@ -1,22 +1,31 @@
 LIB=           tcplay
-SHLIB_MAJOR?=  0
+MAJ_VER=       2
+MIN_VER=       0
+SHLIB_MAJOR?=  ${MAJ_VER}
+SHLIB_MINOR?=  ${MIN_VER}
 WARNS?=                6
 MAN=           tcplay.3
 
 MLINKS+=       tcplay.3        tc_api_init.3
 MLINKS+=       tcplay.3        tc_api_uninit.3
-MLINKS+=       tcplay.3        tc_api_create_volume.3
-MLINKS+=       tcplay.3        tc_api_map_volume.3
-MLINKS+=       tcplay.3        tc_api_unmap_volume.3
-MLINKS+=       tcplay.3        tc_api_check_cipher.3
-MLINKS+=       tcplay.3        tc_api_check_prf_hash.3
-MLINKS+=       tcplay.3        tc_api_get_error_msg.3
-MLINKS+=       tcplay.3        tc_api_get_summary.3
+MLINKS+=       tcplay.3        tc_api_has.3
+MLINKS+=       tcplay.3        tc_api_cipher_iterate.3
+MLINKS+=       tcplay.3        tc_api_prf_iterate.3
+MLINKS+=       tcplay.3        tc_api_task_init.3
+MLINKS+=       tcplay.3        tc_api_task_uninit.3
+MLINKS+=       tcplay.3        tc_api_task_set.3
+MLINKS+=       tcplay.3        tc_api_task_do.3
+MLINKS+=       tcplay.3        tc_api_task_info_get.3
+MLINKS+=       tcplay.3        tc_api_task_get_error.3
 
+CFLAGS+=       -DMAJ_VER=${MAJ_VER} -DMIN_VER=${MIN_VER}
+CFLAGS+=       -D_FILE_OFFSET_BITS=64
+
+SRCS+=         tcplay.c crc32.c safe_mem.c io.c hdr.c humanize.c
+SRCS+=         crypto.c generic_xts.c
+SRCS+=         crypto-dev.c pbkdf2-openssl.c
+SRCS+=         tcplay_api.c
 
-SRCS+=         tcplay_api.c tcplay.c crc32.c safe_mem.c \
-               crypto.c generic_xts.c humanize.c pbkdf2-openssl.c \
-               io.c crypto-dev.c hdr.c
 INCS+=         tcplay_api.h
 
 LDFLAGS+=      -Wl,-version-script=${.CURDIR}/tcplay.map
index 1f6915b..8d47933 100644 (file)
@@ -50,12 +50,7 @@ tc_cipher_chain_populate_keys(struct tc_cipher_chain *cipher_chain,
         * We need to determine the total key bytes as the key locations
         * depend on it.
         */
-       total_key_bytes = 0;
-       for (dummy_chain = cipher_chain;
-           dummy_chain != NULL;
-           dummy_chain = dummy_chain->next) {
-               total_key_bytes += dummy_chain->cipher->klen;
-       }
+       total_key_bytes = tc_cipher_chain_klen(cipher_chain);
 
        /*
         * Now we need to get prepare the keys, as the keys are in
@@ -214,7 +209,7 @@ apply_keyfiles(unsigned char *pass, size_t pass_memsz, const char *keyfiles[],
        uint32_t crc;
 
        if (pass_memsz < MAX_PASSSZ) {
-               tc_log(1, "Not enough memory for password manipulation\n");
+               tc_log(1, "Not enough memory for password manipluation\n");
                return ENOMEM;
        }
 
index d420dda..ee32c04 100644 (file)
@@ -100,7 +100,7 @@ verify_hdr(struct tchdr_dec *hdr)
                return 0;
        }
 
-       crc = crc32(&hdr->keys, 256);
+       crc = crc32((void *)&hdr->keys, 256);
        if (crc != hdr->crc_keys) {
 #ifdef DEBUG
                fprintf(stderr, "CRC32 mismatch (crc_keys)\n");
@@ -127,8 +127,8 @@ verify_hdr(struct tchdr_dec *hdr)
 struct tchdr_enc *
 create_hdr(unsigned char *pass, int passlen, struct pbkdf_prf_algo *prf_algo,
     struct tc_cipher_chain *cipher_chain, size_t sec_sz,
-    size_t total_blocks __unused,
-    off_t offset, size_t blocks, int hidden, struct tchdr_enc **backup_hdr)
+    disksz_t total_blocks __unused,
+    off_t offset, disksz_t blocks, int hidden, int weak, struct tchdr_enc **backup_hdr)
 {
        struct tchdr_enc *ehdr, *ehdr_backup;
        struct tchdr_dec *dhdr;
@@ -169,12 +169,12 @@ create_hdr(unsigned char *pass, int passlen, struct pbkdf_prf_algo *prf_algo,
                goto error;
        }
 
-       if ((error = get_random(ehdr->salt, sizeof(ehdr->salt))) != 0) {
+       if ((error = get_random(ehdr->salt, sizeof(ehdr->salt), weak)) != 0) {
                tc_log(1, "could not get salt\n");
                goto error;
        }
 
-       if ((error = get_random(ehdr_backup->salt, sizeof(ehdr_backup->salt)))
+       if ((error = get_random(ehdr_backup->salt, sizeof(ehdr_backup->salt), weak))
            != 0) {
                tc_log(1, "could not get salt for backup header\n");
                goto error;
@@ -198,7 +198,7 @@ create_hdr(unsigned char *pass, int passlen, struct pbkdf_prf_algo *prf_algo,
 
        memset(dhdr, 0, sizeof(*dhdr));
 
-       if ((error = get_random(dhdr->keys, sizeof(dhdr->keys))) != 0) {
+       if ((error = get_random(dhdr->keys, sizeof(dhdr->keys), weak)) != 0) {
                tc_log(1, "could not get key random bits\n");
                goto error;
        }
@@ -271,3 +271,121 @@ error:
 
        return NULL;
 }
+
+struct tchdr_enc *copy_reencrypt_hdr(unsigned char *pass, int passlen,
+    struct pbkdf_prf_algo *prf_algo, int weak, struct tcplay_info *info,
+    struct tchdr_enc **backup_hdr)
+{
+       struct tchdr_enc *ehdr, *ehdr_backup;
+       unsigned char *key, *key_backup;
+       unsigned char iv[128];
+       int error;
+
+       key = key_backup = NULL;
+       ehdr = ehdr_backup = NULL;
+
+       /* By default stick to current PRF algo */
+       if (prf_algo == NULL)
+               prf_algo = info->pbkdf_prf;
+
+       if ((ehdr = (struct tchdr_enc *)alloc_safe_mem(sizeof(*ehdr))) == NULL) {
+               tc_log(1, "could not allocate safe ehdr memory\n");
+               goto error;
+       }
+
+       if ((ehdr_backup = (struct tchdr_enc *)alloc_safe_mem
+           (sizeof(*ehdr_backup))) == NULL) {
+               tc_log(1, "could not allocate safe ehdr_backup memory\n");
+               goto error;
+       }
+
+       if ((key = alloc_safe_mem(MAX_KEYSZ)) == NULL) {
+               tc_log(1, "could not allocate safe key memory\n");
+               goto error;
+       }
+
+       if ((key_backup = alloc_safe_mem(MAX_KEYSZ)) == NULL) {
+               tc_log(1, "could not allocate safe backup key memory\n");
+               goto error;
+       }
+
+       if ((error = get_random(ehdr->salt, sizeof(ehdr->salt), weak)) != 0) {
+               tc_log(1, "could not get salt\n");
+               goto error;
+       }
+
+       if ((error = get_random(ehdr_backup->salt, sizeof(ehdr_backup->salt), weak))
+           != 0) {
+               tc_log(1, "could not get salt for backup header\n");
+               goto error;
+       }
+
+       error = pbkdf2(prf_algo, (char *)pass, passlen,
+           ehdr->salt, sizeof(ehdr->salt),
+           MAX_KEYSZ, key);
+       if (error) {
+               tc_log(1, "could not derive key\n");
+               goto error;
+       }
+
+       error = pbkdf2(prf_algo, (char *)pass, passlen,
+           ehdr_backup->salt, sizeof(ehdr_backup->salt),
+           MAX_KEYSZ, key_backup);
+       if (error) {
+               tc_log(1, "could not derive backup key\n");
+               goto error;
+       }
+
+       HOST_TO_BE(16, info->hdr->tc_ver);
+       HOST_TO_LE(16, info->hdr->tc_min_ver);
+       HOST_TO_BE(32, info->hdr->crc_keys);
+       HOST_TO_BE(64, info->hdr->vol_ctime);
+       HOST_TO_BE(64, info->hdr->hdr_ctime);
+       HOST_TO_BE(64, info->hdr->sz_vol);
+       HOST_TO_BE(64, info->hdr->sz_hidvol);
+       HOST_TO_BE(64, info->hdr->off_mk_scope);
+       HOST_TO_BE(64, info->hdr->sz_mk_scope);
+       HOST_TO_BE(32, info->hdr->sec_sz);
+       HOST_TO_BE(32, info->hdr->flags);
+       HOST_TO_BE(32, info->hdr->crc_dhdr);
+
+       memset(iv, 0, sizeof(iv));
+       error = tc_encrypt(info->cipher_chain, key, iv,
+           (unsigned char *)info->hdr, sizeof(struct tchdr_dec), ehdr->enc);
+       if (error) {
+               tc_log(1, "Header encryption failed\n");
+               goto error;
+       }
+
+       memset(iv, 0, sizeof(iv));
+       error = tc_encrypt(info->cipher_chain, key_backup, iv,
+           (unsigned char *)info->hdr,
+           sizeof(struct tchdr_dec), ehdr_backup->enc);
+       if (error) {
+               tc_log(1, "Backup header encryption failed\n");
+               goto error;
+       }
+
+       free_safe_mem(key);
+       free_safe_mem(key_backup);
+
+       if (backup_hdr != NULL)
+               *backup_hdr = ehdr_backup;
+       else
+               free_safe_mem(ehdr_backup);
+
+       return ehdr;
+       /* NOT REACHED */
+
+error:
+       if (key)
+               free_safe_mem(key);
+       if (key_backup)
+               free_safe_mem(key_backup);
+       if (ehdr)
+               free_safe_mem(ehdr);
+       if (ehdr_backup)
+               free_safe_mem(ehdr_backup);
+
+       return NULL;
+}
index a19553f..a862422 100644 (file)
@@ -34,6 +34,7 @@
 #include <linux/fs.h>
 #include <sys/ioctl.h>
 #endif
+#include <sys/stat.h>
 #include <sys/uio.h>
 #include <sys/select.h>
 #include <errno.h>
@@ -43,6 +44,7 @@
 #include <string.h>
 #include <termios.h>
 #include <unistd.h>
+#include <signal.h>
 
 #include "tcplay.h"
 
@@ -63,7 +65,7 @@ read_to_safe_mem(const char *file, off_t offset, size_t *sz)
                goto out;
        }
 
-       if ((lseek(fd, offset, SEEK_SET) < 0)) {
+       if ((lseek(fd, offset, (offset >= 0) ? SEEK_SET : SEEK_END) < 0)) {
                tc_log(1, "Error seeking on file %s\n", file);
                goto m_err;
        }
@@ -88,19 +90,26 @@ m_err:
 static size_t get_random_total_bytes = 0;
 static size_t get_random_read_bytes = 0;
 
+
+float
+get_random_read_progress(void)
+{
+       return (get_random_total_bytes == 0) ? 0.0 :
+           (1.0 * get_random_read_bytes) /
+           (1.0 * get_random_total_bytes) * 100.0;
+}
+
 static
 void
 get_random_summary(void)
 {
-       float pct_done;
+       float pct_done = get_random_read_progress();
 
-       pct_done = (1.0 * get_random_read_bytes) /
-           (1.0 * get_random_total_bytes) * 100.0;
        tc_log(0, "Gathering true randomness, %.0f%% done.\n", pct_done);
 }
 
 int
-get_random(unsigned char *buf, size_t len)
+get_random(unsigned char *buf, size_t len, int weak)
 {
        int fd;
        ssize_t r;
@@ -109,13 +118,14 @@ get_random(unsigned char *buf, size_t len)
        struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 }; /* 10 ms */
 
 
-       if ((fd = open("/dev/random", O_RDONLY)) < 0) {
+       if ((fd = open((weak) ? "/dev/urandom" : "/dev/random", O_RDONLY)) < 0) {
                tc_log(1, "Error opening /dev/random\n");
                return -1;
        }
 
        summary_fn = get_random_summary;
        get_random_total_bytes = len;
+       tc_internal_state = STATE_GET_RANDOM;
 
        /* Get random data in 16-byte chunks */
        sz = 16;
@@ -130,6 +140,7 @@ get_random(unsigned char *buf, size_t len)
                            fd, strerror(errno));
                        close(fd);
                        summary_fn = NULL;
+                       tc_internal_state = STATE_UNKNOWN;
                        return -1;
                }
                rd += r;
@@ -139,25 +150,31 @@ get_random(unsigned char *buf, size_t len)
        close(fd);
        summary_fn = NULL;
 
+       tc_internal_state = STATE_UNKNOWN;
        return 0;
 }
 
-static size_t secure_erase_total_bytes = 0;
-static size_t secure_erase_erased_bytes = 0;
+static disksz_t secure_erase_total_bytes = 0;
+static disksz_t secure_erase_erased_bytes = 0;
+
+float
+get_secure_erase_progress(void)
+{
+       return (secure_erase_total_bytes == 0) ? 0.0 :
+           (1.0 * secure_erase_erased_bytes) /
+           (1.0 * secure_erase_total_bytes) * 100.0;
+}
 
 static
 void
 secure_erase_summary(void)
 {
-       float pct_done;
-
-       pct_done = (1.0 * secure_erase_erased_bytes) /
-           (1.0 * secure_erase_total_bytes) * 100.0;
+       float pct_done = get_secure_erase_progress();
        tc_log(0, "Securely erasing, %.0f%% done.\n", pct_done);
 }
 
 int
-secure_erase(const char *dev, size_t bytes, size_t blksz)
+secure_erase(const char *dev, disksz_t bytes, size_t blksz)
 {
        size_t erased = 0;
        int fd_rand, fd;
@@ -184,6 +201,8 @@ secure_erase(const char *dev, size_t bytes, size_t blksz)
        summary_fn = secure_erase_summary;
        secure_erase_total_bytes = bytes;
 
+       tc_internal_state = STATE_ERASE;
+
        sz = ERASE_BUFFER_SIZE;
        while (erased < bytes) {
                secure_erase_erased_bytes = erased;
@@ -196,6 +215,7 @@ secure_erase(const char *dev, size_t bytes, size_t blksz)
                        close(fd);
                        close(fd_rand);
                        summary_fn = NULL;
+                       tc_internal_state = STATE_UNKNOWN;
                        return -1;
                }
 
@@ -207,6 +227,7 @@ secure_erase(const char *dev, size_t bytes, size_t blksz)
                        close(fd);
                        close(fd_rand);
                        summary_fn = NULL;
+                       tc_internal_state = STATE_UNKNOWN;
                        return -1;
                }
 
@@ -218,6 +239,7 @@ secure_erase(const char *dev, size_t bytes, size_t blksz)
 
        summary_fn = NULL;
 
+       tc_internal_state = STATE_UNKNOWN;
        return 0;
 }
 
@@ -248,7 +270,7 @@ get_disk_info(const char *dev, size_t *blocks, size_t *bsize)
 }
 #elif defined(__linux__)
 int
-get_disk_info(const char *dev, size_t *blocks, size_t *bsize)
+get_disk_info(const char *dev, disksz_t *blocks, size_t *bsize)
 {
        uint64_t nbytes;
        int blocksz;
@@ -269,7 +291,7 @@ get_disk_info(const char *dev, size_t *blocks, size_t *bsize)
                return -1;
        }
 
-       *blocks = (size_t)(nbytes / blocksz);
+       *blocks = (disksz_t)(nbytes / blocksz);
        *bsize = (size_t)(blocksz);
 
        close(fd);
@@ -317,7 +339,7 @@ write_to_disk(const char *dev, off_t offset, size_t blksz, void *mem,
                return -1;
        }
 
-       if ((lseek(fd, offset, SEEK_SET) < 0)) {
+       if ((lseek(fd, offset, (offset >= 0) ? SEEK_SET : SEEK_END) < 0)) {
                tc_log(1, "Error seeking on device %s\n", dev);
                close(fd);
                return -1;
@@ -336,29 +358,73 @@ write_to_disk(const char *dev, off_t offset, size_t blksz, void *mem,
        return 0;
 }
 
+
+int
+write_to_file(const char *file, void *mem, size_t bytes)
+{
+       int fd;
+       ssize_t w;
+
+       if ((fd = open(file, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)) < 0) {
+               tc_log(1, "Error opening file %s\n", file);
+               return -1;
+       }
+
+       if ((w = write(fd, mem, bytes)) < 0) {
+               tc_log(1, "Error writing to file %s\n", file);
+               close(fd);
+               return -1;
+       }
+
+       close(fd);
+       return 0;
+}
+
+
+static struct termios termios_old;
+static int tty_fd;
+
+static void sigint_termios(int sa)
+{
+       tcsetattr(tty_fd, TCSAFLUSH, &termios_old);
+       exit(sa);
+}
+
 int
-read_passphrase(const char *prompt, char *pass, size_t passlen, time_t timeout)
+read_passphrase(const char *prompt, char *pass, size_t passlen, size_t bufsz, time_t timeout)
 {
-       struct termios termios_old, termios_new;
+       struct termios termios_new;
        struct timeval to;
        fd_set fds;
        ssize_t n;
-       int fd, r = 0, cfd = 0, nready;
+       int fd = STDIN_FILENO, r = 0, nready;
+       struct sigaction act, old_act;
+       int is_tty = isatty(fd);
 
-       if ((fd = open("/dev/tty", O_RDONLY)) == -1) {
-               fd = STDIN_FILENO;
-               cfd = 1;
-       }
+       if (is_tty == 0)
+               errno = 0;
+
+       memset(pass, 0, bufsz);
 
        printf("%s", prompt);
        fflush(stdout);
 
-       memset(pass, 0, passlen);
+       /* If input is being provided by something which is not a terminal, don't
+        * change the settings. */
+       if (is_tty) {
+               tcgetattr(fd, &termios_old);
+               memcpy(&termios_new, &termios_old, sizeof(termios_new));
+               termios_new.c_lflag &= ~ECHO;
+
+               act.sa_handler = sigint_termios;
+               act.sa_flags   = SA_RESETHAND;
+               sigemptyset(&act.sa_mask);
 
-       tcgetattr(fd, &termios_old);
-       memcpy(&termios_new, &termios_old, sizeof(termios_new));
-       termios_new.c_lflag &= ~ECHO;
-       tcsetattr(fd, TCSAFLUSH, &termios_new);
+               tty_fd = fd;
+               sigaction(SIGINT, &act, &old_act);
+
+               tcsetattr(fd, TCSAFLUSH, &termios_new);
+       }
 
        if (timeout > 0) {
                memset(&to, 0, sizeof(to));
@@ -373,19 +439,28 @@ read_passphrase(const char *prompt, char *pass, size_t passlen, time_t timeout)
                }
        }
 
-       n = read(fd, pass, passlen-1);
+       n = read(fd, pass, bufsz-1);
        if (n > 0) {
                pass[n-1] = '\0'; /* Strip trailing \n */
        } else {
                r = EIO;
        }
 
+       /* Warn about passphrase trimming */
+       if (strlen(pass) > MAX_PASSSZ)
+               tc_log(0, "WARNING: Passphrase is being trimmed to %zu "
+                   "characters, discarding rest.\n", passlen);
+
+       /* Cut off after passlen characters */
+       pass[passlen] = '\0';
+
 out:
-       if (cfd)
-               close(fd);
+       if (is_tty) {
+               tcsetattr(fd, TCSAFLUSH, &termios_old);
+               putchar('\n');
 
-       tcsetattr(fd, TCSAFLUSH, &termios_old);
-       putchar('\n');
+               sigaction(SIGINT, &old_act, NULL);
+       }
 
        return r;
 }
index 0dad202..73ba370 100644 (file)
@@ -145,6 +145,24 @@ _free_safe_mem(void *mem_ptr, const char *file, int line)
        free(mem);
 }
 
+void *
+_strdup_safe_mem(const char *in, const char *file, int line)
+{
+       char *out;
+       size_t sz;
+
+       sz = strlen(in)+1;
+
+       if ((out = _alloc_safe_mem(sz, file, line)) == NULL) {
+               return NULL;
+       }
+
+       memcpy(out, in, sz);
+       out[sz-1] = '\0';
+
+       return out;
+}
+
 void
 check_and_purge_safe_mem(void)
 {
index dcec7f2..6be4481 100644 (file)
 .\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd July 5, 2011
+.Dd January 20, 2014
 .Dt TCPLAY 3
 .Os
 .Sh NAME
 .Nm tc_api_init ,
 .Nm tc_api_uninit ,
-.Nm tc_api_create_volume ,
-.Nm tc_api_map_volume ,
-.Nm tc_api_unmap_volume ,
-.Nm tc_api_check_cipher ,
-.Nm tc_api_check_prf_hash ,
-.Nm tc_api_get_error_msg ,
-.Nm tc_api_get_summary
+.Nm tc_api_has ,
+.Nm tc_api_cipher_iterate ,
+.Nm tc_api_prf_iterate ,
+.Nm tc_api_task_init ,
+.Nm tc_api_task_uninit ,
+.Nm tc_api_task_set ,
+.Nm tc_api_task_do ,
+.Nm tc_api_task_info_get ,
+.Nm tc_api_task_get_error
 .Nd TrueCrypt volume management
 .Sh LIBRARY
 .Lb libtcplay
 .Sh SYNOPSIS
-.In sys/types.h
 .In tcplay_api.h
+.Ft typedef int
+.Fn (*tc_api_cipher_iterator_fn) "void *priv" "const char *name" "int key_length_in_bits" "int ciphers_in_chain"
+.Ft typedef int
+.Fn (*tc_api_prf_iterator_fn) "void *priv" "const char *name"
+.Ft typedef int
+.Fn (*tc_api_state_change_fn) "void *priv" "const char *state" "int enter_state"
 .Ft int
 .Fn tc_api_init "int verbose"
 .Ft int
 .Fn tc_api_uninit "void"
 .Ft int
-.Fn tc_api_create_volume "tc_api_opts *api_opts"
+.Fn tc_api_has "const char *feature"
 .Ft int
-.Fn tc_api_map_volume "tc_api_opts *api_opts"
+.Fn tc_api_cipher_iterate "tc_api_cipher_iterator_fn fn" "void *priv"
 .Ft int
-.Fn tc_api_unmap_volume "tc_api_opts *api_opts"
+.Fn tc_api_prf_iterate "tc_api_prf_iterator_fn fn" "void *priv"
+.Ft tc_api_task
+.Fn tc_api_task_init "const char *op"
 .Ft int
-.Fn tc_api_check_cipher "tc_api_opts *api_opts"
+.Fn tc_api_task_uninit "tc_api_task task"
 .Ft int
-.Fn tc_api_check_prf_hash "tc_api_opts *api_opts"
-.Ft const char *
-.Fn tc_api_get_error_msg "void"
+.Fn tc_api_task_set "tc_api_task task" "const char *key" "..."
+.Ft int
+.Fn tc_api_task_do "tc_api_task task"
+.Ft int
+.Fn tc_api_task_info_get "tc_api_task task" "const char *key" "..."
 .Ft const char *
-.Fn tc_api_get_summary "void"
+.Fn tc_api_task_get_error "tc_api_task task"
 .Sh DESCRIPTION
 The
 .Nm tcplay
-library provides an interface to create, query and map
-TrueCrypt-compatible
-volumes.
-.Pp
-The
-.Fn tc_api_create_volume ,
-.Fn tc_api_map_volume ,
-.Fn tc_api_unmap_volume ,
-.Fn tc_api_check_cipher
-and
-.Fn tc_api_check_prf_hash
-functions take a
-.Vt tc_api_opts
-data structure as only argument, which is defined as follows:
-.Bd -literal
-typedef struct tc_api_opts {
-       /* Common fields */
-       char            *tc_device;
-       char            *tc_passphrase;
-       const char      **tc_keyfiles;
-
-       /* Fields for mapping */
-       char            *tc_map_name;
-       int             tc_password_retries;
-       int             tc_interactive_prompt;
-       unsigned long   tc_prompt_timeout;
-
-       /* Fields for creation */
-       char            *tc_cipher;
-       char            *tc_prf_hash;
-       char            *tc_cipher_hidden;
-       char            *tc_prf_hash_hidden;
-       size_t          tc_size_hidden_in_bytes;
-       char            *tc_passphrase_hidden;
-       const char      **tc_keyfiles_hidden;
-} tc_api_opts;
-.Ed
-.Pp
-where the keyfile fields,
-.Fa tc_keyfiles
-and
-.Fa tc_keyfiles_hidden ,
-are
-.Dv NULL
-terminated arrays of key file strings.
+library provides an interface to create, query, map and manage
+TrueCrypt-compatible volumes.
 .Pp
 The
 .Fn tc_api_init
@@ -130,124 +98,458 @@ function clears up all internal secure memory, such as memory used for
 decrypted headers, passphrases, keyfiles, etc.
 .Pp
 The
-.Fn tc_api_create_volume
-function creates a new volume using the parameters specified in the
-.Fa api_opts
+.Fn tc_api_has
+function checks whether the loaded tcplay library has the feature
+specified by the
+.Fa feature
 argument.
-The new volume will be created on the device specified by
-.Fa tc_device
-using the cipher specified by
-.Fa tc_cipher
-and the pbkdf2 prf hash algorithm specified by
-.Fa tc_prf_hash
-and using the passphrase and keyfiles specified by
-.Fa tc_passphrase
-and
-.Fa tc_keyfiles
-respectively.
-If
-.Fa tc_size_hidden_in_bytes
-is not zero, a hidden volume of the given size will be created, using
-the cipher specified by
-.Fa tc_cipher_hidden
-and the pbkdf2 prf hash algorithm specified by
-.Fa tc_prf_hash_hidden .
-If
-.Fa tc_cipher_hidden
-or
-.Fa tc_prf_hash_hidden
-are
-.Dv NULL ,
-the same algorithm as for the outer volume will be used.
-The passphrase and keyfiles used are specified by
-.Fa tc_passphrase_hidden
+The current version of the
+.Nm tcplay
+library supports the following features:
+.Bl -column "some_long_feature" "Description"
+.It Sy Feature         Ta Sy Description
+.It Li trim            Ta    Allows enabling discards/TRIM when mapping a volume
+.El
+.Pp
+The
+.Fn tc_api_cipher_iterate
+function passes every available cipher chain to the callback provided in the
+.Fa fn
+argument.
+The
+.Fa priv
+argument is passed on every call of the callback function.
+The name of the cipher chain is passed to the callback function in the
+.Fa name
+argument.
+Similarly,
+the
+.Fa ciphers_in_chain
+argument holds the number of ciphers in the current chain,
 and
-.Fa tc_keyfiles_hidden
-respectively.
+.Fa key_length_in_bits
+holds the total key length for the cipher chain,
+in bits.
 .Pp
 The
-.Fn tc_api_map_volume
-function maps a volume using the parameters specified in the
-.Fa api_opts
+.Fn tc_api_prf_iterate
+function passes every available cipher chain to the callback provided in the
+.Fa fn
 argument.
-The volume, which will be mapped as
-.Fa tc_map_name ,
-is specified in
-.Fa tc_device .
 The
-.Fa tc_interactive_prompt
-field determines whether the user will be prompted to enter a passphrase
-interactively or whether the passphrase in
-.Fa tc_passphrase
-will be used.
-If an interactive prompt is used, the prompt will time out after
-.Fa tc_prompt_timeout
-seconds.
-A value of 0 indicates that no timeout will occur.
-The number of passphrase entry retries is defined by
-.Fa tc_password_retries .
-Note that the
-.Fn tc_api_map_volume
-function does not support accessing an outer volume while
-protecting the hidden volume.
-Depending on the passphrase/keyfiles used
-either the outer or the hidden volume will be mapped.
+.Fa priv
+argument is passed on every call of the callback function.
+The name of the PKBDF2 PRF algorithm is passed to the callback function in the
+.Fa name
+argument.
 .Pp
 The
-.Fn tc_api_unmap_volume
-unmaps / closes the volume specified in
-.Fa tc_map_name .
+.Fn tc_api_task_init
+function initializes and returns a
+.Ft tc_api_task
+opaque pointer that can be used to run
+.Nm tcplay
+commands.
+Each task can be used only for a single
+.Fn tc_api_task_do
+call,
+and must be deallocated using
+.Fn tc_api_task_uninit .
+The
+.Fa op
+argument can be one of the following:
+.Bl -tag -width indent
+.It Sy create
+Create a new encrypted TrueCrypt volume.
+.It Sy map
+Map an existing TrueCrypt volume.
+.It Sy info
+Request information about an encrypted TrueCrypt volume.
+.It Sy info_mapped
+Request information about a mapped TrueCrypt volume.
+.It Sy unmap
+Unmap a mapped TrueCrypt volume.
+.It Sy modify
+Modify the TrueCrypt volume by changing the passphrase, keyfiles, PRF algorithm,
+restoring from a backup header, restoring from a header file or saving to a header file.
+.It Sy restore
+Modify the TrueCrypt volume as
+.Sy modify
+does, but without changing the passphrase, keyfiles or PRF algorithm.
+.El
 .Pp
 The
-.Fn tc_api_check_cipher
-function checks whether the cipher specified in the
-.Fa api_opts
-argument field
-.Fa tc_cipher
-is valid.
+.Fn tc_api_task_set
+function allows settting a number of different options for the current task.
+The following table shows which keys are available on calls to
+.Fn tc_api_task_set
+for each of the operations.
+The letter
+.Sy M
+indicates the setting is mandatory,
+whilst
+.Sy *
+indicates the setting is optional.
+.Bl -column "hidden_header_from_filexxx" "createxxx" "infoxxx" "mapxxx" "unmapxxx" "info_mappedxxx" "modifyxxx" "restorexxx"
+.It Sy Key                      Ta Sy create Ta Sy info Ta Sy map Ta Sy unmap Ta Sy info_mapped Ta Sy modify Ta Sy restore
+.It Li dev                      Ta    "M"    Ta    "M"  Ta    "M" Ta    "*"   Ta                Ta    "M"    Ta    "M"
+.It Li map_name                 Ta           Ta         Ta    "M" Ta    "M"   Ta    "M"         Ta           Ta    ""
+.It Li interactive              Ta    "*"    Ta    "*"  Ta    "*" Ta          Ta                Ta    "*"    Ta    "*"
+.It Li retries                  Ta    "*"    Ta    "*"  Ta    "*" Ta          Ta                Ta    "*"    Ta    "*"
+.It Li timeout                  Ta    "*"    Ta    "*"  Ta    "*" Ta          Ta                Ta           Ta    ""
+.It Li state_change_fn          Ta    "*"    Ta         Ta        Ta          Ta                Ta    "*"    Ta    "*"
+.It Li weak_keys_and_salt       Ta    "*"    Ta         Ta        Ta          Ta                Ta    "*"    Ta    "*"
+.It Li secure_erase             Ta    "*"    Ta         Ta        Ta          Ta                Ta           Ta    ""
+.It Li hidden_size_bytes        Ta    "*"    Ta         Ta        Ta          Ta                Ta           Ta    ""
+.It Li prf_algo                 Ta    "*"    Ta         Ta        Ta          Ta                Ta           Ta    ""
+.It Li h_prf_algo               Ta    "*"    Ta         Ta        Ta          Ta                Ta           Ta    ""
+.It Li cipher_chain             Ta    "*"    Ta         Ta        Ta          Ta                Ta           Ta    ""
+.It Li h_cipher_chain           Ta    "*"    Ta         Ta        Ta          Ta                Ta           Ta    ""
+.It Li protect_hidden           Ta           Ta    "*"  Ta    "*" Ta          Ta                Ta           Ta    ""
+.It Li fde                      Ta           Ta    "*"  Ta    "*" Ta          Ta                Ta           Ta    ""
+.It Li sys                      Ta           Ta    "*"  Ta    "*" Ta          Ta                Ta    "?"    Ta    "?"
+.It Li use_backup_header        Ta           Ta    "*"  Ta    "*" Ta          Ta                Ta    "*"    Ta    "*"
+.It Li header_from_file         Ta           Ta    "*"  Ta    "*" Ta          Ta                Ta    "*"    Ta    "*"
+.It Li hidden_header_from_file  Ta           Ta    "*"  Ta    "*" Ta          Ta                Ta    "*"    Ta    "*"
+.It Li allow_trim               Ta           Ta         Ta    "*" Ta          Ta                Ta           Ta    ""
+.It Li save_header_to_file      Ta           Ta         Ta        Ta          Ta                Ta    "*"    Ta    ""
+.It Li passphrase               Ta    "*"    Ta    "*"  Ta    "*" Ta          Ta                Ta    "*"    Ta    "*"
+.It Li h_passphrase             Ta    "*"    Ta    "*"  Ta    "*" Ta          Ta                Ta    "*"    Ta    "*"
+.It Li keyfiles                 Ta    "*"    Ta    "*"  Ta    "*" Ta          Ta                Ta    "*"    Ta    "*"
+.It Li h_keyfiles               Ta    "*"    Ta    "*"  Ta    "*" Ta          Ta                Ta    "*"    Ta    "*"
+.It Li new_passphrase           Ta           Ta         Ta        Ta          Ta                Ta    "*"    Ta    ""
+.It Li new_keyfiles             Ta           Ta         Ta        Ta          Ta                Ta    "*"    Ta    ""
+.It Li new_prf_algo             Ta           Ta         Ta        Ta          Ta                Ta    "*"    Ta    ""
+.El
+The varargs accepted by the
+.Fn tc_api_task_set
+function depend on the key being set.
+.Bl -tag -width indent
+.It dev
+The vararg is of type
+.Ft const char * .
+It sets the device that contains the TrueCrypt volume to operate on.
+.It map_name
+The vararg is of type
+.Ft const char * .
+It set the name of the mapped volume.
+.It interactive
+The vararg is of type
+.Ft int .
+It determines whether the user will be prompted for a passphrase or whether
+the settings are taken from the arguments set using
+.Fn tc_api_task_set .
+.It retries
+The vararg is of type
+.Ft int .
+It determines the number of passphrase retries if
+.Fa interactive
+is set.
+.It weak_keys_and_salt
+The vararg is of type
+.Ft int .
+It determines whether to use a weak source of entropy for key material and/or
+the salt.
+.It secure_erase
+The vararg is of type
+.Ft int .
+It determines whether a secure erase is performed as part of the volume creation.
+.It hidden_size_bytes
+The vararg is of type
+.Ft int64_t .
+If set to
+.Fa 0
+it implies no hidden volume will be created.
+A positive value implies a hidden volume of the specified size in bytes is created.
+.It prf_algo
+The vararg is of type
+.Ft const char *
+and must be a valid PBKDF2 PRF algorithm.
+It determines which PBKDF2 PRF algorithm is used for the outer volume.
+.It h_prf_algo
+The vararg is of type
+.Ft const char *
+and must be a valid PBKDF2 PRF algorithm.
+It determines which PBKDF2 PRF algorithm is used for the hidden volume.
+.It cipher_chain
+The vararg is of type
+.Ft const char *
+and must be a valid
+.Nm tcplay
+cipher chain.
+It determines which cipher chain is used to encrypt the outer volume.
+.It h_cipher_chain
+The vararg is of type
+.Ft const char *
+and must be a valid
+.Nm tcplay
+cipher chain.
+It determines which cipher chain is used to encrypt the hidden volume.
+.It protect_hidden
+The vararg is of type
+.Ft int .
+It determines whether the size of the outer volume should be adjusted to protect
+any hidden volume.
+Using this mode requires both outer and hidden passphrases and keyfiles.
+.It sys
+The vararg is of type
+.Ft const char * .
+It determines whether the volume is a system encrypted volume,
+and if so, which disk is the system disk and hence contains the header.
+If set to
+.Dv NULL
+the volume will implicitly be treated as a non-system encrypted volume.
+.It fde
+The vararg is of type
+.Ft int .
+It determines whether the disk uses full disk encryption or not.
+If specified, the device pointed to by the
+.Fa dev
+setting should be a whole disk device, not any partition.
+The device will be mapped or queried as a whole.
+To access individual partitions, a utility such as
+.Xr kpartx 8
+should be used, which will create additional individual mappings
+for each partition in the decrypted mapped volume.
+For more details on full disk encryption, see
+.Xr tcplay 8 .
+.It use_backup_header
+The vararg is of type
+.Ft int .
+It determines whether the backup header should be used instead of the regular
+header to access the volume.
+.It header_from_file
+The vararg is of type
+.Ft const char * .
+If not
+.Dv NULL
+it forces
+.Nm tcplay
+to use the header in the specified file instead of the regular outer volume header.
+.It hidden_header_from_file
+The vararg is of type
+.Ft const char * .
+If not
+.Dv NULL
+it forces
+.Nm tcplay
+to use the header in the specified file instead of the regular hidden volume header.
+.It allow_trim
+The vararg is of type
+.Ft int .
+It specifies whether the mapped volume should allow discards (TRIM).
+.It save_header_to_file
+The vararg is of type
+.Ft const char * .
+If not
+.Dv NULL
+it forces
+.Nm tcplay
+to write the (modified) header to the specified file instead of replacing the volume
+headers.
+.It passphrase
+The vararg is of type
+.Ft const char * .
+It sets the passphrase that
+.Nm tcplay
+uses to access the volume.
+.It h_passphrase
+The vararg is of type
+.Ft const char * .
+It sets the passphrase that
+.Nm tcplay
+uses to unlock the hidden volume header.
+This option is only used if a hidden volume is being created or the
+.Fa protect_hidden
+setting is set.
+Otherwise
+.Nm tcplay
+will first use the regular passphrase to try to unlock the outer volume and then
+try to unlock the hidden volume header with the same passphrase without ever
+using
+.Fa h_passphrase .
+.It keyfiles
+The vararg is of type
+.Ft const char * .
+If not
+.Dv NULL
+the given keyfile will be added to the keyfile pool.
+Multiple calls to set this option with a non-
+.Dv NULL
+argument result add additional keyfiles.
+If
+.Dv NULL
+all keyfiles are cleared.
+.It h_keyfiles
+The vararg is of type
+.Ft const char * .
+If not
+.Dv NULL
+the given keyfile will be added to the keyfile pool.
+Multiple calls to set this option with a non-
+.Dv NULL
+argument result add additional keyfiles.
+If
+.Dv NULL
+all keyfiles are cleared.
+This option is only used if a hidden volume is being created or the
+.Fa protect_hidden
+setting is set.
+Otherwise
+.Nm tcplay
+will first use the regular keyfiles to try to unlock the outer volume and then
+try to unlock the hidden volume header with the same keyfiles without ever
+using
+.Fa h_keyfiles .
+.It new_passphrase
+The vararg is of type
+.Fa const char * .
+It specifies the new passphrase to use when modifying the volume header.
+.It new_keyfiles
+The vararg is of type
+.Fa const char * .
+If not
+.Dv NULL
+the given keyfile will be added to the new keyfile pool.
+Multiple calls to set this option with a non-
+.Dv NULL
+argument result add additional keyfiles.
+If
+.Dv NULL
+all new keyfiles are cleared.
+When the volume header is modified,
+it will be reencrypted using the new keyfiles.
+.It new_prf_algo
+The vararg is of type
+.Ft const char *
+and must be a valid PBKDF2 PRF algorithm.
+It determines which PBKDF2 PRF algorithm is used when reencrypting the (modified)
+volume header.
+.It state_change_fn
+The first vararg is of type
+.Fa tc_api_state_change_fn
+and the second vararg is of type
+.Fa void * .
+It allows the consumer to provide a callback function which will be called when
+starting and stopping a time-intensive sub-operation such as collecting entropy
+or erasing a volume.
+The second vararg is passed as the
+.Fa priv
+argument to the callback.
+The
+.Fa enter_state
+argument to the callback determines whether
+.Nm tcplay
+is starting or stopping the time-intensive sub-task specified in the
+.Fa state
+argument.
+.El
 .Pp
 The
-.Fn tc_api_check_prf_hash
-function checks whether the prf hash algorithm specified in the
-.Fa api_opts
-argument field
-.Fa tc_prf_hash
-is valid.
+.Fn tc_api_task_do
+function runs the task specified in the
+.Fa task
+argument.
+Before running the task,
+.Fn tc_api_task_do
+performs a simple sanity check of the arguments set previously using
+.Fn tc_api_task_set
+before performing the actual operation.
+After a call to
+.Fn tc_api_task_do
+for the
+.Sy info
+or
+.Sy info_mapped
+operations,
+the queried information is available to be accessed using
+.Fn tc_api_task_info_get .
 .Pp
 The
-.Fn tc_api_get_error_msg
-function should be called whenver another API function returns
-.Dv TC_ERR .
-It returns a string containing a description of the error that
-occurred.
+.Fn tc_api_task_info_get
+function can be used to query the result of a
+.Sy info
+or
+.Sy info_mapped
+operation.
+The
+.Fa task
+argument is the task used in a previous
+.Fn tc_api_task_do
+call.
+The
+.Fa key
+argument can be one of the following:
+.Bl -column "some_long_feature" "very_long_type" "Description"
+.It Sy Key             Ta Sy type       Ta Sy Description
+.It Li device          Ta Ft char *     Ta Corresponding device node
+.It Li cipher          Ta Ft char *     Ta Used cipher chain
+.It Li prf             Ta Ft char *     Ta Used PBKDF2 PRF algorithm
+.It Li key_bits        Ta Ft int *      Ta Number of key bits
+.It Li size            Ta Ft int64_t *  Ta Volume size in bytes
+.It Li iv_offset       Ta Ft int64_t *  Ta IV Offset of volume in bytes
+.It Li block_offset    Ta Ft int64_t *  Ta Block Offset of volume in bytes
+.El
+.Pp
+The second vararg argument must be of the type specified in the above table.
+The first vararg argument is always the size of the storage provided
+in the second argument.
+For a
+.Ft char *
+argument, the size corresponds to the size of the buffer at the provided
+location and must be of type
+.Ft size_t .
+For an
+.Ft int *
+or
+.Ft int64_t *
+argument, it should be the size of the underlying type.
 .Pp
 The
-.Fn tc_api_get_summary
-function returns a string containing a summary of the current
-progress of a certain operation.
-Currently only the volume erasing
-part of creating a new volume can provide a summary.
-When no summary is available, an empty string is returned.
-The output otherwise is equivalent to that of a
-.Dv SIGINFO
-signal when using
-.Xr tcplay 8 .
+.Fn tc_api_task_get_error
+function can be used to get a detailed error message after a failed
+.Fa tc_api_task_do
+call.
+The
+.Fa task
+argument is the task used in a previous
+.Fn tc_api_task_do
+call.
+.Sh NOTES
+TrueCrypt limits passphrases to 64 characters (including the terminating
+null character).
+To be compatible with it,
+.Nm tcplay
+does the same.
+All passphrases (exlcuding keyfiles) are trimmed to 64 characters.
+Similarly, keyfiles are limited to a size of 1 MB, but up to 256
+keyfiles can be used.
 .Sh RETURN VALUES
 All functions except
-.Fn tc_api_get_error_msg
+.Fn tc_api_task_init
 and
-.Fn tc_api_get_summary
+.Fn tc_api_task_get_error
 return either
 .Dv TC_OK
-to signal that the operation completed successfully, or
+to indicate that the operation completed successfully, or
+.Dv TC_ERR_UNIMPL
+to indicate that the operation is not implemented
+, or
 .Dv TC_ERR
-to signal that an error occurred.
+to indicate that any other error occured.
 .Pp
 The
 .Fn tc_api_get_error_msg
-and
-.Fn tc_api_get_summary
-always return a valid, but possibly empty, string.
+function always return a valid, but possibly empty (or irrelevant,
+if not called after an error occurred) string.
+.Pp
+The
+.Fn tc_api_task_init
+function returns
+.Dv NULL
+if an error occurred and an opaque
+.Ft tc_api_task
+otherwise.
 .Sh COMPATIBILITY
 The
 .Nm tcplay
@@ -255,7 +557,8 @@ library offers full compatibility with TrueCrypt volumes including
 hidden
 volumes, system encryption (map-only), keyfiles and cipher cascading.
 .Sh SEE ALSO
-.Xr tcplay 8
+.Xr tcplay 8 ,
+.Xr kpartx 8
 .Sh HISTORY
 The
 .Nm tcplay
index d83a265..cd882cf 100644 (file)
  * SUCH DAMAGE.
  */
 
-#if defined(__linux__)
-#define _GNU_SOURCE /* for asprintf */
-#endif
-
+#define _BSD_SOURCE
 #include <sys/types.h>
+#include <sys/stat.h>
 
 #if defined(__DragonFly__)
 #include <sys/param.h>
@@ -54,6 +52,8 @@
 #include <uuid.h>
 #endif
 
+#include <dirent.h>
+
 #include "crc32.h"
 #include "tcplay.h"
 #include "humanize.h"
@@ -68,6 +68,7 @@
 summary_fn_t summary_fn = NULL;
 int tc_internal_verbose = 1;
 char tc_internal_log_buffer[LOG_BUFFER_SZ];
+int tc_internal_state = STATE_UNKNOWN;
 
 void
 tc_log(int is_err, const char *fmt, ...)
@@ -106,9 +107,9 @@ struct tc_crypto_algo tc_crypto_algos[] = {
        { "TWOFISH-128-XTS",    "twofish-xts-plain",    32,     8 },
        { "SERPENT-128-XTS",    "serpent-xts-plain",    32,     8 },
 #endif
-       { "AES-256-XTS",        "aes-xts-plain",        64,     8 },
-       { "TWOFISH-256-XTS",    "twofish-xts-plain",    64,     8 },
-       { "SERPENT-256-XTS",    "serpent-xts-plain",    64,     8 },
+       { "AES-256-XTS",        "aes-xts-plain64",      64,     8 },
+       { "TWOFISH-256-XTS",    "twofish-xts-plain64",  64,     8 },
+       { "SERPENT-256-XTS",    "serpent-xts-plain64",  64,     8 },
        { NULL,                 NULL,                   0,      0 }
 };
 
@@ -195,6 +196,105 @@ tc_build_cipher_chains(void)
        return 0;
 }
 
+static
+struct tc_cipher_chain *
+tc_dup_cipher_chain(struct tc_cipher_chain *src)
+{
+       struct tc_cipher_chain *first = NULL, *prev = NULL, *elem;
+
+       for (; src != NULL; src = src->next) {
+               if ((elem = alloc_safe_mem(sizeof(*elem))) == NULL) {
+                       tc_log(1, "Error allocating memory for "
+                           "duplicate cipher chain\n");
+                       return NULL;
+               }
+
+               memcpy(elem, src, sizeof(*elem));
+
+               if (src->key != NULL) {
+                       if ((elem->key = alloc_safe_mem(src->cipher->klen)) == NULL) {
+                               tc_log(1, "Error allocating memory for "
+                                   "duplicate key in cipher chain\n");
+                               return NULL;
+                       }
+
+                       memcpy(elem->key, src->key, src->cipher->klen);
+               }
+
+               if (first == NULL)
+                       first = elem;
+
+               elem->next = NULL;
+               elem->prev = prev;
+
+               if (prev != NULL)
+                       prev->next = elem;
+
+               prev = elem;
+       }
+
+       return first;
+}
+
+static
+int
+tc_free_cipher_chain(struct tc_cipher_chain *chain)
+{
+       struct tc_cipher_chain *next = chain;
+
+       while ((chain = next) != NULL) {
+               next = chain->next;
+
+               if (chain->key != NULL)
+                       free_safe_mem(chain->key);
+               free_safe_mem(chain);
+       }
+
+       return 0;
+}
+
+int
+tc_cipher_chain_length(struct tc_cipher_chain *chain)
+{
+       int len = 0;
+
+       for (; chain != NULL; chain = chain->next)
+               ++len;
+
+       return len;
+}
+
+int
+tc_cipher_chain_klen(struct tc_cipher_chain *chain)
+{
+       int klen_bytes = 0;
+
+       for (; chain != NULL; chain = chain->next) {
+               klen_bytes += chain->cipher->klen;
+       }
+
+       return klen_bytes;
+}
+
+char *
+tc_cipher_chain_sprint(char *buf, size_t bufsz, struct tc_cipher_chain *chain)
+{
+       static char sbuf[256];
+       int n = 0;
+
+       if (buf == NULL) {
+               buf = sbuf;
+               bufsz = sizeof(sbuf);
+       }
+
+       for (; chain != NULL; chain = chain->next) {
+               n += snprintf(buf+n, bufsz-n, "%s%s", chain->cipher->name,
+                   (chain->next != NULL) ? "," : "\0");
+       }
+
+       return buf;
+}
+
 #ifdef DEBUG
 static void
 print_hex(unsigned char *buf, off_t start, size_t len)
@@ -211,36 +311,45 @@ print_hex(unsigned char *buf, off_t start, size_t len)
 void
 print_info(struct tcplay_info *info)
 {
-       struct tc_cipher_chain *cipher_chain;
-       int klen = 0;
-
-       printf("PBKDF2 PRF:\t\t%s\n", info->pbkdf_prf->name);
-       printf("PBKDF2 iterations:\t%d\n", info->pbkdf_prf->iteration_count);
+       printf("Device:\t\t\t%s\n", info->dev);
 
-       printf("Cipher:\t\t\t");
-       for (cipher_chain = info->cipher_chain;
-           cipher_chain != NULL;
-           cipher_chain = cipher_chain->next) {
-               printf("%s%c", cipher_chain->cipher->name,
-                   (cipher_chain->next != NULL) ? ',' : '\n');
-               klen += cipher_chain->cipher->klen;
+       if (info->pbkdf_prf != NULL) {
+               printf("PBKDF2 PRF:\t\t%s\n", info->pbkdf_prf->name);
+               printf("PBKDF2 iterations:\t%d\n",
+                   info->pbkdf_prf->iteration_count);
        }
 
-       printf("Key Length:\t\t%d bits\n", klen*8);
-       printf("CRC Key Data:\t\t%#x\n", info->hdr->crc_keys);
-       printf("Sector size:\t\t%d\n", info->hdr->sec_sz);
-       printf("Volume size:\t\t%zu sectors\n", info->size);
+       printf("Cipher:\t\t\t%s\n",
+           tc_cipher_chain_sprint(NULL, 0, info->cipher_chain));
+
+       printf("Key Length:\t\t%d bits\n",
+           8*tc_cipher_chain_klen(info->cipher_chain));
+
+       if (info->hdr != NULL) {
+               printf("CRC Key Data:\t\t%#x\n", info->hdr->crc_keys);
+               printf("Sector size:\t\t%d\n", info->hdr->sec_sz);
+       } else {
+               printf("Sector size:\t\t512\n");
+       }
+       printf("Volume size:\t\t%"DISKSZ_FMT" sectors\n", info->size);
 #if 0
        /* Don't print this; it's always 0 and is rather confusing */
        printf("Volume offset:\t\t%"PRIu64"\n", (uint64_t)info->start);
 #endif
-       printf("IV offset:\t\t%"PRIu64"\n", (uint64_t)info->skip);
-       printf("Block offset:\t\t%"PRIu64"\n", (uint64_t)info->offset);
+
+#ifdef DEBUG
+       printf("Vol Flags:\t\t%d\n", info->volflags);
+#endif
+
+       printf("IV offset:\t\t%"PRIu64" sectors\n",
+           (uint64_t)info->skip);
+       printf("Block offset:\t\t%"PRIu64" sectors\n",
+           (uint64_t)info->offset);
 }
 
 static
 struct tcplay_info *
-new_info(const char *dev, struct tc_cipher_chain *cipher_chain,
+new_info(const char *dev, int flags, struct tc_cipher_chain *cipher_chain,
     struct pbkdf_prf_algo *prf, struct tchdr_dec *hdr, off_t start)
 {
        struct tc_cipher_chain *chain_start;
@@ -255,14 +364,22 @@ new_info(const char *dev, struct tc_cipher_chain *cipher_chain,
                return NULL;
        }
 
-       info->dev = dev;
+       strncpy(info->dev, dev, sizeof(info->dev));
        info->cipher_chain = cipher_chain;
        info->pbkdf_prf = prf;
        info->start = start;
        info->hdr = hdr;
+       info->blk_sz = hdr->sec_sz;
        info->size = hdr->sz_mk_scope / hdr->sec_sz;    /* volume size */
        info->skip = hdr->off_mk_scope / hdr->sec_sz;   /* iv skip */
-       info->offset = hdr->off_mk_scope / hdr->sec_sz; /* block offset */
+
+       info->volflags = hdr->flags;
+       info->flags = flags;
+
+       if (TC_FLAG_SET(flags, SYS))
+               info->offset = 0; /* offset is 0 for system volumes */
+       else
+               info->offset = hdr->off_mk_scope / hdr->sec_sz; /* block offset */
 
        /* Associate a key out of the key pool with each cipher in the chain */
        error = tc_cipher_chain_populate_keys(cipher_chain, hdr->keys);
@@ -283,6 +400,19 @@ new_info(const char *dev, struct tc_cipher_chain *cipher_chain,
 }
 
 int
+free_info(struct tcplay_info *info)
+{
+       if (info->cipher_chain)
+               tc_free_cipher_chain(info->cipher_chain);
+       if (info->hdr)
+               free_safe_mem(info->hdr);
+
+       free_safe_mem(info);
+
+       return 0;
+}
+
+int
 adjust_info(struct tcplay_info *info, struct tcplay_info *hinfo)
 {
        if (hinfo->hdr->sz_hidvol == 0)
@@ -293,11 +423,12 @@ adjust_info(struct tcplay_info *info, struct tcplay_info *hinfo)
 }
 
 int
-process_hdr(const char *dev, unsigned char *pass, int passlen,
+process_hdr(const char *dev, int flags, unsigned char *pass, int passlen,
     struct tchdr_enc *ehdr, struct tcplay_info **pinfo)
 {
        struct tchdr_dec *dhdr;
        struct tcplay_info *info;
+       struct tc_cipher_chain *cipher_chain = NULL;
        unsigned char *key;
        int i, j, found, error;
 
@@ -334,11 +465,12 @@ process_hdr(const char *dev, unsigned char *pass, int passlen,
 #endif
 
                for (j = 0; !found && tc_cipher_chains[j] != NULL; j++) {
+                       cipher_chain = tc_dup_cipher_chain(tc_cipher_chains[j]);
 #ifdef DEBUG
                        printf("\nTrying cipher chain %d\n", j);
 #endif
 
-                       dhdr = decrypt_hdr(ehdr, tc_cipher_chains[j], key);
+                       dhdr = decrypt_hdr(ehdr, cipher_chain, key);
                        if (dhdr == NULL) {
                                tc_log(1, "hdr decryption failed for cipher "
                                    "chain %d\n", j);
@@ -360,6 +492,7 @@ process_hdr(const char *dev, unsigned char *pass, int passlen,
                                found = 1;
                        } else {
                                free_safe_mem(dhdr);
+                               tc_free_cipher_chain(cipher_chain);
                        }
                }
        }
@@ -369,8 +502,8 @@ process_hdr(const char *dev, unsigned char *pass, int passlen,
        if (!found)
                return EINVAL;
 
-       if ((info = new_info(dev, tc_cipher_chains[j-1], &pbkdf_prf_algos[i-1],
-           dhdr, 0)) == NULL) {
+       if ((info = new_info(dev, flags, cipher_chain,
+           &pbkdf_prf_algos[i-1], dhdr, 0)) == NULL) {
                free_safe_mem(dhdr);
                return ENOMEM;
        }
@@ -381,16 +514,13 @@ process_hdr(const char *dev, unsigned char *pass, int passlen,
 }
 
 int
-create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles,
-    const char *h_keyfiles[], int n_hkeyfiles, struct pbkdf_prf_algo *prf_algo,
-    struct tc_cipher_chain *cipher_chain, struct pbkdf_prf_algo *h_prf_algo,
-    struct tc_cipher_chain *h_cipher_chain, char *passphrase,
-    char *h_passphrase, size_t size_hidden_bytes_in, int interactive)
+create_volume(struct tcplay_opts *opts)
 {
        char *pass, *pass_again;
        char *h_pass = NULL;
        char buf[1024];
-       size_t blocks, blksz, hidden_blocks = 0;
+       disksz_t blocks, hidden_blocks = 0;
+       size_t blksz;
        struct tchdr_enc *ehdr, *hehdr;
        struct tchdr_enc *ehdr_backup, *hehdr_backup;
        uint64_t tmp;
@@ -401,16 +531,16 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles
        ehdr_backup = hehdr_backup = NULL;
        ret = -1; /* Default to returning error */
 
-       if (cipher_chain == NULL)
-               cipher_chain = tc_cipher_chains[0];
-       if (prf_algo == NULL)
-               prf_algo = &pbkdf_prf_algos[0];
-       if (h_cipher_chain == NULL)
-               h_cipher_chain = cipher_chain;
-       if (h_prf_algo == NULL)
-               h_prf_algo = prf_algo;
+       if (opts->cipher_chain == NULL)
+               opts->cipher_chain = tc_cipher_chains[0];
+       if (opts->prf_algo == NULL)
+               opts->prf_algo = &pbkdf_prf_algos[0];
+       if (opts->h_cipher_chain == NULL)
+               opts->h_cipher_chain = opts->cipher_chain;
+       if (opts->h_prf_algo == NULL)
+               opts->h_prf_algo = opts->prf_algo;
 
-       if ((error = get_disk_info(dev, &blocks, &blksz)) != 0) {
+       if ((error = get_disk_info(opts->dev, &blocks, &blksz)) != 0) {
                tc_log(1, "could not get disk info\n");
                return -1;
        }
@@ -421,16 +551,17 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles
                return -1;
        }
 
-       if (interactive) {
-               if (((pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) ||
-                  ((pass_again = alloc_safe_mem(MAX_PASSSZ)) == NULL)) {
+       if (opts->interactive) {
+               if (((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) ||
+                  ((pass_again = alloc_safe_mem(PASS_BUFSZ)) == NULL)) {
                        tc_log(1, "could not allocate safe passphrase memory\n");
                        goto out;
                }
 
-               if ((error = read_passphrase("Passphrase: ", pass, MAX_PASSSZ, 0) ||
-                  (read_passphrase("Repeat passphrase: ", pass_again,
-                  MAX_PASSSZ, 0)))) {
+               if ((error = read_passphrase("Passphrase: ", pass, MAX_PASSSZ,
+                   PASS_BUFSZ, 0) ||
+                   (read_passphrase("Repeat passphrase: ", pass_again,
+                   MAX_PASSSZ, PASS_BUFSZ, 0)))) {
                        tc_log(1, "could not read passphrase\n");
                        goto out;
                }
@@ -444,38 +575,40 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles
                pass_again = NULL;
        } else {
                /* In batch mode, use provided passphrase */
-               if ((pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) {
+               if ((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) {
                        tc_log(1, "could not allocate safe "
                            "passphrase memory");
                        goto out;
                }
 
-               if (passphrase != NULL)
-                       strcpy(pass, passphrase);
+               if (opts->passphrase != NULL) {
+                       strncpy(pass, opts->passphrase, MAX_PASSSZ);
+                       pass[MAX_PASSSZ] = '\0';
+               }
        }
 
-       if (nkeyfiles > 0) {
+       if (opts->nkeyfiles > 0) {
                /* Apply keyfiles to 'pass' */
-               if ((error = apply_keyfiles((unsigned char *)pass, MAX_PASSSZ,
-                   keyfiles, nkeyfiles))) {
+               if ((error = apply_keyfiles((unsigned char *)pass, PASS_BUFSZ,
+                   opts->keyfiles, opts->nkeyfiles))) {
                        tc_log(1, "could not apply keyfiles\n");
                        goto out;
                }
        }
 
-       if (hidden) {
-               if (interactive) {
-                       if (((h_pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) ||
-                          ((pass_again = alloc_safe_mem(MAX_PASSSZ)) == NULL)) {
+       if (opts->hidden) {
+               if (opts->interactive) {
+                       if (((h_pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) ||
+                          ((pass_again = alloc_safe_mem(PASS_BUFSZ)) == NULL)) {
                                tc_log(1, "could not allocate safe "
                                    "passphrase memory\n");
                                goto out;
                        }
 
                        if ((error = read_passphrase("Passphrase for hidden volume: ",
-                          h_pass, MAX_PASSSZ, 0) ||
+                          h_pass, MAX_PASSSZ, PASS_BUFSZ, 0) ||
                           (read_passphrase("Repeat passphrase: ", pass_again,
-                          MAX_PASSSZ, 0)))) {
+                          MAX_PASSSZ, PASS_BUFSZ, 0)))) {
                                tc_log(1, "could not read passphrase\n");
                                goto out;
                        }
@@ -490,36 +623,38 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles
                        pass_again = NULL;
                } else {
                        /* In batch mode, use provided passphrase */
-                       if ((h_pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) {
+                       if ((h_pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) {
                                tc_log(1, "could not allocate safe "
                                    "passphrase memory");
                                goto out;
                        }
 
-                       if (h_passphrase != NULL)
-                               strcpy(h_pass, h_passphrase);
+                       if (opts->h_passphrase != NULL) {
+                               strncpy(h_pass, opts->h_passphrase, MAX_PASSSZ);
+                               h_pass[MAX_PASSSZ] = '\0';
+                       }
                }
 
-               if (n_hkeyfiles > 0) {
+               if (opts->n_hkeyfiles > 0) {
                        /* Apply keyfiles to 'h_pass' */
                        if ((error = apply_keyfiles((unsigned char *)h_pass,
-                           MAX_PASSSZ, h_keyfiles, n_hkeyfiles))) {
+                           PASS_BUFSZ, opts->h_keyfiles, opts->n_hkeyfiles))) {
                                tc_log(1, "could not apply keyfiles\n");
                                goto out;
                        }
                }
 
-               if (interactive) {
+               if (opts->interactive) {
                        hidden_blocks = 0;
                } else {
-                       hidden_blocks = size_hidden_bytes_in/blksz;
+                       hidden_blocks = opts->hidden_size_bytes/blksz;
                        if (hidden_blocks == 0) {
                                tc_log(1, "hidden_blocks to create volume "
                                    "cannot be zero!\n");
                                goto out;
                        }
 
-                       if (size_hidden_bytes_in >=
+                       if (opts->hidden_size_bytes >=
                            (blocks*blksz) - MIN_VOL_BYTES) {
                                tc_log(1, "Hidden volume needs to be "
                                    "smaller than the outer volume\n");
@@ -531,10 +666,10 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles
                while (hidden_blocks == 0) {
                        if ((r = _humanize_number(buf, sizeof(buf),
                            (uint64_t)(blocks * blksz))) < 0) {
-                               sprintf(buf, "%zu bytes", (blocks * blksz));
+                               sprintf(buf, "%"DISKSZ_FMT" bytes", (blocks * blksz));
                        }
 
-                       printf("The total volume size of %s is %s (bytes)\n", dev, buf);
+                       printf("The total volume size of %s is %s (bytes)\n", opts->dev, buf);
                        memset(buf, 0, sizeof(buf));
                        printf("Size of hidden volume (e.g. 127M):  ");
                        fflush(stdout);
@@ -564,13 +699,14 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles
                }
        }
 
-       if (interactive) {
+       if (opts->interactive) {
                /* Show summary and ask for confirmation */
                printf("Summary of actions:\n");
-               printf(" - Completely erase *EVERYTHING* on %s\n", dev);
-               printf(" - Create %svolume on %s\n", hidden?("outer "):"", dev);
-               if (hidden) {
-                       printf(" - Create hidden volume of %zu bytes at end of "
+               if (opts->secure_erase)
+                       printf(" - Completely erase *EVERYTHING* on %s\n", opts->dev);
+               printf(" - Create %svolume on %s\n", opts->hidden?("outer "):"", opts->dev);
+               if (opts->hidden) {
+                       printf(" - Create hidden volume of %"DISKSZ_FMT" bytes at end of "
                            "outer volume\n",
                            hidden_blocks * blksz);
                }
@@ -588,58 +724,78 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles
                }
        }
 
-       tc_log(0, "Securely erasing the volume...\nThis process may take "
-           "some time depending on the size of the volume\n");
-
        /* erase volume */
-       if ((error = secure_erase(dev, blocks * blksz, blksz)) != 0) {
-               tc_log(1, "could not securely erase device %s\n", dev);
-               goto out;
+       if (opts->secure_erase) {
+               tc_log(0, "Securely erasing the volume...\nThis process may take "
+                   "some time depending on the size of the volume\n");
+
+               if (opts->state_change_fn)
+                       opts->state_change_fn(opts->api_ctx, "secure_erase", 1);
+
+               if ((error = secure_erase(opts->dev, blocks * blksz, blksz)) != 0) {
+                       tc_log(1, "could not securely erase device %s\n", opts->dev);
+                       goto out;
+               }
+
+               if (opts->state_change_fn)
+                       opts->state_change_fn(opts->api_ctx, "secure_erase", 0);
        }
 
        tc_log(0, "Creating volume headers...\nDepending on your system, this "
            "process may take a few minutes as it uses true random data which "
            "might take a while to refill\n");
 
+       if (opts->weak_keys_and_salt) {
+               tc_log(0, "WARNING: Using a weak random generator to get "
+                   "entropy for the key material. Odds are this is NOT "
+                   "what you want.\n");
+       }
+
+       if (opts->state_change_fn)
+               opts->state_change_fn(opts->api_ctx, "create_header", 1);
+
        /* create encrypted headers */
        ehdr = create_hdr((unsigned char *)pass,
-           (nkeyfiles > 0)?MAX_PASSSZ:strlen(pass),
-           prf_algo, cipher_chain, blksz, blocks, VOL_RSVD_BYTES_START/blksz,
-           blocks - (MIN_VOL_BYTES/blksz), 0, &ehdr_backup);
+           (opts->nkeyfiles > 0)?MAX_PASSSZ:strlen(pass),
+           opts->prf_algo, opts->cipher_chain, blksz, blocks, VOL_RSVD_BYTES_START/blksz,
+           blocks - (MIN_VOL_BYTES/blksz), 0, opts->weak_keys_and_salt, &ehdr_backup);
        if (ehdr == NULL) {
                tc_log(1, "Could not create header\n");
                goto out;
        }
 
-       if (hidden) {
+       if (opts->hidden) {
                hehdr = create_hdr((unsigned char *)h_pass,
-                   (n_hkeyfiles > 0)?MAX_PASSSZ:strlen(h_pass), h_prf_algo,
-                   h_cipher_chain,
+                   (opts->n_hkeyfiles > 0)?MAX_PASSSZ:strlen(h_pass), opts->h_prf_algo,
+                   opts->h_cipher_chain,
                    blksz, blocks,
                    blocks - (VOL_RSVD_BYTES_END/blksz) - hidden_blocks,
-                   hidden_blocks, 1, &hehdr_backup);
+                   hidden_blocks, 1, opts->weak_keys_and_salt, &hehdr_backup);
                if (hehdr == NULL) {
                        tc_log(1, "Could not create hidden volume header\n");
                        goto out;
                }
        }
 
+       if (opts->state_change_fn)
+               opts->state_change_fn(opts->api_ctx, "create_header", 0);
+
        tc_log(0, "Writing volume headers to disk...\n");
 
-       if ((error = write_to_disk(dev, 0, blksz, ehdr, sizeof(*ehdr))) != 0) {
+       if ((error = write_to_disk(opts->dev, 0, blksz, ehdr, sizeof(*ehdr))) != 0) {
                tc_log(1, "Could not write volume header to device\n");
                goto out;
        }
 
        /* Write backup header; it's offset is relative to the end */
-       if ((error = write_to_disk(dev, (blocks*blksz - BACKUP_HDR_OFFSET_END),
+       if ((error = write_to_disk(opts->dev, (blocks*blksz - BACKUP_HDR_OFFSET_END),
            blksz, ehdr_backup, sizeof(*ehdr_backup))) != 0) {
                tc_log(1, "Could not write backup volume header to device\n");
                goto out;
        }
 
-       if (hidden) {
-               if ((error = write_to_disk(dev, HDR_OFFSET_HIDDEN, blksz, hehdr,
+       if (opts->hidden) {
+               if ((error = write_to_disk(opts->dev, HDR_OFFSET_HIDDEN, blksz, hehdr,
                    sizeof(*hehdr))) != 0) {
                        tc_log(1, "Could not write hidden volume header to "
                            "device\n");
@@ -647,7 +803,7 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles
                }
 
                /* Write backup hidden header; offset is relative to end */
-               if ((error = write_to_disk(dev,
+               if ((error = write_to_disk(opts->dev,
                    (blocks*blksz - BACKUP_HDR_HIDDEN_OFFSET_END), blksz,
                    hehdr_backup, sizeof(*hehdr_backup))) != 0) {
                        tc_log(1, "Could not write backup hidden volume "
@@ -680,12 +836,8 @@ out:
        return ret;
 }
 
-static
 struct tcplay_info *
-info_map_common(const char *dev, int sflag, const char *sys_dev,
-    int protect_hidden, const char *keyfiles[], int nkeyfiles,
-    const char *h_keyfiles[], int n_hkeyfiles, char *passphrase,
-    char *passphrase_hidden, int interactive, int retries, time_t timeout)
+info_map_common(struct tcplay_opts *opts, char *passphrase_out)
 {
        struct tchdr_enc *ehdr, *hehdr = NULL;
        struct tcplay_info *info, *hinfo = NULL;
@@ -693,15 +845,30 @@ info_map_common(const char *dev, int sflag, const char *sys_dev,
        char *h_pass;
        int error, error2 = 0;
        size_t sz;
-       size_t blocks, blksz;
+       size_t blksz;
+       disksz_t blocks;
+       int is_hidden = 0;
+       int try_empty = 0;
+       int retries;
 
-       if ((error = get_disk_info(dev, &blocks, &blksz)) != 0) {
+       if ((error = get_disk_info(opts->dev, &blocks, &blksz)) != 0) {
                tc_log(1, "could not get disk information\n");
                return NULL;
        }
 
-       if (retries < 1)
+       if (opts->retries < 1)
                retries = 1;
+       else
+               retries = opts->retries;
+
+       /*
+        * Add one retry so we can do a first try without asking for
+        * a password if keyfiles are passed in.
+        */
+       if (opts->interactive && (opts->nkeyfiles > 0)) {
+               try_empty = 1;
+               ++retries;
+       }
 
        info = NULL;
 
@@ -714,56 +881,68 @@ info_map_common(const char *dev, int sflag, const char *sys_dev,
                ehdr = hehdr = NULL;
                info = hinfo = NULL;
 
-               if ((pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) {
+               if ((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) {
                        tc_log(1, "could not allocate safe passphrase memory\n");
                        goto out;
                }
 
-               if (interactive) {
+               if (try_empty) {
+                       pass[0] = '\0';
+               } else if (opts->interactive) {
                        if ((error = read_passphrase("Passphrase: ", pass,
-                           MAX_PASSSZ, timeout))) {
+                           MAX_PASSSZ, PASS_BUFSZ, opts->timeout))) {
                                tc_log(1, "could not read passphrase\n");
                                /* XXX: handle timeout differently? */
                                goto out;
                        }
+                       pass[MAX_PASSSZ] = '\0';
                } else {
                        /* In batch mode, use provided passphrase */
-                       if (passphrase != NULL)
-                               strcpy(pass, passphrase);
+                       if (opts->passphrase != NULL) {
+                               strncpy(pass, opts->passphrase, MAX_PASSSZ);
+                               pass[MAX_PASSSZ] = '\0';
+                       }
+               }
+
+               if (passphrase_out != NULL) {
+                       strcpy(passphrase_out, pass);
                }
 
-               if (nkeyfiles > 0) {
+               if (opts->nkeyfiles > 0) {
                        /* Apply keyfiles to 'pass' */
-                       if ((error = apply_keyfiles((unsigned char *)pass, MAX_PASSSZ,
-                           keyfiles, nkeyfiles))) {
+                       if ((error = apply_keyfiles((unsigned char *)pass, PASS_BUFSZ,
+                           opts->keyfiles, opts->nkeyfiles))) {
                                tc_log(1, "could not apply keyfiles");
                                goto out;
                        }
                }
 
-               if (protect_hidden) {
-                       if ((h_pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) {
+               if (opts->protect_hidden) {
+                       if ((h_pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) {
                                tc_log(1, "could not allocate safe passphrase memory\n");
                                goto out;
                        }
 
-                       if (interactive) {
+                       if (opts->interactive) {
                                if ((error = read_passphrase(
                                    "Passphrase for hidden volume: ", h_pass,
-                                   MAX_PASSSZ, timeout))) {
+                                   MAX_PASSSZ, PASS_BUFSZ, opts->timeout))) {
                                        tc_log(1, "could not read passphrase\n");
                                        goto out;
                                }
+                               h_pass[MAX_PASSSZ] = '\0';
                        } else {
                                /* In batch mode, use provided passphrase */
-                               if (passphrase_hidden != NULL)
-                                       strcpy(h_pass, passphrase_hidden);
+                               if (opts->h_passphrase != NULL) {
+                                       strncpy(h_pass, opts->h_passphrase, MAX_PASSSZ);
+                                       h_pass[MAX_PASSSZ] = '\0';
+                               }
                        }
 
-                       if (n_hkeyfiles > 0) {
+                       if (opts->n_hkeyfiles > 0) {
                                /* Apply keyfiles to 'pass' */
-                               if ((error = apply_keyfiles((unsigned char *)h_pass, MAX_PASSSZ,
-                                   h_keyfiles, n_hkeyfiles))) {
+                               if ((error = apply_keyfiles((unsigned char *)h_pass, PASS_BUFSZ,
+                                   opts->h_keyfiles, opts->n_hkeyfiles))) {
                                        tc_log(1, "could not apply keyfiles");
                                        goto out;
                                }
@@ -773,29 +952,52 @@ info_map_common(const char *dev, int sflag, const char *sys_dev,
                /* Always read blksz-sized chunks */
                sz = blksz;
 
-               ehdr = (struct tchdr_enc *)read_to_safe_mem((sflag) ? sys_dev : dev,
-                   (sflag) ? HDR_OFFSET_SYS : 0, &sz);
-               if (ehdr == NULL) {
-                       tc_log(1, "error read hdr_enc: %s", dev);
-                       goto out;
+               if (TC_FLAG_SET(opts->flags, HDR_FROM_FILE)) {
+                       ehdr = (struct tchdr_enc *)read_to_safe_mem(
+                           opts->hdr_file_in, 0, &sz);
+                       if (ehdr == NULL) {
+                               tc_log(1, "error read hdr_enc: %s", opts->hdr_file_in);
+                               goto out;
+                       }
+               } else {
+                       ehdr = (struct tchdr_enc *)read_to_safe_mem(
+                           (TC_FLAG_SET(opts->flags, SYS)) ? opts->sys_dev : opts->dev,
+                           (TC_FLAG_SET(opts->flags, SYS) || TC_FLAG_SET(opts->flags, FDE)) ?
+                           HDR_OFFSET_SYS :
+                           (!TC_FLAG_SET(opts->flags, BACKUP)) ? 0 : -BACKUP_HDR_OFFSET_END,
+                           &sz);
+                       if (ehdr == NULL) {
+                               tc_log(1, "error read hdr_enc: %s", opts->dev);
+                               goto out;
+                       }
                }
 
-               if (!sflag) {
+               if (!TC_FLAG_SET(opts->flags, SYS)) {
                        /* Always read blksz-sized chunks */
                        sz = blksz;
 
-                       hehdr = (struct tchdr_enc *)read_to_safe_mem(dev,
-                           HDR_OFFSET_HIDDEN, &sz);
-                       if (hehdr == NULL) {
-                               tc_log(1, "error read hdr_enc: %s", dev);
-                               goto out;
+                       if (TC_FLAG_SET(opts->flags, H_HDR_FROM_FILE)) {
+                               hehdr = (struct tchdr_enc *)read_to_safe_mem(
+                                   opts->h_hdr_file_in, 0, &sz);
+                               if (hehdr == NULL) {
+                                       tc_log(1, "error read hdr_enc: %s", opts->h_hdr_file_in);
+                                       goto out;
+                               }
+                       } else {
+                               hehdr = (struct tchdr_enc *)read_to_safe_mem(opts->dev,
+                                   (!TC_FLAG_SET(opts->flags, BACKUP)) ? HDR_OFFSET_HIDDEN :
+                                   -BACKUP_HDR_HIDDEN_OFFSET_END, &sz);
+                               if (hehdr == NULL) {
+                                       tc_log(1, "error read hdr_enc: %s", opts->dev);
+                                       goto out;
+                               }
                        }
                } else {
                        hehdr = NULL;
                }
 
-               error = process_hdr(dev, (unsigned char *)pass,
-                   (nkeyfiles > 0)?MAX_PASSSZ:strlen(pass),
+               error = process_hdr(opts->dev, opts->flags, (unsigned char *)pass,
+                   (opts->nkeyfiles > 0)?MAX_PASSSZ:strlen(pass),
                    ehdr, &info);
 
                /*
@@ -803,33 +1005,31 @@ info_map_common(const char *dev, int sflag, const char *sys_dev,
                 * volume, or the decryption/verification of the main header
                 * failed.
                 */
-               if (hehdr && (error || protect_hidden)) {
+               if (hehdr && (error || opts->protect_hidden)) {
                        if (error) {
-                               error2 = process_hdr(dev, (unsigned char *)pass,
-                                   (nkeyfiles > 0)?MAX_PASSSZ:strlen(pass), hehdr,
+                               error2 = process_hdr(opts->dev, opts->flags, (unsigned char *)pass,
+                                   (opts->nkeyfiles > 0)?MAX_PASSSZ:strlen(pass), hehdr,
                                    &info);
-                       } else if (protect_hidden) {
-                               error2 = process_hdr(dev, (unsigned char *)h_pass,
-                                   (n_hkeyfiles > 0)?MAX_PASSSZ:strlen(h_pass), hehdr,
+                               is_hidden = !error2;
+                       } else if (opts->protect_hidden) {
+                               error2 = process_hdr(opts->dev, opts->flags, (unsigned char *)h_pass,
+                                   (opts->n_hkeyfiles > 0)?MAX_PASSSZ:strlen(h_pass), hehdr,
                                    &hinfo);
                        }
                }
 
                /* We need both to protect a hidden volume */
-               if ((protect_hidden && (error || error2)) ||
+               if ((opts->protect_hidden && (error || error2)) ||
                    (error && error2)) {
-                       tc_log(1, "Incorrect password or not a TrueCrypt volume\n");
+                       if (!try_empty)
+                               tc_log(1, "Incorrect password or not a TrueCrypt volume\n");
 
                        if (info) {
-                               if (info->hdr)
-                                       free_safe_mem(info->hdr);
-                               free_safe_mem(info);
+                               free_info(info);
                                info = NULL;
                        }
                        if (hinfo) {
-                               if (hinfo->hdr)
-                                       free_safe_mem(hinfo->hdr);
-                               free_safe_mem(hinfo);
+                               free_info(hinfo);
                                hinfo = NULL;
                        }
 
@@ -849,36 +1049,36 @@ info_map_common(const char *dev, int sflag, const char *sys_dev,
                                free_safe_mem(hehdr);
                                hehdr = NULL;
                        }
+
+                       try_empty = 0;
                        continue;
                }
 
-               if (protect_hidden) {
+               if (opts->protect_hidden) {
                        if (adjust_info(info, hinfo) != 0) {
                                tc_log(1, "Could not protect hidden volume\n");
-                               if (info) {
-                                       if (info->hdr)
-                                               free_safe_mem(info->hdr);
-                                       free_safe_mem(info);
-                               }
+                               if (info)
+                                       free_info(info);
                                info = NULL;
 
-                               if (hinfo->hdr)
-                                       free_safe_mem(hinfo->hdr);
-                               free_safe_mem(hinfo);
+                               if (hinfo)
+                                       free_info(hinfo);
                                hinfo = NULL;
+
                                goto out;
                        }
 
-                       if (hinfo->hdr)
-                               free_safe_mem(hinfo->hdr);
-                       free_safe_mem(hinfo);
-                       hinfo = NULL;
+                       if (hinfo) {
+                               free_info(hinfo);
+                               hinfo = NULL;
+                       }
                }
+               try_empty = 0;
         }
 
 out:
        if (hinfo)
-               free_safe_mem(hinfo);
+               free_info(hinfo);
        if (pass)
                free_safe_mem(pass);
        if (h_pass)
@@ -888,70 +1088,556 @@ out:
        if (hehdr)
                free_safe_mem(hehdr);
 
+       if (info != NULL)
+               info->hidden = is_hidden;
+
        return info;
 }
 
 int
-info_volume(const char *device, int sflag, const char *sys_dev,
-    int protect_hidden, const char *keyfiles[], int nkeyfiles,
-    const char *h_keyfiles[], int n_hkeyfiles,
-    char *passphrase, char *passphrase_hidden, int interactive, int retries,
-    time_t timeout)
+info_mapped_volume(struct tcplay_opts *opts)
 {
        struct tcplay_info *info;
 
-       info = info_map_common(device, sflag, sys_dev, protect_hidden,
-           keyfiles, nkeyfiles, h_keyfiles, n_hkeyfiles,
-           passphrase, passphrase_hidden, interactive, retries, timeout);
-
+       info = dm_info_map(opts->map_name);
        if (info != NULL) {
-               if (interactive)
+               if (opts->interactive)
                        print_info(info);
-               if (info->hdr)
-                       free_safe_mem(info->hdr);
-               free_safe_mem(info);
+
+               free_info(info);
 
                return 0;
                /* NOT REACHED */
+       } else if (opts->interactive) {
+               tc_log(1, "Could not retrieve information about mapped "
+                   "volume %s. Does it exist?\n", opts->map_name);
        }
 
        return -1;
 }
 
 int
-map_volume(const char *map_name, const char *device, int sflag,
-    const char *sys_dev, int protect_hidden, const char *keyfiles[],
-    int nkeyfiles, const char *h_keyfiles[], int n_hkeyfiles,
-    char *passphrase, char *passphrase_hidden, int interactive, int retries,
-    time_t timeout)
+info_volume(struct tcplay_opts *opts)
+{
+       struct tcplay_info *info;
+
+       info = info_map_common(opts, NULL);
+
+       if (info != NULL) {
+               if (opts->interactive)
+                       print_info(info);
+
+               free_info(info);
+
+               return 0;
+               /* NOT REACHED */
+       }
+
+       return -1;
+}
 
+int
+map_volume(struct tcplay_opts *opts)
 {
        struct tcplay_info *info;
        int error;
 
-       info = info_map_common(device, sflag, sys_dev, protect_hidden,
-           keyfiles, nkeyfiles, h_keyfiles, n_hkeyfiles,
-           passphrase, passphrase_hidden, interactive, retries, timeout);
+       info = info_map_common(opts, NULL);
 
        if (info == NULL)
                return -1;
 
-       if ((error = dm_setup(map_name, info)) != 0) {
-               tc_log(1, "Could not set up mapping %s\n", map_name);
-               if (info->hdr)
-                       free_safe_mem(info->hdr);
-               free_safe_mem(info);
+       if ((error = dm_setup(opts->map_name, info)) != 0) {
+               tc_log(1, "Could not set up mapping %s\n", opts->map_name);
+               free_info(info);
                return -1;
        }
 
-       if (interactive)
+       if (opts->interactive)
                printf("All ok!\n");
 
-       free_safe_mem(info);
+       free_info(info);
 
        return 0;
 }
 
+int
+modify_volume(struct tcplay_opts *opts)
+{
+       struct tcplay_info *info;
+       struct tchdr_enc *ehdr, *ehdr_backup;
+       const char *new_passphrase = opts->new_passphrase;
+       const char **new_keyfiles = opts->new_keyfiles;
+       struct pbkdf_prf_algo *new_prf_algo = opts->new_prf_algo;
+       int n_newkeyfiles = opts->n_newkeyfiles;
+       char *pass, *pass_again;
+       int ret = -1;
+       off_t offset, offset_backup = 0;
+       const char *dev;
+       size_t blksz;
+       disksz_t blocks;
+       int error;
+
+       ehdr = ehdr_backup = NULL;
+       pass = pass_again = NULL;
+       info = NULL;
+
+       if (TC_FLAG_SET(opts->flags, ONLY_RESTORE)) {
+               if (opts->interactive) {
+                       if ((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) {
+                               tc_log(1, "could not allocate safe "
+                                   "passphrase memory");
+                               goto out;
+                       }
+               } else {
+                       new_passphrase = opts->passphrase;
+               }
+               new_keyfiles = opts->keyfiles;
+               n_newkeyfiles = opts->nkeyfiles;
+               new_prf_algo = NULL;
+       }
+
+       info = info_map_common(opts, pass);
+       if (info == NULL)
+               goto out;
+
+       if (opts->interactive && !TC_FLAG_SET(opts->flags, ONLY_RESTORE)) {
+               if (((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) ||
+                  ((pass_again = alloc_safe_mem(PASS_BUFSZ)) == NULL)) {
+                       tc_log(1, "could not allocate safe passphrase memory\n");
+                       goto out;
+               }
+
+               if ((error = read_passphrase("New passphrase: ", pass, MAX_PASSSZ,
+                   PASS_BUFSZ, 0) ||
+                   (read_passphrase("Repeat passphrase: ", pass_again,
+                   MAX_PASSSZ, PASS_BUFSZ, 0)))) {
+                       tc_log(1, "could not read passphrase\n");
+                       goto out;
+               }
+
+               if (strcmp(pass, pass_again) != 0) {
+                       tc_log(1, "Passphrases don't match\n");
+                       goto out;
+               }
+
+               free_safe_mem(pass_again);
+               pass_again = NULL;
+       } else if (!opts->interactive) {
+               /* In batch mode, use provided passphrase */
+               if ((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) {
+                       tc_log(1, "could not allocate safe "
+                           "passphrase memory");
+                       goto out;
+               }
+
+               if (new_passphrase != NULL) {
+                       strncpy(pass, new_passphrase, MAX_PASSSZ);
+                       pass[MAX_PASSSZ] = '\0';
+               }
+       }
+
+       if (n_newkeyfiles > 0) {
+               /* Apply keyfiles to 'pass' */
+               if ((error = apply_keyfiles((unsigned char *)pass, PASS_BUFSZ,
+                   new_keyfiles, n_newkeyfiles))) {
+                       tc_log(1, "could not apply keyfiles\n");
+                       goto out;
+               }
+       }
+
+       ehdr = copy_reencrypt_hdr((unsigned char *)pass,
+           (opts->n_newkeyfiles > 0)?MAX_PASSSZ:strlen(pass),
+           new_prf_algo, opts->weak_keys_and_salt, info, &ehdr_backup);
+       if (ehdr == NULL) {
+               tc_log(1, "Could not create header\n");
+               goto out;
+       }
+
+       dev = (TC_FLAG_SET(opts->flags, SYS)) ? opts->sys_dev : opts->dev;
+       if (TC_FLAG_SET(opts->flags, SYS) || TC_FLAG_SET(opts->flags, FDE)) {
+               /* SYS and FDE don't have backup headers (as far as I understand) */
+               if (info->hidden) {
+                       offset = HDR_OFFSET_HIDDEN;
+               } else {
+                       offset = HDR_OFFSET_SYS;
+               }
+       } else {
+               if (info->hidden) {
+                       offset = HDR_OFFSET_HIDDEN;
+                       offset_backup = -BACKUP_HDR_HIDDEN_OFFSET_END;
+               } else {
+                       offset = 0;
+                       offset_backup = -BACKUP_HDR_OFFSET_END;
+               }
+       }
+
+       if ((error = get_disk_info(dev, &blocks, &blksz)) != 0) {
+               tc_log(1, "could not get disk information\n");
+               goto out;
+       }
+
+       tc_log(0, "Writing new volume headers to disk/file...\n");
+
+       if (TC_FLAG_SET(opts->flags, SAVE_TO_FILE)) {
+               if ((error = write_to_file(opts->hdr_file_out, ehdr, sizeof(*ehdr))) != 0) {
+                       tc_log(1, "Could not write volume header to file\n");
+                       goto out;
+               }
+       } else {
+               if ((error = write_to_disk(dev, offset, blksz, ehdr,
+                   sizeof(*ehdr))) != 0) {
+                       tc_log(1, "Could not write volume header to device\n");
+                       goto out;
+               }
+
+               if (!TC_FLAG_SET(opts->flags, SYS) && !TC_FLAG_SET(opts->flags, FDE)) {
+                       if ((error = write_to_disk(dev, offset_backup, blksz,
+                           ehdr_backup, sizeof(*ehdr_backup))) != 0) {
+                               tc_log(1, "Could not write backup volume header to device\n");
+                               goto out;
+                       }
+               }
+       }
+
+       /* Everything went ok */
+       tc_log(0, "All done!\n");
+
+       ret = 0;
+
+out:
+       if (pass)
+               free_safe_mem(pass);
+       if (pass_again)
+               free_safe_mem(pass_again);
+       if (ehdr)
+               free_safe_mem(ehdr);
+       if (ehdr_backup)
+               free_safe_mem(ehdr_backup);
+       if (info)
+               free_safe_mem(info);
+
+       return ret;
+}
+
+static
+int
+dm_get_info(const char *name, struct dm_info *dmi)
+{
+       struct dm_task *dmt = NULL;
+       int error = -1;
+
+       if ((dmt = dm_task_create(DM_DEVICE_INFO)) == NULL)
+               goto out;
+
+       if ((dm_task_set_name(dmt, name)) == 0)
+               goto out;
+
+       if ((dm_task_run(dmt)) == 0)
+               goto out;
+
+       if ((dm_task_get_info(dmt, dmi)) == 0)
+               goto out;
+
+       error = 0;
+
+out:
+       if (dmt)
+               dm_task_destroy(dmt);
+
+       return error;
+}
+
+#if defined(__DragonFly__)
+static
+int
+xlate_maj_min(const char *start_path __unused, int max_depth __unused,
+    char *buf, size_t bufsz, uint32_t maj, uint32_t min)
+{
+       dev_t dev = makedev(maj, min);
+
+       snprintf(buf, bufsz, "/dev/%s", devname(dev, S_IFCHR));
+       return 1;
+}
+#else
+static
+int
+xlate_maj_min(const char *start_path, int max_depth, char *buf, size_t bufsz,
+    uint32_t maj, uint32_t min)
+{
+       dev_t dev = makedev(maj, min);
+       char path[PATH_MAX];
+       struct stat sb;
+       struct dirent *ent;
+       DIR *dirp;
+       int found = 0;
+
+       if (max_depth <= 0)
+               return -1;
+
+       if ((dirp = opendir(start_path)) == NULL)
+               return -1;
+
+       while ((ent = readdir(dirp)) != NULL) {
+               /* d_name, d_type, DT_BLK, DT_CHR, DT_DIR, DT_LNK */
+               if (ent->d_name[0] == '.')
+                       continue;
+
+               /* Linux' /dev is littered with junk, so skip over it */
+               /*
+                * The dm-<number> devices seem to be the raw DM devices
+                * things in mapper/ link to.
+                */
+               if (((strcmp(ent->d_name, "block")) == 0) ||
+                   ((strcmp(ent->d_name, "fd")) == 0) ||
+                    (((strncmp(ent->d_name, "dm-", 3) == 0) && strlen(ent->d_name) <= 5)))
+                       continue;
+
+               snprintf(path, PATH_MAX, "%s/%s", start_path, ent->d_name);
+
+               if ((stat(path, &sb)) < 0)
+                       continue;
+
+               if (S_ISDIR(sb.st_mode)) {
+                       found = !xlate_maj_min(path, max_depth-1, buf, bufsz, maj, min);
+                       if (found)
+                               break;
+               }
+
+               if (!S_ISBLK(sb.st_mode))
+                       continue;
+
+               if (sb.st_rdev != dev)
+                       continue;
+
+               snprintf(buf, bufsz, "%s", path);
+               found = 1;
+               break;
+       }
+
+       if (dirp)
+               closedir(dirp);
+
+       return found ? 0 : -ENOENT;
+}
+#endif
+
+static
+struct tcplay_dm_table *
+dm_get_table(const char *name)
+{
+       struct tcplay_dm_table *tc_table;
+       struct dm_task *dmt = NULL;
+       void *next = NULL;
+       uint64_t start, length;
+       char *target_type;
+       char *params;
+       char *p1;
+       int c = 0;
+       uint32_t maj, min;
+
+       if ((tc_table = (struct tcplay_dm_table *)alloc_safe_mem(sizeof(*tc_table))) == NULL) {
+               tc_log(1, "could not allocate safe tc_table memory\n");
+               return NULL;
+       }
+
+       if ((dmt = dm_task_create(DM_DEVICE_TABLE)) == NULL)
+               goto error;
+
+       if ((dm_task_set_name(dmt, name)) == 0)
+               goto error;
+
+       if ((dm_task_run(dmt)) == 0)
+               goto error;
+
+       tc_table->start = (off_t)0;
+       tc_table->size = (size_t)0;
+
+       do {
+               next = dm_get_next_target(dmt, next, &start, &length,
+                   &target_type, &params);
+
+               tc_table->size += (size_t)length;
+               strncpy(tc_table->target, target_type,
+                   sizeof(tc_table->target));
+
+               /* Skip any leading whitespace */
+               while (params && *params == ' ')
+                       params++;
+
+               if (strcmp(target_type, "crypt") == 0) {
+                       while ((p1 = strsep(&params, " ")) != NULL) {
+                               /* Skip any whitespace before the next strsep */
+                               while (params && *params == ' ')
+                                       params++;
+
+                               /* Process p1 */
+                               if (c == 0) {
+                                       /* cipher */
+                                       strncpy(tc_table->cipher, p1,
+                                           sizeof(tc_table->cipher));
+                               } else if (c == 2) {
+                                       /* iv offset */
+                                       tc_table->skip = (off_t)strtoll(p1, NULL, 10);
+                               } else if (c == 3) {
+                                       /* major:minor */
+                                       maj = strtoul(p1, NULL, 10);
+                                       while (*p1 != ':' && *p1 != '\0')
+                                               p1++;
+                                       min = strtoul(++p1, NULL, 10);
+                                       if ((xlate_maj_min("/dev", 2, tc_table->device,
+                                           sizeof(tc_table->device), maj, min)) != 0)
+                                               snprintf(tc_table->device,
+                                                   sizeof(tc_table->device),
+                                                   "%u:%u", maj, min);
+                               } else if (c == 4) {
+                                       /* block offset */
+                                       tc_table->offset = (off_t)strtoll(p1,
+                                           NULL, 10);
+                               }
+                               ++c;
+                       }
+
+                       if (c < 5) {
+                               tc_log(1, "could not get all the info required from "
+                                   "the table\n");
+                               goto error;
+                       }
+               }
+       } while (next != NULL);
+
+       if (dmt)
+               dm_task_destroy(dmt);
+
+#ifdef DEBUG
+       printf("device: %s\n", tc_table->device);
+       printf("target: %s\n", tc_table->target);
+       printf("cipher: %s\n", tc_table->cipher);
+       printf("size:   %ju\n", tc_table->size);
+       printf("offset: %"PRId64"\n", tc_table->offset);
+       printf("skip:   %"PRId64"\n", tc_table->skip);
+#endif
+
+       return tc_table;
+
+error:
+       if (dmt)
+               dm_task_destroy(dmt);
+       if (tc_table)
+               free_safe_mem(tc_table);
+
+       return NULL;
+}
+
+struct tcplay_info *
+dm_info_map(const char *map_name)
+{
+       struct dm_task *dmt = NULL;
+       struct dm_info dmi[3];
+       struct tcplay_dm_table *dm_table[3];
+       struct tc_crypto_algo *crypto_algo;
+       struct tcplay_info *info;
+       char map[PATH_MAX];
+       char ciphers[512];
+       int i, outermost = -1;
+
+       memset(dm_table, 0, sizeof(dm_table));
+
+       if ((info = (struct tcplay_info *)alloc_safe_mem(sizeof(*info))) == NULL) {
+               tc_log(1, "could not allocate safe info memory\n");
+               return NULL;
+       }
+
+       strncpy(map, map_name, PATH_MAX);
+       for (i = 0; i < 3; i++) {
+               if ((dm_get_info(map, &dmi[i])) != 0)
+                       goto error;
+
+               if (dmi[i].exists)
+                       dm_table[i] = dm_get_table(map);
+
+               snprintf(map, PATH_MAX, "%s.%d", map_name, i);
+       }
+
+       if (dmt)
+               dm_task_destroy(dmt);
+
+       if (dm_table[0] == NULL)
+               goto error;
+
+       /*
+        * Process our dmi, dm_table fun into the info structure.
+        */
+       /* First find which cipher chain we are using */
+       ciphers[0] = '\0';
+       for (i = 0; i < 3; i++) {
+               if (dm_table[i] == NULL)
+                       continue;
+
+               if (outermost < i)
+                       outermost = i;
+
+               crypto_algo = &tc_crypto_algos[0];
+               while ((crypto_algo != NULL) &&
+                   (strcmp(dm_table[i]->cipher, crypto_algo->dm_crypt_str) != 0))
+                       ++crypto_algo;
+               if (crypto_algo == NULL) {
+                       tc_log(1, "could not find corresponding cipher\n");
+                       goto error;
+               }
+               strcat(ciphers, crypto_algo->name);
+               strcat(ciphers, ",");
+       }
+       ciphers[strlen(ciphers)-1] = '\0';
+
+       info->cipher_chain = check_cipher_chain(ciphers, 1);
+       if (info->cipher_chain == NULL) {
+               tc_log(1, "could not find cipher chain\n");
+               goto error;
+       }
+
+       /* Copy over the name */
+       strncpy(info->dev, dm_table[outermost]->device, sizeof(info->dev));
+
+       /* Other fields */
+       info->hdr = NULL;
+       info->pbkdf_prf = NULL;
+       info->start = dm_table[outermost]->start;
+       info->size = dm_table[0]->size;
+       info->skip = dm_table[outermost]->skip;
+       info->offset = dm_table[outermost]->offset;
+       info->blk_sz = 512;
+
+       return info;
+
+error:
+       if (dmt)
+               dm_task_destroy(dmt);
+       if (info)
+               free_safe_mem(info);
+       for (i = 0; i < 3; i++)
+               if (dm_table[i] != NULL)
+                       free_safe_mem(dm_table[i]);
+
+       return NULL;
+}
+
+static
+int
+dm_exists_device(const char *name)
+{
+       struct dm_info dmi;
+       int exists = 0;
+
+       if (dm_get_info(name, &dmi) != 0)
+               goto out;
+
+       exists = dmi.exists;
+
+out:
+       return exists;
+}
+
 static
 int
 dm_remove_device(const char *name)
@@ -983,14 +1669,14 @@ dm_setup(const char *mapname, struct tcplay_info *info)
        struct dm_task *dmt = NULL;
        struct dm_info dmi;
        char *params = NULL;
-       char *uu;
+       char *uu, *uu_temp;
        char *uu_stack[64];
        int uu_stack_idx;
 #if defined(__DragonFly__)
        uint32_t status;
 #endif
        int r, ret = 0;
-       int j;
+       int j, len;
        off_t start, offset;
        char dev[PATH_MAX];
        char map[PATH_MAX];
@@ -1004,29 +1690,35 @@ dm_setup(const char *mapname, struct tcplay_info *info)
        }
 
        strcpy(dev, info->dev);
-       start = info->start;
-       offset = info->offset;
+
+       /*
+        * Device Mapper blocks are always 512-byte blocks, so convert
+        * from the "native" block size to the dm block size here.
+        */
+       start = INFO_TO_DM_BLOCKS(info, start);
+       offset = INFO_TO_DM_BLOCKS(info, offset);
        uu_stack_idx = 0;
 
+       /*
+         * Find length of cipher chain. Could use the for below, but doesn't
+         * really matter.
+         */
+       len = tc_cipher_chain_length(info->cipher_chain);
+
        /* Get to the end of the chain */
        for (cipher_chain = info->cipher_chain; cipher_chain->next != NULL;
            cipher_chain = cipher_chain->next)
                ;
 
-       for (j= 0; cipher_chain != NULL;
-           cipher_chain = cipher_chain->prev, j++) {
+       /*
+         * Start j at len-2, as we want to use .0, and the final one has no
+         * suffix.
+         */
+       for (j = len-2; cipher_chain != NULL;
+           cipher_chain = cipher_chain->prev, j--) {
 
                cookie = 0;
 
-               /* aes-cbc-essiv:sha256 7997f8af... 0 /dev/ad0s0a 8 */
-               /*                         iv off---^  block off--^ */
-               snprintf(params, 512, "%s %s %"PRIu64 " %s %"PRIu64,
-                   cipher_chain->cipher->dm_crypt_str, cipher_chain->dm_key,
-                   (uint64_t)info->skip, dev, (uint64_t)offset);
-#ifdef DEBUG
-               printf("Params: %s\n", params);
-#endif
-
                if ((dmt = dm_task_create(DM_DEVICE_CREATE)) == NULL) {
                        tc_log(1, "dm_task_create failed\n");
                        ret = -1;
@@ -1050,12 +1742,12 @@ dm_setup(const char *mapname, struct tcplay_info *info)
 
 #if defined(__linux__)
                uuid_generate(info->uuid);
-               if ((uu = malloc(1024)) == NULL) {
+               if ((uu_temp = malloc(1024)) == NULL) {
                        tc_log(1, "uuid_unparse memory failed\n");
                        ret = -1;
                        goto out;
                }
-               uuid_unparse(info->uuid, uu);
+               uuid_unparse(info->uuid, uu_temp);
 #elif defined(__DragonFly__)
                uuid_create(&info->uuid, &status);
                if (status != uuid_s_ok) {
@@ -1064,14 +1756,24 @@ dm_setup(const char *mapname, struct tcplay_info *info)
                        goto out;
                }
 
-               uuid_to_string(&info->uuid, &uu, &status);
-               if (uu == NULL) {
+               uuid_to_string(&info->uuid, &uu_temp, &status);
+               if (uu_temp == NULL) {
                        tc_log(1, "uuid_to_string failed\n");
                        ret = -1;
                        goto out;
                }
 #endif
 
+               if ((uu = malloc(1024)) == NULL) {
+                       free(uu_temp);
+                       tc_log(1, "uuid second malloc failed\n");
+                       ret = -1;
+                       goto out;
+               }
+
+               snprintf(uu, 1024, "CRYPT-TCPLAY-%s", uu_temp);
+               free(uu_temp);
+
                if ((dm_task_set_uuid(dmt, uu)) == 0) {
                        free(uu);
                        tc_log(1, "dm_task_set_uuid failed\n");
@@ -1081,7 +1783,41 @@ dm_setup(const char *mapname, struct tcplay_info *info)
 
                free(uu);
 
-               if ((dm_task_add_target(dmt, start, info->size, "crypt", params)) == 0) {
+               if (TC_FLAG_SET(info->flags, FDE)) {
+                       /*
+                        * When the full disk encryption (FDE) flag is set,
+                        * we map the first N sectors using a linear target
+                        * as they aren't encrypted.
+                        */
+
+                       /*  /dev/ad0s0a              0 */
+                       /* dev---^       block off --^ */
+                       snprintf(params, 512, "%s 0", dev);
+
+                       if ((dm_task_add_target(dmt, 0,
+                               INFO_TO_DM_BLOCKS(info, offset),
+                               "linear", params)) == 0) {
+                               tc_log(1, "dm_task_add_target failed\n");
+                               ret = -1;
+                               goto out;
+                       }
+
+                       start = INFO_TO_DM_BLOCKS(info, offset);
+               }
+
+               /* aes-cbc-essiv:sha256 7997f8af... 0 /dev/ad0s0a 8 <opts> */
+               /*                         iv off---^  block off--^ <opts> */
+               snprintf(params, 512, "%s %s %"PRIu64 " %s %"PRIu64 " %s",
+                   cipher_chain->cipher->dm_crypt_str, cipher_chain->dm_key,
+                   (uint64_t)INFO_TO_DM_BLOCKS(info, skip), dev,
+                   (uint64_t)offset,
+                   TC_FLAG_SET(info->flags, ALLOW_TRIM) ? "1 allow_discards" : "");
+#ifdef DEBUG
+               printf("Params: %s\n", params);
+#endif
+
+               if ((dm_task_add_target(dmt, start,
+                   INFO_TO_DM_BLOCKS(info, size), "crypt", params)) == 0) {
                        tc_log(1, "dm_task_add_target failed\n");
                        ret = -1;
                        goto out;
@@ -1095,7 +1831,7 @@ dm_setup(const char *mapname, struct tcplay_info *info)
 
                if ((dm_task_run(dmt)) == 0) {
                        dm_udev_wait(cookie);
-                       tc_log(1, "dm_task_task_run failed\n");
+                       tc_log(1, "dm_task_run failed\n");
                        ret = -1;
                        goto out;
                }
@@ -1109,7 +1845,10 @@ dm_setup(const char *mapname, struct tcplay_info *info)
 
                dm_udev_wait(cookie);
 
-               asprintf(&uu_stack[uu_stack_idx++], "%s", map);
+               if ((r = asprintf(&uu_stack[uu_stack_idx++], "%s", map)) < 0)
+                       tc_log(1, "warning, asprintf failed. won't be able to "
+                           "unroll changes\n");
+
 
                offset = 0;
                start = 0;
@@ -1131,7 +1870,8 @@ out:
                        printf("Unrolling dm changes! j = %d (%s)\n", j-1,
                            uu_stack[j-1]);
 #endif
-                       if ((r = dm_remove_device(uu_stack[--j])) != 0) {
+                       if ((uu_stack[j-1] == NULL) ||
+                           ((r = dm_remove_device(uu_stack[--j])) != 0)) {
                                tc_log(1, "Tried to unroll dm changes, "
                                    "giving up.\n");
                                break;
@@ -1163,9 +1903,10 @@ dm_teardown(const char *mapname, const char *device __unused)
        }
 
        /* Try to remove other cascade devices */
-       for (i = 2; i >= 0; i--) {
+       for (i = 0; i < 2; i++) {
                sprintf(map, "%s.%d", mapname, i);
-               dm_remove_device(map);
+               if (dm_exists_device(map))
+                       dm_remove_device(map);
        }
 
        return 0;
@@ -1195,7 +1936,7 @@ check_cipher(const char *cipher, int quiet)
 }
 
 struct tc_cipher_chain *
-check_cipher_chain(char *cipher_chain, int quiet)
+check_cipher_chain(const char *cipher_chain, int quiet)
 {
        struct tc_cipher_chain *cipher = NULL;
        int i,k, nciphers = 0, mismatch = 0;
@@ -1264,7 +2005,7 @@ check_cipher_chain(char *cipher_chain, int quiet)
 }
 
 struct pbkdf_prf_algo *
-check_prf_algo(char *algo, int quiet)
+check_prf_algo(const char *algo, int quiet)
 {
        int i, found = 0;
 
@@ -1299,3 +2040,148 @@ tc_play_init(void)
 
        return 0;
 }
+
+struct tcplay_opts *opts_init(void)
+{
+       struct tcplay_opts *opts;
+
+       if ((opts = (struct tcplay_opts *)alloc_safe_mem(sizeof(*opts))) == NULL) {
+               tc_log(1, "could not allocate safe opts memory\n");
+               return NULL;
+       }
+
+       memset(opts, 0, sizeof(*opts));
+
+       opts->retries = DEFAULT_RETRIES;
+       opts->secure_erase = 1;
+
+       return opts;
+}
+
+int
+opts_add_keyfile(struct tcplay_opts *opts, const char *keyfile)
+{
+       const char *keyf;
+
+       if (opts->nkeyfiles == MAX_KEYFILES)
+               return -1;
+
+       if ((keyf = strdup_safe_mem(keyfile)) == NULL) {
+               return -1;
+       }
+
+       opts->keyfiles[opts->nkeyfiles++] = keyf;
+
+       return 0;
+}
+
+int
+opts_add_keyfile_hidden(struct tcplay_opts *opts, const char *keyfile)
+{
+       const char *keyf;
+
+       if (opts->n_hkeyfiles == MAX_KEYFILES)
+               return -1;
+
+       if ((keyf = strdup_safe_mem(keyfile)) == NULL) {
+               return -1;
+       }
+
+       opts->h_keyfiles[opts->n_hkeyfiles++] = keyf;
+
+       return 0;
+}
+
+int
+opts_add_keyfile_new(struct tcplay_opts *opts, const char *keyfile)
+{
+       const char *keyf;
+
+       if (opts->n_newkeyfiles == MAX_KEYFILES)
+               return -1;
+
+       if ((keyf = strdup_safe_mem(keyfile)) == NULL) {
+               return -1;
+       }
+
+       opts->new_keyfiles[opts->n_newkeyfiles++] = keyf;
+
+       return 0;
+}
+
+void
+opts_clear_keyfile(struct tcplay_opts *opts)
+{
+       int i;
+
+       for (i = 0; i < opts->nkeyfiles; i++) {
+               free_safe_mem(opts->keyfiles[i]);
+       }
+
+       opts->nkeyfiles = 0;
+}
+
+void
+opts_clear_keyfile_hidden(struct tcplay_opts *opts)
+{
+       int i;
+
+       for (i = 0; i < opts->n_hkeyfiles; i++) {
+               free_safe_mem(opts->h_keyfiles[i]);
+       }
+
+       opts->n_hkeyfiles = 0;
+}
+
+
+void
+opts_clear_keyfile_new(struct tcplay_opts *opts)
+{
+       int i;
+
+       for (i = 0; i < opts->n_newkeyfiles; i++) {
+               free_safe_mem(opts->new_keyfiles[i]);
+       }
+
+       opts->n_newkeyfiles = 0;
+}
+
+
+void
+opts_free(struct tcplay_opts *opts)
+{
+       int i;
+
+       for (i = 0; i < opts->nkeyfiles; i++) {
+               free_safe_mem(opts->keyfiles[i]);
+       }
+
+       for (i = 0; i < opts->n_hkeyfiles; i++) {
+               free_safe_mem(opts->h_keyfiles[i]);
+       }
+
+       for (i = 0; i < opts->n_newkeyfiles; i++) {
+               free_safe_mem(opts->new_keyfiles[i]);
+       }
+
+       if (opts->dev)
+               free_safe_mem(opts->dev);
+       if (opts->passphrase)
+               free_safe_mem(opts->passphrase);
+       if (opts->h_passphrase)
+               free_safe_mem(opts->h_passphrase);
+       if (opts->new_passphrase)
+               free_safe_mem(opts->new_passphrase);
+       if (opts->map_name)
+               free_safe_mem(opts->map_name);
+       if (opts->sys_dev)
+               free_safe_mem(opts->sys_dev);
+       if (opts->hdr_file_in)
+               free_safe_mem(opts->hdr_file_in);
+       if (opts->h_hdr_file_in)
+               free_safe_mem(opts->h_hdr_file_in);
+       if (opts->hdr_file_out)
+               free_safe_mem(opts->hdr_file_out);
+
+       free_safe_mem(opts);
+}
index 64a2a69..6f982d0 100644 (file)
  * SUCH DAMAGE.
  */
 
-/* Version of tcplay */
-#define MAJ_VER                        0
-#define MIN_VER                        9
+/* Version of tcplay specified during build (CMakeLists.txt, Makefile.classic) */
 
+#ifndef _TCPLAY_H
+#define _TCPLAY_H
 
 #define MAX_BLKSZ              4096
 #define MAX_KEYSZ              192
@@ -38,6 +38,7 @@
 #define HDR_OFFSET_SYS         31744   /* 512 * (63 -1) */
 #define TC_SIG                 "TRUE"
 #define MAX_PASSSZ             64
+#define PASS_BUFSZ             256
 #define KPOOL_SZ               64
 #define MAX_KFILE_SZ           1048576 /* 1 MB */
 #define MAX_KEYFILES           256
 #define TC_VOLFLAG_SYSTEM      0x01    /* system encryption */
 #define TC_VOLFLAG_INPLACE     0x02    /* non-system in-place-encrypted volume */
 
+#define TC_VOLFLAG_SET(f, x)   ((f & TC_VOLFLAG_##x) == TC_VOLFLAG_##x)
+
 #define LOG_BUFFER_SZ          1024
 #if 0
 #define DEBUG 1
 #endif
 
+#define TC_FLAG_SYS            0x0001
+#define TC_FLAG_FDE            0x0002
+#define TC_FLAG_BACKUP         0x0004
+#define TC_FLAG_ONLY_RESTORE   0x0008
+#define TC_FLAG_ALLOW_TRIM     0x0010
+#define TC_FLAG_SAVE_TO_FILE   0x0020
+#define TC_FLAG_HDR_FROM_FILE  0x0040
+#define TC_FLAG_H_HDR_FROM_FILE        0x0080
+
+#define TC_FLAG_SET(f, x)      ((f & TC_FLAG_##x) == TC_FLAG_##x)
+
+#include <limits.h>
 #include <inttypes.h>
 
 #if defined(__DragonFly__)
 #include <uuid/uuid.h>
 #endif
 
+
+typedef uint64_t disksz_t;
+#define DISKSZ_FMT PRIu64
+
+
 struct pbkdf_prf_algo {
        const char *name;
        int iteration_count;
@@ -103,47 +123,131 @@ struct tchdr_dec {
        uint32_t        crc_keys;       /* CRC32 of the key section */
        uint64_t        vol_ctime;      /* Volume creation time */
        uint64_t        hdr_ctime;      /* Header creation time */
-       uint64_t        sz_hidvol;      /*  Size of hidden volume (set to zero
-                                           in non-hidden volumes) */
-       uint64_t        sz_vol;         /*  Size of volume */
-       uint64_t        off_mk_scope;   /*  Byte offset of the start of the
-                                           master key scope */
-       uint64_t        sz_mk_scope;    /*  Size of the encrypted area within
-                                           the master key scope */
-       uint32_t        flags;          /*  Flag bits
-                                           (bit 0: system encryption;
-                                           bit 1: non-system in-place-encrypted volume;
-                                           bits 2–31 are reserved) */
-       uint32_t        sec_sz;         /*  Sector size (in bytes) */
+       uint64_t        sz_hidvol;      /* Size of hidden volume (set to zero
+                                          in non-hidden volumes) */
+       uint64_t        sz_vol;         /* Size of volume */
+       uint64_t        off_mk_scope;   /* Byte offset of the start of the
+                                          master key scope */
+       uint64_t        sz_mk_scope;    /* Size of the encrypted area within
+                                          the master key scope */
+       uint32_t        flags;          /* Flag bits
+                                          (bit 0: system encryption;
+                                          bit 1: non-system in-place-encrypted volume;
+                                          bits 2–31 are reserved) */
+       uint32_t        sec_sz;         /* Sector size (in bytes) */
        unsigned char   unused3[120];
        uint32_t        crc_dhdr;       /* CRC32 of dec. header (except keys) */
        unsigned char   keys[256];
 } __attribute__((__packed__));
 
 struct tcplay_info {
-       const char *dev;
+       char dev[PATH_MAX];
        struct tchdr_dec *hdr;
        struct tc_cipher_chain *cipher_chain;
        struct pbkdf_prf_algo *pbkdf_prf;
        char key[MAX_KEYSZ*2 + 1];
-       off_t start;    /* Logical volume offset in table */
-       size_t size;    /* Volume size */
 
-       off_t skip;     /* IV offset */
-       off_t offset;   /* Block offset */
+       int flags;
+       int volflags;
+
+       uint32_t blk_sz;
+
+       off_t start;    /* Logical volume offset in table (in blk_sz blocks) */
+       disksz_t size;  /* Volume size (in blk_sz blocks) */
+
+       off_t skip;     /* IV offset (in blk_sz blocks) */
+       off_t offset;   /* Block offset (in blk_sz blocks) */
 
        /* Populated by dm_setup */
        uuid_t uuid;
+
+       int hidden;
+};
+
+#define INFO_TO_DM_BLOCKS(info, memb) \
+    (((info)->memb * (uint64_t)((info)->blk_sz))/512)
+
+struct tcplay_dm_table {
+       char device[PATH_MAX];  /* Underlying device */
+       char target[256];       /* DM Target type */
+       off_t start;            /* Logical volume offset in table */
+       disksz_t size;          /* Volume size */
+
+       char cipher[256];       /* Cipher */
+       off_t skip;             /* IV offset */
+       off_t offset;           /* Block offset */
 };
 
+
+typedef int (*tc_state_change_fn)(void *, const char *, int);
+
+struct tcplay_opts {
+       /* (Mostly) common options */
+       const char      *dev;
+       const char      *keyfiles[MAX_KEYFILES];
+       int             nkeyfiles;
+       const char      *h_keyfiles[MAX_KEYFILES];
+       int             n_hkeyfiles;
+       struct pbkdf_prf_algo   *prf_algo;
+       struct tc_cipher_chain  *cipher_chain;
+       struct pbkdf_prf_algo   *h_prf_algo;
+       struct tc_cipher_chain  *h_cipher_chain;
+       const char      *passphrase;
+       const char      *h_passphrase;
+       int             interactive;
+       int             weak_keys_and_salt;
+
+       /* Options for create */
+       int             hidden;
+       disksz_t        hidden_size_bytes;
+       int             secure_erase; /* XXX: default to 1! */
+
+       /* Options for map, info_mapped */
+       const char      *map_name;
+
+       /* Options for info, map, modify */
+       int             flags;
+       const char      *sys_dev;
+       int             protect_hidden;
+       int             retries;        /* XXX: default to DEFAULT_RETRIES */
+       time_t          timeout;
+
+       const char      *hdr_file_in;
+       const char      *h_hdr_file_in;
+
+       /* Options for modify only */
+       struct pbkdf_prf_algo   *new_prf_algo;
+       const char      *new_passphrase;
+       const char      *hdr_file_out;
+       const char      *new_keyfiles[MAX_KEYFILES];
+       int             n_newkeyfiles;
+
+       void            *api_ctx;
+       tc_state_change_fn      state_change_fn;
+};
+
+
+struct tcplay_opts *opts_init(void);
+int opts_add_keyfile(struct tcplay_opts *opts, const char *keyfile);
+int opts_add_keyfile_hidden(struct tcplay_opts *opts, const char *keyfile);
+int opts_add_keyfile_new(struct tcplay_opts *opts, const char *keyfile);
+void opts_free(struct tcplay_opts *opts);
+void opts_clear_keyfile(struct tcplay_opts *opts);
+void opts_clear_keyfile_hidden(struct tcplay_opts *opts);
+void opts_clear_keyfile_new(struct tcplay_opts *opts);
+
 void *read_to_safe_mem(const char *file, off_t offset, size_t *sz);
-int get_random(unsigned char *buf, size_t len);
-int secure_erase(const char *dev, size_t bytes, size_t blksz);
-int get_disk_info(const char *dev, size_t *blocks, size_t *bsize);
+int get_random(unsigned char *buf, size_t len, int weak);
+int secure_erase(const char *dev, disksz_t bytes, size_t blksz);
+int get_disk_info(const char *dev, disksz_t *blocks, size_t *bsize);
 int write_to_disk(const char *dev, off_t offset, size_t blksz, void *mem,
     size_t bytes);
+int write_to_file(const char *file, void *mem, size_t bytes);
 int read_passphrase(const char *prompt, char *pass, size_t passlen,
-    time_t timeout);
+    size_t bufsz, time_t timeout);
+float get_random_read_progress(void);
+float get_secure_erase_progress(void);
+
 
 int tc_crypto_init(void);
 int tc_cipher_chain_populate_keys(struct tc_cipher_chain *cipher_chain,
@@ -169,56 +273,73 @@ int apply_keyfiles(unsigned char *pass, size_t pass_memsz, const char *keyfiles[
 
 struct tchdr_enc *create_hdr(unsigned char *pass, int passlen,
     struct pbkdf_prf_algo *prf_algo, struct tc_cipher_chain *cipher_chain,
-    size_t sec_sz, size_t total_blocks,
-    off_t offset, size_t blocks, int hidden,
+    size_t sec_sz, disksz_t total_blocks,
+    off_t offset, disksz_t blocks, int hidden, int weak,
     struct tchdr_enc **backup_hdr);
 struct tchdr_dec *decrypt_hdr(struct tchdr_enc *ehdr,
     struct tc_cipher_chain *cipher_chain, unsigned char *key);
 int verify_hdr(struct tchdr_dec *hdr);
+struct tchdr_enc *copy_reencrypt_hdr(unsigned char *pass, int passlen,
+    struct pbkdf_prf_algo *prf_algo, int weak, struct tcplay_info *info,
+    struct tchdr_enc **backup_hdr);
 
 void *_alloc_safe_mem(size_t req_sz, const char *file, int line);
+void *_strdup_safe_mem(const char *in, const char *file, int line);
 void _free_safe_mem(void *mem, const char *file, int line);
 void check_and_purge_safe_mem(void);
 
 struct tc_crypto_algo *check_cipher(const char *cipher, int quiet);
-struct tc_cipher_chain *check_cipher_chain(char *cipher_chain, int quiet);
-struct pbkdf_prf_algo *check_prf_algo(char *algo, int quiet);
+struct tc_cipher_chain *check_cipher_chain(const char *cipher_chain, int quiet);
+struct pbkdf_prf_algo *check_prf_algo(const char *algo, int quiet);
 
 int tc_play_init(void);
-void tc_log(int err, const char *fmt, ...) __printflike(2, 3);
+void tc_log(int err, const char *fmt, ...);
+int tc_cipher_chain_klen(struct tc_cipher_chain *chain);
+int tc_cipher_chain_length(struct tc_cipher_chain *chain);
+char *tc_cipher_chain_sprint(char *buf, size_t bufsz,
+    struct tc_cipher_chain *chain);
+int free_info(struct tcplay_info *info);
 void print_info(struct tcplay_info *info);
 int adjust_info(struct tcplay_info *info, struct tcplay_info *hinfo);
-int process_hdr(const char *dev, unsigned char *pass, int passlen,
+int process_hdr(const char *dev, int flags, unsigned char *pass, int passlen,
     struct tchdr_enc *ehdr, struct tcplay_info **pinfo);
-int create_volume(const char *dev, int hidden, const char *keyfiles[],
-    int nkeyfiles, const char *h_keyfiles[], int n_hkeyfiles,
-    struct pbkdf_prf_algo *prf_algo, struct tc_cipher_chain *cipher_chain,
-    struct pbkdf_prf_algo *h_prf_algo, struct tc_cipher_chain *h_cipher_chain,
-    char *passphrase, char *h_passphrase, size_t hidden_bytes_in,
-    int interactive);
-int info_volume(const char *device, int sflag, const char *sys_dev,
-    int protect_hidden, const char *keyfiles[], int nkeyfiles,
-    const char *h_keyfiles[], int n_hkeyfiles,
-    char *passphrase, char *passphrase_hidden, int interactive, int retries,
-    time_t timeout);
-int map_volume(const char *map_name, const char *device, int sflag,
-    const char *sys_dev, int protect_hidden, const char *keyfiles[],
-    int nkeyfiles, const char *h_keyfiles[], int n_hkeyfiles,
-    char *passphrase, char *passphrase_hidden, int interactive, int retries,
-    time_t timeout);
+int create_volume(struct tcplay_opts *opts);
+struct tcplay_info *info_map_common(struct tcplay_opts *opts,
+    char *passphrase_out);
+int info_mapped_volume(struct tcplay_opts *opts);
+int info_volume(struct tcplay_opts *opts);
+int map_volume(struct tcplay_opts *opts);
+int modify_volume(struct tcplay_opts *opts);
 int dm_setup(const char *mapname, struct tcplay_info *info);
 int dm_teardown(const char *mapname, const char *device);
+struct tcplay_info *dm_info_map(const char *map_name);
 
 typedef void(*summary_fn_t)(void);
 
 extern int tc_internal_verbose;
 extern char tc_internal_log_buffer[];
 extern summary_fn_t summary_fn;
+extern struct pbkdf_prf_algo pbkdf_prf_algos[];
+extern struct tc_cipher_chain *tc_cipher_chains[MAX_CIPHER_CHAINS];
+
+#define STATE_UNKNOWN          0
+#define STATE_GET_RANDOM       1
+#define STATE_ERASE            2
+
+extern int tc_internal_state;
+#ifndef        __DECONST
+#define        __DECONST(type, var)    ((type)(uintptr_t)(const void *)(var))
+#endif
 
 #define alloc_safe_mem(x) \
        _alloc_safe_mem(x, __FILE__, __LINE__)
 
+#define strdup_safe_mem(x) \
+       _strdup_safe_mem(x, __FILE__, __LINE__)
+
 #define free_safe_mem(x) \
-       _free_safe_mem(x, __FILE__, __LINE__)
+       _free_safe_mem(__DECONST(void *, x), __FILE__, __LINE__)
 
 #define __unused       __attribute__((__unused__))
+
+#endif
index d63cd0b..6f8f377 100644 (file)
@@ -2,13 +2,14 @@
 global:
        tc_api_init;
        tc_api_uninit;
-       tc_api_create_volume;
-       tc_api_map_volume;
-       tc_api_unmap_volume;
-       tc_api_check_cipher;
-       tc_api_check_prf_hash;
-       tc_api_get_error_msg;
-       tc_api_get_summary;
+       tc_api_has;
+       tc_api_cipher_iterate;
+       tc_api_prf_iterate;
+       tc_api_task_init;
+       tc_api_task_uninit;
+       tc_api_task_set;
+       tc_api_task_do;
+       tc_api_task_info_get;
 local:
        *;
 };
index c83c295..1d7cba6 100644 (file)
 #include <stdlib.h>
 #include <unistd.h>
 #include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
 
 #include "tcplay.h"
 #include "tcplay_api.h"
+#include "tcplay_api_internal.h"
+
 
 int
 tc_api_init(int verbose)
@@ -54,145 +59,745 @@ tc_api_uninit(void)
        return TC_OK;
 }
 
-const char *
-tc_api_get_error_msg(void)
+
+static const char *_caps[] = {
+       "trim",
+       NULL
+};
+
+int
+tc_api_has(const char *feature)
 {
-       return tc_internal_log_buffer;
+       const char *cap;
+       int i;
+
+       for (cap = _caps[0], i = 0; cap != NULL; cap = _caps[++i]) {
+               if ((strcmp(cap, feature)) == 0)
+                       return TC_OK;
+       }
+
+       return TC_ERR_UNIMPL;
 }
 
-const char *
-tc_api_get_summary(void)
+int
+tc_api_cipher_iterate(tc_api_cipher_iterator_fn fn, void *priv)
 {
-       if (summary_fn != NULL) {
-               summary_fn();
-               return tc_internal_log_buffer;
+       int i;
+       struct tc_cipher_chain *chain;
+       int klen;
+       int length;
+       char buf[1024];
+
+       if (fn == NULL) {
+               errno = EFAULT;
+               return TC_ERR;
+       }
+
+       for (i = 0, chain = tc_cipher_chains[0]; chain != NULL;
+            chain = tc_cipher_chains[++i]) {
+               tc_cipher_chain_sprint(buf, sizeof(buf), chain);
+               klen = tc_cipher_chain_klen(chain);
+               length = tc_cipher_chain_length(chain);
+               if ((fn(priv, buf, klen, length)) < 0)
+                       break;
        }
 
-       return NULL;
+       return TC_OK;
 }
 
 int
-tc_api_create_volume(tc_api_opts *api_opts)
+tc_api_prf_iterate(tc_api_prf_iterator_fn fn, void *priv)
 {
-       int nkeyfiles, n_hkeyfiles = 0;
-       int create_hidden;
-       int err;
+       int i;
 
-       if ((api_opts == NULL) ||
-           (api_opts->tc_device == NULL)) {
+       if (fn == NULL) {
                errno = EFAULT;
                return TC_ERR;
        }
 
-       if ((err = tc_api_check_cipher(api_opts)) != TC_OK)
-               return TC_ERR;
+       /* start at 1 due to RIPEMD weirdness... */
+       for (i = 1; pbkdf_prf_algos[i].name != NULL; i++) {
+               if ((fn(priv, pbkdf_prf_algos[i].name)) < 0)
+                       break;
+       }
 
-       if ((err = tc_api_check_prf_hash(api_opts)) != TC_OK)
-               return TC_ERR;
+       return TC_OK;
+}
 
-       for (nkeyfiles = 0; (nkeyfiles < MAX_KEYFILES) &&
-           (api_opts->tc_keyfiles != NULL) &&
-           (api_opts->tc_keyfiles[nkeyfiles] != NULL); nkeyfiles++)
-               ;
 
-       create_hidden = 0;
+const char *
+tc_api_task_get_error(tc_api_task task __unused)
+{
+       return tc_internal_log_buffer;
+}
+
 
-       if (api_opts->tc_size_hidden_in_bytes > 0) {
-               create_hidden = 1;
-               for (n_hkeyfiles = 0; (n_hkeyfiles < MAX_KEYFILES) &&
-                   (api_opts->tc_keyfiles_hidden != NULL) &&
-                   (api_opts->tc_keyfiles_hidden[n_hkeyfiles] != NULL);
-                   n_hkeyfiles++)
-                       ;
+#define _match(k, v) (strcmp(k, v) == 0)
+
+tc_api_task
+tc_api_task_init(const char *op)
+{
+       tc_api_task task = NULL;
+       int fail = 1;
+
+       if ((task = alloc_safe_mem(sizeof(*task))) == NULL) {
+               errno = ENOMEM;
+               goto out;
        }
 
-       err = create_volume(api_opts->tc_device, create_hidden,
-           api_opts->tc_keyfiles, nkeyfiles,
-           api_opts->tc_keyfiles_hidden, n_hkeyfiles,
-           check_prf_algo(api_opts->tc_prf_hash, 1),
-           check_cipher_chain(api_opts->tc_cipher, 1),
-           check_prf_algo(api_opts->tc_prf_hash_hidden, 1),
-           check_cipher_chain(api_opts->tc_cipher_hidden, 1),
-           api_opts->tc_passphrase, api_opts->tc_passphrase_hidden,
-           api_opts->tc_size_hidden_in_bytes, 0 /* non-interactive */);
+       if ((task->opts = opts_init()) == NULL) {
+               errno = ENOMEM;
+               goto out;
+       }
 
-       return (err) ? TC_ERR : TC_OK;
+       if (_match(op, "create")) {
+               task->op = TC_OP_CREATE;
+       } else if (_match(op, "map")) {
+               task->op = TC_OP_MAP;
+       } else if (_match(op, "unmap")) {
+               task->op = TC_OP_UNMAP;
+       } else if (_match(op, "info")) {
+               task->op = TC_OP_INFO;
+       } else if (_match(op, "info_mapped")) {
+               task->op = TC_OP_INFO_MAPPED;
+       } else if (_match(op, "modify")) {
+               task->op = TC_OP_MODIFY;
+       } else if (_match(op, "restore")) {
+               task->op = TC_OP_RESTORE;
+       } else {
+               errno = EINVAL;
+               goto out;
+       }
+
+       fail = 0;
+
+out:
+       if (fail && task != NULL) {
+               if (task->opts != NULL)
+                       opts_free(task->opts);
+               free_safe_mem(task);
+       }
+
+       return fail ? NULL : task;
 }
 
 int
-tc_api_map_volume(tc_api_opts *api_opts)
+tc_api_task_uninit(tc_api_task task)
 {
-       int nkeyfiles;
-       int err;
+       if (task->last_info != NULL)
+               free_info(task->last_info);
+       opts_free(task->opts);
+       free_safe_mem(task);
 
-       if ((api_opts == NULL) ||
-           (api_opts->tc_device == NULL)) {
+       return TC_OK;
+}
+
+
+#define _set_str(k) \
+       do {                                                    \
+               if ((opts->k = strdup_safe_mem(s)) == NULL) {   \
+                       errno = ENOMEM;                         \
+                       r = TC_ERR;                             \
+                       goto out;                               \
+               }                                               \
+       } while (0)
+
+#define _clr_str(k) \
+       do {                                                    \
+               if (opts->k)                                    \
+                       free_safe_mem(opts->k);                 \
+               opts->k = NULL;                                 \
+       } while (0)
+
+int
+tc_api_task_set(tc_api_task task, const char *key, ...)
+{
+       struct tcplay_opts *opts;
+       va_list ap;
+       const char *s;
+       int64_t i64;
+       int i;
+       tc_api_state_change_fn sc_fn;
+       void *vp;
+       int r = TC_OK;
+
+       if (task == NULL || ((opts = task->opts) == NULL)) {
                errno = EFAULT;
                return TC_ERR;
        }
 
-       for (nkeyfiles = 0; (nkeyfiles < MAX_KEYFILES) &&
-           (api_opts->tc_keyfiles != NULL) &&
-           (api_opts->tc_keyfiles[nkeyfiles] != NULL); nkeyfiles++)
-               ;
+       va_start(ap, key);
+
+       if (_match(key, "interactive")) {
+               i = va_arg(ap, int);
+               opts->interactive = i;
+       } else if (_match(key, "weak_keys_and_salt")) {
+               i = va_arg(ap, int);
+               opts->weak_keys_and_salt = i;
+       } else if (_match(key, "secure_erase")) {
+               i = va_arg(ap, int);
+               opts->secure_erase = i;
+       } else if (_match(key, "protect_hidden")) {
+               i = va_arg(ap, int);
+               opts->protect_hidden = i;
+       } else if (_match(key, "fde")) {
+               i = va_arg(ap, int);
+               if (i)
+                       opts->flags |= TC_FLAG_FDE;
+               else
+                       opts->flags &= ~TC_FLAG_FDE;
+       } else if (_match(key, "use_backup_header")) {
+               i = va_arg(ap, int);
+               if (i)
+                       opts->flags |= TC_FLAG_BACKUP;
+               else
+                       opts->flags &= ~TC_FLAG_BACKUP;
+       } else if (_match(key, "allow_trim")) {
+               i = va_arg(ap, int);
+               if (i)
+                       opts->flags |= TC_FLAG_ALLOW_TRIM;
+               else
+                       opts->flags &= ~TC_FLAG_ALLOW_TRIM;
+       } else if (_match(key, "hidden_size_bytes")) {
+               i64 = va_arg(ap, int64_t);
+               opts->hidden_size_bytes = (disksz_t)i64;
+               opts->hidden = (i64 > 0);
+       } else if (_match(key, "retries")) {
+               i = va_arg(ap, int);
+               opts->retries = i;
+       } else if (_match(key, "timeout")) {
+               i = va_arg(ap, int);
+               opts->timeout = (time_t)i;
+       } else if (_match(key, "save_header_to_file")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       _set_str(hdr_file_out);
+                       opts->flags |= TC_FLAG_SAVE_TO_FILE;
+               } else {
+                       _clr_str(hdr_file_out);
+                       opts->flags &= ~TC_FLAG_SAVE_TO_FILE;
+               }
+       } else if (_match(key, "header_from_file")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       _set_str(hdr_file_in);
+                       opts->flags |= TC_FLAG_HDR_FROM_FILE;
+               } else {
+                       _clr_str(hdr_file_in);
+                       opts->flags &= ~TC_FLAG_HDR_FROM_FILE;
+               }
+       } else if (_match(key, "hidden_header_from_file")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       _set_str(h_hdr_file_in);
+                       opts->flags |= TC_FLAG_H_HDR_FROM_FILE;
+               } else {
+                       _clr_str(h_hdr_file_in);
+                       opts->flags &= ~TC_FLAG_H_HDR_FROM_FILE;
+               }
+       } else if (_match(key, "sys")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       _set_str(sys_dev);
+                       opts->flags |= TC_FLAG_SYS;
+               } else {
+                       _clr_str(sys_dev);
+                       opts->flags &= ~TC_FLAG_SYS;
+               }
+       } else if (_match(key, "passphrase")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       _set_str(passphrase);
+               } else {
+                       _clr_str(passphrase);
+               }
+       } else if (_match(key, "h_passphrase")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       _set_str(h_passphrase);
+               } else {
+                       _clr_str(h_passphrase);
+               }
+       } else if (_match(key, "new_passphrase")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       _set_str(new_passphrase);
+               } else {
+                       _clr_str(new_passphrase);
+               }
+       } else if (_match(key, "dev")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       _set_str(dev);
+               } else {
+                       _clr_str(dev);
+               }
+       } else if (_match(key, "map_name")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       _set_str(map_name);
+               } else {
+                       _clr_str(map_name);
+               }
+       } else if (_match(key, "keyfiles")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       opts_add_keyfile(opts, s);
+               } else {
+                       opts_clear_keyfile(opts);
+               }
+       } else if (_match(key, "h_keyfiles")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       opts_add_keyfile_hidden(opts, s);
+               } else {
+                       opts_clear_keyfile_hidden(opts);
+               }
+       } else if (_match(key, "new_keyfiles")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       opts_add_keyfile_new(opts, s);
+               } else {
+                       opts_clear_keyfile_new(opts);
+               }
+       } else if (_match(key, "prf_algo")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       if ((opts->prf_algo = check_prf_algo(s, 1)) == NULL) {
+                               errno = ENOENT;
+                               r = TC_ERR;
+                               goto out;
+                       }
+               } else {
+                       opts->prf_algo = NULL;
+               }
+       } else if (_match(key, "h_prf_algo")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       if ((opts->h_prf_algo = check_prf_algo(s, 1)) == NULL) {
+                               errno = ENOENT;
+                               r = TC_ERR;
+                               goto out;
+                       }
+               } else {
+                       opts->h_prf_algo = NULL;
+               }
+       } else if (_match(key, "new_prf_algo")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       if ((opts->new_prf_algo = check_prf_algo(s, 1)) == NULL) {
+                               errno = ENOENT;
+                               r = TC_ERR;
+                               goto out;
+                       }
+               } else {
+                       opts->new_prf_algo = NULL;
+               }
+       } else if (_match(key, "cipher_chain")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       if ((opts->cipher_chain = check_cipher_chain(s, 1)) == NULL) {
+                               errno = ENOENT;
+                               r = TC_ERR;
+                               goto out;
+                       }
+               } else {
+                       opts->cipher_chain = NULL;
+               }
+       } else if (_match(key, "h_cipher_chain")) {
+               s = va_arg(ap, const char *);
+               if (s != NULL) {
+                       if ((opts->h_cipher_chain = check_cipher_chain(s, 1)) == NULL) {
+                               errno = ENOENT;
+                               r = TC_ERR;
+                               goto out;
+                       }
+               } else {
+                       opts->h_cipher_chain = NULL;
+               }
+       } else if (_match(key, "state_change_fn")) {
+               sc_fn = va_arg(ap, tc_api_state_change_fn);
+               opts->state_change_fn = sc_fn;
+               vp = va_arg(ap, void *);
+               opts->api_ctx = vp;
+       } else {
+               r = TC_ERR_UNIMPL;
+       }
 
-       err = map_volume(api_opts->tc_map_name, api_opts->tc_device,
-           /* sflag */ 0, /* sys_dev */ NULL,
-           /* protect_hidden */ 0, api_opts->tc_keyfiles, nkeyfiles,
-           /* h_keyfiles[] */ NULL, /* n_hkeyfiles */ 0,
-           api_opts->tc_passphrase, /* passphrase_hidden */ NULL,
-           api_opts->tc_interactive_prompt, api_opts->tc_password_retries,
-           (time_t)api_opts->tc_prompt_timeout);
+out:
+       va_end(ap);
 
-       return (err) ? TC_ERR : TC_OK;
+       return r;
 }
 
+#define _not_null(x) \
+       if (opts->x == NULL) {  \
+               return -1;      \
+       }
+
+#define _null(x) \
+       if (opts->x != NULL) {  \
+               return -1;      \
+       }
+
+#define _zero(x) \
+       if (opts->x != 0) {     \
+               return -1;      \
+       }
+
+#define _not_set(x) \
+       if (TC_FLAG_SET(opts->flags, x)) {      \
+               return -1;                      \
+       }
+
+static
 int
-tc_api_unmap_volume(tc_api_opts *api_opts)
+_opts_check_create(struct tcplay_opts *opts)
 {
-       int err;
+       _not_null(dev);
+       _not_set(SYS);
+       _not_set(FDE);
+       _not_set(BACKUP);
+       _not_set(ONLY_RESTORE);
+       _not_set(ALLOW_TRIM);
+       _not_set(SAVE_TO_FILE);
+       _not_set(HDR_FROM_FILE);
+       _not_set(H_HDR_FROM_FILE);
+
+       _null(map_name);
+       _zero(protect_hidden);
+       _null(new_passphrase);
+       _null(new_prf_algo);
+       _zero(n_newkeyfiles);
+
+       if (opts->hidden_size_bytes && !opts->hidden) {
+               return -1;
+       }
 
-       if ((api_opts == NULL) ||
-           (api_opts->tc_map_name == NULL)) {
-               errno = EFAULT;
-               return TC_ERR;
+       return 0;
+}
+
+static
+int
+_opts_check_map(struct tcplay_opts *opts)
+{
+       _not_null(dev);
+       _not_null(map_name);
+       _not_set(ONLY_RESTORE);
+       _not_set(SAVE_TO_FILE);
+       _zero(hidden);
+       _zero(hidden_size_bytes);
+       _null(new_passphrase);
+       _null(new_prf_algo);
+       _zero(n_newkeyfiles);
+       _null(prf_algo);
+       _null(h_prf_algo);
+       _null(cipher_chain);
+       _null(h_cipher_chain);
+
+       if (!opts->protect_hidden) {
+               _zero(n_hkeyfiles);
+               //_null(h_passphrase);
        }
 
-       err = dm_teardown(api_opts->tc_map_name, api_opts->tc_device);
-       return (err) ? TC_ERR : TC_OK;
+       return 0;
 }
 
+static
 int
-tc_api_check_cipher(tc_api_opts *api_opts)
+_opts_check_unmap(struct tcplay_opts *opts)
 {
-       struct tc_cipher_chain *chain;
+       _not_null(map_name);
+       /* XXX: _not_null(dev); ? */
+       _zero(nkeyfiles);
+       _zero(n_hkeyfiles);
+       _null(prf_algo);
+       _null(cipher_chain);
+       _null(h_prf_algo);
+       _null(h_cipher_chain);
+       _null(passphrase);
+       _null(h_passphrase);
+       _zero(hidden);
+       _zero(protect_hidden);
+       _null(new_prf_algo);
+       _null(new_passphrase);
+       _zero(n_newkeyfiles);
+       _not_set(SYS);
+       _not_set(FDE);
+       _not_set(BACKUP);
+       _not_set(ONLY_RESTORE);
+       _not_set(ALLOW_TRIM);
+       _not_set(SAVE_TO_FILE);
+       _not_set(HDR_FROM_FILE);
+       _not_set(H_HDR_FROM_FILE);
+
+       return 0;
+}
 
-       if (api_opts == NULL || api_opts->tc_cipher == NULL) {
-               errno = EFAULT;
-               return TC_ERR;
+static
+int
+_opts_check_info(struct tcplay_opts *opts)
+{
+       _not_null(dev);
+       _null(map_name);
+       _not_set(ONLY_RESTORE);
+       _not_set(SAVE_TO_FILE);
+       _zero(hidden);
+       _zero(hidden_size_bytes);
+       _null(new_passphrase);
+       _null(new_prf_algo);
+       _zero(n_newkeyfiles);
+       _null(prf_algo);
+       _null(h_prf_algo);
+       _null(cipher_chain);
+       _null(h_cipher_chain);
+
+       if (!opts->protect_hidden) {
+               _zero(n_hkeyfiles);
+               //_null(h_passphrase);
        }
 
-       if ((chain = check_cipher_chain(api_opts->tc_cipher, 1)) != NULL)
-               return TC_OK;
+       return 0;
+}
+
+static
+int
+_opts_check_info_mapped(struct tcplay_opts *opts)
+{
+       _not_null(map_name);
+       /* XXX: _not_null(dev); ? */
+       _zero(nkeyfiles);
+       _zero(n_hkeyfiles);
+       _null(prf_algo);
+       _null(cipher_chain);
+       _null(h_prf_algo);
+       _null(h_cipher_chain);
+       _null(passphrase);
+       _null(h_passphrase);
+       _zero(hidden);
+       _zero(protect_hidden);
+       _null(new_prf_algo);
+       _null(new_passphrase);
+       _zero(n_newkeyfiles);
+       _not_set(SYS);
+       _not_set(FDE);
+       _not_set(BACKUP);
+       _not_set(ONLY_RESTORE);
+       _not_set(ALLOW_TRIM);
+       _not_set(SAVE_TO_FILE);
+       _not_set(HDR_FROM_FILE);
+       _not_set(H_HDR_FROM_FILE);
+
+       return 0;
+}
+
+static
+int
+_opts_check_modify(struct tcplay_opts *opts)
+{
+       _not_null(dev);
+       _null(map_name);
+       _zero(hidden);
+       _zero(hidden_size_bytes);
+       _null(prf_algo);
+       _null(h_prf_algo);
+       _null(cipher_chain);
+       _null(h_cipher_chain);
+
+       if (!opts->protect_hidden) {
+               _zero(n_hkeyfiles);
+               _null(h_passphrase);
+       }
 
-       errno = ENOENT;
-       return TC_ERR;
+       return 0;
 }
 
+
+static
 int
-tc_api_check_prf_hash(tc_api_opts *api_opts)
+_opts_check_restore(struct tcplay_opts *opts)
 {
-       struct pbkdf_prf_algo *prf_hash;
+       if ((_opts_check_modify(opts)) < 0)
+               return -1;
 
-       if (api_opts == NULL || api_opts->tc_prf_hash == NULL) {
+       _null(new_prf_algo);
+       _zero(n_newkeyfiles);
+       _null(new_passphrase);
+
+       return 0;
+}
+
+int
+tc_api_task_do(tc_api_task task)
+{
+       struct tcplay_opts *opts;
+       int r = TC_OK;
+
+       if (task == NULL || ((opts = task->opts) == NULL)) {
                errno = EFAULT;
                return TC_ERR;
        }
 
-       if ((prf_hash = check_prf_algo(api_opts->tc_prf_hash, 1)) != NULL)
-               return TC_OK;
+       if (task->last_info != NULL) {
+               free_info(task->last_info);
+       }
 
-       errno = ENOENT;
-       return TC_ERR;
+       switch (task->op) {
+       case TC_OP_CREATE:
+               if ((r = _opts_check_create(task->opts)) != 0) {
+                       errno = EINVAL;
+                       return r;
+               }
+               r = create_volume(opts);
+               break;
+
+       case TC_OP_MAP:
+               if ((r = _opts_check_map(task->opts)) != 0) {
+                       errno = EINVAL;
+                       return r;
+               }
+               r = map_volume(opts);
+               break;
+
+       case TC_OP_UNMAP:
+               if ((r = _opts_check_unmap(task->opts)) != 0) {
+                       errno = EINVAL;
+                       return r;
+               }
+               r = dm_teardown(opts->map_name, opts->dev);
+               break;
+
+       case TC_OP_INFO:
+               if ((r = _opts_check_info(task->opts)) != 0) {
+                       errno = EINVAL;
+                       return r;
+               }
+               if ((task->last_info = info_map_common(opts, NULL)) == NULL) {
+                       r = TC_ERR;
+               }
+               break;
+
+       case TC_OP_INFO_MAPPED:
+               if ((r = _opts_check_info_mapped(task->opts)) != 0) {
+                       errno = EINVAL;
+                       return r;
+               }
+               if ((task->last_info = dm_info_map(opts->map_name)) == NULL) {
+                       r = TC_ERR;
+               }
+               break;
+
+       case TC_OP_MODIFY:
+               if ((r = _opts_check_modify(task->opts)) != 0) {
+                       errno = EINVAL;
+                       return r;
+               }
+               r = modify_volume(opts);
+               break;
+
+       case TC_OP_RESTORE:
+               if ((r = _opts_check_restore(task->opts)) != 0) {
+                       errno = EINVAL;
+                       return r;
+               }
+               opts->flags |= TC_FLAG_ONLY_RESTORE;
+               r = modify_volume(opts);
+               opts->flags &= ~TC_FLAG_ONLY_RESTORE;
+               break;
+       }
+
+       return r;
 }
 
+
+int
+tc_api_task_info_get(tc_api_task task, const char *key, ...)
+{
+       char buf[1024];
+       va_list ap;
+       struct tcplay_info *info;
+       char *s;
+       int *ip;
+       int64_t *i64p;
+       int r = TC_OK;
+       size_t sz;
+
+       if (task == NULL || ((info = task->last_info) == NULL)) {
+               errno = EFAULT;
+               return TC_ERR;
+       }
+
+       va_start(ap, key);
+       sz = va_arg(ap, size_t);
+       if (sz < 1) {
+               errno = EINVAL;
+               r = TC_ERR;
+               goto out;
+       }
+
+       if (_match(key, "device")) {
+               s = va_arg(ap, char *);
+               strncpy(s, info->dev, sz);
+               s[sz-1] = '\0';
+       } else if (_match(key, "cipher")) {
+               s = va_arg(ap, char *);
+               tc_cipher_chain_sprint(buf, sizeof(buf), info->cipher_chain);
+               strncpy(s, buf, sz);
+               s[sz-1] = '\0';
+       } else if (_match(key, "prf")) {
+               s = va_arg(ap, char *);
+               if (info->pbkdf_prf)
+                       strncpy(s, info->pbkdf_prf->name, sz);
+               else
+                       strncpy(s, "(unknown)", sz);
+               s[sz-1] = '\0';
+       } else if (_match(key, "key_bits")) {
+               if (sz != sizeof(int)) {
+                       errno = EFAULT;
+                       r = TC_ERR;
+                       goto out;
+               }
+               ip = va_arg(ap, int *);
+               *ip = 8*tc_cipher_chain_klen(info->cipher_chain);
+       } else if (_match(key, "size")) {
+               if (sz != sizeof(int64_t)) {
+                       errno = EFAULT;
+                       r = TC_ERR;
+                       goto out;
+               }
+               i64p = va_arg(ap, int64_t *);
+               if (info->hdr)
+                       *i64p = (int64_t)info->size * (int64_t)info->hdr->sec_sz;
+               else
+                       *i64p = (int64_t)info->size * (int64_t)info->blk_sz;
+       } else if (_match(key, "iv_offset")) {
+               if (sz != sizeof(int64_t)) {
+                       errno = EFAULT;
+                       r = TC_ERR;
+                       goto out;
+               }
+               i64p = va_arg(ap, int64_t *);
+               if (info->hdr)
+                       *i64p = (int64_t)info->skip * (int64_t)info->hdr->sec_sz;
+               else
+                       *i64p = (int64_t)info->skip * (int64_t)info->blk_sz;
+       } else if (_match(key, "block_offset")) {
+               if (sz != sizeof(int64_t)) {
+                       errno = EFAULT;
+                       r = TC_ERR;
+                       goto out;
+               }
+               i64p = va_arg(ap, int64_t *);
+               if (info->hdr)
+                       *i64p = (int64_t)info->offset * (int64_t)info->hdr->sec_sz;
+               else
+                       *i64p = (int64_t)info->offset * (int64_t)info->blk_sz;
+       } else {
+               r = TC_ERR_UNIMPL;
+       }
+
+out:
+       va_end(ap);
+
+       return r;
+}
index 4743643..40a5ebc 100644 (file)
  * SUCH DAMAGE.
  */
 
-#define TC_OK  0
-#define TC_ERR -1
-
-typedef struct tc_api_opts {
-       /* Common fields */
-       char            *tc_device;
-       char            *tc_passphrase;
-       const char      **tc_keyfiles;
-
-       /* Fields for mapping */
-       char            *tc_map_name;
-       int             tc_password_retries;
-       int             tc_interactive_prompt;
-       unsigned long   tc_prompt_timeout;
-
-       /* Fields for creation */
-       char            *tc_cipher;
-       char            *tc_prf_hash;
-       char            *tc_cipher_hidden;
-       char            *tc_prf_hash_hidden;
-       size_t          tc_size_hidden_in_bytes;
-       char            *tc_passphrase_hidden;
-       const char      **tc_keyfiles_hidden;
-} tc_api_opts;
+#ifndef _TCPLAY_API_H
+#define _TCPLAY_API_H
+
+#include <stddef.h>
+
+#define TC_OK          0
+#define TC_ERR         -1
+#define TC_ERR_UNIMPL  -255
+
+#define TC_STATE_ENTER 1
+#define TC_STATE_EXIT  0
+
+struct _tc_api_task;
+
+typedef struct _tc_api_task *tc_api_task;
+
+typedef int (*tc_api_cipher_iterator_fn)(void *, const char *, int /* klen */, int /* length */);
+typedef int (*tc_api_prf_iterator_fn)(void *, const char *);
+typedef int (*tc_api_state_change_fn)(void *, const char *, int);
+
+#ifdef __cplusplus
+extern "C" {
+#endif
 
 int tc_api_init(int verbose);
 int tc_api_uninit(void);
-int tc_api_create_volume(tc_api_opts *api_opts);
-int tc_api_map_volume(tc_api_opts *api_opts);
-int tc_api_unmap_volume(tc_api_opts *api_opts);
-int tc_api_check_cipher(tc_api_opts *api_opts);
-int tc_api_check_prf_hash(tc_api_opts *api_opts);
-const char *tc_api_get_error_msg(void);
-const char *tc_api_get_summary(void);
 
+int tc_api_has(const char *feature);
+
+int tc_api_cipher_iterate(tc_api_cipher_iterator_fn fn, void *priv);
+int tc_api_prf_iterate(tc_api_prf_iterator_fn fn, void *priv);
+
+
+tc_api_task tc_api_task_init(const char *op);
+int tc_api_task_uninit(tc_api_task task);
+int tc_api_task_set(tc_api_task task, const char *key, ...);
+int tc_api_task_do(tc_api_task task);
+
+int tc_api_task_info_get(tc_api_task task, const char *key, ...);
+const char *tc_api_task_get_error(tc_api_task task);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
similarity index 61%
copy from lib/libtcplay/tcplay_api.h
copy to lib/libtcplay/tcplay_api_internal.h
index 4743643..fb0717c 100644 (file)
  * SUCH DAMAGE.
  */
 
-#define TC_OK  0
-#define TC_ERR -1
+#ifndef _TCPLAY_API_INTERNAL_H
+#define _TCPLAY_API_INTERNAL_H
 
-typedef struct tc_api_opts {
-       /* Common fields */
-       char            *tc_device;
-       char            *tc_passphrase;
-       const char      **tc_keyfiles;
+#include <stddef.h>
+#include "tcplay.h"
 
-       /* Fields for mapping */
-       char            *tc_map_name;
-       int             tc_password_retries;
-       int             tc_interactive_prompt;
-       unsigned long   tc_prompt_timeout;
+typedef enum tc_api_op {
+       TC_OP_CREATE,
+       TC_OP_MAP,
+       TC_OP_UNMAP,
+       TC_OP_INFO,
+       TC_OP_INFO_MAPPED,
+       TC_OP_MODIFY,
+       TC_OP_RESTORE
+} tc_api_op;
 
-       /* Fields for creation */
-       char            *tc_cipher;
-       char            *tc_prf_hash;
-       char            *tc_cipher_hidden;
-       char            *tc_prf_hash_hidden;
-       size_t          tc_size_hidden_in_bytes;
-       char            *tc_passphrase_hidden;
-       const char      **tc_keyfiles_hidden;
-} tc_api_opts;
-
-int tc_api_init(int verbose);
-int tc_api_uninit(void);
-int tc_api_create_volume(tc_api_opts *api_opts);
-int tc_api_map_volume(tc_api_opts *api_opts);
-int tc_api_unmap_volume(tc_api_opts *api_opts);
-int tc_api_check_cipher(tc_api_opts *api_opts);
-int tc_api_check_prf_hash(tc_api_opts *api_opts);
-const char *tc_api_get_error_msg(void);
-const char *tc_api_get_summary(void);
+struct _tc_api_task {
+       tc_api_op               op;
+       struct tcplay_opts      *opts;
+       struct tcplay_info      *last_info;
+};
 
+#endif
diff --git a/lib/libtcplay/tcplay_api_test.c b/lib/libtcplay/tcplay_api_test.c
deleted file mode 100644 (file)
index 3be7ae6..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#include "tcplay_api.h"
-
-int
-main(void)
-{
-       tc_api_opts api_opts;
-       int error;
-
-       error = tc_api_init(/* verbose */1);
-       assert(error == 0);
-
-       memset(&api_opts, 0, sizeof(api_opts));
-       api_opts.tc_device = "/dev/vn1s0";
-       api_opts.tc_passphrase = "apitest2";
-       api_opts.tc_keyfiles = NULL;
-       api_opts.tc_keyfiles_hidden = NULL;
-       api_opts.tc_size_hidden_in_bytes = 12000*512;
-       api_opts.tc_passphrase_hidden = "apihidden";
-       api_opts.tc_cipher = "AES-256-XTS,TWOFISH-256-XTS,SERPENT-256-XTS";
-       api_opts.tc_cipher_hidden = "SERPENT-256-XTS,TWOFISH-256-XTS";
-       api_opts.tc_prf_hash = "whirlpool";
-       api_opts.tc_prf_hash_hidden = "RIPEMD160";
-
-       error = tc_api_create_volume(&api_opts);
-       if (error)
-               printf("API ERROR: %s\n", tc_api_get_error_msg());
-       assert(error == 0);
-
-       error = tc_api_uninit();
-       assert(error == 0);
-
-       error = tc_api_init(/*verbose */ 1);
-       assert(error == 0);
-
-       api_opts.tc_passphrase = NULL;
-       api_opts.tc_map_name = "dragonfly-test";
-       api_opts.tc_password_retries = 5;
-       api_opts.tc_interactive_prompt = 1;
-       api_opts.tc_prompt_timeout = 5;
-       error = tc_api_map_volume(&api_opts);
-       if (error)
-               printf("API MAP ERROR: %s\n", tc_api_get_error_msg());
-       assert(error == 0);
-
-       system("dmsetup ls");
-       tc_api_unmap_volume(&api_opts);
-
-       error = tc_api_uninit();
-       assert(error == 0);
-
-       return 0;
-}
-
index de81a62..5ccca30 100644 (file)
@@ -1,17 +1,23 @@
 TCPLAY_DIR=     ${.CURDIR}/../../lib/libtcplay
 
+MAJ_VER=       2
+MIN_VER=       0
+
 PROG=   tcplay
 MAN=   tcplay.8
 WARNS?=        6
 
-SRCS=  main.c
-SRCS+= tcplay.c crc32.c safe_mem.c io.c crypto-dev.c hdr.c
-SRCS+= crypto.c generic_xts.c humanize.c pbkdf2-openssl.c
+SRCS+=         tcplay.c crc32.c safe_mem.c io.c hdr.c humanize.c
+SRCS+=         crypto.c generic_xts.c
+SRCS+=         crypto-dev.c pbkdf2-openssl.c
+SRCS+=         main.c
 
 LDADD= -lcrypto -ldm -lprop -lutil
 DPADD= ${LIBCRYPTO} ${LIBDM} ${LIBPROP} ${LIBUTIL}
 
 CFLAGS+=       -I${TCPLAY_DIR}
+CFLAGS+=       -DMAJ_VER=${MAJ_VER} -DMIN_VER=${MIN_VER}
+CFLAGS+=       -D_FILE_OFFSET_BITS=64
 
 .PATH: ${TCPLAY_DIR}
 
index 4c085ae..1d15a77 100644 (file)
 #define SIGINFO SIGUSR1
 #endif
 
+#define FLAG_LONG_FDE          0xff01
+#define FLAG_LONG_USE_BACKUP   0xff02
+#define FLAG_LONG_MOD          0xff04
+#define FLAG_LONG_MOD_KF       0xff08
+#define FLAG_LONG_MOD_PRF      0xff10
+#define FLAG_LONG_MOD_NONE     0xff20
+#define FLAG_LONG_MOD_TO_FILE  0xff40
+#define FLAG_LONG_USE_HDR_FILE 0xfe01
+#define FLAG_LONG_USE_HHDR_FILE        0xfe02
+#define FLAG_LONG_NO_RETRIES   0xfabc
+
+
 static
 void
 sig_handler(int sig)
@@ -56,12 +68,21 @@ void
 usage(void)
 {
        fprintf(stderr,
-           "usage: tcplay -c -d device [-g] [-a pbkdb_hash] [-b cipher]\n"
+           "usage: tcplay -c -d device [-g] [-z] [-w] [-a pbkdf_hash] [-b cipher]\n"
            "              [-f keyfile_hidden] [-k keyfile] [-x pbkdf_hash] [-y cipher]\n"
            "       tcplay -i -d device [-e] [-f keyfile_hidden] [-k keyfile]\n"
-           "              [-s system_devcie]\n"
+           "              [-s system_device] [--fde] [--use-backup]\n"
+           "              [--use-hdr-file=hdr_file] [--use-hidden-hdr-file=hdr_file]\n"
            "       tcplay -m mapping -d device [-e] [-f keyfile_hidden] [-k keyfile]\n"
-           "              [-s system_device]\n"
+           "              [-s system_device] [--fde] [--use-backup] [--allow-trim]\n"
+           "              [--use-hdr-file=hdr_file] [--use-hidden-hdr-file=hdr_file]\n"
+           "       tcplay --modify -d device [-k keyfile] [--new-keyfile=keyfile]\n"
+           "              [--new-pbkdf-prf=pbkdf_hash] [-s system_device] [--fde]\n"
+           "              [--use-backup] [--save-hdr-to-file=hdr_file] [-w]\n"
+           "              [--use-hdr-file=hdr_file] [--use-hidden-hdr-file=hdr_file]\n"
+           "       tcplay --modify -d device [-k keyfile] --restore-from-backup-hdr [-w]\n"
+           "       tcplay -j mapping\n"
+           "       tcplay -u mapping\n"
            "       tcplay -h | -v\n"
            "\n"
            "Valid commands are:\n"
@@ -71,9 +92,16 @@ usage(void)
            "\t Print help message and exit.\n"
            " -i, --info\n"
            "\t Gives information about the TC volume specified by -d or --device.\n"
+           " -j <mapping name>, --info-mapped=<mapping name>\n"
+           "\t Gives information about the mapped TC volume under the given mapping.\n"
            " -m <mapping name>, --map=<mapping name>\n"
            "\t Creates a dm-crypt mapping with the given name for the device\n"
            "\t specified by -d or --device.\n"
+           " -u <mapping name>, --unmap=<mapping name>\n"
+           "\t Removes a dm-crypt mapping with the given name.\n"
+           " --modify\n"
+           "\t Changes the volume's passphrase, keyfile and optionally the hashing\n"
+           "\t function used for the PBKDF password derivation.\n"
            " -v, --version\n"
            "\t Print version message and exit.\n"
            "\n"
@@ -96,12 +124,67 @@ usage(void)
            "\t Specifies which cipher to use when creating a new hidden volume.\n"
            "\t By default, the same as for the outer volume will be used.\n"
            "\t To see valid options, specify '-y help'.\n"
+           " -z, --insecure-erase\n"
+           "\t Skips the erase of the disk. Possible security hazard.\n"
+           " -w, --weak-keys\n"
+           "\t Uses a weak source of entropy (urandom) for key material.\n"
+           "\t WARNING: This is a REALLY REALLY bad idea for anything but\n"
+           "\t testing.\n"
+           "\n"
+           "Valid options for --modify are:\n"
+           " --new-keyfile=<key file>\n"
+           "\t Specifies a key file to use for the password derivation, when\n"
+           "\t re-encrypting the header, can appear multiple times.\n"
+           " --new-pbkdf-prf=<pbkdf prf algorithm>\n"
+           "\t Specifies which hashing function to use for the PBKDF password\n"
+           "\t derivation when re-encrypting the header.\n"
+           "\t To see valid options, specify '-a help'.\n"
+           " -s <disk path>, --system-encryption=<disk path>\n"
+           "\t Specifies that the disk (e.g. /dev/da0) is using system encryption.\n"
+           " --fde\n"
+           "\t Specifies that the disk (e.g. /dev/da0) is using full disk encryption.\n"
+           " --use-backup\n"
+           "\t Uses the backup headers (at the end of the volume) instead of the\n"
+           "\t primary headers. Both normal and backup headers will be modified!\n"
+           "\t This is useful when your primary headers have been corrupted.\n"
+           " --use-hdr-file=<header file>\n"
+           "\t Use the header in the specified file instead of the main header on the\n"
+           "\t disk as source for the modify operation.\n"
+           " --use-hidden-hdr-file=<header file>\n"
+           "\t Use the header in the specified file instead of the hidden header on the\n"
+           "\t disk as source for the modify operation.\n"
+           " --restore-from-backup-hdr\n"
+           "\t Implies --use-backup, no new PBKDF hashing function, no new keyfiles\n"
+           "\t and no new passphrase.\n"
+           "\t In other words, this will simply restore both headers from the backup\n"
+           "\t header. This option cannot be used to restore from a backup header file.\n"
+           " -w, --weak-keys\n"
+           "\t Uses a weak source of entropy (urandom) for salt material. The\n"
+           "\t key material is not affected, as the master keys are kept intact.\n"
+           "\t WARNING: This is a bad idea for anything but testing.\n"
+           " --save-hdr-backup=<header file>\n"
+           "\t Saves the modified header in the specified file instead of updating\n"
+           "\t the header files on disk.\n"
            "\n"
            "Valid options for --info and --map are:\n"
            " -e, --protect-hidden\n"
            "\t Protect a hidden volume when mounting the outer volume.\n"
            " -s <disk path>, --system-encryption=<disk path>\n"
            "\t Specifies that the disk (e.g. /dev/da0) is using system encryption.\n"
+           " -t, --allow-trim\n"
+           "\t Allow discards (TRIM command) on mapped volume.\n"
+           " --fde\n"
+           "\t Specifies that the disk (e.g. /dev/da0) is using full disk encryption.\n"
+           " --use-backup\n"
+           "\t Uses the backup headers (at the end of the volume) instead of the\n"
+           "\t primary headers.\n"
+           "\t This is useful when your primary headers have been corrupted.\n"
+           " --use-hdr-file=<header file>\n"
+           "\t Use the header in the specified file instead of the main header on the\n"
+           "\t disk.\n"
+           " --use-hidden-hdr-file=<header file>\n"
+           "\t Use the header in the specified file instead of the hidden header on the\n"
+           "\t disk.\n"
            "\n"
            "Valid options common to all commands are:\n"
            " -d <device path>, --device=<device path>\n"
@@ -115,7 +198,7 @@ usage(void)
            "\t multiple times.\n"
            );
 
-       exit(1);
+       exit(EXIT_FAILURE);
 }
 
 static struct option longopts[] = {
@@ -126,65 +209,85 @@ static struct option longopts[] = {
        { "pbkdf-prf",          required_argument,      NULL, 'a' },
        { "pbkdf-prf-hidden",   required_argument,      NULL, 'x' },
        { "info",               no_argument,            NULL, 'i' },
+       { "info-mapped",        required_argument,      NULL, 'j' },
        { "map",                required_argument,      NULL, 'm' },
        { "keyfile",            required_argument,      NULL, 'k' },
        { "keyfile-hidden",     required_argument,      NULL, 'f' },
        { "protect-hidden",     no_argument,            NULL, 'e' },
        { "device",             required_argument,      NULL, 'd' },
        { "system-encryption",  required_argument,      NULL, 's' },
+       { "allow-trim",         no_argument,            NULL, 't' },
+       { "fde",                no_argument,            NULL, FLAG_LONG_FDE },
+       { "use-backup",         no_argument,            NULL, FLAG_LONG_USE_BACKUP },
+       { "use-hdr-file",       required_argument,      NULL, FLAG_LONG_USE_HDR_FILE },
+       { "use-hidden-hdr-file",required_argument,      NULL, FLAG_LONG_USE_HHDR_FILE },
+       { "modify",             no_argument,            NULL, FLAG_LONG_MOD },
+       { "new-keyfile",        required_argument,      NULL, FLAG_LONG_MOD_KF },
+       { "new-pbkdf-prf",      required_argument,      NULL, FLAG_LONG_MOD_PRF },
+       { "restore-from-backup-hdr", no_argument,       NULL, FLAG_LONG_MOD_NONE },
+       { "save-hdr-backup",    required_argument,      NULL, FLAG_LONG_MOD_TO_FILE },
+       { "unmap",              required_argument,      NULL, 'u' },
        { "version",            no_argument,            NULL, 'v' },
+       { "weak-keys",          no_argument,            NULL, 'w' },
+       { "insecure-erase",     no_argument,            NULL, 'z' },
        { "help",               no_argument,            NULL, 'h' },
+       { "no-retries",         no_argument,            NULL, FLAG_LONG_NO_RETRIES },
        { NULL,                 0,                      NULL, 0   },
 };
 
+#define _set_str_opt(opt) \
+       do {                                                                    \
+               if ((opts->opt = strdup_safe_mem(optarg)) == NULL) {            \
+                       fprintf(stderr, "Could not allocate safe mem.\n");      \
+                       exit(EXIT_FAILURE);                                     \
+               }                                                               \
+       } while(0)
+
 int
 main(int argc, char *argv[])
 {
-       const char *dev = NULL, *sys_dev = NULL, *map_name = NULL;
-       const char *keyfiles[MAX_KEYFILES];
-       const char *h_keyfiles[MAX_KEYFILES];
-       int nkeyfiles;
-       int n_hkeyfiles;
+       struct tcplay_opts *opts;
        int ch, error;
-       int sflag = 0, info_vol = 0, map_vol = 0, protect_hidden = 0,
-           create_vol = 0, contain_hidden = 0;
-       struct pbkdf_prf_algo *prf = NULL;
-       struct tc_cipher_chain *cipher_chain = NULL;
-       struct pbkdf_prf_algo *h_prf = NULL;
-       struct tc_cipher_chain *h_cipher_chain = NULL;
+       int info_vol = 0, map_vol = 0,
+           unmap_vol = 0, info_map = 0,
+           create_vol = 0, modify_vol = 0;
 
        if ((error = tc_play_init()) != 0) {
                fprintf(stderr, "Initialization failed, exiting.");
-               exit(1);
+               exit(EXIT_FAILURE);
        }
 
        atexit(check_and_purge_safe_mem);
        signal(SIGUSR1, sig_handler);
        signal(SIGINFO, sig_handler);
 
-       nkeyfiles = 0;
-       n_hkeyfiles = 0;
+       if ((opts = opts_init()) == NULL) {
+               fprintf(stderr, "Initialization failed (opts), exiting.");
+               exit(EXIT_FAILURE);
+       }
+
+       opts->interactive = 1;
 
-       while ((ch = getopt_long(argc, argv, "a:b:cd:ef:ghik:m:s:vx:y:",
+       while ((ch = getopt_long(argc, argv, "a:b:cd:ef:ghij:k:m:s:tu:vwx:y:z",
            longopts, NULL)) != -1) {
                switch(ch) {
                case 'a':
-                       if (prf != NULL)
+                       if (opts->prf_algo != NULL)
                                usage();
-                       if ((prf = check_prf_algo(optarg, 0)) == NULL) {
+                       if ((opts->prf_algo = check_prf_algo(optarg, 0)) == NULL) {
                                if (strcmp(optarg, "help") == 0)
-                                       exit(0);
+                                       exit(EXIT_SUCCESS);
                                else
                                        usage();
                                /* NOT REACHED */
                        }
                        break;
                case 'b':
-                       if (cipher_chain != NULL)
+                       if (opts->cipher_chain != NULL)
                                usage();
-                       if ((cipher_chain = check_cipher_chain(optarg, 0)) == NULL) {
+                       if ((opts->cipher_chain = check_cipher_chain(optarg, 0)) == NULL) {
                                if (strcmp(optarg, "help") == 0)
-                                       exit(0);
+                                       exit(EXIT_SUCCESS);
                                else
                                        usage();
                                /* NOT REACHED */
@@ -194,57 +297,128 @@ main(int argc, char *argv[])
                        create_vol = 1;
                        break;
                case 'd':
-                       dev = optarg;
+                       _set_str_opt(dev);
                        break;
                case 'e':
-                       protect_hidden = 1;
+                       opts->protect_hidden = 1;
                        break;
                case 'f':
-                       h_keyfiles[n_hkeyfiles++] = optarg;
+                       if ((error = opts_add_keyfile_hidden(opts, optarg)) != 0) {
+                               fprintf(stderr, "Could not add keyfile: %s\n", optarg);
+                               exit(EXIT_FAILURE);
+                       }
                        break;
                case 'g':
-                       contain_hidden = 1;
+                       opts->hidden = 1;
                        break;
                case 'i':
                        info_vol = 1;
                        break;
+               case 'j':
+                       info_map = 1;
+                       _set_str_opt(map_name);
+                       break;
                case 'k':
-                       keyfiles[nkeyfiles++] = optarg;
+                       if ((error = opts_add_keyfile(opts, optarg)) != 0) {
+                               fprintf(stderr, "Could not add keyfile: %s\n", optarg);
+                               exit(EXIT_FAILURE);
+                       }
                        break;
                case 'm':
                        map_vol = 1;
-                       map_name = optarg;
+                       _set_str_opt(map_name);
                        break;
                case 's':
-                       sflag = 1;
-                       sys_dev = optarg;
+                       opts->flags |= TC_FLAG_SYS;
+                       _set_str_opt(sys_dev);
+                       break;
+               case 't':
+                       opts->flags |= TC_FLAG_ALLOW_TRIM;
+                       break;
+               case 'u':
+                       unmap_vol = 1;
+                       _set_str_opt(map_name);
                        break;
                case 'v':
                        printf("tcplay v%d.%d\n", MAJ_VER, MIN_VER);
-                       exit(0);
+                       exit(EXIT_SUCCESS);
                        /* NOT REACHED */
+               case 'w':
+                       fprintf(stderr, "WARNING: Using urandom as source of "
+                           "entropy for key material is a really bad idea.\n");
+                       opts->weak_keys_and_salt = 1;
+                       break;
                case 'x':
-                       if (h_prf != NULL)
+                       if (opts->h_prf_algo != NULL)
                                usage();
-                       if ((h_prf = check_prf_algo(optarg, 0)) == NULL) {
+                       if ((opts->h_prf_algo = check_prf_algo(optarg, 0)) == NULL) {
                                if (strcmp(optarg, "help") == 0)
-                                       exit(0);
+                                       exit(EXIT_SUCCESS);
                                else
                                        usage();
                                /* NOT REACHED */
                        }
                        break;
                case 'y':
-                       if (h_cipher_chain != NULL)
+                       if (opts->h_cipher_chain != NULL)
                                usage();
-                       if ((h_cipher_chain = check_cipher_chain(optarg, 0)) == NULL) {
+                       if ((opts->h_cipher_chain = check_cipher_chain(optarg, 0)) == NULL) {
                                if (strcmp(optarg, "help") == 0)
-                                       exit(0);
+                                       exit(EXIT_SUCCESS);
                                else
                                        usage();
                                /* NOT REACHED */
                        }
                        break;
+               case 'z':
+                       opts->secure_erase = 0;
+                       break;
+               case FLAG_LONG_FDE:
+                       opts->flags |= TC_FLAG_FDE;
+                       break;
+               case FLAG_LONG_USE_BACKUP:
+                       opts->flags |= TC_FLAG_BACKUP;
+                       break;
+               case FLAG_LONG_USE_HDR_FILE:
+                       opts->flags |= TC_FLAG_HDR_FROM_FILE;
+                       _set_str_opt(hdr_file_in);
+                       break;
+               case FLAG_LONG_USE_HHDR_FILE:
+                       opts->flags |= TC_FLAG_H_HDR_FROM_FILE;
+                       _set_str_opt(h_hdr_file_in);
+                       break;
+               case FLAG_LONG_MOD:
+                       modify_vol = 1;
+                       break;
+               case FLAG_LONG_MOD_KF:
+                       if ((error = opts_add_keyfile_new(opts, optarg)) != 0) {
+                               fprintf(stderr, "Could not add keyfile: %s\n", optarg);
+                               exit(EXIT_FAILURE);
+                       }
+                       break;
+               case FLAG_LONG_MOD_PRF:
+                       if (opts->new_prf_algo != NULL)
+                               usage();
+                       if ((opts->new_prf_algo = check_prf_algo(optarg, 0)) == NULL) {
+                               if (strcmp(optarg, "help") == 0)
+                                       exit(EXIT_SUCCESS);
+                               else
+                                       usage();
+                               /* NOT REACHED */
+                       }
+                       break;
+               case FLAG_LONG_MOD_NONE:
+                       opts->new_prf_algo = NULL;
+                       opts->flags |= TC_FLAG_ONLY_RESTORE;
+                       opts->flags |= TC_FLAG_BACKUP;
+                       break;
+               case FLAG_LONG_MOD_TO_FILE:
+                       opts->flags |= TC_FLAG_SAVE_TO_FILE;
+                       _set_str_opt(hdr_file_out);
+                       break;
+               case FLAG_LONG_NO_RETRIES:
+                       opts->retries = 1;
+                       break;
                case 'h':
                case '?':
                default:
@@ -257,37 +431,44 @@ main(int argc, char *argv[])
        argv += optind;
 
        /* Check arguments */
-       if (!((map_vol || info_vol || create_vol) && dev != NULL) ||
-           (map_vol && info_vol) ||
-           (map_vol && create_vol) ||
-           (create_vol && info_vol) ||
-           (contain_hidden && !create_vol) ||
-           (sflag && (sys_dev == NULL)) ||
-           (map_vol && (map_name == NULL)) ||
-           (!(protect_hidden || create_vol) && n_hkeyfiles > 0)) {
+       if (!(((map_vol || info_vol || create_vol || modify_vol) && opts->dev != NULL) ||
+           ((unmap_vol || info_map) && opts->map_name != NULL)) ||
+           (TC_FLAG_SET(opts->flags, SYS) && TC_FLAG_SET(opts->flags, FDE)) ||
+           (map_vol + info_vol + create_vol + unmap_vol + info_map + modify_vol > 1) ||
+           (opts->hidden && !create_vol) ||
+           (TC_FLAG_SET(opts->flags, SYS) && (opts->sys_dev == NULL)) ||
+           (TC_FLAG_SET(opts->flags, ONLY_RESTORE) && (opts->n_newkeyfiles > 0 || opts->new_prf_algo != NULL)) ||
+           (TC_FLAG_SET(opts->flags, BACKUP) && (opts->sys_dev != NULL || TC_FLAG_SET(opts->flags, FDE))) ||
+           (map_vol && (opts->map_name == NULL)) ||
+           (unmap_vol && (opts->map_name == NULL)) ||
+           (!modify_vol && opts->n_newkeyfiles > 0) ||
+           (!modify_vol && opts->new_prf_algo != NULL) ||
+           (!modify_vol && TC_FLAG_SET(opts->flags, ONLY_RESTORE)) ||
+           (!modify_vol && TC_FLAG_SET(opts->flags, SAVE_TO_FILE)) ||
+           (!(opts->protect_hidden || create_vol) && opts->n_hkeyfiles > 0)) {
                usage();
                /* NOT REACHED */
        }
 
        /* Create a new volume */
        if (create_vol) {
-               error = create_volume(dev, contain_hidden, keyfiles, nkeyfiles,
-                   h_keyfiles, n_hkeyfiles, prf, cipher_chain, h_prf,
-                   h_cipher_chain, NULL, NULL,
-                   0, 1 /* interactive */);
+               error = create_volume(opts);
                if (error) {
-                       tc_log(1, "could not create new volume on %s\n", dev);
+                       tc_log(1, "could not create new volume on %s\n", opts->dev);
                }
+       } else if (info_map) {
+               error = info_mapped_volume(opts);
        } else if (info_vol) {
-               error = info_volume(dev, sflag, sys_dev, protect_hidden,
-                   keyfiles, nkeyfiles, h_keyfiles, n_hkeyfiles, NULL, NULL,
-                   1 /* interactive */, DEFAULT_RETRIES, 0);
+               error = info_volume(opts);
        } else if (map_vol) {
-               error = map_volume(map_name,
-                   dev, sflag, sys_dev, protect_hidden,
-                   keyfiles, nkeyfiles, h_keyfiles, n_hkeyfiles, NULL, NULL,
-                   1 /* interactive */, DEFAULT_RETRIES, 0);
+               error = map_volume(opts);
+       } else if (unmap_vol) {
+               error = dm_teardown(opts->map_name, NULL);
+       } else if (modify_vol) {
+               error = modify_volume(opts);
        }
 
        return error;
 }
+
+#undef _set_str_opt
index 54abdb3..ee357f8 100644 (file)
@@ -29,7 +29,7 @@
 .\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd July 5, 2011
+.Dd December 08, 2013
 .Dt TCPLAY 8
 .Os
 .Sh NAME
@@ -40,6 +40,8 @@
 .Fl c
 .Fl d Ar device
 .Op Fl g
+.Op Fl z
+.Op Fl w
 .Op Fl a Ar pbkdf_hash
 .Op Fl b Ar cipher
 .Op Fl f Ar keyfile_hidden
 .Op Fl f Ar keyfile_hidden
 .Op Fl k Ar keyfile
 .Op Fl s Ar system_device
+.Op Fl -fde
+.Op Fl -use-backup
+.Op Fl -use-hdr-file Ar hdr_file
+.Op Fl -use-hidden-hdr-file Ar hdr_file
+.Nm
+.Fl j Ar mapping
 .Nm
 .Fl m Ar mapping
 .Fl d Ar device
 .Op Fl f Ar keyfile_hidden
 .Op Fl k Ar keyfile
 .Op Fl s Ar system_device
+.Op Fl t
+.Op Fl -fde
+.Op Fl -use-backup
+.Op Fl -use-hdr-file Ar hdr_file
+.Op Fl -use-hidden-hdr-file Ar hdr_file
+.Nm
+.Fl -modify
+.Fl d Ar device
+.Op Fl k Ar keyfile
+.Op Fl -new-keyfile Ar new_keyfile
+.Op Fl -new-pbkdf-prf Ar pbkdf_hash
+.Op Fl s Ar system_device
+.Op Fl -fde
+.Op Fl -use-backup
+.Op Fl -use-hdr-file Ar hdr_file
+.Op Fl -use-hidden-hdr-file Ar hdr_file
+.Op Fl -save-hdr-backup Ar hdr_file
+.Op Fl w
+.Nm
+.Fl -modify
+.Fl d Ar device
+.Op Fl k Ar keyfile
+.Fl -restore-from-backup-hdr
+.Op Fl w
+.Nm
+.Fl u Ar mapping
 .Nm
 .Fl h | v
 .Sh DESCRIPTION
@@ -79,6 +113,16 @@ Print help message and exit.
 .It Fl i , Fl -info
 Print out information about the encrypted device specified by
 .Fl -device .
+.It Fl j Ar mapping , Fl -info-mapped Ns = Ns Ar mapping
+Print out information about the mapped tcplay volume specified
+by
+.Ar mapping .
+Information such as key CRC and the PBKDF2 PRF is not available
+via this command.
+.It Fl -modify
+Modify the volume header.
+This mode allows changing passphrase, keyfiles, PBKDF2 PRF as
+well as restoring from a backup header.
 .It Fl m Ar mapping , Fl -map Ns = Ns Ar mapping
 Map the encrypted TrueCrypt volume on the device specified by
 .Fl -device
@@ -86,6 +130,19 @@ as a
 .Xr dm 4
 mapping called
 .Ar mapping .
+The
+.Ar mapping
+argument should not contain any spaces or special characters.
+.It Fl u Ar mapping , Fl -unmap Ns = Ns Ar mapping
+Removes (unmaps) the
+.Xr dm 4
+mapping specified by
+.Ar mapping
+as well as any related cascade mappings.
+If you mapped a volume using full disk encryption and created
+mapping for individual partitions using
+.Xr kpartx 8 ,
+you must remove these prior to unmapping the volume.
 .It Fl v, Fl -version
 Print version message and exit.
 .El
@@ -107,6 +164,9 @@ If you only intend to map a hidden volume, the
 option has to be used.
 This option can appear multiple times; if so, multiple
 keyfiles will be used.
+This option is not valid in the
+.Fl -modify
+mode.
 .It Fl k Ar keyfile , Fl -keyfile Ns = Ns Ar keyfile
 Specifies a
 .Ar keyfile
@@ -137,6 +197,12 @@ volume are those specified by
 .Fl -keyfile-hidden .
 The user will be prompted for the size of the hidden volume
 interactively.
+.It Fl w, Fl -weak-keys
+Use
+.Xr urandom 4
+for key material instead of a strong entropy source.
+This is in general a really bad idea and should only be used
+for testing.
 .It Fl x Ar pbkdf_hash , Fl -pbkdf-prf-hidden Ns = Ns Ar pbkdf_hash
 Specifies which hash algorithm to use for the PBKDF2 password
 derivation for the hidden volume.
@@ -155,12 +221,16 @@ If no cipher is specified, the same as for the outer volume
 will be used.
 To see which algorithms are supported, specify
 .Fl -cipher-hidden Ns = Ns Cm help .
+.It Fl z, Fl -insecure-erase
+Skips the secure erase of the disk.
+Use this option carefully as it is a security risk!
 .El
 .Pp
 Additional options for the
-.Fl -info
-and
+.Fl -info ,
 .Fl -map
+and
+.Fl -modify
 commands are:
 .Bl -tag -width indent
 .It Fl e, Fl -protect-hidden
@@ -169,18 +239,115 @@ its reported size will be adjusted accordingly to the size of
 the hidden volume contained in it.
 Both the hidden volume and outer volume passphrase and keyfiles
 will be required.
+This option only applies to the
+.Fl -info
+and
+.Fl -map
+commands.
 .It Fl s Ar system_device , Fl -system-encryption Ns = Ns Ar system_device
 This option is required if you are attempting to access a device
 that uses system encryption, for example an encrypted
 .Tn Windows
 system partition.
+It does not apply to disks using full disk encryption.
 The
 .Fl -device
 option will point at the actual encrypted partition, while the
 .Ar system_device
 argument will point to the parent device (i.e.\& underlying physical disk)
 of the encrypted partition.
+.It Fl -fde
+This option is intended to be used with disks using full disk encryption (FDE).
+When a disk has been encrypted using TrueCrypt's FDE, the complete disk
+is encrypted except for the first 63 sectors.
+The
+.Fl -device
+option should point to the whole disk device, not to any particular
+partition.
+The resultant mapping will cover the whole disk, and will not appear as
+separate partitions.
+To access individual partitions after mapping,
+.Xr kpartx 8
+can be used.
+.It Fl -use-backup
+This option is intended to be used when the primary headers of a volume
+have been corrupted.
+This option will force
+.Nm
+to use the backup headers, which are located at the end of the device,
+to access the volume.
+.El
+.Pp
+Additional options only for the
+.Fl -map
+command are:
+.Bl -tag -width indent
+.It Fl t , Fl -allow-trim
+This option enables TRIM (discard) support on the mapped volume.
+.El
+.Pp
+Additional options only for the
+.Fl -modify
+command are:
+.Bl -tag -width indent
+.It Fl -new-pbkdf-prf Ns = Ns Ar pbkdf_hash
+Specifies which hash algorithm to use for the PBKDF2 password
+derivation on reencrypting the volume header.
+If this option is not specified, the reencrypted header will
+use the current PRF.
+To see which algorithms are supported, specify
+.Fl -pbkdf-prf Ns = Ns Cm help .
+.It Fl -new-keyfile Ns = Ns Ar keyfile
+Specifies a
+.Ar keyfile
+to use in addition to the new passphrase on reencrypting the
+volume header.
+This option can appear multiple times; if so, multiple
+keyfiles will be used.
+.It Fl -restore-from-backup-hdr
+If this option is specified, neither
+.Fl -new-pbkdf-prf
+nor
+.Fl -new-keyfile
+should be specified.
+This option implies
+.Fl -use-backup .
+Use this option to restore the volume headers from the backup
+header.
 .El
+.Pp
+Sending a
+.Dv SIGINFO
+or
+.Dv SIGUSR1
+signal to a running
+.Nm
+process makes it print progress on slower tasks
+such as gathering entropy or wiping the volume.
+.Sh NOTES
+TrueCrypt limits passphrases to 64 characters (including the terminating
+null character).
+To be compatible with it,
+.Nm
+does the same.
+All passphrases (excluding keyfiles) are trimmed to 64 characters.
+Similarly, keyfiles are limited to a size of 1 MB, but up to
+256 keyfiles can be used.
+.Sh PLAUSIBLE DENIABILITY
+.Nm
+offers plausible deniability. Hidden volumes are created within an outer
+volume.
+Which volume is accessed solely depends on the passphrase and keyfile(s)
+used.
+If the passphrase and keyfiles for the outer volume are specified,
+no information about the existance of the hidden volume is exposed.
+Without knowledge of the passphrase and keyfile(s) of the hidden volume
+its existence remains unexposed.
+The hidden volume can be protected when mapping the outer volume by
+using the
+.Fl -protect-hidden
+option and specifying the passphrase and keyfiles for both the outer
+and hidden volumes.
 .Sh EXAMPLES
 Create a new TrueCrypt volume on
 .Pa /dev/vn0
@@ -224,9 +391,142 @@ using the keyfile
 .Fl -device Ns = Ns Cm /dev/vn0
 .Fl -keyfile Ns = Ns Cm hidden.key
 .Ed
+.Pp
+Map and mount the volume in the file
+.Pa secvol
+on Linux:
+.Bd -ragged -offset indent
+.Sy losetup Cm /dev/loop1 Cm secvol
+.Ed
+.Bd -ragged -offset indent
+.Nm Fl -map Ns = Ns Cm secv
+.Fl -device Ns = Ns Cm /dev/loop1
+.Ed
+.Bd -ragged -offset indent
+.Sy mount Cm /dev/mapper/secv Cm /mnt
+.Ed
+.Pp
+Similarly on
+.Dx :
+.Bd -ragged -offset indent
+.Sy vnconfig Cm vn1 Cm secvol
+.Ed
+.Bd -ragged -offset indent
+.Nm Fl -map Ns = Ns Cm secv
+.Fl -device Ns = Ns Cm /dev/vn1
+.Ed
+.Bd -ragged -offset indent
+.Sy mount Cm /dev/mapper/secv Cm /mnt
+.Ed
+.Pp
+Unmapping the volume
+.Sy truecrypt2
+on both Linux and
+.Dx
+after unmounting:
+.Bd -ragged -offset indent
+.Sy dmsetup Cm remove Cm truecrypt2
+.Ed
+.Pp
+Or alternatively:
+.Bd -ragged -offset indent
+.Nm Fl -unmap Ns = Ns Cm truecrypt2
+.Ed
+.Pp
+A hidden volume whose existance can be plausibly denied and its outer volume
+can for example be created with
+.Bd -ragged -offset indent
+.Nm Fl -create
+.Fl -hidden
+.Fl -device Ns = Ns Cm /dev/loop0
+.Fl -cipher Ns = Ns Cm AES-256-XTS,TWOFISH-256-XTS
+.Fl -pbkdf-prf Ns = Ns Cm whirlpool
+.Fl -keyfile Ns = Ns Cm one.key
+.Fl -cipher-hidden Ns = Ns Cm AES-256-XTS
+.Fl -pbkdf-prf-hidden Ns = Ns Cm whirlpool
+.Fl -keyfile-hidden Ns = Ns Cm hidden.key
+.Ed
+.Pp
+.Nm
+will prompt the user for the passphrase for both the outer and hidden volume
+as well as the size of the hidden volume inside the outer volume.
+The hidden volume will be created inside the area spanned by the outer volume.
+The hidden volume can optionally use a different cipher and prf function
+as specified by the
+.Fl -cipher-hidden
+and
+.Fl -pbkdf-prf-hidden
+options.
+Which volume is later accessed depends only on which passphrase and keyfile(s)
+are being used,
+so that the existance of the hidden volume remains unknown without knowledge
+of the passphrase and keyfile it is protected by since it is located within
+the outer volume.
+To map the outer volume without potentially damaging the hidden volume,
+the passphrase and keyfile(s) of the hidden volume must be known and provided
+alongside the
+.Fl -protect-hidden
+option.
+.Pp
+A disk encrypted using full disk encryption can be mapped using
+.Bd -ragged -offset indent
+.Nm Fl -map Ns = Ns Cm tcplay_sdb
+.Fl -device Ns = Ns Cm /dev/sdb
+.Fl -fde
+.Ed
+.Pp
+To access individual partitions on the now mapped disk,
+the following command will generate mappings for each
+individual partition on the encrypted disk:
+.Bd -ragged -offset indent
+.Sy kpartx Fl -av Cm /dev/mapper/tcplay_sdb
+.Ed
+.Pp
+To restore the main volume header from the backup header, the following
+command can be used:
+.Bd -ragged -offset indent
+.Nm Fl -modify
+.Fl -device Ns = Ns Cm /dev/sdb
+.Fl -restore-from-backup-hdr
+.Ed
+.Pp
+As with most other commands, which header is saved (used as source) depends
+on the passphrase and keyfiles used.
+.Pp
+To save a backup copy of a header, the following command can be used:
+.Bd -ragged -offset indent
+.Nm Fl -modify
+.Fl -device Ns = Ns Cm /dev/sdb
+.Fl -save-hdr-backup Ns = Ns Cm /tmp/sdb_backup_header.hdr
+.Ed
+.Pp
+As with most other commands, which header is saved (used as source) depends
+on the passphrase and keyfiles used.
+.Pp
+To restore a header from a backup header file, the following command can be
+used:
+.Bd -ragged -offset indent
+.Nm Fl -modify
+.Nm -use-hdr-file Ns = Ns Cm /tmp/sdb_backup_header.hdr
+.Ed
+.Pp
+Similarly, to restore a hidden header from a backup header file:
+.Bd -ragged -offset indent
+.Nm Fl -modify
+.Nm -use-hidden-hdr-file Ns = Ns Cm /tmp/sdb_backup_hidden_header.hdr
+.Ed
+.Pp
+Which header is used as the source of the operation will still depend on the
+passphrase and keyfiles used.
+Even if you use the
+.Fl -use-hidden-hdr-file
+option, if you specify the passphrase and keyfiles for the main header, the
+main header will be used instead.
 .Sh SEE ALSO
 .Xr crypttab 5 ,
-.Xr cryptsetup 8
+.Xr cryptsetup 8 ,
+.Xr dmsetup 8 ,
+.Xr kpartx 8
 .Sh HISTORY
 The
 .Nm