From 57922fa5cc21f455226d4725d6b6d303710b8534 Mon Sep 17 00:00:00 2001 From: pjd Date: Wed, 27 Jul 2005 21:43:37 +0000 Subject: Add GEOM_ELI class which provides GEOM providers encryption. For features list and usage see manual page: geli(8). Sponsored by: Wheel Sp. z o.o. http://www.wheel.pl MFC after: 1 week --- sbin/geom/class/eli/Makefile | 17 + sbin/geom/class/eli/geli.8 | 511 ++++++++++++++++ sbin/geom/class/eli/geom_eli.c | 1246 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1774 insertions(+) create mode 100644 sbin/geom/class/eli/Makefile create mode 100644 sbin/geom/class/eli/geli.8 create mode 100644 sbin/geom/class/eli/geom_eli.c (limited to 'sbin') diff --git a/sbin/geom/class/eli/Makefile b/sbin/geom/class/eli/Makefile new file mode 100644 index 0000000..18f3869 --- /dev/null +++ b/sbin/geom/class/eli/Makefile @@ -0,0 +1,17 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../misc ${.CURDIR}/../../../../sys/geom/eli ${.CURDIR}/../../../../sys/crypto/sha2 + +CLASS= eli +SRCS= g_eli_crypto.c +SRCS+= g_eli_key.c +SRCS+= pkcs5v2.c +SRCS+= sha2.c + +DPADD= ${LIBMD} ${LIBCRYPTO} +LDADD= -lmd -lcrypto + +NO_MAN= +CFLAGS+=-I${.CURDIR}/../../../../sys + +.include diff --git a/sbin/geom/class/eli/geli.8 b/sbin/geom/class/eli/geli.8 new file mode 100644 index 0000000..edf11e7 --- /dev/null +++ b/sbin/geom/class/eli/geli.8 @@ -0,0 +1,511 @@ +.\" Copyright (c) 2005 Pawel Jakub Dawidek +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd April 11, 2005 +.Dt GELI 8 +.Os +.Sh NAME +.Nm geli +.Nd "control utility for cryptographic GEOM class" +.Sh SYNOPSIS +To compile GEOM_ELI into your kernel, place the following lines in your kernel +configuration file: +.Bd -ragged -offset indent +.Cd "device crypto" +.Cd "device cryptodev" +.Cd "options GEOM_ELI" +.Ed +.Pp +Alternately, to load the GEOM_ELI module at boot time, place the following line +in your +.Xr loader.conf 5 : +.Bd -literal -offset indent +geom_eli_load="YES" +.Ed +.Pp +Usage of the +.Xr geli 8 +utility: +.Pp +.Nm +.Cm init +.Op Fl bPv +.Op Fl a Ar algo +.Op Fl i Ar iterations +.Op Fl K Ar newkeyfile +.Op Fl l Ar keylen +.Op Fl s Ar sectorsize +.Ar prov +.Nm +.Cm label - an alias for +.Cm init +.Nm +.Cm attach +.Op Fl dpv +.Op Fl k Ar keyfile +.Ar prov +.Nm +.Cm detach +.Op Fl fl +.Ar prov ... +.Nm +.Cm stop - an alias for +.Cm detach +.Nm +.Cm onetime +.Op Fl d +.Op Fl a Ar algo +.Op Fl l Ar keylen +.Op Fl s Ar sectorsize +.Ar prov ... +.Nm +.Cm setkey +.Op Fl pPv +.Op Fl k Ar keyfile +.Op Fl K Ar newkeyfile +.Op Fl n Ar keyno +.Ar prov +.Nm +.Cm delkey +.Op Fl afv +.Op Fl n Ar keyno +.Ar prov +.Nm +.Cm kill +.Op Fl av +.Op Ar prov ... +.Nm +.Cm backup +.Op Fl v +.Ar prov +.Ar file +.Nm +.Cm restore +.Op Fl v +.Ar file +.Ar prov +.Nm +.Cm clear +.Op Fl v +.Ar prov ... +.Nm +.Cm dump +.Op Fl v +.Ar prov ... +.Nm +.Cm list +.Nm +.Cm status +.Nm +.Cm load +.Nm +.Cm unload +.Sh DESCRIPTION +The +.Nm +utility is used to configure encryption on GEOM providers. +.Pp +Here is the list of the most important features: +.Pp +.Bl -bullet -offset indent -compact +.It +Utilize the +.Xr crypto 9 +framework, so when there is a crypto hardware available, +.Nm +will make use of it automatically. +If cryptography needs to be done in software, +a dedicated kernel thread will be started to do the crypto work in there. +.It +Supports many cryptographic algorithms (currently +.Nm AES , +.Nm Blowfish +and +.Nm 3DES ) . +.It +Can create a key from a couple of components (user entered passphrase, random +bits from a file, etc.). +.It +Allows to encrypt root partition - user will be asked for the passphrase before +root file system is mounted. +.It +User's passphrase is strengthen with: +.Rs +.%A B. Kaliski +.%T "PKCS #5: Password-Based Cryptography Specification, Version 2.0." +.%R RFC +.%N 2898 +.Re +.It +Allows to use two independent keys (e.g. +.Qq "user key" +and +.Qq "company key" ) . +.It +It is fast - +.Nm +performs simple sector-to-sector encryption. +.It +Allows to backup/restore Master Keys, so when user have to quickly destroy keys, +it is able to get the data back by restoring keys from the backup. +.It +Provider can be configured to automatically detach on last close (so user don't +have to remember to detach provider after unmounting file system). +.It +Allows to attach provider with a random, one-time keys - useful for swap +partitions and temporary file systems. +.El +.Pp +The first argument to +.Nm +indicates an action to be performed: +.Bl -tag -width ".Cm onetime" +.It Cm init +Initialize provider which needs to be encrypted. +Here you can setup cryptographic algorithm to use, key length, etc. +The last provider's sector is used to store metadata. +.Pp +Additional options include: +.Bl -tag -width ".Fl a Ar algo" +.It Fl a Ar algo +Encryption algorithm to use. +Currently supported algorithms are: +.Nm AES , +.Nm Blowfish +and +.Nm 3DES . +The default is +.Nm AES . +.It Fl b +Ask for the passphrase on boot, before root partition is mounted. +This allows to use encrypted root partition. +One will still need bootable unencrypted storage with +.Pa /boot/ +directory, which can be a CD-ROM disc or USB pen-drive, which can be removed +after boot. +.It Fl i Ar iterations +Number of iterations to use with PKCS#5v2. +If this option is not specified +.Nm +will find the number of iterations which is equal to 2 seconds of crypto work. +If 0 is given, PKCS#5v2 will not be used. +.It Fl K Ar newkeyfile +Specifies a file which contains part of the key. +If +.Ar newkeyfile +is given as -, standard input will be used. +Here is how more than one file with the key component can be used: +.Bd -literal -offset indent +# cat key1 key2 key3 | geli init -K - /dev/da0 +.Ed +.It Fl l Ar keylen +Key length to use with the given cryptographic algorithm. +If not given, the default key length for the given algorithm is used, which is: +128 for +.Nm AES , +128 for +.Nm Blowfish +and 192 for +.Nm 3DES . +.It Fl s Ar sectorsize +Change decrypted provider's sector size. +Increasing sector size allows to increase performance, because we need to +generate IV and do encrypt/decrypt for every single sector - less number +of sectors means less work to do. +.It Fl P +Do not use passphrase as the key component. +.El +.It Cm attach +Attach the given provider. The master key will be decrypted using the given +passphrase/keyfile and a new GEOM provider will be created using the given +provider's name with an +.Qq .eli +suffix. +.Pp +Additional options include: +.Bl -tag -width ".Fl a Ar algo" +.It Fl d +If specified, decrypted provider will be detached automatically on last close. +This can help with short memory - user doesn't have to remember to detach +provider after unmounting file system. +It only works when provider was opened for writing, so it will not work if +file system on the provider is mounted read-only. +Probably better choice is the +.Fl l +option for the +.Cm detach +subcommand. +.It Fl k Ar keyfile +Specifies a file which contains part of the key. +For more information see description of +.Fl K +option for the +.Cm init +subcommand. +.It Fl p +Do not use passphrase as the key component. +.El +.It Cm detach +Detach the given providers, which means remove devfs entry and clear the keys +from memory. +.Pp +Additional options include: +.Bl -tag -width ".Fl a Ar algo" +.It Fl f +Force detach - detach even if provider is open. +.It Fl l +Mark provider to detach on last close. +If this option is specified provider will not be detached until it is open, +but when it will be closed last time, it will be automatically detached (even +if it was only opened for reading). +.El +.It Cm onetime +Attach the given providers with a random, one-time keys. +The command can be used to encrypt swap partitions or temporary file systems. +.Pp +Additional options include: +.Bl -tag -width ".Fl a Ar algo" +.It Fl a Ar algo +Encryption algorithm to use. +For more information see description of the +.Cm init +subcommand. +.It Fl d +Detach on last close. +Note, the option is not usable for temporary file system, because provider will +be detached after creating file system on it. +It still can (and should be) used for swap partitions. +For more information see description of the +.Cm attach +subcommand. +.It Fl l Ar keylen +Key length to use with the given cryptographic algorithm. +For more information see description of the +.Cm init +subcommand. +.It Fl s Ar sectorsize +Change decrypted provider's sector size. +For more information see description of the +.Cm init +subcommand. +.El +.It Cm setkey +Change or setup (if not yet initialized) selected key. +There is one master key, which can be encrypted with two independent user keys. +With the +.Cm init +subcommand only key number 0 is initialized. +The key can be always changed: for attached provider, for detached provider or +on the backup file. +When provider is attached, user don't have to provide an old passphrase/keyfile. +.Pp +Additional options include: +.Bl -tag -width ".Fl a Ar algo" +.It Fl k Ar keyfile +Specifies a file which contains part of the old key. +.It Fl K Ar newkeyfile +Specifies a file which contains part of the new key. +.It Fl n Ar keyno +Specifies number of the key to change (could be 0 or 1). +If provider is attached and no key number is given, the key used for attaching +provider will be changed. +If provider is detached (or we're operating on a backup file) and no key number +is given, the key decrypted with passphrase/keyfile will be changed. +.It Fl p +Do not use passphrase as the old key component. +.It Fl P +Do not use passphrase as the new key component. +.El +.It Cm delkey +Destroy (overwrite with random data) selected key. +If one is destroying keys for an attached provider, provider won't be detached +even if all keys will be destroyed. +It can be even rescued with the +.Cm setkey +subcommand. +.Bl -tag -width ".Fl a Ar algo" +.It Fl a +Destroy all keys (doesn't need +.Fl f +option). +.It Fl f +Force key destruction. This option is needed to destroy the last key. +.It Fl n Ar keyno +Specifies the key number. +If provider is attached and no key number is given, the key used for attaching +provider will be destroyed. +If provider is detached (or we're operating on a backup file) the key number +has to be given. +.El +.It Cm kill +The command should be used in emergency situations. +It will destroy all keys on the given provider and will detach it forcibly +(if it is attached). +This is absolutely one-way command - if you don't have metadata backup, your data +is gone for good. +.Bl -tag -width ".Fl a Ar algo" +.It Fl a +If specified, all currently attached providers will be killed. +.El +.It Cm backup +Backup metadata from the given provider to the given file. +.It Cm restore +Restore metadata from the given file to the given provider. +.It Cm clear +Clear metadata from the given providers. +.It Cm dump +Dump metadata stored on the given providers. +.It Cm list +See +.Xr geom 8 . +.It Cm status +See +.Xr geom 8 . +.It Cm load +See +.Xr geom 8 . +.It Cm unload +See +.Xr geom 8 . +.El +.Pp +Additional options include: +.Bl -tag -width ".Fl v" +.It Fl v +Be more verbose. +.El +.Sh SYSCTL VARIABLES +The following +.Xr sysctl 8 +variables can be used to control the behavior of the +.Nm ELI +GEOM class. +The default value is shown next to each variable. +.Bl -tag -width indent +.It Va kern.geom.eli.debug : No 0 +Debug level of the +.Nm ELI +GEOM class. +This can be set to a number between 0 and 3 inclusive. +If set to 0 minimal debug information is printed, and if set to 3 the +maximum amount of debug information is printed. +This variable could be set in +.Pa /boot/loader.conf . +.It Va kern.geom.eli.tries : No 3 +Number of times user is asked for the passphrase. +This is only used for providers which should be attached on boot (before root +file system is mounted). +If set to 0, attaching providers on boot will be disabled. +This variable should be set in +.Pa /boot/loader.conf . +.It Va kern.geom.eli.overwrites : No 5 +Specifies how many times Master-Key will be overwriten with random values when +it is destroyed. After this operation it is filled with zeros. +.It Va kern.geom.eli.visible_passphrase : No 0 +If set to 1, passphrase entered on boot (before root file system is mounted) +will be visible. +This possibility should be used with caution as entered passphrase can be logged +and exposed via +.Xr dmesg 8 . +This variable should be set in +.Pa /boot/loader.conf . +.It Va kern.geom.eli.threads : No 1 +Specifies how many kernel threads should be used for doing software +cryptography. +It's purpose is to increase performance on SMP systems. +This variable could be set in +.Pa /boot/loader.conf . +.El +.Sh EXIT STATUS +Exit status is 0 on success, and 1 if the command fails. +.Sh EXAMPLES +Initialize provider which is going to be encrypted with a passphrase and random +data from a file on the user's pen drive. +Use 4kB sector size. +Attach the provider, create a file system and mount it. +Do the work. +Unmount provider and detach it: +.Bd -literal -offset indent +# dd if=/dev/random of=/mnt/pendrive/da2.key bs=64 count=1 +# geli init -s 4096 -K /mnt/pendrive/da2.key /dev/da2 +Enter new passphrase: +Reenter new passphrase: +# geli attach -k /mnt/pendrive/da2.key /dev/da2 +Enter passphrase: +# dd if=/dev/random of=/dev/da2.eli bs=1m +# newfs /dev/da2.eli +# mount /dev/da2.eli /mnt/secret +\&... +# umount /mnt/secret +# geli detach da2.eli +.Ed +.Pp +Create encrypted provider, but use two key: one for your girlfriend and one for +you (so there will be no tragedy if she forget her passphrase): +.Bd -literal -offset indent +# geli init /dev/da2 +Enter new passphrase: (enter your passphrase) +Reenter new passphrase: +# geli setkey -n 1 /dev/da2 +Enter passphrase: (enter your passphrase) +Enter new passphrase: (let your girlfriend to enter her passphrase ...) +Reenter new passphrase: (... twice) +.Ed +.Pp +You are security-person in your company. +Create encrypted provider for use by the user, but remember that users forget +their passphrases, so backup Master Key with your own random key: +.Bd -literal -offset indent +# dd if=/dev/random of=/mnt/pendrive/keys/`hostname` bs=64 count=1 +# geli init -P -K /mnt/pendrive/keys/`hostname` /dev/ad0s1e +# geli backup /dev/ad0s1e /mnt/pendrive/backups/`hostname` +(use key number 0, so encrypted Master Key by you will be overwriten) +# geli setkey -n 0 -k /mnt/pendrive/keys/`hostname` /dev/ad0s1e +(allow the user to enter his passphrase) +Enter new passphrase: +Reenter new passphrase: +.Ed +.Pp +Encrypted swap partition setup: +.Bd -literal -offset indent +# dd if=/dev/random of=/dev/ad0s1b bs=1m +# geli onetime -d -a 3des ad0s1b +# swapon /dev/ad0s1b.eli +.Ed +.Sh SEE ALSO +.Xr crypto 4 , +.Xr crypto 9 , +.Xr gbde 4 , +.Xr gbde 8 , +.Xr geom 4 , +.Xr geom 8 +.Sh HISTORY +The +.Nm +utility appeared in +.Fx 5.5 . +.Sh AUTHORS +.An Pawel Jakub Dawidek Aq pjd@FreeBSD.org diff --git a/sbin/geom/class/eli/geom_eli.c b/sbin/geom/class/eli/geom_eli.c new file mode 100644 index 0000000..f01f557 --- /dev/null +++ b/sbin/geom/class/eli/geom_eli.c @@ -0,0 +1,1246 @@ +/*- + * Copyright (c) 2004 Pawel Jakub Dawidek + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "core/geom.h" +#include "misc/subr.h" + + +uint32_t lib_version = G_LIB_VERSION; +uint32_t version = G_ELI_VERSION; + +static char algo[] = "aes"; +static intmax_t keylen = 0; +static intmax_t keyno = -1; +static intmax_t iterations = -1; +static intmax_t sectorsize = 0; +static char keyfile[] = "", newkeyfile[] = ""; + +static void eli_main(struct gctl_req *req, unsigned flags); +static void eli_init(struct gctl_req *req); +static void eli_attach(struct gctl_req *req); +static void eli_setkey(struct gctl_req *req); +static void eli_delkey(struct gctl_req *req); +static void eli_kill(struct gctl_req *req); +static void eli_backup(struct gctl_req *req); +static void eli_restore(struct gctl_req *req); +static void eli_clear(struct gctl_req *req); +static void eli_dump(struct gctl_req *req); + +/* + * Available commands: + * + * init [-bhPv] [-a algo] [-i iterations] [-l keylen] [-K newkeyfile] prov + * label - alias for 'init' + * attach [-dpv] [-k keyfile] prov + * detach [-fl] prov ... + * stop - alias for 'detach' + * onetime [-d] [-a algo] [-l keylen] prov ... + * setkey [-pPv] [-n keyno] [-k keyfile] [-K newkeyfile] prov + * delkey [-afv] [-n keyno] prov + * kill [-av] [prov ...] + * backup [-v] prov file + * restore [-v] file prov + * clear [-v] prov ... + * dump [-v] prov ... + */ +struct g_command class_commands[] = { + { "init", G_FLAG_VERBOSE, eli_main, + { + { 'a', "algo", algo, G_TYPE_STRING }, + { 'b', "boot", NULL, G_TYPE_NONE }, + { 'i', "iterations", &iterations, G_TYPE_NUMBER }, + { 'K', "newkeyfile", newkeyfile, G_TYPE_STRING }, + { 'l', "keylen", &keylen, G_TYPE_NUMBER }, + { 'P', "nonewpassphrase", NULL, G_TYPE_NONE }, + { 's', "sectorsize", §orsize, G_TYPE_NUMBER }, + G_OPT_SENTINEL + }, + "[-bPv] [-a algo] [-i iterations] [-l keylen] [-K newkeyfile] [-s sectorsize] prov" + }, + { "label", G_FLAG_VERBOSE, eli_main, + { + { 'a', "algo", algo, G_TYPE_STRING }, + { 'b', "boot", NULL, G_TYPE_NONE }, + { 'i', "iterations", &iterations, G_TYPE_NUMBER }, + { 'K', "newkeyfile", newkeyfile, G_TYPE_STRING }, + { 'l', "keylen", &keylen, G_TYPE_NUMBER }, + { 'P', "nonewpassphrase", NULL, G_TYPE_NONE }, + { 's', "sectorsize", §orsize, G_TYPE_NUMBER }, + G_OPT_SENTINEL + }, + "- an alias for 'init'" + }, + { "attach", G_FLAG_VERBOSE | G_FLAG_LOADKLD, eli_main, + { + { 'd', "detach", NULL, G_TYPE_NONE }, + { 'k', "keyfile", keyfile, G_TYPE_STRING }, + { 'p', "nopassphrase", NULL, G_TYPE_NONE }, + G_OPT_SENTINEL + }, + "[-dpv] [-k keyfile] prov" + }, + { "detach", 0, NULL, + { + { 'f', "force", NULL, G_TYPE_NONE }, + { 'l', "last", NULL, G_TYPE_NONE }, + G_OPT_SENTINEL + }, + "[-fl] prov ..." + }, + { "stop", 0, NULL, + { + { 'f', "force", NULL, G_TYPE_NONE }, + { 'l', "last", NULL, G_TYPE_NONE }, + G_OPT_SENTINEL + }, + "- an alias for 'detach'" + }, + { "onetime", G_FLAG_VERBOSE | G_FLAG_LOADKLD, NULL, + { + { 'a', "algo", algo, G_TYPE_STRING }, + { 'd', "detach", NULL, G_TYPE_NONE }, + { 'l', "keylen", &keylen, G_TYPE_NUMBER }, + { 's', "sectorsize", §orsize, G_TYPE_NUMBER }, + G_OPT_SENTINEL + }, + "[-d] [-a algo] [-l keylen] [-s sectorsize] prov ..." + }, + { "setkey", G_FLAG_VERBOSE, eli_main, + { + { 'k', "keyfile", keyfile, G_TYPE_STRING }, + { 'K', "newkeyfile", newkeyfile, G_TYPE_STRING }, + { 'n', "keyno", &keyno, G_TYPE_NUMBER }, + { 'p', "nopassphrase", NULL, G_TYPE_NONE }, + { 'P', "nonewpassphrase", NULL, G_TYPE_NONE }, + G_OPT_SENTINEL + }, + "[-pPv] [-n keyno] [-k keyfile] [-K newkeyfile] prov" + }, + { "delkey", G_FLAG_VERBOSE, eli_main, + { + { 'a', "all", NULL, G_TYPE_NONE }, + { 'f', "force", NULL, G_TYPE_NONE }, + { 'n', "keyno", &keyno, G_TYPE_NUMBER }, + G_OPT_SENTINEL + }, + "[-afv] [-n keyno] prov" + }, + { "kill", G_FLAG_VERBOSE, eli_main, + { + { 'a', "all", NULL, G_TYPE_NONE }, + G_OPT_SENTINEL + }, + "[-av] [prov ...]" + }, + { "backup", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS, + "[-v] prov file" + }, + { "restore", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS, + "[-v] file prov" + }, + { "clear", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS, + "[-v] prov ..." + }, + { "dump", G_FLAG_VERBOSE, eli_main, G_NULL_OPTS, + "[-v] prov ..." + }, + G_CMD_SENTINEL +}; + +static int verbose = 0; + +static int +eli_protect(struct gctl_req *req) +{ + struct rlimit rl; + + /* Disable core dumps. */ + rl.rlim_cur = 0; + rl.rlim_max = 0; + if (setrlimit(RLIMIT_CORE, &rl) == -1) { + gctl_error(req, "Cannot disable core dumps: %s.", + strerror(errno)); + return (-1); + } + /* Disable swapping. */ + if (mlockall(MCL_FUTURE) == -1) { + gctl_error(req, "Cannot lock memory: %s.", strerror(errno)); + return (-1); + } + return (0); +} + +static void +eli_main(struct gctl_req *req, unsigned flags) +{ + const char *name; + + if (eli_protect(req) == -1) + return; + + if ((flags & G_FLAG_VERBOSE) != 0) + verbose = 1; + + name = gctl_get_asciiparam(req, "verb"); + if (name == NULL) { + gctl_error(req, "No '%s' argument.", "verb"); + return; + } + if (strcmp(name, "init") == 0 || strcmp(name, "label") == 0) + eli_init(req); + else if (strcmp(name, "attach") == 0) + eli_attach(req); + else if (strcmp(name, "setkey") == 0) + eli_setkey(req); + else if (strcmp(name, "delkey") == 0) + eli_delkey(req); + else if (strcmp(name, "kill") == 0) + eli_kill(req); + else if (strcmp(name, "backup") == 0) + eli_backup(req); + else if (strcmp(name, "restore") == 0) + eli_restore(req); + else if (strcmp(name, "dump") == 0) + eli_dump(req); + else if (strcmp(name, "clear") == 0) + eli_clear(req); + else + gctl_error(req, "Unknown command: %s.", name); +} + +static void +arc4rand(unsigned char *buf, size_t size) +{ + uint32_t *buf4; + size_t size4; + unsigned i; + + buf4 = (uint32_t *)buf; + size4 = size / 4; + + for (i = 0; i < size4; i++) + buf4[i] = arc4random(); + for (i *= 4; i < size; i++) + buf[i] = arc4random() % 0xff; +} + +static int +eli_is_attached(const char *prov) +{ + char name[MAXPATHLEN]; + unsigned secsize; + + /* + * Not the best way to do it, but the easiest. + * We try to open provider and check if it is a GEOM provider + * by asking about its sectorsize. + */ + snprintf(name, sizeof(name), "%s%s", prov, G_ELI_SUFFIX); + secsize = g_get_sectorsize(name); + if (secsize > 0) + return (1); + return (0); +} + +static unsigned char * +eli_genkey(struct gctl_req *req, struct g_eli_metadata *md, unsigned char *key, + int new) +{ + struct hmac_ctx ctx; + const char *str; + int *nopassphrase; + int error; + + nopassphrase = gctl_get_paraml(req, + new ? "nonewpassphrase" : "nopassphrase", sizeof(*nopassphrase)); + if (nopassphrase == NULL) { + gctl_error(req, "No '%s' argument.", + new ? "nonewpassphrase" : "nopassphrase"); + return (NULL); + } + + g_eli_crypto_hmac_init(&ctx, NULL, 0); + + str = gctl_get_asciiparam(req, new ? "newkeyfile" : "keyfile"); + if (str == NULL) { + gctl_error(req, "No '%s' argument.", + new ? "newkeyfile" : "keyfile"); + return (NULL); + } + if (str[0] != '\0') { + char buf[MAXPHYS]; + ssize_t done; + int fd; + + if (strcmp(str, "-") == 0) + fd = STDIN_FILENO; + else { + fd = open(str, O_RDONLY); + if (fd == -1) { + gctl_error(req, "Cannot open keyfile %s: %s.", + str, strerror(errno)); + return (NULL); + } + } + while ((done = read(fd, buf, sizeof(buf))) > 0) + g_eli_crypto_hmac_update(&ctx, buf, done); + error = errno; + if (strcmp(str, "-") != 0) + close(fd); + bzero(buf, sizeof(buf)); + if (done == -1) { + gctl_error(req, "Cannot read keyfile %s: %s.", str, + strerror(error)); + return (NULL); + } + } + + if (!*nopassphrase) { + char buf1[BUFSIZ], buf2[BUFSIZ], *p; + + for (;;) { + p = readpassphrase( + new ? "Enter new passphrase:" : "Enter passphrase:", + buf1, sizeof(buf1), RPP_ECHO_OFF | RPP_REQUIRE_TTY); + if (p == NULL) { + bzero(buf1, sizeof(buf1)); + gctl_error(req, "Cannot read passphrase: %s.", + strerror(errno)); + return (NULL); + } + + if (new) { + p = readpassphrase("Reenter new passphrase: ", + buf2, sizeof(buf2), + RPP_ECHO_OFF | RPP_REQUIRE_TTY); + if (p == NULL) { + bzero(buf1, sizeof(buf1)); + gctl_error(req, + "Cannot read passphrase: %s.", + strerror(errno)); + return (NULL); + } + + if (strcmp(buf1, buf2) != 0) { + bzero(buf2, sizeof(buf2)); + fprintf(stderr, "They didn't match.\n"); + continue; + } + bzero(buf2, sizeof(buf2)); + } + break; + } + /* + * Field md_iterations equal to -1 means "choose some sane + * value for me". + */ + if (md->md_iterations == -1) { + assert(new); + if (verbose) + printf("Calculating number of iterations...\n"); + md->md_iterations = pkcs5v2_calculate(2000000); + assert(md->md_iterations > 0); + if (verbose) { + printf("Done, using %d iterations.\n", + md->md_iterations); + } + } + /* + * If md_iterations is equal to 0, user don't want PKCS5v2. + */ + if (md->md_iterations == 0) { + g_eli_crypto_hmac_update(&ctx, md->md_salt, + sizeof(md->md_salt)); + g_eli_crypto_hmac_update(&ctx, buf1, strlen(buf1)); + } else /* if (md->md_iterations > 0) */ { + unsigned char dkey[G_ELI_USERKEYLEN]; + + pkcs5v2_genkey(dkey, sizeof(dkey), md->md_salt, + sizeof(md->md_salt), buf1, md->md_iterations); + g_eli_crypto_hmac_update(&ctx, dkey, sizeof(dkey)); + bzero(dkey, sizeof(dkey)); + } + bzero(buf1, sizeof(buf1)); + } + g_eli_crypto_hmac_final(&ctx, key, 0); + return (key); +} + +static int +eli_metadata_read(struct gctl_req *req, const char *prov, + struct g_eli_metadata *md) +{ + unsigned char sector[sizeof(struct g_eli_metadata)]; + int error; + + if (g_get_sectorsize(prov) == 0) { + int fd; + + /* This is a file probably. */ + fd = open(prov, O_RDONLY); + if (fd == -1) { + gctl_error(req, "Cannot open %s: %s.", prov, + strerror(errno)); + return (-1); + } + if (read(fd, sector, sizeof(sector)) != sizeof(sector)) { + gctl_error(req, "Cannot read metadata from %s: %s.", + prov, strerror(errno)); + close(fd); + return (-1); + } + close(fd); + } else { + /* This is a GEOM provider. */ + error = g_metadata_read(prov, sector, sizeof(sector), + G_ELI_MAGIC); + if (error != 0) { + gctl_error(req, "Cannot read metadata from %s: %s.", + prov, strerror(error)); + return (-1); + } + } + if (eli_metadata_decode(sector, md) != 0) { + gctl_error(req, "MD5 hash mismatch for %s.", prov); + return (-1); + } + return (0); +} + +static int +eli_metadata_store(struct gctl_req *req, const char *prov, + struct g_eli_metadata *md) +{ + unsigned char sector[sizeof(struct g_eli_metadata)]; + int error; + + eli_metadata_encode(md, sector); + if (g_get_sectorsize(prov) == 0) { + int fd; + + /* This is a file probably. */ + fd = open(prov, O_WRONLY | O_TRUNC); + if (fd == -1) { + gctl_error(req, "Cannot open %s: %s.", prov, + strerror(errno)); + bzero(sector, sizeof(sector)); + return (-1); + } + if (write(fd, sector, sizeof(sector)) != sizeof(sector)) { + gctl_error(req, "Cannot write metadata to %s: %s.", + prov, strerror(errno)); + bzero(sector, sizeof(sector)); + close(fd); + return (-1); + } + close(fd); + } else { + /* This is a GEOM provider. */ + error = g_metadata_store(prov, sector, sizeof(sector)); + if (error != 0) { + gctl_error(req, "Cannot write metadata to %s: %s.", + prov, strerror(errno)); + bzero(sector, sizeof(sector)); + return (-1); + } + } + bzero(sector, sizeof(sector)); + return (0); +} + +static void +eli_init(struct gctl_req *req) +{ + struct g_eli_metadata md; + unsigned char sector[sizeof(struct g_eli_metadata)]; + unsigned char key[G_ELI_USERKEYLEN]; + const char *str, *prov; + int *nargs, *boot; + unsigned secsize; + off_t mediasize; + intmax_t *valp; + int error; + + nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); + if (nargs == NULL) { + gctl_error(req, "No '%s' argument.", "nargs"); + return; + } + if (*nargs != 1) { + gctl_error(req, "Too few arguments."); + return; + } + prov = gctl_get_asciiparam(req, "arg0"); + if (prov == NULL) { + gctl_error(req, "No 'arg%u' argument.", 0); + return; + } + mediasize = g_get_mediasize(prov); + secsize = g_get_sectorsize(prov); + if (mediasize == 0 || secsize == 0) { + gctl_error(req, "Cannot get informations about %s: %s.", prov, + strerror(errno)); + return; + } + + bzero(&md, sizeof(md)); + strlcpy(md.md_magic, G_ELI_MAGIC, sizeof(md.md_magic)); + md.md_version = G_ELI_VERSION; + md.md_flags = 0; + boot = gctl_get_paraml(req, "boot", sizeof(*boot)); + if (boot == NULL) { + gctl_error(req, "No '%s' argument.", "boot"); + return; + } + if (*boot) { + int *nonewpassphrase; + + /* Part of key cannot be read on boot from a file. */ + str = gctl_get_asciiparam(req, "newkeyfile"); + if (str == NULL) { + gctl_error(req, "No '%s' argument.", "newkeyfile"); + return; + } + if (str[0] != '\0') { + gctl_error(req, + "Options -b and -K are mutually exclusive."); + return; + } + /* Key has to be given as a passphrase on boot. */ + nonewpassphrase = gctl_get_paraml(req, "nonewpassphrase", + sizeof(*nonewpassphrase)); + if (nonewpassphrase == NULL) { + gctl_error(req, "No '%s' argument.", "nonewpassphrase"); + return; + } + if (*nonewpassphrase) { + gctl_error(req, + "Options -b and -P are mutually exclusive."); + return; + } + md.md_flags |= G_ELI_FLAG_BOOT; + } + str = gctl_get_asciiparam(req, "algo"); + if (str == NULL) { + gctl_error(req, "No '%s' argument.", "algo"); + return; + } + md.md_algo = g_eli_str2algo(str); + if (md.md_algo < CRYPTO_ALGORITHM_MIN || + md.md_algo > CRYPTO_ALGORITHM_MAX) { + gctl_error(req, "Invalid encryption algorithm."); + return; + } + valp = gctl_get_paraml(req, "keylen", sizeof(*valp)); + if (valp == NULL) { + gctl_error(req, "No '%s' argument.", "keylen"); + return; + } + md.md_keylen = *valp; + md.md_keylen = g_eli_keylen(md.md_algo, md.md_keylen); + if (md.md_keylen == 0) { + gctl_error(req, "Invalid key length."); + return; + } + md.md_provsize = mediasize; + + valp = gctl_get_paraml(req, "iterations", sizeof(*valp)); + if (valp == NULL) { + gctl_error(req, "No '%s' argument.", "iterations"); + return; + } + md.md_iterations = *valp; + + valp = gctl_get_paraml(req, "sectorsize", sizeof(*valp)); + if (valp == NULL) { + gctl_error(req, "No '%s' argument.", "sectorsize"); + return; + } + if (*valp == 0) + md.md_sectorsize = secsize; + else { + if (*valp < 0 || (*valp % secsize) != 0) { + gctl_error(req, "Invalid sector size."); + return; + } + md.md_sectorsize = *valp; + } + + md.md_keys = 0x01; + arc4rand(md.md_salt, sizeof(md.md_salt)); + arc4rand(md.md_mkeys, sizeof(md.md_mkeys)); + + /* Generate user key. */ + if (eli_genkey(req, &md, key, 1) == NULL) { + bzero(key, sizeof(key)); + bzero(&md, sizeof(md)); + return; + } + + /* Encrypt the first and the only Master Key. */ + error = g_eli_mkey_encrypt(md.md_algo, key, md.md_keylen, md.md_mkeys); + bzero(key, sizeof(key)); + if (error != 0) { + bzero(&md, sizeof(md)); + gctl_error(req, "Cannot encrypt Master Key: %s.", + strerror(error)); + return; + } + + eli_metadata_encode(&md, sector); + bzero(&md, sizeof(md)); + error = g_metadata_store(prov, sector, sizeof(sector)); + bzero(sector, sizeof(sector)); + if (error != 0) { + gctl_error(req, "Cannot store metadata on %s: %s.", prov, + strerror(error)); + return; + } + if (verbose) + printf("Metadata value stored on %s.\n", prov); +} + +static void +eli_attach(struct gctl_req *req) +{ + struct g_eli_metadata md; + unsigned char key[G_ELI_USERKEYLEN]; + const char *prov; + int *nargs; + + nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); + if (nargs == NULL) { + gctl_error(req, "No '%s' argument.", "nargs"); + return; + } + if (*nargs != 1) { + gctl_error(req, "Too few arguments."); + return; + } + prov = gctl_get_asciiparam(req, "arg0"); + if (prov == NULL) { + gctl_error(req, "No 'arg%u' argument.", 0); + return; + } + + if (eli_metadata_read(req, prov, &md) == -1) + return; + + if (eli_genkey(req, &md, key, 0) == NULL) { + bzero(key, sizeof(key)); + return; + } + + gctl_ro_param(req, "key", sizeof(key), key); + if (gctl_issue(req) == NULL) { + if (verbose) + printf("Attched to %s.\n", prov); + } + bzero(key, sizeof(key)); +} + +static void +eli_setkey_attached(struct gctl_req *req, const char *prov) +{ + struct g_eli_metadata md; + unsigned char key[G_ELI_USERKEYLEN]; + + if (eli_metadata_read(req, prov, &md) == -1) + return; + + /* Generate key for Master Key encryption. */ + if (eli_genkey(req, &md, key, 1) == NULL) { + bzero(key, sizeof(key)); + return; + } + + gctl_ro_param(req, "key", sizeof(key), key); + gctl_issue(req); + bzero(key, sizeof(key)); +} + +static void +eli_setkey_detached(struct gctl_req *req, const char *prov) +{ + struct g_eli_metadata md; + unsigned char key[G_ELI_USERKEYLEN], mkey[G_ELI_DATAIVKEYLEN]; + unsigned char *mkeydst; + intmax_t *valp; + unsigned nkey; + int error; + + if (eli_metadata_read(req, prov, &md) == -1) + return; + + /* Generate key for Master Key decryption. */ + if (eli_genkey(req, &md, key, 0) == NULL) { + bzero(key, sizeof(key)); + return; + } + + /* Decrypt Master Key. */ + error = g_eli_mkey_decrypt(&md, key, mkey, &nkey); + bzero(key, sizeof(key)); + if (error != 0) { + bzero(&md, sizeof(md)); + if (error == -1) + gctl_error(req, "Wrong key for %s.", prov); + else /* if (error > 0) */ { + gctl_error(req, "Cannot decrypt Master Key: %s.", + strerror(error)); + } + return; + } + if (verbose) + printf("Decrypted Master Key %u.\n", nkey); + + valp = gctl_get_paraml(req, "keyno", sizeof(*valp)); + if (valp == NULL) { + gctl_error(req, "No '%s' argument.", "keyno"); + return; + } + if (*valp != -1) + nkey = *valp; +#if 0 + else + ; /* Use the key number which was found during decryption. */ +#endif + if (nkey >= G_ELI_MAXMKEYS) { + gctl_error(req, "Invalid '%s' argument.", "keyno"); + return; + } + + mkeydst = md.md_mkeys + nkey * G_ELI_MKEYLEN; + md.md_keys |= (1 << nkey); + + bcopy(mkey, mkeydst, sizeof(mkey)); + bzero(mkey, sizeof(mkey)); + + /* Generate key for Master Key encryption. */ + if (eli_genkey(req, &md, key, 1) == NULL) { + bzero(key, sizeof(key)); + bzero(&md, sizeof(md)); + return; + } + + /* Encrypt the Master-Key with the new key. */ + error = g_eli_mkey_encrypt(md.md_algo, key, md.md_keylen, mkeydst); + bzero(key, sizeof(key)); + if (error != 0) { + bzero(&md, sizeof(md)); + gctl_error(req, "Cannot encrypt Master Key: %s.", + strerror(error)); + return; + } + + /* Store metadata with fresh key. */ + eli_metadata_store(req, prov, &md); + bzero(&md, sizeof(md)); +} + +static void +eli_setkey(struct gctl_req *req) +{ + const char *prov; + int *nargs; + + nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); + if (nargs == NULL) { + gctl_error(req, "No '%s' argument.", "nargs"); + return; + } + if (*nargs != 1) { + gctl_error(req, "Too few arguments."); + return; + } + prov = gctl_get_asciiparam(req, "arg0"); + if (prov == NULL) { + gctl_error(req, "No 'arg%u' argument.", 0); + return; + } + + if (eli_is_attached(prov)) + eli_setkey_attached(req, prov); + else + eli_setkey_detached(req, prov); +} + +static void +eli_delkey_attached(struct gctl_req *req, const char *prov __unused) +{ + + gctl_issue(req); +} + +static void +eli_delkey_detached(struct gctl_req *req, const char *prov) +{ + struct g_eli_metadata md; + unsigned char *mkeydst; + intmax_t *valp; + unsigned nkey; + int *all, *force; + + if (eli_metadata_read(req, prov, &md) == -1) + return; + + all = gctl_get_paraml(req, "all", sizeof(*all)); + if (all == NULL) { + gctl_error(req, "No '%s' argument.", "all"); + return; + } + + if (*all) + arc4rand(md.md_mkeys, sizeof(md.md_mkeys)); + else { + force = gctl_get_paraml(req, "force", sizeof(*force)); + if (force == NULL) { + gctl_error(req, "No '%s' argument.", "force"); + return; + } + + valp = gctl_get_paraml(req, "keyno", sizeof(*valp)); + if (valp == NULL) { + gctl_error(req, "No '%s' argument.", "keyno"); + return; + } + if (*valp == -1) { + gctl_error(req, "Key number has to be specified."); + return; + } + nkey = *valp; + if (nkey >= G_ELI_MAXMKEYS) { + gctl_error(req, "Invalid '%s' argument.", "keyno"); + return; + } + if (!(md.md_keys & (1 << nkey)) && !*force) { + gctl_error(req, "Master Key %u is not set.", nkey); + return; + } + md.md_keys &= ~(1 << nkey); + if (md.md_keys == 0 && !*force) { + gctl_error(req, "This is the last Master Key. Use '-f' " + "option if you really want to remove it."); + return; + } + mkeydst = md.md_mkeys + nkey * G_ELI_MKEYLEN; + arc4rand(mkeydst, G_ELI_MKEYLEN); + } + + eli_metadata_store(req, prov, &md); + bzero(&md, sizeof(md)); +} + +static void +eli_delkey(struct gctl_req *req) +{ + const char *prov; + int *nargs; + + nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); + if (nargs == NULL) { + gctl_error(req, "No '%s' argument.", "nargs"); + return; + } + if (*nargs != 1) { + gctl_error(req, "Too few arguments."); + return; + } + prov = gctl_get_asciiparam(req, "arg0"); + if (prov == NULL) { + gctl_error(req, "No 'arg%u' argument.", 0); + return; + } + + if (eli_is_attached(prov)) + eli_delkey_attached(req, prov); + else + eli_delkey_detached(req, prov); +} + +static void +eli_kill_detached(struct gctl_req *req, const char *prov) +{ + struct g_eli_metadata md; + int error; + + /* + * NOTE: Maybe we should verify if this is geli provider first, + * but 'kill' command is quite critical so better don't waste + * the time. + */ +#if 0 + error = g_metadata_read(prov, (unsigned char *)&md, sizeof(md), + G_ELI_MAGIC); + if (error != 0) { + gctl_error(req, "Cannot read metadata from %s: %s.", prov, + strerror(error)); + return; + } +#endif + + arc4rand((unsigned char *)&md, sizeof(md)); + error = g_metadata_store(prov, (unsigned char *)&md, sizeof(md)); + if (error != 0) { + gctl_error(req, "Cannot write metadata to %s: %s.", prov, + strerror(error)); + } +} + +static void +eli_kill(struct gctl_req *req) +{ + const char *prov; + char param[16]; + unsigned i; + int *nargs, *all; + + nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); + if (nargs == NULL) { + gctl_error(req, "No '%s' argument.", "nargs"); + return; + } + all = gctl_get_paraml(req, "all", sizeof(*all)); + if (all == NULL) { + gctl_error(req, "No '%s' argument.", "all"); + return; + } + if (!*all && *nargs == 0) { + gctl_error(req, "Too few arguments."); + return; + } + /* + * How '-a' option combine with a list of providers: + * Delete Master Keys from all attached providers: + * geli kill -a + * Delete Master Keys from all attached provider and from + * detached da0 and da1: + * geli kill -a da0 da1 + * Delete Master Keys from (attached or detached) da0 and da1: + * geli kill da0 da1 + */ + + /* + * First attached providers. + */ + gctl_issue(req); + /* + * Now the rest. + */ + for (i = 0; i < (unsigned)*nargs; i++) { + snprintf(param, sizeof(param), "arg%u", i); + prov = gctl_get_asciiparam(req, param); + + if (!eli_is_attached(prov)) + eli_kill_detached(req, prov); + } +} + +static void +eli_backup(struct gctl_req *req) +{ + struct g_eli_metadata md; + const char *file, *prov; + unsigned secsize; + unsigned char *sector; + off_t mediasize; + int *nargs, filefd, provfd; + + nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); + if (nargs == NULL) { + gctl_error(req, "No '%s' argument.", "nargs"); + return; + } + if (*nargs != 2) { + gctl_error(req, "Invalid number of arguments."); + return; + } + + prov = gctl_get_asciiparam(req, "arg0"); + if (prov == NULL) { + gctl_error(req, "No 'arg%u' argument.", 0); + return; + } + file = gctl_get_asciiparam(req, "arg1"); + if (file == NULL) { + gctl_error(req, "No 'arg%u' argument.", 1); + return; + } + + provfd = filefd = -1; + sector = NULL; + secsize = 0; + + provfd = open(prov, O_RDONLY); + if (provfd == -1 && errno == ENOENT && prov[0] != '/') { + char devprov[MAXPATHLEN]; + + snprintf(devprov, sizeof(devprov), "%s%s", _PATH_DEV, prov); + provfd = open(devprov, O_RDONLY); + } + if (provfd == -1) { + gctl_error(req, "Cannot open %s: %s.", prov, strerror(errno)); + return; + } + filefd = open(file, O_WRONLY | O_TRUNC | O_CREAT, 0600); + if (filefd == -1) { + gctl_error(req, "Cannot open %s: %s.", file, strerror(errno)); + goto out; + } + + mediasize = g_get_mediasize(prov); + secsize = g_get_sectorsize(prov); + if (mediasize == 0 || secsize == 0) { + gctl_error(req, "Cannot get informations about %s: %s.", prov, + strerror(errno)); + return; + } + + sector = malloc(secsize); + if (sector == NULL) { + gctl_error(req, "Cannot allocate memory."); + return; + } + + /* Read metadata from the provider. */ + if (pread(provfd, sector, secsize, mediasize - secsize) != + (ssize_t)secsize) { + gctl_error(req, "Cannot read metadata: %s.", strerror(errno)); + goto out; + } + /* Check if this is geli provider. */ + if (eli_metadata_decode(sector, &md) != 0) { + gctl_error(req, "MD5 hash mismatch: not a geli provider?"); + goto out; + } + /* Write metadata to the destination file. */ + if (write(filefd, sector, secsize) != (ssize_t)secsize) { + gctl_error(req, "Cannot write to %s: %s.", file, + strerror(errno)); + goto out; + } +out: + if (provfd > 0) + close(provfd); + if (filefd > 0) + close(filefd); + if (sector != NULL) { + bzero(sector, secsize); + free(sector); + } +} + +static void +eli_restore(struct gctl_req *req) +{ + struct g_eli_metadata md; + const char *file, *prov; + unsigned char *sector; + unsigned secsize; + off_t mediasize; + int *nargs, filefd, provfd; + + nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); + if (nargs == NULL) { + gctl_error(req, "No '%s' argument.", "nargs"); + return; + } + if (*nargs != 2) { + gctl_error(req, "Invalid number of arguments."); + return; + } + + file = gctl_get_asciiparam(req, "arg0"); + if (file == NULL) { + gctl_error(req, "No 'arg%u' argument.", 1); + return; + } + prov = gctl_get_asciiparam(req, "arg1"); + if (prov == NULL) { + gctl_error(req, "No 'arg%u' argument.", 0); + return; + } + + provfd = filefd = -1; + sector = NULL; + secsize = 0; + + filefd = open(file, O_RDONLY); + if (filefd == -1) { + gctl_error(req, "Cannot open %s: %s.", file, strerror(errno)); + goto out; + } + provfd = open(prov, O_WRONLY); + if (provfd == -1 && errno == ENOENT && prov[0] != '/') { + char devprov[MAXPATHLEN]; + + snprintf(devprov, sizeof(devprov), "%s%s", _PATH_DEV, prov); + provfd = open(devprov, O_WRONLY); + } + if (provfd == -1) { + gctl_error(req, "Cannot open %s: %s.", prov, strerror(errno)); + return; + } + + mediasize = g_get_mediasize(prov); + secsize = g_get_sectorsize(prov); + if (mediasize == 0 || secsize == 0) { + gctl_error(req, "Cannot get informations about %s: %s.", prov, + strerror(errno)); + return; + } + + sector = malloc(secsize); + if (sector == NULL) { + gctl_error(req, "Cannot allocate memory."); + return; + } + + /* Read metadata from the backup file. */ + if (read(filefd, sector, secsize) != (ssize_t)secsize) { + gctl_error(req, "Cannot read from %s: %s.", file, + strerror(errno)); + goto out; + } + /* Check if this file contains geli metadata. */ + if (eli_metadata_decode(sector, &md) != 0) { + gctl_error(req, "MD5 hash mismatch: not a geli backup file?"); + goto out; + } + /* Read metadata from the provider. */ + if (pwrite(provfd, sector, secsize, mediasize - secsize) != + (ssize_t)secsize) { + gctl_error(req, "Cannot write metadata: %s.", strerror(errno)); + goto out; + } +out: + if (provfd > 0) + close(provfd); + if (filefd > 0) + close(filefd); + if (sector != NULL) { + bzero(sector, secsize); + free(sector); + } +} + +static void +eli_clear(struct gctl_req *req) +{ + const char *name; + char param[16]; + int *nargs, error, i; + + nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); + if (nargs == NULL) { + gctl_error(req, "No '%s' argument.", "nargs"); + return; + } + if (*nargs < 1) { + gctl_error(req, "Too few arguments."); + return; + } + + for (i = 0; i < *nargs; i++) { + snprintf(param, sizeof(param), "arg%u", i); + name = gctl_get_asciiparam(req, param); + + error = g_metadata_clear(name, G_ELI_MAGIC); + if (error != 0) { + fprintf(stderr, "Cannot clear metadata on %s: %s.\n", + name, strerror(error)); + gctl_error(req, "Not fully done."); + continue; + } + if (verbose) + printf("Metadata cleared on %s.\n", name); + } +} + +static void +eli_dump(struct gctl_req *req) +{ + struct g_eli_metadata md, tmpmd; + const char *name; + char param[16]; + int *nargs, error, i; + + nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs)); + if (nargs == NULL) { + gctl_error(req, "No '%s' argument.", "nargs"); + return; + } + if (*nargs < 1) { + gctl_error(req, "Too few arguments."); + return; + } + + for (i = 0; i < *nargs; i++) { + snprintf(param, sizeof(param), "arg%u", i); + name = gctl_get_asciiparam(req, param); + + error = g_metadata_read(name, (unsigned char *)&tmpmd, + sizeof(tmpmd), G_ELI_MAGIC); + if (error != 0) { + fprintf(stderr, "Cannot read metadata from %s: %s.\n", + name, strerror(error)); + gctl_error(req, "Not fully done."); + continue; + } + if (eli_metadata_decode((unsigned char *)&tmpmd, &md) != 0) { + fprintf(stderr, "MD5 hash mismatch for %s, skipping.\n", + name); + gctl_error(req, "Not fully done."); + continue; + } + printf("Metadata on %s:\n", name); + eli_metadata_dump(&md); + printf("\n"); + } +} -- cgit v1.1