/* $OpenBSD: bioctl.c,v 1.157 2023/10/07 12:20:10 kn Exp $ */ /* * Copyright (c) 2004, 2005 Marco Peereboom * All rights reserved. * * 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 AUTHORS 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 AUTHORS 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 /* NODEV */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct locator { int channel; int target; int lun; }; struct timing { int interval; int start; }; static void __dead usage(void); const char *str2locator(const char *, struct locator *); const char *str2patrol(const char *, struct timing *); void bio_status(struct bio_status *); int bio_parse_devlist(char *, dev_t *); void bio_kdf_derive(struct sr_crypto_kdfinfo *, struct sr_crypto_pbkdf *, char *, int); void bio_kdf_generate(struct sr_crypto_kdfinfo *); int bcrypt_pbkdf_autorounds(void); void derive_key(u_int32_t, int, u_int8_t *, size_t, u_int8_t *, size_t, char *, int); void bio_inq(char *); void bio_alarm(char *); int bio_getvolbyname(char *); void bio_setstate(char *, int, char *); void bio_setblink(char *, char *, int); void bio_blink(char *, int, int); void bio_createraid(u_int16_t, char *, char *); void bio_deleteraid(char *); void bio_changepass(char *); u_int32_t bio_createflags(char *); char *bio_vis(char *); void bio_diskinq(char *); void bio_patrol(char *); int devh = -1; int human; int verbose; u_int32_t cflags = 0; int rflag = -1; /* auto */ char *passfile; void *bio_cookie; int interactive = 1; int main(int argc, char *argv[]) { struct bio_locate bl; u_int64_t func = 0; char *devicename = NULL; char *realname = NULL, *al_arg = NULL; char *bl_arg = NULL, *dev_list = NULL; char *key_disk = NULL; const char *errstr; int ch, blink = 0, changepass = 0, diskinq = 0; int ss_func = 0; u_int16_t cr_level = 0; int biodev = 0; if (argc < 2) usage(); while ((ch = getopt(argc, argv, "a:b:C:c:dH:hik:l:O:Pp:qr:R:st:u:v")) != -1) { switch (ch) { case 'a': /* alarm */ func |= BIOC_ALARM; al_arg = optarg; break; case 'b': /* blink */ func |= BIOC_BLINK; blink = BIOC_SBBLINK; bl_arg = optarg; break; case 'C': /* creation flags */ cflags = bio_createflags(optarg); break; case 'c': /* create */ func |= BIOC_CREATERAID; if (strcmp(optarg, "1C") == 0) { cr_level = 0x1C; } else if (isdigit((unsigned char)*optarg)) { cr_level = strtonum(optarg, 0, 10, &errstr); if (errstr != NULL) errx(1, "Invalid RAID level"); } else if (strlen(optarg) == 1) { cr_level = *optarg; } else { errx(1, "Invalid RAID level"); } break; case 'd': /* delete volume */ func |= BIOC_DELETERAID; break; case 'u': /* unblink */ func |= BIOC_BLINK; blink = BIOC_SBUNBLINK; bl_arg = optarg; break; case 'H': /* set hotspare */ func |= BIOC_SETSTATE; ss_func = BIOC_SSHOTSPARE; al_arg = optarg; break; case 'h': human = 1; break; case 'i': /* inquiry */ func |= BIOC_INQ; break; case 'k': /* Key disk. */ key_disk = optarg; break; case 'l': /* device list */ func |= BIOC_DEVLIST; dev_list = optarg; break; case 'P': /* Change passphrase. */ changepass = 1; break; case 'p': passfile = optarg; break; case 'r': if (strcmp(optarg, "auto") == 0) { rflag = -1; break; } rflag = strtonum(optarg, 16, 1<<30, &errstr); if (errstr != NULL) errx(1, "number of KDF rounds is %s: %s", errstr, optarg); break; case 'O': /* set a chunk to offline */ func |= BIOC_SETSTATE; ss_func = BIOC_SSOFFLINE; al_arg = optarg; break; case 'R': /* rebuild to provided chunk/CTL */ func |= BIOC_SETSTATE; ss_func = BIOC_SSREBUILD; al_arg = optarg; break; case 's': interactive = 0; break; case 't': /* patrol */ func |= BIOC_PATROL; al_arg = optarg; break; case 'v': verbose = 1; break; case 'q': diskinq = 1; break; default: usage(); /* NOTREACHED */ } } argc -= optind; argv += optind; if (argc != 1 || (changepass && func != 0)) usage(); if (func == 0) func |= BIOC_INQ; devicename = argv[0]; if (devicename == NULL) errx(1, "need device"); devh = opendev(devicename, O_RDWR, OPENDEV_PART, &realname); if (devh == -1) { devh = open("/dev/bio", O_RDWR); if (devh == -1) err(1, "Can't open %s", "/dev/bio"); memset(&bl, 0, sizeof(bl)); bl.bl_name = devicename; if (ioctl(devh, BIOCLOCATE, &bl) == -1) errx(1, "Can't locate %s device via %s", bl.bl_name, "/dev/bio"); bio_cookie = bl.bl_bio.bio_cookie; biodev = 1; devicename = NULL; } if (diskinq) { bio_diskinq(devicename); } else if (changepass && !biodev) { bio_changepass(devicename); } else if (func & BIOC_INQ) { bio_inq(devicename); } else if (func == BIOC_ALARM) { bio_alarm(al_arg); } else if (func == BIOC_BLINK) { bio_setblink(devicename, bl_arg, blink); } else if (func == BIOC_PATROL) { bio_patrol(al_arg); } else if (func == BIOC_SETSTATE) { bio_setstate(al_arg, ss_func, argv[0]); } else if (func == BIOC_DELETERAID && !biodev) { bio_deleteraid(devicename); } else if (func & BIOC_CREATERAID || func & BIOC_DEVLIST) { if (!(func & BIOC_CREATERAID)) errx(1, "need -c parameter"); if (!(func & BIOC_DEVLIST)) errx(1, "need -l parameter"); if (!biodev) errx(1, "must use bio device"); bio_createraid(cr_level, dev_list, key_disk); } return (0); } static void __dead usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-hiqv] [-a alarm-function] " "[-b channel:target[.lun]]\n" "\t[-H channel:target[.lun]] " "[-R chunk | channel:target[.lun]]\n" "\t[-t patrol-function] " "[-u channel:target[.lun]] " "device\n" " %s [-dhiPqsv] " "[-C flag[,...]] [-c raidlevel] [-k keydisk]\n" "\t[-l chunk[,...]] " "[-O device | channel:target[.lun]]\n" "\t[-p passfile] [-R chunk | channel:target[.lun]]\n" "\t[-r rounds] " "device\n", __progname, __progname); exit(1); } const char * str2locator(const char *string, struct locator *location) { const char *errstr; char parse[80], *targ, *lun; strlcpy(parse, string, sizeof parse); targ = strchr(parse, ':'); if (targ == NULL) return ("target not specified"); *targ++ = '\0'; lun = strchr(targ, '.'); if (lun != NULL) { *lun++ = '\0'; location->lun = strtonum(lun, 0, 256, &errstr); if (errstr) return (errstr); } else location->lun = 0; location->target = strtonum(targ, 0, 256, &errstr); if (errstr) return (errstr); location->channel = strtonum(parse, 0, 256, &errstr); if (errstr) return (errstr); return (NULL); } const char * str2patrol(const char *string, struct timing *timing) { const char *errstr; char parse[80], *interval = NULL, *start = NULL; timing->interval = 0; timing->start = 0; strlcpy(parse, string, sizeof parse); interval = strchr(parse, '.'); if (interval != NULL) { *interval++ = '\0'; start = strchr(interval, '.'); if (start != NULL) *start++ = '\0'; } if (interval != NULL) { /* -1 == continuously */ timing->interval = strtonum(interval, -1, INT_MAX, &errstr); if (errstr) return (errstr); } if (start != NULL) { timing->start = strtonum(start, 0, INT_MAX, &errstr); if (errstr) return (errstr); } return (NULL); } void bio_status(struct bio_status *bs) { extern char *__progname; char *prefix; int i; if (strlen(bs->bs_controller)) prefix = bs->bs_controller; else prefix = __progname; for (i = 0; i < bs->bs_msg_count; i++) fprintf(bs->bs_msgs[i].bm_type == BIO_MSG_INFO ? stdout : stderr, "%s: %s\n", prefix, bs->bs_msgs[i].bm_msg); if (bs->bs_status == BIO_STATUS_ERROR) { if (bs->bs_msg_count == 0) errx(1, "unknown error"); else exit(1); } } void bio_inq(char *name) { char *status, *cache; char size[64], scsiname[16], volname[32]; char percent[20], seconds[20]; int i, d, volheader, hotspare, unused; char encname[16], serial[32]; struct bioc_inq bi; struct bioc_vol bv; struct bioc_disk bd; memset(&bi, 0, sizeof(bi)); bi.bi_bio.bio_cookie = bio_cookie; if (ioctl(devh, BIOCINQ, &bi) == -1) { if (errno == ENOTTY) bio_diskinq(name); else err(1, "BIOCINQ"); return; } bio_status(&bi.bi_bio.bio_status); volheader = 0; for (i = 0; i < bi.bi_novol; i++) { memset(&bv, 0, sizeof(bv)); bv.bv_bio.bio_cookie = bio_cookie; bv.bv_volid = i; bv.bv_percent = -1; bv.bv_seconds = 0; if (ioctl(devh, BIOCVOL, &bv) == -1) err(1, "BIOCVOL"); bio_status(&bv.bv_bio.bio_status); if (name && strcmp(name, bv.bv_dev) != 0) continue; if (!volheader) { volheader = 1; printf("%-11s %-10s %14s %-8s\n", "Volume", "Status", "Size", "Device"); } percent[0] = '\0'; seconds[0] = '\0'; if (bv.bv_percent != -1) snprintf(percent, sizeof percent, " %d%% done", bv.bv_percent); if (bv.bv_seconds) snprintf(seconds, sizeof seconds, " %u seconds", bv.bv_seconds); switch (bv.bv_status) { case BIOC_SVONLINE: status = BIOC_SVONLINE_S; break; case BIOC_SVOFFLINE: status = BIOC_SVOFFLINE_S; break; case BIOC_SVDEGRADED: status = BIOC_SVDEGRADED_S; break; case BIOC_SVBUILDING: status = BIOC_SVBUILDING_S; break; case BIOC_SVREBUILD: status = BIOC_SVREBUILD_S; break; case BIOC_SVSCRUB: status = BIOC_SVSCRUB_S; break; case BIOC_SVINVALID: default: status = BIOC_SVINVALID_S; } switch (bv.bv_cache) { case BIOC_CVWRITEBACK: cache = BIOC_CVWRITEBACK_S; break; case BIOC_CVWRITETHROUGH: cache = BIOC_CVWRITETHROUGH_S; break; case BIOC_CVUNKNOWN: default: cache = BIOC_CVUNKNOWN_S; } snprintf(volname, sizeof volname, "%s %u", bi.bi_dev, bv.bv_volid); unused = 0; hotspare = 0; if (bv.bv_level == -1 && bv.bv_nodisk == 1) hotspare = 1; else if (bv.bv_level == -2 && bv.bv_nodisk == 1) unused = 1; else { if (human) fmt_scaled(bv.bv_size, size); else snprintf(size, sizeof size, "%14llu", bv.bv_size); printf("%11s %-10s %14s %-7s ", volname, status, size, bv.bv_dev); switch (bv.bv_level) { case 'C': printf("CRYPTO%s%s\n", percent, seconds); break; case 'c': printf("CONCAT%s%s\n", percent, seconds); break; case 0x1C: case 0x1E: printf("RAID%X%s%s %s\n", bv.bv_level, percent, seconds, cache); break; default: printf("RAID%u%s%s %s\n", bv.bv_level, percent, seconds, cache); break; } } for (d = 0; d < bv.bv_nodisk; d++) { memset(&bd, 0, sizeof(bd)); bd.bd_bio.bio_cookie = bio_cookie; bd.bd_diskid = d; bd.bd_volid = i; bd.bd_patrol.bdp_percent = -1; bd.bd_patrol.bdp_seconds = 0; if (ioctl(devh, BIOCDISK, &bd) == -1) err(1, "BIOCDISK"); bio_status(&bd.bd_bio.bio_status); switch (bd.bd_status) { case BIOC_SDONLINE: status = BIOC_SDONLINE_S; break; case BIOC_SDOFFLINE: status = BIOC_SDOFFLINE_S; break; case BIOC_SDFAILED: status = BIOC_SDFAILED_S; break; case BIOC_SDREBUILD: status = BIOC_SDREBUILD_S; break; case BIOC_SDHOTSPARE: status = BIOC_SDHOTSPARE_S; break; case BIOC_SDUNUSED: status = BIOC_SDUNUSED_S; break; case BIOC_SDSCRUB: status = BIOC_SDSCRUB_S; break; case BIOC_SDINVALID: default: status = BIOC_SDINVALID_S; } if (hotspare || unused) ; /* use volname from parent volume */ else snprintf(volname, sizeof volname, " %3u", bd.bd_diskid); if (bv.bv_level == 'C' && bd.bd_size == 0) snprintf(size, sizeof size, "%14s", "key disk"); else if (human) fmt_scaled(bd.bd_size, size); else snprintf(size, sizeof size, "%14llu", bd.bd_size); snprintf(scsiname, sizeof scsiname, "%u:%u.%u", bd.bd_channel, bd.bd_target, bd.bd_lun); if (bd.bd_procdev[0]) strlcpy(encname, bd.bd_procdev, sizeof encname); else strlcpy(encname, "noencl", sizeof encname); if (bd.bd_serial[0]) strlcpy(serial, bd.bd_serial, sizeof serial); else strlcpy(serial, "unknown serial", sizeof serial); percent[0] = '\0'; seconds[0] = '\0'; if (bd.bd_patrol.bdp_percent != -1) snprintf(percent, sizeof percent, " patrol %d%% done", bd.bd_patrol.bdp_percent); if (bd.bd_patrol.bdp_seconds) snprintf(seconds, sizeof seconds, " %u seconds", bd.bd_patrol.bdp_seconds); printf("%11s %-10s %14s %-7s %-6s <%s>\n", volname, status, size, scsiname, encname, bd.bd_vendor); if (verbose) printf("%11s %-10s %14s %-7s %-6s '%s'%s%s\n", "", "", "", "", "", serial, percent, seconds); } } } void bio_alarm(char *arg) { struct bioc_alarm ba; memset(&ba, 0, sizeof(ba)); ba.ba_bio.bio_cookie = bio_cookie; switch (arg[0]) { case 'q': /* silence alarm */ /* FALLTHROUGH */ case 's': ba.ba_opcode = BIOC_SASILENCE; break; case 'e': /* enable alarm */ ba.ba_opcode = BIOC_SAENABLE; break; case 'd': /* disable alarm */ ba.ba_opcode = BIOC_SADISABLE; break; case 't': /* test alarm */ ba.ba_opcode = BIOC_SATEST; break; case 'g': /* get alarm state */ ba.ba_opcode = BIOC_GASTATUS; break; default: errx(1, "invalid alarm function: %s", arg); } if (ioctl(devh, BIOCALARM, &ba) == -1) err(1, "BIOCALARM"); bio_status(&ba.ba_bio.bio_status); if (arg[0] == 'g') printf("alarm is currently %s\n", ba.ba_status ? "enabled" : "disabled"); } int bio_getvolbyname(char *name) { int id = -1, i; struct bioc_inq bi; struct bioc_vol bv; memset(&bi, 0, sizeof(bi)); bi.bi_bio.bio_cookie = bio_cookie; if (ioctl(devh, BIOCINQ, &bi) == -1) err(1, "BIOCINQ"); bio_status(&bi.bi_bio.bio_status); for (i = 0; i < bi.bi_novol; i++) { memset(&bv, 0, sizeof(bv)); bv.bv_bio.bio_cookie = bio_cookie; bv.bv_volid = i; if (ioctl(devh, BIOCVOL, &bv) == -1) err(1, "BIOCVOL"); bio_status(&bv.bv_bio.bio_status); if (name && strcmp(name, bv.bv_dev) != 0) continue; id = i; break; } return (id); } void bio_setstate(char *arg, int status, char *devicename) { struct bioc_setstate bs; struct locator location; struct stat sb; const char *errstr; memset(&bs, 0, sizeof(bs)); if (stat(arg, &sb) == -1) { /* use CTL */ errstr = str2locator(arg, &location); if (errstr) errx(1, "Target %s: %s", arg, errstr); bs.bs_channel = location.channel; bs.bs_target = location.target; bs.bs_lun = location.lun; } else { /* use other id */ bs.bs_other_id = sb.st_rdev; bs.bs_other_id_type = BIOC_SSOTHER_DEVT; } bs.bs_bio.bio_cookie = bio_cookie; bs.bs_status = status; if (status != BIOC_SSHOTSPARE) { /* make sure user supplied a sd device */ bs.bs_volid = bio_getvolbyname(devicename); if (bs.bs_volid == -1) errx(1, "invalid device %s", devicename); } if (ioctl(devh, BIOCSETSTATE, &bs) == -1) err(1, "BIOCSETSTATE"); bio_status(&bs.bs_bio.bio_status); } void bio_setblink(char *name, char *arg, int blink) { struct locator location; struct bioc_blink bb; struct bioc_inq bi; struct bioc_vol bv; struct bioc_disk bd; const char *errstr; int v, d, rv; errstr = str2locator(arg, &location); if (errstr) errx(1, "Target %s: %s", arg, errstr); /* try setting blink on the device directly */ memset(&bb, 0, sizeof(bb)); bb.bb_bio.bio_cookie = bio_cookie; bb.bb_status = blink; bb.bb_target = location.target; bb.bb_channel = location.channel; rv = ioctl(devh, BIOCBLINK, &bb); if (rv == 0 && bb.bb_bio.bio_status.bs_status == BIO_STATUS_UNKNOWN) return; if (rv == 0 && bb.bb_bio.bio_status.bs_status == BIO_STATUS_SUCCESS) { bio_status(&bb.bb_bio.bio_status); return; } /* if the blink didn't work, try to find something that will */ memset(&bi, 0, sizeof(bi)); bi.bi_bio.bio_cookie = bio_cookie; if (ioctl(devh, BIOCINQ, &bi) == -1) err(1, "BIOCINQ"); bio_status(&bi.bi_bio.bio_status); for (v = 0; v < bi.bi_novol; v++) { memset(&bv, 0, sizeof(bv)); bv.bv_bio.bio_cookie = bio_cookie; bv.bv_volid = v; if (ioctl(devh, BIOCVOL, &bv) == -1) err(1, "BIOCVOL"); bio_status(&bv.bv_bio.bio_status); if (name && strcmp(name, bv.bv_dev) != 0) continue; for (d = 0; d < bv.bv_nodisk; d++) { memset(&bd, 0, sizeof(bd)); bd.bd_bio.bio_cookie = bio_cookie; bd.bd_volid = v; bd.bd_diskid = d; if (ioctl(devh, BIOCDISK, &bd) == -1) err(1, "BIOCDISK"); bio_status(&bd.bd_bio.bio_status); if (bd.bd_channel == location.channel && bd.bd_target == location.target && bd.bd_lun == location.lun) { if (bd.bd_procdev[0] != '\0') bio_blink(bd.bd_procdev, location.target, blink); else warnx("Disk %s is not in an enclosure", arg); return; } } } warnx("Disk %s does not exist", arg); } void bio_blink(char *enclosure, int target, int blinktype) { int bioh; struct bio_locate bl; struct bioc_blink blink; bioh = open("/dev/bio", O_RDWR); if (bioh == -1) err(1, "Can't open %s", "/dev/bio"); memset(&bl, 0, sizeof(bl)); bl.bl_name = enclosure; if (ioctl(bioh, BIOCLOCATE, &bl) == -1) errx(1, "Can't locate %s device via %s", enclosure, "/dev/bio"); memset(&blink, 0, sizeof(blink)); blink.bb_bio.bio_cookie = bio_cookie; blink.bb_status = blinktype; blink.bb_target = target; if (ioctl(bioh, BIOCBLINK, &blink) == -1) err(1, "BIOCBLINK"); bio_status(&blink.bb_bio.bio_status); close(bioh); } void bio_createraid(u_int16_t level, char *dev_list, char *key_disk) { struct bioc_createraid create; struct sr_crypto_kdfinfo kdfinfo; struct sr_crypto_pbkdf kdfhint; struct stat sb; int rv, no_dev, fd; dev_t *dt; u_int16_t min_disks = 0; if (!dev_list) errx(1, "no devices specified"); dt = calloc(1, BIOC_CRMAXLEN); if (!dt) err(1, "not enough memory for dev_t list"); no_dev = bio_parse_devlist(dev_list, dt); switch (level) { case 0: min_disks = 2; break; case 1: min_disks = 2; break; case 5: min_disks = 3; break; case 'C': case 0x1C: min_disks = 1; break; case 'c': min_disks = 1; break; default: errx(1, "unsupported RAID level"); } if (no_dev < min_disks) errx(1, "not enough disks"); /* for crypto raid we only allow one single chunk */ if (level == 'C' && no_dev != min_disks) errx(1, "not exactly one partition"); memset(&create, 0, sizeof(create)); create.bc_bio.bio_cookie = bio_cookie; create.bc_level = level; create.bc_dev_list_len = no_dev * sizeof(dev_t); create.bc_dev_list = dt; create.bc_flags = BIOC_SCDEVT | cflags; create.bc_key_disk = NODEV; if ((level == 'C' || level == 0x1C) && key_disk == NULL) { memset(&kdfinfo, 0, sizeof(kdfinfo)); memset(&kdfhint, 0, sizeof(kdfhint)); create.bc_flags |= BIOC_SCNOAUTOASSEMBLE; create.bc_opaque = &kdfhint; create.bc_opaque_size = sizeof(kdfhint); create.bc_opaque_flags = BIOC_SOOUT; /* try to get KDF hint */ if (ioctl(devh, BIOCCREATERAID, &create) == -1) err(1, "ioctl"); bio_status(&create.bc_bio.bio_status); if (create.bc_opaque_status == BIOC_SOINOUT_OK) { bio_kdf_derive(&kdfinfo, &kdfhint, "Passphrase: ", 0); memset(&kdfhint, 0, sizeof(kdfhint)); } else { bio_kdf_generate(&kdfinfo); } create.bc_opaque = &kdfinfo; create.bc_opaque_size = sizeof(kdfinfo); create.bc_opaque_flags = BIOC_SOIN; } else if ((level == 'C' || level == 0x1C) && key_disk != NULL) { /* Get device number for key disk. */ fd = opendev(key_disk, O_RDONLY, OPENDEV_BLCK, NULL); if (fd == -1) err(1, "could not open %s", key_disk); if (fstat(fd, &sb) == -1) { int saved_errno = errno; close(fd); errc(1, saved_errno, "could not stat %s", key_disk); } close(fd); create.bc_key_disk = sb.st_rdev; memset(&kdfinfo, 0, sizeof(kdfinfo)); kdfinfo.genkdf.len = sizeof(kdfinfo.genkdf); kdfinfo.genkdf.type = SR_CRYPTOKDFT_KEYDISK; kdfinfo.len = sizeof(kdfinfo); kdfinfo.flags = SR_CRYPTOKDF_HINT; create.bc_opaque = &kdfinfo; create.bc_opaque_size = sizeof(kdfinfo); create.bc_opaque_flags = BIOC_SOIN; } rv = ioctl(devh, BIOCCREATERAID, &create); explicit_bzero(&kdfinfo, sizeof(kdfinfo)); if (rv == -1) err(1, "BIOCCREATERAID"); bio_status(&create.bc_bio.bio_status); free(dt); } void bio_kdf_derive(struct sr_crypto_kdfinfo *kdfinfo, struct sr_crypto_pbkdf *kdfhint, char* prompt, int verify) { if (!kdfinfo) errx(1, "invalid KDF info"); if (!kdfhint) errx(1, "invalid KDF hint"); if (kdfhint->generic.len != sizeof(*kdfhint)) errx(1, "KDF hint has invalid size"); kdfinfo->flags = SR_CRYPTOKDF_KEY; kdfinfo->len = sizeof(*kdfinfo); derive_key(kdfhint->generic.type, kdfhint->rounds, kdfinfo->maskkey, sizeof(kdfinfo->maskkey), kdfhint->salt, sizeof(kdfhint->salt), prompt, verify); } void bio_kdf_generate(struct sr_crypto_kdfinfo *kdfinfo) { if (!kdfinfo) errx(1, "invalid KDF info"); if (rflag == -1) rflag = bcrypt_pbkdf_autorounds(); kdfinfo->pbkdf.generic.len = sizeof(kdfinfo->pbkdf); kdfinfo->pbkdf.generic.type = SR_CRYPTOKDFT_BCRYPT_PBKDF; kdfinfo->pbkdf.rounds = rflag; kdfinfo->flags = SR_CRYPTOKDF_KEY | SR_CRYPTOKDF_HINT; kdfinfo->len = sizeof(*kdfinfo); /* generate salt */ arc4random_buf(kdfinfo->pbkdf.salt, sizeof(kdfinfo->pbkdf.salt)); derive_key(kdfinfo->pbkdf.generic.type, kdfinfo->pbkdf.rounds, kdfinfo->maskkey, sizeof(kdfinfo->maskkey), kdfinfo->pbkdf.salt, sizeof(kdfinfo->pbkdf.salt), "New passphrase: ", interactive); } int bio_parse_devlist(char *lst, dev_t *dt) { char *s, *e; u_int32_t sz = 0; int no_dev = 0, i, x; struct stat sb; char dev[PATH_MAX]; int fd; if (!lst) errx(1, "invalid device list"); s = e = lst; /* make sure we have a valid device list like /dev/sdNa,/dev/sdNNa */ while (*e != '\0') { if (*e == ',') s = e + 1; else if (*(e + 1) == '\0' || *(e + 1) == ',') { /* got one */ sz = e - s + 1; strlcpy(dev, s, sz + 1); fd = opendev(dev, O_RDONLY, OPENDEV_BLCK, NULL); if (fd == -1) err(1, "could not open %s", dev); if (fstat(fd, &sb) == -1) { int saved_errno = errno; close(fd); errc(1, saved_errno, "could not stat %s", dev); } close(fd); dt[no_dev] = sb.st_rdev; no_dev++; if (no_dev > (int)(BIOC_CRMAXLEN / sizeof(dev_t))) errx(1, "too many devices on device list"); } e++; } for (i = 0; i < no_dev; i++) for (x = 0; x < no_dev; x++) if (dt[i] == dt[x] && x != i) errx(1, "duplicate device in list"); return (no_dev); } u_int32_t bio_createflags(char *lst) { char *s, *e, fs[32]; u_int32_t sz = 0; u_int32_t flags = 0; if (!lst) errx(1, "invalid flags list"); s = e = lst; /* make sure we have a valid flags list like force,noassemeble */ while (*e != '\0') { if (*e == ',') s = e + 1; else if (*(e + 1) == '\0' || *(e + 1) == ',') { /* got one */ sz = e - s + 1; switch (s[0]) { case 'f': flags |= BIOC_SCFORCE; break; case 'n': flags |= BIOC_SCNOAUTOASSEMBLE; break; default: strlcpy(fs, s, sz + 1); errx(1, "invalid flag %s", fs); } } e++; } return (flags); } void bio_deleteraid(char *dev) { struct bioc_deleteraid bd; memset(&bd, 0, sizeof(bd)); bd.bd_bio.bio_cookie = bio_cookie; /* XXX make this a dev_t instead of a string */ strlcpy(bd.bd_dev, dev, sizeof bd.bd_dev); if (ioctl(devh, BIOCDELETERAID, &bd) == -1) err(1, "BIOCDELETERAID"); bio_status(&bd.bd_bio.bio_status); } void bio_changepass(char *dev) { struct bioc_discipline bd; struct sr_crypto_kdfpair kdfpair; struct sr_crypto_kdfinfo kdfinfo1, kdfinfo2; struct sr_crypto_pbkdf kdfhint; int rv; memset(&bd, 0, sizeof(bd)); memset(&kdfhint, 0, sizeof(kdfhint)); memset(&kdfinfo1, 0, sizeof(kdfinfo1)); memset(&kdfinfo2, 0, sizeof(kdfinfo2)); /* XXX use dev_t instead of string. */ strlcpy(bd.bd_dev, dev, sizeof(bd.bd_dev)); bd.bd_cmd = SR_IOCTL_GET_KDFHINT; bd.bd_size = sizeof(kdfhint); bd.bd_data = &kdfhint; if (ioctl(devh, BIOCDISCIPLINE, &bd) == -1) err(1, "BIOCDISCIPLINE"); bio_status(&bd.bd_bio.bio_status); /* Current passphrase. */ bio_kdf_derive(&kdfinfo1, &kdfhint, "Old passphrase: ", 0); if (rflag == -1) { rflag = bcrypt_pbkdf_autorounds(); /* Use previous number of rounds for the same KDF if higher. */ if (kdfhint.generic.type == SR_CRYPTOKDFT_BCRYPT_PBKDF && rflag < kdfhint.rounds) rflag = kdfhint.rounds; } /* New passphrase. */ bio_kdf_generate(&kdfinfo2); kdfpair.kdfinfo1 = &kdfinfo1; kdfpair.kdfsize1 = sizeof(kdfinfo1); kdfpair.kdfinfo2 = &kdfinfo2; kdfpair.kdfsize2 = sizeof(kdfinfo2); bd.bd_cmd = SR_IOCTL_CHANGE_PASSPHRASE; bd.bd_size = sizeof(kdfpair); bd.bd_data = &kdfpair; rv = ioctl(devh, BIOCDISCIPLINE, &bd); memset(&kdfhint, 0, sizeof(kdfhint)); explicit_bzero(&kdfinfo1, sizeof(kdfinfo1)); explicit_bzero(&kdfinfo2, sizeof(kdfinfo2)); if (rv == -1) err(1, "BIOCDISCIPLINE"); bio_status(&bd.bd_bio.bio_status); } #define BIOCTL_VIS_NBUF 4 #define BIOCTL_VIS_BUFLEN 80 char * bio_vis(char *s) { static char rbuf[BIOCTL_VIS_NBUF][BIOCTL_VIS_BUFLEN]; static uint idx = 0; char *buf; buf = rbuf[idx++]; if (idx == BIOCTL_VIS_NBUF) idx = 0; strnvis(buf, s, BIOCTL_VIS_BUFLEN, VIS_NL|VIS_CSTYLE); return (buf); } void bio_diskinq(char *sd_dev) { struct dk_inquiry di; if (ioctl(devh, DIOCINQ, &di) == -1) err(1, "DIOCINQ"); printf("%s: <%s, %s, %s>, serial %s\n", sd_dev, bio_vis(di.vendor), bio_vis(di.product), bio_vis(di.revision), bio_vis(di.serial)); } void bio_patrol(char *arg) { struct bioc_patrol bp; struct timing timing; const char *errstr; memset(&bp, 0, sizeof(bp)); bp.bp_bio.bio_cookie = bio_cookie; switch (arg[0]) { case 'a': bp.bp_opcode = BIOC_SPAUTO; break; case 'm': bp.bp_opcode = BIOC_SPMANUAL; break; case 'd': bp.bp_opcode = BIOC_SPDISABLE; break; case 'g': /* get patrol state */ bp.bp_opcode = BIOC_GPSTATUS; break; case 's': /* start/stop patrol */ if (strncmp("sta", arg, 3) == 0) bp.bp_opcode = BIOC_SPSTART; else bp.bp_opcode = BIOC_SPSTOP; break; default: errx(1, "invalid patrol function: %s", arg); } switch (arg[0]) { case 'a': errstr = str2patrol(arg, &timing); if (errstr) errx(1, "Patrol %s: %s", arg, errstr); bp.bp_autoival = timing.interval; bp.bp_autonext = timing.start; break; } if (ioctl(devh, BIOCPATROL, &bp) == -1) err(1, "BIOCPATROL"); bio_status(&bp.bp_bio.bio_status); if (arg[0] == 'g') { const char *mode, *status; char interval[40]; interval[0] = '\0'; switch (bp.bp_mode) { case BIOC_SPMAUTO: mode = "auto"; snprintf(interval, sizeof interval, " interval=%d next=%d", bp.bp_autoival, bp.bp_autonext - bp.bp_autonow); break; case BIOC_SPMMANUAL: mode = "manual"; break; case BIOC_SPMDISABLED: mode = "disabled"; break; default: mode = "unknown"; break; } switch (bp.bp_status) { case BIOC_SPSSTOPPED: status = "stopped"; break; case BIOC_SPSREADY: status = "ready"; break; case BIOC_SPSACTIVE: status = "active"; break; case BIOC_SPSABORTED: status = "aborted"; break; default: status = "unknown"; break; } printf("patrol mode: %s%s\n", mode, interval); printf("patrol status: %s\n", status); } } /* * Measure this system's performance by measuring the time for 100 rounds. * We are aiming for something that takes around 1s. */ int bcrypt_pbkdf_autorounds(void) { struct timespec before, after; char buf[SR_CRYPTO_MAXKEYBYTES], salt[128]; int r = 100; int duration; clock_gettime(CLOCK_THREAD_CPUTIME_ID, &before); if (bcrypt_pbkdf("testpassword", strlen("testpassword"), salt, sizeof(salt), buf, sizeof(buf), r) != 0) errx(1, "bcrypt pbkdf failed"); clock_gettime(CLOCK_THREAD_CPUTIME_ID, &after); duration = after.tv_sec - before.tv_sec; duration *= 1000000; duration += (after.tv_nsec - before.tv_nsec) / 1000; duration /= r; r = 1000000 / duration; if (r < 16) r = 16; return r; } void derive_key(u_int32_t type, int rounds, u_int8_t *key, size_t keysz, u_int8_t *salt, size_t saltsz, char *prompt, int verify) { FILE *f; size_t pl; struct stat sb; char passphrase[1024], verifybuf[1024]; int rpp_flag = RPP_ECHO_OFF; if (!key) errx(1, "Invalid key"); if (!salt) errx(1, "Invalid salt"); if (type != SR_CRYPTOKDFT_PKCS5_PBKDF2 && type != SR_CRYPTOKDFT_BCRYPT_PBKDF) errx(1, "unknown KDF type %d", type); if (rounds < (type == SR_CRYPTOKDFT_PKCS5_PBKDF2 ? 1000 : 16)) errx(1, "number of KDF rounds is too small: %d", rounds); /* get passphrase */ if (passfile) { if ((f = fopen(passfile, "r")) == NULL) err(1, "invalid passphrase file"); if (fstat(fileno(f), &sb) == -1) err(1, "can't stat passphrase file"); if (sb.st_uid != 0) errx(1, "passphrase file must be owned by root"); if ((sb.st_mode & ~S_IFMT) != (S_IRUSR | S_IWUSR)) errx(1, "passphrase file has the wrong permissions"); if (fgets(passphrase, sizeof(passphrase), f) == NULL) err(1, "can't read passphrase file"); pl = strlen(passphrase); if (pl > 0 && passphrase[pl - 1] == '\n') passphrase[pl - 1] = '\0'; else errx(1, "invalid passphrase length"); fclose(f); } else { rpp_flag |= interactive ? RPP_REQUIRE_TTY : RPP_STDIN; retry: if (readpassphrase(prompt, passphrase, sizeof(passphrase), rpp_flag) == NULL) err(1, "unable to read passphrase"); if (*passphrase == '\0') { warnx("invalid passphrase length"); if (interactive) goto retry; exit(1); } } if (verify && !passfile) { /* request user to re-type it */ if (readpassphrase("Re-type passphrase: ", verifybuf, sizeof(verifybuf), rpp_flag) == NULL) { explicit_bzero(passphrase, sizeof(passphrase)); err(1, "unable to read passphrase"); } if ((strlen(passphrase) != strlen(verifybuf)) || (strcmp(passphrase, verifybuf) != 0)) { explicit_bzero(passphrase, sizeof(passphrase)); explicit_bzero(verifybuf, sizeof(verifybuf)); if (interactive) { warnx("Passphrases did not match, try again"); goto retry; } errx(1, "Passphrases did not match"); } /* forget the re-typed one */ explicit_bzero(verifybuf, sizeof(verifybuf)); } /* derive key from passphrase */ if (type == SR_CRYPTOKDFT_PKCS5_PBKDF2) { if (verbose) printf("Deriving key using PKCS#5 PBKDF2 with %i rounds...\n", rounds); if (pkcs5_pbkdf2(passphrase, strlen(passphrase), salt, saltsz, key, keysz, rounds) != 0) errx(1, "pkcs5_pbkdf2 failed"); } else if (type == SR_CRYPTOKDFT_BCRYPT_PBKDF) { if (verbose) printf("Deriving key using bcrypt PBKDF with %i rounds...\n", rounds); if (bcrypt_pbkdf(passphrase, strlen(passphrase), salt, saltsz, key, keysz, rounds) != 0) errx(1, "bcrypt_pbkdf failed"); } else { errx(1, "unknown KDF type %d", type); } /* forget passphrase */ explicit_bzero(passphrase, sizeof(passphrase)); }