root/sbin/cgdconfig/cgdconfig.c
/* $NetBSD: cgdconfig.c,v 1.64 2026/03/17 17:24:47 christos Exp $ */

/*-
 * Copyright (c) 2002, 2003 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Roland C. Dowdeswell.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
__COPYRIGHT("@(#) Copyright (c) 2002, 2003\
 The NetBSD Foundation, Inc.  All rights reserved.");
__RCSID("$NetBSD: cgdconfig.c,v 1.64 2026/03/17 17:24:47 christos Exp $");
#endif

#ifdef HAVE_ARGON2
#include <argon2.h>
#endif
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <util.h>
#include <paths.h>
#include <dirent.h>

/* base64 gunk */
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>

#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/bootblock.h>
#include <sys/disklabel.h>
#include <sys/disklabel_gpt.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/resource.h>
#include <sys/statvfs.h>
#include <sys/bitops.h>
#include <sys/queue.h>

#include <dev/cgdvar.h>

#include <ufs/ffs/fs.h>

#ifdef HAVE_ZFS
#include <sys/vdev_impl.h>
#include <sha2.h>
#endif

#include "params.h"
#include "pkcs5_pbkdf2.h"
#include "utils.h"
#include "cgdconfig.h"
#include "prog_ops.h"
#include "hkdf_hmac_sha256.h"

#define CGDCONFIG_CFILE         CGDCONFIG_DIR "/cgd.conf"

enum action {
         ACTION_DEFAULT,                /* default -> configure */
         ACTION_CONFIGURE,              /* configure, with paramsfile */
         ACTION_UNCONFIGURE,            /* unconfigure */
         ACTION_GENERATE,               /* generate a paramsfile */
         ACTION_GENERATE_CONVERT,       /* generate a ``dup'' paramsfile */
         ACTION_CONFIGALL,              /* configure all from config file */
         ACTION_UNCONFIGALL,            /* unconfigure all from config file */
         ACTION_CONFIGSTDIN,            /* configure, key from stdin */
         ACTION_LIST,                   /* list configured devices */
         ACTION_PRINTKEY,               /* print key to stdout */
         ACTION_PRINTALLKEYS,           /* print all keys to stdout */
};

/* if nflag is set, do not configure/unconfigure the cgd's */

static int      nflag = 0;

/* if Sflag is set, generate shared keys */

static int      Sflag = 0;

/* if pflag is set to PFLAG_STDIN read from stdin rather than getpass(3) */

#define PFLAG_GETPASS           0x01
#define PFLAG_GETPASS_ECHO      0x02
#define PFLAG_GETPASS_MASK      0x03
#define PFLAG_STDIN             0x04
static int      pflag = PFLAG_GETPASS;

/*
 * When configuring all cgds, save a cache of shared keys for key
 * derivation.  If the _first_ verification with a shared key fails, we
 * chuck it and start over; if _subsequent_ verifications fail, we
 * assume the disk is wrong and give up on it immediately.
 */

struct sharedkey {
        int                      alg;
        string_t                *id;
        bits_t                  *key;
        LIST_ENTRY(sharedkey)    list;
        SLIST_ENTRY(sharedkey)   used;
        int                      verified;
};
static LIST_HEAD(, sharedkey) sharedkeys;
SLIST_HEAD(sharedkeyhits, sharedkey);

static int      configure(int, char **, struct params *, int);
static int      configure_stdin(struct params *, int argc, char **);
static int      generate(struct params *, int, char **, const char *,
                    const char *);
static int      generate_convert(struct params *, int, char **, const char *,
                    const char *);
static int      unconfigure(int, char **, struct params *, int);
static int      do_all(const char *, int, char **,
                       int (*)(int, char **, struct params *, int));
static int      do_list(int, char **);
static int      printkey(const char *, const char *, const char *, ...)
                    __printflike(3,4);
static int      printkey1(int, char **, struct params *, int);
static int      do_printkey(int, char **);

#define CONFIG_FLAGS_FROMALL    1       /* called from configure_all() */
#define CONFIG_FLAGS_FROMMAIN   2       /* called from main() */

static int       configure_params(int, const char *, const char *,
                                  struct params *);
static void      eliminate_cores(void);
static bits_t   *getkey(const char *, struct keygen *, size_t,
                     struct sharedkeyhits *);
static bits_t   *getkey_storedkey(const char *, struct keygen *, size_t);
static bits_t   *getkey_randomkey(const char *, struct keygen *, size_t, int);
#ifdef HAVE_ARGON2
static bits_t   *getkey_argon2id(const char *, struct keygen *, size_t);
#endif
static bits_t   *getkey_pkcs5_pbkdf2(const char *, struct keygen *, size_t,
                                     int);
static bits_t   *getkey_shell_cmd(const char *, struct keygen *, size_t);
static char     *maybe_getpass(char *);
static int       opendisk_werror(const char *, char *, size_t);
static int       unconfigure_fd(int);
static int       verify(struct params *, int);
static int       verify_disklabel(int);
static int       verify_ffs(int);
static int       verify_reenter(struct params *);
static int       verify_mbr(int);
static int       verify_gpt(int);
#ifdef HAVE_ZFS
static int       verify_zfs(int);
#endif

__dead static void       usage(void);

/* Verbose Framework */
static unsigned verbose = 0;

#define VERBOSE(x,y)    if (verbose >= x) y
#define VPRINTF(x,y)    if (verbose >= x) (void)printf y

static void
usage(void)
{

        (void)fprintf(stderr, "usage: %s [-enpv] [-V vmeth] cgd dev "
            "[paramsfile]\n", getprogname());
        (void)fprintf(stderr, "       %s -C [-enpv] [-f configfile]\n",
            getprogname());
        (void)fprintf(stderr, "       %s -G [-enpSv] [-i ivmeth] [-k kgmeth] "
            "[-P paramsfile] [-o outfile] paramsfile\n", getprogname());
        (void)fprintf(stderr, "       %s -g [-Sv] [-i ivmeth] [-k kgmeth] "
            "[-P paramsfile] [-o outfile] alg [keylen]\n", getprogname());
        (void)fprintf(stderr, "       %s -l [-v[v]] [cgd]\n", getprogname());
        (void)fprintf(stderr, "       %s -s [-nv] [-i ivmeth] cgd dev alg "
            "[keylen]\n", getprogname());
        (void)fprintf(stderr, "       %s -t paramsfile\n", getprogname());
        (void)fprintf(stderr, "       %s -T [-f configfile]\n", getprogname());
        (void)fprintf(stderr, "       %s -U [-nv] [-f configfile]\n",
            getprogname());
        (void)fprintf(stderr, "       %s -u [-nv] cgd\n", getprogname());
        exit(EXIT_FAILURE);
}

static int
parse_size_t(const char *s, size_t *l)
{
        char *endptr;
        long v;

        errno = 0;
        v = strtol(s, &endptr, 10);
        if ((v == LONG_MIN || v == LONG_MAX) && errno)
                return -1;
        if (v < INT_MIN || v > INT_MAX) {
                errno = ERANGE;
                return -1;
        }
        if (endptr == s) {
                errno = EINVAL;
                return -1;
        }
        *l = (size_t)v;
        return 0;
}

static void
set_action(enum action *action, enum action value)
{
        if (*action != ACTION_DEFAULT)
                usage();
        *action = value;
}

int
main(int argc, char **argv)
{
        struct params *p;
        struct params *tp;
        struct keygen *kg;
        enum action action = ACTION_DEFAULT;
        int     ch;
        const char      *cfile = NULL;
        const char      *outfile = NULL;
        const char      *Pfile = NULL;

        setprogname(*argv);
        if (hkdf_hmac_sha256_selftest())
                err(EXIT_FAILURE, "Crypto self-test failed");
        eliminate_cores();
        if (mlockall(MCL_FUTURE))
                err(EXIT_FAILURE, "Can't lock memory");
        p = params_new();
        kg = NULL;

        while ((ch = getopt(argc, argv, "CGP:STUV:b:ef:gi:k:lno:sptuv")) != -1)
                switch (ch) {
                case 'C':
                        set_action(&action, ACTION_CONFIGALL);
                        break;
                case 'G':
                        set_action(&action, ACTION_GENERATE_CONVERT);
                        break;
                case 'P':
                        if (Pfile)
                                usage();
                        Pfile = estrdup(optarg);
                        break;
                case 'S':
                        Sflag = 1;
                        break;
                case 'T':
                        set_action(&action, ACTION_PRINTALLKEYS);
                        break;
                case 'U':
                        set_action(&action, ACTION_UNCONFIGALL);
                        break;
                case 'V':
                        tp = params_verify_method(string_fromcharstar(optarg));
                        if (!tp)
                                usage();
                        p = params_combine(p, tp);
                        break;
                case 'b':
                        {
                                size_t size;

                                if (parse_size_t(optarg, &size) == -1)
                                        usage();
                                tp = params_bsize(size);
                                if (!tp)
                                        usage();
                                p = params_combine(p, tp);
                        }
                        break;
                case 'e':
                        pflag = PFLAG_GETPASS_ECHO;
                        break;
                case 'f':
                        if (cfile)
                                usage();
                        cfile = estrdup(optarg);
                        break;
                case 'g':
                        set_action(&action, ACTION_GENERATE);
                        break;
                case 'i':
                        tp = params_ivmeth(string_fromcharstar(optarg));
                        p = params_combine(p, tp);
                        break;
                case 'k':
                        kg = keygen_method(string_fromcharstar(optarg));
                        if (!kg)
                                usage();
                        keygen_addlist(&p->keygen, kg);
                        break;
                case 'l':
                        set_action(&action, ACTION_LIST);
                        break;
                case 'n':
                        nflag = 1;
                        break;
                case 'o':
                        if (outfile)
                                usage();
                        outfile = estrdup(optarg);
                        break;
                case 'p':
                        pflag = PFLAG_STDIN;
                        break;
                case 's':
                        set_action(&action, ACTION_CONFIGSTDIN);
                        break;
                case 't':
                        set_action(&action, ACTION_PRINTKEY);
                        break;
                case 'u':
                        set_action(&action, ACTION_UNCONFIGURE);
                        break;
                case 'v':
                        verbose++;
                        break;
                default:
                        usage();
                        /* NOTREACHED */
                }

        argc -= optind;
        argv += optind;

        if (!outfile)
                outfile = "";
        if (!cfile)
                cfile = "";

        if (prog_init && prog_init() == -1)
                err(1, "init failed");

        /* validate the consistency of the arguments */
        if (Pfile != NULL &&
            action != ACTION_GENERATE &&
            action != ACTION_GENERATE_CONVERT) {
                warnx("-P is only for use with -g/-G action");
                usage();
        }
        if (Pfile != NULL && !Sflag) {
                warnx("-P only makes sense with -S flag");
        }
        if (Sflag &&
            action != ACTION_GENERATE &&
            action != ACTION_GENERATE_CONVERT) {
                warnx("-S is only for use with -g/-G action");
                usage();
        }

        switch (action) {
        case ACTION_DEFAULT:    /* ACTION_CONFIGURE is the default */
        case ACTION_CONFIGURE:
                return configure(argc, argv, p, CONFIG_FLAGS_FROMMAIN);
        case ACTION_UNCONFIGURE:
                return unconfigure(argc, argv, NULL, CONFIG_FLAGS_FROMMAIN);
        case ACTION_GENERATE:
                return generate(p, argc, argv, outfile, Pfile);
        case ACTION_GENERATE_CONVERT:
                return generate_convert(p, argc, argv, outfile, Pfile);
        case ACTION_CONFIGALL:
                return do_all(cfile, argc, argv, configure);
        case ACTION_UNCONFIGALL:
                return do_all(cfile, argc, argv, unconfigure);
        case ACTION_CONFIGSTDIN:
                return configure_stdin(p, argc, argv);
        case ACTION_LIST:
                return do_list(argc, argv);
        case ACTION_PRINTKEY:
                return do_printkey(argc, argv);
        case ACTION_PRINTALLKEYS:
                return do_all(cfile, argc, argv, printkey1);
        default:
                errx(EXIT_FAILURE, "undefined action");
                /* NOTREACHED */
        }
}

static bits_t *
getsubkey_hkdf_hmac_sha256(bits_t *key, bits_t *info, size_t subkeylen)
{
        bits_t          *ret = NULL;
        uint8_t         *tmp;

        tmp = emalloc(BITS2BYTES(subkeylen));
        if (hkdf_hmac_sha256(tmp, BITS2BYTES(subkeylen),
                bits_getbuf(key), BITS2BYTES(bits_len(key)),
                bits_getbuf(info), BITS2BYTES(bits_len(info)))) {
                warnx("failed to derive HKDF-HMAC-SHA256 subkey");
                goto out;
        }

        ret = bits_new(tmp, subkeylen);

out:    free(tmp);
        return ret;
}

static bits_t *
getsubkey(int alg, bits_t *key, bits_t *info, size_t subkeylen)
{

        switch (alg) {
        case SHARED_ALG_HKDF_HMAC_SHA256:
                return getsubkey_hkdf_hmac_sha256(key, info, subkeylen);
        default:
                warnx("unrecognised shared key derivation method %d", alg);
                return NULL;
        }
}

static bits_t *
getkey(const char *dev, struct keygen *kg, size_t len0,
    struct sharedkeyhits *skh)
{
        bits_t  *ret = NULL;
        bits_t  *tmp;

        VPRINTF(3, ("getkey(\"%s\", %p, %zu) called\n", dev, kg, len0));
        for (; kg; kg=kg->next) {
                struct sharedkey *sk = NULL;
                size_t len = len0;

                /*
                 * If shared, determine the shared key's length and
                 * probe the cache of shared keys.
                 */
                if (kg->kg_sharedid) {
                        const char *id = string_tocharstar(kg->kg_sharedid);

                        len = kg->kg_sharedlen;
                        LIST_FOREACH(sk, &sharedkeys, list) {
                                if (kg->kg_sharedalg == sk->alg &&
                                    kg->kg_sharedlen == bits_len(sk->key) &&
                                    strcmp(id, string_tocharstar(sk->id)) == 0)
                                        break;
                        }
                        if (sk) {
                                tmp = sk->key;
                                goto derive;
                        }
                }

                switch (kg->kg_method) {
                case KEYGEN_STOREDKEY:
                        tmp = getkey_storedkey(dev, kg, len);
                        break;
                case KEYGEN_RANDOMKEY:
                        tmp = getkey_randomkey(dev, kg, len, 1);
                        break;
                case KEYGEN_URANDOMKEY:
                        tmp = getkey_randomkey(dev, kg, len, 0);
                        break;
#ifdef HAVE_ARGON2
                case KEYGEN_ARGON2ID:
                        tmp = getkey_argon2id(dev, kg, len);
                        break;
#endif
                case KEYGEN_PKCS5_PBKDF2_SHA1:
                        tmp = getkey_pkcs5_pbkdf2(dev, kg, len, 0);
                        break;
                /* provide backwards compatibility for old config files */
                case KEYGEN_PKCS5_PBKDF2_OLD:
                        tmp = getkey_pkcs5_pbkdf2(dev, kg, len, 1);
                        break;
                case KEYGEN_SHELL_CMD:
                        tmp = getkey_shell_cmd(dev, kg, len);
                        break;
                default:
                        warnx("unrecognised keygen method %d in getkey()",
                            kg->kg_method);
                        if (ret)
                                bits_free(ret);
                        return NULL;
                }

                /*
                 * If shared, cache the key.
                 */
                if (kg->kg_sharedid) {
                        assert(sk == NULL);
                        sk = ecalloc(1, sizeof(*sk));
                        sk->alg = kg->kg_sharedalg;
                        sk->id = string_dup(kg->kg_sharedid);
                        sk->key = tmp;
                        LIST_INSERT_HEAD(&sharedkeys, sk, list);
                        sk->verified = 0;
                }

derive:         if (kg->kg_sharedid) {
                        assert(sk != NULL);
                        /*
                         * tmp holds the master key, owned by the
                         * struct sharedkey record; replace it by the
                         * derived subkey.
                         */
                        tmp = getsubkey(kg->kg_sharedalg, tmp,
                            kg->kg_sharedinfo, len0);
                        if (tmp == NULL) {
                                if (ret)
                                        bits_free(ret);
                                return NULL;
                        }
                        if (skh)
                                SLIST_INSERT_HEAD(skh, sk, used);
                }
                if (ret)
                        ret = bits_xor_d(tmp, ret);
                else
                        ret = tmp;
        }

        return ret;
}

/*ARGSUSED*/
static bits_t *
getkey_storedkey(const char *target, struct keygen *kg, size_t keylen)
{
        return bits_dup(kg->kg_key);
}

/*ARGSUSED*/
static bits_t *
getkey_randomkey(const char *target, struct keygen *kg, size_t keylen, int hard)
{
        return bits_getrandombits(keylen, hard);
}

static char *
maybe_getpass(char *prompt)
{
        char     buf[1024];
        char    *p = NULL;
        char    *tmp, *pass;

        switch (pflag) {
        case PFLAG_GETPASS:
                p = getpass_r(prompt, buf, sizeof(buf));
                break;

        case PFLAG_GETPASS_ECHO:
                p = getpassfd(prompt, buf, sizeof(buf), NULL,
                    GETPASS_ECHO|GETPASS_ECHO_NL|GETPASS_NEED_TTY, 0);
                break;

        case PFLAG_STDIN:
                p = fgets(buf, sizeof(buf), stdin);
                if (p) {
                        tmp = strchr(p, '\n');
                        if (tmp)
                                *tmp = '\0';
                }
                break;

        default:
                errx(EXIT_FAILURE, "pflag set inappropriately?");
        }

        if (!p)
                err(EXIT_FAILURE, "failed to read passphrase");

        pass = estrdup(p);
        explicit_memset(buf, 0, sizeof(buf));

        return pass;
}

/*ARGSUSED*/
/*
 * XXX take, and pass through, a compat flag that indicates whether we
 * provide backwards compatibility with a previous bug.  The previous
 * behaviour is indicated by the keygen method pkcs5_pbkdf2, and a
 * non-zero compat flag. The new default, and correct keygen method is
 * called pcks5_pbkdf2/sha1.  When the old method is removed, so will
 * be the compat argument.
 */
static bits_t *
getkey_pkcs5_pbkdf2(const char *target, struct keygen *kg, size_t keylen,
    int compat)
{
        bits_t          *ret;
        char            *passp;
        char             buf[1024];
        u_int8_t        *tmp;

        snprintf(buf, sizeof(buf), "%s's passphrase%s:", target,
            pflag & PFLAG_GETPASS_ECHO ? " (echo)" : "");
        passp = maybe_getpass(buf);
        if (pkcs5_pbkdf2(&tmp, BITS2BYTES(keylen), (uint8_t *)passp,
            strlen(passp),
            bits_getbuf(kg->kg_salt), BITS2BYTES(bits_len(kg->kg_salt)),
            kg->kg_iterations, compat)) {
                warnx("failed to generate PKCS#5 PBKDF2 key");
                return NULL;
        }

        ret = bits_new(tmp, keylen);
        kg->kg_key = bits_dup(ret);
        explicit_memset(passp, 0, strlen(passp));
        free(passp);
        free(tmp);
        return ret;
}

#ifdef HAVE_ARGON2
static bits_t *
getkey_argon2id(const char *target, struct keygen *kg, size_t keylen)
{
        bits_t *ret;
        char *passp;
        char buf[1024];
        uint8_t raw[256];
        int error;

        snprintf(buf, sizeof(buf), "%s's passphrase%s:", target,
            pflag & PFLAG_GETPASS_ECHO ? " (echo)" : "");
        passp = maybe_getpass(buf);
        if ((error = argon2_hash(kg->kg_iterations, kg->kg_memory,
            kg->kg_parallelism,
            passp, strlen(passp),
            bits_getbuf(kg->kg_salt),
            BITS2BYTES(bits_len(kg->kg_salt)),
            raw, sizeof(raw),
            NULL, 0,
            Argon2_id, kg->kg_version)) != ARGON2_OK) {
                warnx("failed to generate Argon2id key, error code %d", error);
                return NULL;
        }

        ret = bits_new(raw, keylen);
        kg->kg_key = bits_dup(ret);
        explicit_memset(passp, 0, strlen(passp));
        explicit_memset(raw, 0, sizeof(raw));
        free(passp);
        return ret;
}
#endif

/*ARGSUSED*/
static bits_t *
getkey_shell_cmd(const char *target, struct keygen *kg, size_t keylen)
{
        FILE    *f;
        bits_t  *ret;
        int     status;

        if ((f = popen(string_tocharstar(kg->kg_cmd), "r")) == NULL)
                errx(1, "command failed");
        if ((ret = bits_fget(f, keylen)) == NULL)
                errx(1, "command output too short");
        if ((status = pclose(f)) != 0)
                err(1, "command failed with status %d", status);

        return ret;
}

/*ARGSUSED*/
static int
unconfigure(int argc, char **argv, struct params *inparams, int flags)
{
        int     fd;
        int     ret;
        char    buf[MAXPATHLEN] = "";

        /* only complain about additional arguments, if called from main() */
        if (flags == CONFIG_FLAGS_FROMMAIN && argc != 1)
                usage();

        /* if called from do_all(), then ensure that 2 or 3 args exist */
        if (flags == CONFIG_FLAGS_FROMALL && (argc < 2 || argc > 3))
                return -1;

        fd = opendisk1(*argv, O_RDWR, buf, sizeof(buf), 1, prog_open);
        if (fd == -1) {
                int saved_errno = errno;

                warn("can't open cgd \"%s\", \"%s\"", *argv, buf);

                /* this isn't fatal with nflag != 0 */
                if (!nflag)
                        return saved_errno;
        }

        VPRINTF(1, ("%s (%s): clearing\n", *argv, buf));

        if (nflag)
                return 0;

        ret = unconfigure_fd(fd);
        (void)prog_close(fd);
        return ret;
}

static int
unconfigure_fd(int fd)
{
        struct  cgd_ioctl ci;

        if (prog_ioctl(fd, CGDIOCCLR, &ci) == -1) {
                warn("ioctl");
                return -1;
        }

        return 0;
}

/*ARGSUSED*/
static int
configure(int argc, char **argv, struct params *inparams, int flags)
{
        struct params   *p;
        struct keygen   *kg;
        int              fd;
        int              loop = 0;
        int              ret;
        char             cgdname[PATH_MAX];
        char             devicename[PATH_MAX];
        const char      *dev = NULL;    /* XXX: gcc */

        if (argc < 2 || argc > 3) {
                /* print usage and exit, only if called from main() */
                if (flags == CONFIG_FLAGS_FROMMAIN) {
                        warnx("wrong number of args");
                        usage();
                }
                return -1;
        }

        if ((
          fd = opendisk1(*argv, O_RDWR, cgdname, sizeof(cgdname), 1, prog_open)
            ) != -1) {
                struct cgd_user cgu;

                cgu.cgu_unit = -1;
                if (prog_ioctl(fd, CGDIOCGET, &cgu) != -1 && cgu.cgu_dev != 0) {
                        warnx("device %s already in use", *argv);
                        prog_close(fd);
                        return -1;
                }
                prog_close(fd);
        }

        dev = getfsspecname(devicename, sizeof(devicename), argv[1]);
        if (dev == NULL) {
                warnx("getfsspecname failed: %s", devicename);
                return -1;
        }

        if (argc == 2) {
                char pfile[MAXPATHLEN];

                /* make string writable for basename */
                strlcpy(pfile, dev, sizeof(pfile));
                p = params_cget(basename(pfile));
        } else
                p = params_cget(argv[2]);

        if (!p)
                return -1;

        /*
         * over-ride with command line specifications and fill in default
         * values.
         */

        p = params_combine(p, inparams);
        ret = params_filldefaults(p);
        if (ret) {
                params_free(p);
                return ret;
        }

        if (!params_verify(p)) {
                warnx("params invalid");
                return -1;
        }

        /*
         * loop over configuring the disk and checking to see if it
         * verifies properly.  We open and close the disk device each
         * time, because if the user passes us the block device we
         * need to flush the buffer cache.
         *
         * We only loop if one of the verification methods prompts for
         * a password.
         */

        for (kg = p->keygen;
            (pflag & PFLAG_GETPASS_MASK) && kg;
            kg = kg->next)
                if (kg->kg_method == KEYGEN_ARGON2ID ||
                    kg->kg_method == KEYGEN_PKCS5_PBKDF2_SHA1 ||
                    kg->kg_method == KEYGEN_PKCS5_PBKDF2_OLD) {
                        loop = 1;
                        break;
                }

        for (;;) {
                struct sharedkeyhits skh;
                struct sharedkey *sk, *sk1;
                int all_verified;

                SLIST_INIT(&skh);

                fd = opendisk_werror(argv[0], cgdname, sizeof(cgdname));
                if (fd == -1)
                        return -1;

                if (p->key)
                        bits_free(p->key);

                p->key = getkey(argv[1], p->keygen, p->keylen, &skh);
                if (!p->key)
                        goto bail_err;

                ret = configure_params(fd, cgdname, dev, p);
                if (ret)
                        goto bail_err;

                ret = verify(p, fd);
                if (ret == -1) {
                        (void)unconfigure_fd(fd);
                        goto bail_err;
                }
                if (ret == 0) {         /* success */
                        SLIST_FOREACH(sk, &skh, used)
                                sk->verified = 1;
                        break;
                }

                (void)unconfigure_fd(fd);
                (void)prog_close(fd);

                /*
                 * For shared keys: If the shared keys were all
                 * verified already, assume something is wrong with the
                 * disk and give up.  If not, flush the cache of the
                 * ones that have not been verified in case we can try
                 * again with passphrase re-entry.
                 */
                if (!SLIST_EMPTY(&skh)) {
                        all_verified = 1;
                        SLIST_FOREACH_SAFE(sk, &skh, used, sk1) {
                                all_verified &= sk->verified;
                                if (!sk->verified) {
                                        LIST_REMOVE(sk, list);
                                        free(sk);
                                }
                        }
                        if (all_verified)
                                loop = 0;
                }

                if (!loop) {
                        warnx("verification failed permanently");
                        goto bail_err;
                }

                warnx("verification failed, please reenter passphrase");
        }

        params_free(p);
        (void)prog_close(fd);
        return 0;

 bail_err:;
        params_free(p);
        (void)prog_close(fd);
        return -1;
}

static int
configure_stdin(struct params *p, int argc, char **argv)
{
        int              fd;
        int              ret;
        char             cgdname[PATH_MAX];
        char             devicename[PATH_MAX];
        const char      *dev;

        if (argc < 3 || argc > 4)
                usage();

        dev = getfsspecname(devicename, sizeof(devicename), argv[1]);
        if (dev == NULL) {
                warnx("getfsspecname failed: %s", devicename);
                return -1;
        }

        p->algorithm = string_fromcharstar(argv[2]);
        if (argc > 3) {
                size_t keylen;

                if (parse_size_t(argv[3], &keylen) == -1) {
                        warn("failed to parse key length");
                        return -1;
                }
                p->keylen = keylen;
        }

        ret = params_filldefaults(p);
        if (ret)
                return ret;

        fd = opendisk_werror(argv[0], cgdname, sizeof(cgdname));
        if (fd == -1)
                return -1;

        p->key = bits_fget(stdin, p->keylen);
        if (!p->key) {
                warnx("failed to read key from stdin");
                return -1;
        }

        return configure_params(fd, cgdname, dev, p);
}

static int
opendisk_werror(const char *cgd, char *buf, size_t buflen)
{
        int     fd;

        VPRINTF(3, ("opendisk_werror(%s, %s, %zu) called.\n", cgd,buf,buflen));

        /* sanity */
        if (!cgd || !buf)
                return -1;

        if (nflag) {
                if (strlcpy(buf, cgd, buflen) >= buflen)
                        return -1;
                return 0;
        }

        fd = opendisk1(cgd, O_RDWR, buf, buflen, 0, prog_open);
        if (fd == -1)
                warnx("can't open cgd \"%s\", \"%s\"", cgd, buf);

        return fd;
}

static int
configure_params(int fd, const char *cgd, const char *dev, struct params *p)
{
        struct cgd_ioctl ci;

        /* sanity */
        if (!cgd || !dev)
                return -1;

        (void)memset(&ci, 0x0, sizeof(ci));
        ci.ci_disk = dev;
        ci.ci_alg = string_tocharstar(p->algorithm);
        ci.ci_ivmethod = string_tocharstar(p->ivmeth);
        ci.ci_key = bits_getbuf(p->key);
        ci.ci_keylen = p->keylen;
        ci.ci_blocksize = p->bsize;

        VPRINTF(1, ("    with alg %s keylen %zu blocksize %zu ivmethod %s\n",
            string_tocharstar(p->algorithm), p->keylen, p->bsize,
            string_tocharstar(p->ivmeth)));
        VPRINTF(2, ("key: "));
        VERBOSE(2, bits_fprint(stdout, p->key));
        VPRINTF(2, ("\n"));

        if (nflag)
                return 0;

        if (prog_ioctl(fd, CGDIOCSET, &ci) == -1) {
                int saved_errno = errno;
                warn("ioctl");
                return saved_errno;
        }

        return 0;
}

/*
 * verify returns 0 for success, -1 for unrecoverable error, or 1 for retry.
 */

#define SCANSIZE        8192

static int
verify(struct params *p, int fd)
{

        switch (p->verify_method) {
        case VERIFY_NONE:
                return 0;
        case VERIFY_DISKLABEL:
                return verify_disklabel(fd);
        case VERIFY_FFS:
                return verify_ffs(fd);
        case VERIFY_REENTER:
                return verify_reenter(p);
        case VERIFY_MBR:
                return verify_mbr(fd);
        case VERIFY_GPT:
                return verify_gpt(fd);
#ifdef HAVE_ZFS
        case VERIFY_ZFS:
                return verify_zfs(fd);
#endif
        default:
                warnx("unimplemented verification method");
                return -1;
        }
}

static int
verify_disklabel(int fd)
{
        struct  disklabel l;
        ssize_t ret;
        char    buf[SCANSIZE];

        /*
         * we simply scan the first few blocks for a disklabel, ignoring
         * any MBR/filecore sorts of logic.  MSDOS and RiscOS can't read
         * a cgd, anyway, so it is unlikely that there will be non-native
         * partition information.
         */

        ret = prog_pread(fd, buf, SCANSIZE, 0);
        if (ret < 0) {
                warn("can't read disklabel area");
                return -1;
        }

        /* now scan for the disklabel */

        return disklabel_scan(&l, buf, (size_t)ret);
}

static int
verify_mbr(int fd)
{
        struct mbr_sector mbr;
        ssize_t ret;
        char    buf[SCANSIZE];

        /*
         * we read the first blocks to avoid sector size issues and
         * verify the MBR in the beginning
         */

        ret = prog_pread(fd, buf, SCANSIZE, 0);
        if (ret < 0) {
                warn("can't read mbr area");
                return -1;
        }

        memcpy(&mbr, buf, sizeof(mbr));
        if (le16toh(mbr.mbr_magic) != MBR_MAGIC)
                return 1;

        return 0;
}

static uint32_t crc32_tab[] = {
        0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
        0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
        0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
        0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
        0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
        0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
        0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
        0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
        0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
        0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
        0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
        0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
        0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
        0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
        0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
        0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
        0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
        0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
        0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
        0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
        0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
        0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
        0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
        0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
        0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
        0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
        0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
        0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
        0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
        0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
        0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
        0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
        0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
        0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
        0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
        0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
        0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
        0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
        0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
        0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
        0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
        0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
        0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};

static uint32_t
crc32(const void *buf, size_t size)
{
        const uint8_t *p;
        uint32_t crc;

        p = buf;
        crc = ~0U;

        while (size--)
                crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8);

        return crc ^ ~0U;
}

static int
verify_gpt(int fd)
{
        struct   gpt_hdr hdr;
        ssize_t  ret;
        char     buf[SCANSIZE];
        unsigned blksize;
        size_t   off;

        /*
         * we read the first blocks to avoid sector size issues and
         * verify the GPT header.
         */

        ret = prog_pread(fd, buf, SCANSIZE, 0);
        if (ret < 0) {
                warn("can't read gpt area");
                return -1;
        }

        for (blksize = DEV_BSIZE;
             (off = (blksize * GPT_HDR_BLKNO)) <= SCANSIZE - sizeof(hdr);
             blksize <<= 1) {

                memcpy(&hdr, &buf[off], sizeof(hdr));
                if (memcmp(hdr.hdr_sig, GPT_HDR_SIG, sizeof(hdr.hdr_sig)) == 0
                    && le32toh(hdr.hdr_revision) == GPT_HDR_REVISION
                    && le32toh(hdr.hdr_size) == GPT_HDR_SIZE) {

                        hdr.hdr_crc_self = 0;
                        if (crc32(&hdr, sizeof(hdr))) {
                                return 0;
                        }
                }
        }

        return 1;
}

#ifdef HAVE_ZFS

#define ZIO_CHECKSUM_BE(zcp)                                    \
{                                                               \
        (zcp)->zc_word[0] = BE_64((zcp)->zc_word[0]);           \
        (zcp)->zc_word[1] = BE_64((zcp)->zc_word[1]);           \
        (zcp)->zc_word[2] = BE_64((zcp)->zc_word[2]);           \
        (zcp)->zc_word[3] = BE_64((zcp)->zc_word[3]);           \
}

static int
verify_zfs(int fd)
{
        off_t vdev_size;
        int rv = 1;

        if (prog_ioctl(fd, DIOCGMEDIASIZE, &vdev_size) == -1) {
                warn("%s: ioctl", __func__);
                return rv;
        }

        vdev_size = P2ALIGN(vdev_size, (uint64_t)sizeof(vdev_label_t));
        vdev_phys_t *vdev_phys = emalloc(sizeof(*vdev_phys));
        for (size_t i = 0; i < VDEV_LABELS; i++) {
                off_t vdev_phys_off = (i < VDEV_LABELS / 2 ?
                    i * sizeof(vdev_label_t) :
                    vdev_size - (VDEV_LABELS - i) * sizeof(vdev_label_t))
                    + offsetof(vdev_label_t, vl_vdev_phys);

                ssize_t ret = prog_pread(fd, vdev_phys, sizeof(*vdev_phys),
                    vdev_phys_off);
                if (ret == -1) {
                        warn("%s: read failed", __func__);
                        goto out;
                }
                if ((size_t)ret < sizeof(*vdev_phys)) {
                        warnx("%s: incomplete block", __func__);
                        goto out;
                }

                bool byteswap;
                switch (vdev_phys->vp_zbt.zec_magic) {
                case BSWAP_64(ZEC_MAGIC):
                        byteswap = true;
                        break;
                case ZEC_MAGIC:
                        byteswap = false;
                        break;
                default:
                        goto out;
                }

                zio_cksum_t cksum_found = vdev_phys->vp_zbt.zec_cksum;
                if (byteswap) {
                        ZIO_CHECKSUM_BSWAP(&cksum_found);
                }

                ZIO_SET_CHECKSUM(&vdev_phys->vp_zbt.zec_cksum,
                    vdev_phys_off, 0, 0, 0);
                if (byteswap) {
                        ZIO_CHECKSUM_BSWAP(&vdev_phys->vp_zbt.zec_cksum);
                }

                SHA256_CTX ctx;
                zio_cksum_t cksum_real;

                SHA256Init(&ctx);
                SHA256Update(&ctx, (uint8_t *)vdev_phys, sizeof *vdev_phys);
                SHA256Final(&cksum_real, &ctx);

                /*
                 * For historical reasons the on-disk sha256 checksums are
                 * always in big endian format.
                 * (see cddl/osnet/dist/uts/common/fs/zfs/sha256.c)
                 */
                ZIO_CHECKSUM_BE(&cksum_real);

                if (!ZIO_CHECKSUM_EQUAL(cksum_found, cksum_real)) {
                        warnx("%s: checksum mismatch on vdev label %zu",
                            __func__, i);
                        warnx("%s: found %#jx, %#jx, %#jx, %#jx", __func__,
                            (uintmax_t)cksum_found.zc_word[0],
                            (uintmax_t)cksum_found.zc_word[1],
                            (uintmax_t)cksum_found.zc_word[2],
                            (uintmax_t)cksum_found.zc_word[3]);
                        warnx("%s: expected %#jx, %#jx, %#jx, %#jx", __func__,
                            (uintmax_t)cksum_real.zc_word[0],
                            (uintmax_t)cksum_real.zc_word[1],
                            (uintmax_t)cksum_real.zc_word[2],
                            (uintmax_t)cksum_real.zc_word[3]);
                        goto out;
                }
        }
        rv = 0;
out:
        free(vdev_phys);
        return rv;
}

#endif

static off_t sblock_try[] = SBLOCKSEARCH;

static int
verify_ffs(int fd)
{
        size_t  i;

        for (i = 0; sblock_try[i] != -1; i++) {
                union {
                    char        buf[SBLOCKSIZE];
                    struct      fs fs;
                } u;
                ssize_t ret;

                ret = prog_pread(fd, &u, sizeof(u), sblock_try[i]);
                if (ret < 0) {
                        warn("pread");
                        break;
                } else if ((size_t)ret < sizeof(u)) {
                        warnx("pread: incomplete block");
                        break;
                }
                switch (u.fs.fs_magic) {
                case FS_UFS1_MAGIC:
                case FS_UFS2_MAGIC:
                case FS_UFS2EA_MAGIC:
                case FS_UFS1_MAGIC_SWAPPED:
                case FS_UFS2_MAGIC_SWAPPED:
                case FS_UFS2EA_MAGIC_SWAPPED:
                        return 0;
                default:
                        continue;
                }
        }

        return 1;       /* failure */
}

static int
verify_reenter(struct params *p)
{
        struct keygen *kg;
        bits_t *orig_key, *key = NULL;
        int ret;

        ret = 0;
        for (kg = p->keygen; kg && !ret; kg = kg->next) {
                if (kg->kg_method != KEYGEN_ARGON2ID &&
                    kg->kg_method != KEYGEN_PKCS5_PBKDF2_SHA1 &&
                    kg->kg_method != KEYGEN_PKCS5_PBKDF2_OLD)
                        continue;

                orig_key = kg->kg_key;
                kg->kg_key = NULL;

                switch (kg->kg_method) {
#ifdef HAVE_ARGON2
                case KEYGEN_ARGON2ID:
                        key = getkey_argon2id("re-enter device", kg,
                                bits_len(orig_key));
                        break;
#endif
                case KEYGEN_PKCS5_PBKDF2_SHA1:
                        key = getkey_pkcs5_pbkdf2("re-enter device", kg,
                                bits_len(orig_key), 0);
                        break;
                case KEYGEN_PKCS5_PBKDF2_OLD:
                        key = getkey_pkcs5_pbkdf2("re-enter device", kg,
                                bits_len(orig_key), 1);
                        break;
                default:
                        warnx("unsupported keygen method");
                        kg->kg_key = orig_key;
                        return -1;
                }

                ret = !bits_match(key, orig_key);

                bits_free(key);
                bits_free(kg->kg_key);
                kg->kg_key = orig_key;
        }

        return ret;
}

static int
generate(struct params *p, int argc, char **argv, const char *outfile,
    const char *Pfile)
{
        int      ret;

        if (argc < 1 || argc > 2)
                usage();

        p->algorithm = string_fromcharstar(argv[0]);
        if (argc > 1) {
                size_t keylen;

                if (parse_size_t(argv[1], &keylen) == -1) {
                        warn("Failed to parse key length");
                        return -1;
                }
                p->keylen = keylen;
        }

        ret = params_filldefaults(p);
        if (ret)
                return ret;

        if (Pfile) {
                struct params *pp;

                pp = params_cget(Pfile);
                if (pp == NULL)
                        return -1;
                if (!params_verify(pp)) {
                        params_free(pp);
                        warnx("invalid parameters file \"%s\"", Pfile);
                        return -1;
                }
                p = params_combine(pp, p);
                keygen_stripstored(&p->keygen);
                if (!p->keygen) {
                        warnx("no keygen in parameters file \"%s\"", Pfile);
                        return -1;
                }
        } else {
                if (!p->keygen) {
                        p->keygen = keygen_generate(KEYGEN_PKCS5_PBKDF2_SHA1);
                        if (!p->keygen)
                                return -1;
                }

                if (keygen_filldefaults(p->keygen, p->keylen)) {
                        warnx("Failed to generate defaults for keygen");
                        return -1;
                }
        }

        if (Sflag) {
                if (Pfile)
                        ret = keygen_tweakshared(p->keygen);
                else
                        ret = keygen_makeshared(p->keygen);
                if (ret)
                        return ret;
        }

        if (!params_verify(p)) {
                warnx("invalid parameters generated");
                return -1;
        }

        return params_cput(p, outfile);
}

static int
generate_convert(struct params *p, int argc, char **argv, const char *outfile,
    const char *Pfile)
{
        struct params   *oldp;
        struct keygen   *kg;
        int              ret;

        if (argc != 1)
                usage();

        oldp = params_cget(*argv);
        if (!oldp)
                return -1;

        /* for sanity, we ensure that none of the keygens are randomkey */
        for (kg=p->keygen; kg; kg=kg->next)
                if ((kg->kg_method == KEYGEN_RANDOMKEY) ||
                    (kg->kg_method == KEYGEN_URANDOMKEY)) {
                        warnx("can't preserve randomly generated key");
                        goto bail;
                }
        for (kg=oldp->keygen; kg; kg=kg->next)
                if ((kg->kg_method == KEYGEN_RANDOMKEY) ||
                    (kg->kg_method == KEYGEN_URANDOMKEY)) {
                        warnx("can't preserve randomly generated key");
                        goto bail;
                }

        if (!params_verify(oldp)) {
                warnx("invalid old parameters file \"%s\"", *argv);
                return -1;
        }

        oldp->key = getkey("old file", oldp->keygen, oldp->keylen, NULL);

        /* we copy across the non-keygen info, here. */

        string_free(p->algorithm);
        string_free(p->ivmeth);

        p->algorithm = string_dup(oldp->algorithm);
        p->ivmeth = string_dup(oldp->ivmeth);
        p->keylen = oldp->keylen;
        p->bsize = oldp->bsize;
        if (p->verify_method == VERIFY_UNKNOWN)
                p->verify_method = oldp->verify_method;

        params_free(oldp);

        if (Pfile) {
                struct params *pp;

                pp = params_cget(Pfile);
                if (pp == NULL)
                        return -1;
                if (!params_verify(pp)) {
                        params_free(pp);
                        warnx("invalid parameters file \"%s\"", Pfile);
                        return -1;
                }
                p = params_combine(pp, p);
                keygen_stripstored(&p->keygen);
                if (!p->keygen) {
                        warnx("no keygen in parameters file \"%s\"", Pfile);
                        return -1;
                }
        } else {
                if (!p->keygen) {
                        p->keygen = keygen_generate(KEYGEN_PKCS5_PBKDF2_SHA1);
                        if (!p->keygen)
                                return -1;
                }
                (void)params_filldefaults(p);
                (void)keygen_filldefaults(p->keygen, p->keylen);
        }

        if (Sflag) {
                if (Pfile)
                        ret = keygen_tweakshared(p->keygen);
                else
                        ret = keygen_makeshared(p->keygen);
                if (ret)
                        return ret;
        }

        p->key = getkey("new file", p->keygen, p->keylen, NULL);

        kg = keygen_generate(KEYGEN_STOREDKEY);
        kg->kg_key = bits_xor(p->key, oldp->key);
        keygen_addlist(&p->keygen, kg);

        if (!params_verify(p)) {
                warnx("can't generate new parameters file");
                return -1;
        }

        return params_cput(p, outfile);
 bail:;
        params_free(oldp);
        return -1;
}

static int
/*ARGSUSED*/
do_all(const char *cfile, int argc, char **argv,
       int (*conf)(int, char **, struct params *, int))
{
        FILE             *f;
        size_t            len;
        size_t            lineno;
        int               my_argc;
        int               ret;
        const char       *fn;
        char             *line;
        char            **my_argv;

        if (argc > 0)
                usage();

        if (!cfile[0])
                fn = CGDCONFIG_CFILE;
        else
                fn = cfile;

        f = fopen(fn, "r");
        if (f == NULL) {
                warn("could not open config file \"%s\"", fn);
                return -1;
        }

        ret = 0;
        lineno = 0;
        for (;;) {
                line = fparseln(f, &len, &lineno, "\\\\#", FPARSELN_UNESCALL);
                if (!line)
                        break;
                if (!*line)
                        continue;

                my_argv = words(line, &my_argc);
                ret = conf(my_argc, my_argv, NULL, CONFIG_FLAGS_FROMALL);
                if (ret) {
                        warnx("action failed on \"%s\" line %lu", fn,
                            (u_long)lineno);
                        break;
                }
                words_free(my_argv, my_argc);
        }
        return ret;
}

static const char *
iv_method(int mode)
{

        switch (mode) {
        case CGD_CIPHER_CBC_ENCBLKNO8:
                return "encblkno8";
        case CGD_CIPHER_CBC_ENCBLKNO1:
                return "encblkno1";
        default:
                return "unknown";
        }
}


static void
show(const char *dev)
{
        char path[64];
        struct cgd_user cgu;
        int fd;

        fd = opendisk(dev, O_RDONLY, path, sizeof(path), 0);
        if (fd == -1) {
                warn("open: %s", dev);
                return;
        }

        cgu.cgu_unit = -1;
        if (prog_ioctl(fd, CGDIOCGET, &cgu) == -1) {
                close(fd);
                err(1, "CGDIOCGET");
        }

        printf("%s: ", dev);

        if (cgu.cgu_dev == 0) {
                printf("not in use");
                goto out;
        }

        dev = devname(cgu.cgu_dev, S_IFBLK);
        if (dev != NULL) {
                printf("%s ", dev);
        } else {
                printf("dev %llu,%llu ",
                    (unsigned long long)major(cgu.cgu_dev),
                    (unsigned long long)minor(cgu.cgu_dev));
        }

        if (verbose)
                printf("%s ", cgu.cgu_alg);
        if (verbose > 1) {
                printf("keylen %d ", cgu.cgu_keylen);
                printf("blksize %zd ", cgu.cgu_blocksize);
                printf("%s ", iv_method(cgu.cgu_mode));
        }

 out:;
        putchar('\n');
        close(fd);
}

static int
do_list(int argc, char **argv)
{

        if (argc != 0 && argc != 1)
                usage();

        if (argc) {
                show(argv[0]);
                return 0;
        }

        DIR *dirp;
        struct dirent *dp;
        __BITMAP_TYPE(, uint32_t, 65536) bm;

        __BITMAP_ZERO(&bm);

        if ((dirp = opendir(_PATH_DEV)) == NULL)
                err(1, "opendir: %s", _PATH_DEV);

        while ((dp = readdir(dirp)) != NULL) {
                char *ep;
                if (strncmp(dp->d_name, "rcgd", 4) != 0)
                        continue;
                errno = 0;
                int n = (int)strtol(dp->d_name + 4, &ep, 0);
                if (ep == dp->d_name + 4 || errno != 0) {
                        warnx("bad name %s", dp->d_name);
                        continue;
                }
                *ep = '\0';
                if (__BITMAP_ISSET(n, &bm))
                        continue;
                __BITMAP_SET(n, &bm);
                show(dp->d_name + 1);
        }

        closedir(dirp);
        return 0;
}

static int
printkey(const char *dev, const char *paramsfile, const char *fmt, ...)
{
        va_list va;
        struct params *p;
        const uint8_t *raw;
        size_t nbits, nbytes;
        size_t nb64;
        char *b64;
        int ret;

        p = params_cget(paramsfile);
        if (p == NULL)
                return -1;
        if (!params_verify(p)) {
                warnx("invalid parameters file \"%s\"", paramsfile);
                return -1;
        }
        p->key = getkey(dev, p->keygen, p->keylen, NULL);
        raw = bits_getbuf(p->key);
        nbits = bits_len(p->key);
        assert(nbits <= INT_MAX - 7);
        nbytes = BITS2BYTES(nbits);
        assert(nbytes <= 3*(INT_MAX/4) - 2);

        nb64 = 4*((nbytes + 2)/3);
        b64 = emalloc(nb64 + 2);
        ret = __b64_ntop(raw, nbytes, b64, nb64 + 1);
        assert(ret == (int)nb64);
        b64[nb64] = '\n';
        b64[nb64 + 1] = '\0';

        va_start(va, fmt);
        vprintf(fmt, va);
        va_end(va);
        if (fwrite(b64, nb64 + 1, 1, stdout) != 1)
                err(1, "fwrite");
        fflush(stdout);
        return ferror(stdout);
}

static int
printkey1(int argc, char **argv, struct params *inparams, int flags)
{
        char devicename[PATH_MAX], paramsfilebuf[PATH_MAX];
        const char *dev, *paramsfile;

        assert(flags == CONFIG_FLAGS_FROMALL);

        if (argc < 2 || argc > 3)
                return -1;

        dev = getfsspecname(devicename, sizeof(devicename), argv[1]);
        if (dev == NULL) {
                warnx("getfsspecname failed: %s", devicename);
                return -1;
        }

        if (argc == 2) {
                strlcpy(paramsfilebuf, dev, sizeof(paramsfilebuf));
                paramsfile = basename(paramsfilebuf);
        } else {
                paramsfile = argv[2];
        }

        return printkey(dev, paramsfile, "%s: ", dev);
}

static int
do_printkey(int argc, char **argv)
{

        if (argc != 1)
                usage();
        return printkey("key", argv[0], "");
}

static void
eliminate_cores(void)
{
        struct rlimit   rlp;

        rlp.rlim_cur = 0;
        rlp.rlim_max = 0;
        if (setrlimit(RLIMIT_CORE, &rlp) == -1)
                err(EXIT_FAILURE, "Can't disable cores");
}