#!/bin/sh
#
# Small shell script utility to setup an encryption target
# via device-mapper and the dm-crypt target.
#
# Copyright (C) 2003 Christophe Saout <christophe@saout.de>
#
# You are free to modify and distribute this file under
# the terms of the GNU General Public License v2
#

# Defaults

CIPHER=aes
HASH=rmd160
KEYSIZE=32

# You should not need to change these

DEVFS=/dev/
DMPATH=/dev/mapper/
DMTARGET=crypt
SKIPPED=0
OFFSET=0
BDEVSIZE=""
KEY=""

# Begin functions

die() {
	echo "$*" 1>&2
	exit 1
}

check() {
	LIST="dmsetup hexdump sed head awk ls"
	if [ "$HASH" != "plain" ]; then
		LIST="$LIST hashalot"
	fi
	for i in $LIST; do
		which $i &> /dev/null \
			|| die "Error: $i not found in search path."
	done

	dmsetup version &> /dev/null \
		|| die "Error: No device mapper support in kernel."
}

syntax() {
	echo "Syntax: $0 [<OPTIONS>] <action> <name> [<device>]"
	echo -e "\t<OPTIONS>:"
	echo -e "\t    -c <cipher> (see /proc/crypto)"
	echo -e "\t    -h {plain/<hash>} (see hashalot)"
	echo -e "\t    -s <keysize> (in bytes)"
	echo -e "\t    -b <size> (in sectors)"
	echo -e "\t    -o <offset> (in sectors)"
	echo -e "\t    -p <skipped> (in sectors)"
	echo -e "\t<action> is one of:"
	echo -e "\t    create - create device"
	echo -e "\t    remove - remove device"
	echo -e "\t    reload - modify active device"
	echo -e "\t    resize - resize active device"
	echo -e "\t    status - show device status"
	echo -e "\t<name> is the device to create under $DMPATH"
	echo -e "\t<device> is the encrypted device"
	exit 1
}

getopt() {
	case "$1" in
	    -c)
		CIPHER="$2"
		return 2
		;;
	    -h)
		HASH="$2"
		return 2
		;;
	    -s)
		KEYSIZE="$2"
		[ -n "$KEYSIZE" -a $KEYSIZE -gt 0 ] \
			|| echo "Error: Invalid keysize."
		return 2
		;;
	    -b)
		BDEVSIZE="$2"
		[ -n "$BDEVSIZE" -a $BDEVSIZE -gt 0 ] \
			|| echo "Error: Invalid block device size."
		return 2
		;;
	    -o)
		OFFSET="$2"
		[ -n "$OFFSET" -a $OFFSET -gt 0 ] \
			|| echo "Error: Invalid block device offset."
		return 2
		;;
	    -p)
		SKIPPED="$2"
		[ -n "$SKIPPED" -a $SKIPPED -gt 0 ] \
			|| echo "Error: Invalid skip offset."
		return 2
		;;
	esac
	return 0
}

finddev() {
	find $DEVFS -type b -print0 2> /dev/null \
		| xargs -0 ls -l -n \
		| sed -e 's/,/: /' \
		| awk '{ print $5 $6 " " $NF }' \
		| sed -n -e "/^$1 /s/^[^ ]* // p" \
		| head -n 1
}

getkey() {
	if [ -n "$KEY" ]; then
		echo $KEY
		return 0
	fi
	( 
		if [ "$HASH" == "plain" ]; then
			read -p "Enter passphrase: " -r -s PASS
			echo -n "$PASS"
		else
			hashalot $HASH 2> /dev/null \
				|| die "Error: unknown hash $HASH."
		fi
	) \
		| hexdump -e "\"\" $KEYSIZE/1 \"%02x\" \"\\n\"" \
		| sed -e 's/ /0/g' | head -n 1
}

mktable() {
	[ -b "$DEVICE" ] || "Error: $DEVICE not a valid block device."

	if [ -z "$BDEVSIZE" ]; then
		BDEVSIZE="`blockdev --getsize $DEVICE 2> /dev/null`"
	fi
	[ -n "$BDEVSIZE" -a "$BDEVSIZE" -gt 0 ] \
		|| die "Error: Could not get size of $DEVICE."

	local KEY="`getkey`"
	[ -n "$KEY" ] || die "Error: Could not get key."

	echo 0 $BDEVSIZE $DMTARGET $CIPHER $KEY $SKIPPED $DEVICE $OFFSET
}

gettable() {
	[ -e "$DMPATH$NAME" ] \
		|| die "Error: $DMPATH$NAME does not exist."
	[ -b "$DMPATH$NAME" ] \
		|| die "Error: $DMPATH$NAME is not a block device."
	local OPTIONS="`dmsetup table \"\$NAME\" 2> /dev/null`"
	local SIGNATURE="`echo \"\$OPTIONS\" | awk '{ print $3 " " $1 " " $9 }'`"
	[ "$SIGNATURE" == "$DMTARGET 0 " ] \
		|| die "Error: Not a supported crypt target on $DMPATH$NAME."
	SIZE="`echo \"\$OPTIONS\" | awk '{ print $2 }'`"
	CIPHER="`echo \"\$OPTIONS\" | awk '{ print $4 }'`"
	KEY="`echo \"\$OPTIONS\" | awk '{ print $5 }'`"
	SKIPPED="`echo \"\$OPTIONS\" | awk '{ print $6 }'`"
	local DEVNUM="`echo \"\$OPTIONS\" | awk '{ print $7 }'`"
	DEVICE="`finddev $DEVNUM`"
	[ -n "$DEVICE" ] \
		|| die "Error: Could not find device $DEVNUM."
	OFFSET="`echo \"\$OPTIONS\" | awk '{ print $8 }'`"
}

setup() {
	[ -n "$DEVICE" ] || syntax
	if [ "$1" == "yes" ]; then
		[ -e "$DMPATH$NAME" ] \
			|| die "Error: $DMPATH$NAME does not exist."
		if ! mktable | dmsetup reload "$NAME" &> /dev/null; then
			die "Error: Could not reload table," \
			    "see syslog for details."
		fi
		dmsetup resume "$NAME" &> /dev/null \
			|| die "Error: Problem resuming $DMPATH$NAME."
	else
		[ -b "$DMPATH$NAME" ] \
			&& die "Error: $DMPATH$NAME already exists."
		if ! mktable | dmsetup create "$NAME" &> /dev/null; then
			dmsetup remove "$NAME" &> /dev/null
			die "Error: Could not create device," \
			    "see syslog for details."
		fi
	fi
}

# End functions

until getopt "$@"; do
	shift $?
done

MODE="$1"
NAME="$2"
DEVICE="$3"

if [ -z "$MODE" ]; then
	syntax
	exit 1
fi

if [ -z "$NAME" ]; then
	syntax
	exit 1
fi

check

case "$MODE" in
    create)
	setup no
	;;
    remove)
	if ! dmsetup remove "$NAME" &> /dev/null; then
		[ -b "$DMPATH$NAME" ] \
			|| die "Error: $DMPATH$NAME does not exist."
		die "Error: Could not remove $DMPATH$NAME, still in use?"
	fi
	;;
    reload)
	setup yes
	;;
    resize)
	gettable
	setup yes
	unset KEY
	;;
    status)
	if [ -e "$DMPATH$NAME" ]; then
		gettable "$NAME"
		echo "$DMPATH$NAME is active:"
		echo "  cipher:  $CIPHER"
		echo "  keysize: $[${#KEY}/2] bytes"
		echo "  device:  $DEVICE"
		echo "  offset:  $SKIPPED sectors"
		echo "  size:    $SIZE sectors"
		[ $SKIPPED -gt 0 ] && echo "   skipped: $SKIPPED"
		unset KEY
	else
		echo "$DMPATH$NAME does not exist."
		exit 1
	fi
	;;
    *)
	syntax
esac

exit 0
