#!/bin/sh
#
#  Copyright (c) 02005 sense.lab <senselab@systemausfall.org>
#
#  License:  This script is distributed under the terms of version 2
#            of the GNU GPL. See the LICENSE file included with the package.
#
# $Id$
#
# this script is responsible for all dangerous actions, that require root privileges
# every action should be checked at least TWICE a day for open holes :)
# usually will get call via sudo
#
# called by:
#	- cbox-manage.sh
#

set -eu

LIB_DIR=$(dirname "$0")
LIB_DIR=$(cd "$LIB_DIR"; pwd)

test "$(id -u)" -ne 0 && echo "$(basename $0) - only root may call this script" >&2 && exit 100

# read the default setting file, if it exists
test -e /etc/default/cryptobox && . /etc/default/cryptobox

# set CONF_FILE to default value, if not configured in /etc/default/cryptobox
CONF_FILE=${CONF_FILE:-/etc/cryptobox/cryptobox.conf}
# parse config file
. "$CONF_FILE"
# parse distribution specific file
. "$DISTRIBUTION_CONF"

CB_SCRIPT="$LIB_DIR/cbox-manage.sh"
CONFIG_MARKER=cryptobox.marker


############ some useful functions ###############

# check if the given device is part of the SCAN_DEVICE list
# every entry in SCAN_DEVICES is matched as "^/dev/${SCAN_DEVICE}[^/]*$" against
# the given device
# other devices may not be touched
function is_device_allowed()
# parameter: device
{
	for a in $SCAN_DEVICES
		do	echo "$1" | grep -q "^/dev/${a}[^/]*$" && return 0
	  done
	return 1
}


# return the uuid of the partition (if possible)
# this works at least for luks, ext2/3 and vfat partitions
function get_device_uuid() {
	local UUID
	# check for luksUUID or ext2/3-uuid
	if is_luks_device "$1"
		then	UUID=$("$CRYPTSETUP" luksUUID "$1")
		else	test -x "$BLKID" && UUID=$("$BLKID" -s UUID -o value -c /dev/null -w /dev/null "$1" 2>/dev/null)
	  fi
	if test -z "$UUID"
	  then	get_device_flat_name "$1"
	  else	echo "$UUID"
	 fi
	return 0
}


# the device name is "flattened"
function get_device_flat_name() {
	echo "$1" | sed 's#/#_#g'
}


# the basename of the mountpoint for this device - should be somehow human_readable
function get_device_mnt_name() {
	"$CB_SCRIPT" get_device_name "$1"
}


# every devmapper name should look like a UUID
function is_uuid_valid() {
	local hex=[0-9a-f]
	echo "$1" | grep -q "^$hex\{8\}-$hex\{4\}-$hex\{4\}-$hex\{4\}-$hex\{12\}$"
}


# parameter ExitCode ErrorMessage
function error_msg() {
	echo "CBOX-ERROR: [$(basename $0) - $ACTION] - $2" >&2
	exit $1
}


# parameter: device sfdisk_layout_setup
# e.g.: /dev/hda "0,1,L \n,,L\n"
function partition_device() {
	# TODO: allow different layouts
	# TODO: skip config partition if a configuration is already active
	# sfdisk -n doesn't actually write (for testing purpose)
	if echo -e "$2" | "$SFDISK" -n "$1"
		then	echo -e "$2" | "$SFDISK" "$1" || return 1
		else	return 2
	  fi
	true
}


function is_luks_device()
# parameter: device
{
	"$CRYPTSETUP" isLuks "$1" 2>/dev/null
}


################ main ####################

ACTION=unknown
test $# -gt 0 && ACTION=$1 && shift


case "$ACTION" in
	partition_disk )
		test $# -ne 2 && error_msg 1 "wrong number of parameters"
		is_device_allowed "$1" || \
			error_msg 3 "this device ($1) is not listed in SCAN_DEVICES (see $CONF_FILE)"
		partition_device "$1" "$2" || \
			error_msg 2 "failed to create new partition table on device $1"
		;;
	mount )
		# parameters: device
		# returns the relative name of the mointpoint for success
		test $# -ne 1 && error_msg 1 "wrong number of parameters"
		is_device_allowed "$1" || \
			error_msg 3 "this device ($1) is not listed in SCAN_DEVICES (see $CONF_FILE)"
		mnt_name=$(get_device_mnt_name "$1")
		mountpoint -q "$MNT_PARENT/$mnt_name" && \
			error_msg 5 "a device with the same name ($mnt_name) is already mounted"
		mkdir -p "$MNT_PARENT/$mnt_name"
		if is_luks_device "$1"
		   then	"$CRYPTSETUP" luksOpen "$1" "$mnt_name" || \
					error_msg 6 "could not open encrypted device $1"
				if mount "$DEV_MAPPER_DIR/$mnt_name" "$MNT_PARENT/$mnt_name"
					then	true
					else	"$CRYPTSETUP" luksClose "$mnt_name" || true
							error_msg 7 "wrong password for $1 supplied"
				  fi
		   else	mount "$1" "$MNT_PARENT/$mnt_name" || \
					error_msg 8 "invalid filesystem on device $1"
		  fi
		# just in case, that there is no ext2/3 filesystem:
		# set uid option (will fail silently for ext2/3)
		# TODO: there is no FILE_USER setting anymore - do we still need it?
		#mount -o remount,uid="$FILE_USER" "$MNT_PARENT/$name" 2>/dev/null || true
		# adapt top-level permission to current setup - again: may fail silently
		#chown "$FILE_USER" "$MNT_PARENT/$name" 2>/dev/null || true
		true
		;;
	umount )
		#parameter: device
		test $# -ne 1 && error_msg 1 "wrong number of parameters"
		is_device_allowed "$1" || \
			error_msg 3 "this device ($1) is not listed in SCAN_DEVICES (see $CONF_FILE)"
		mnt_name=$(get_device_mnt_name "$1")
		mountpoint -q "$MNT_PARENT/$mnt_name" || \
			error_msg 9 "the device ($1) is not mounted as '$mnt_name'"
		# try to unmount - do it in lazy mode
		umount -l "$MNT_PARENT/$mnt_name"
		# TODO: check, what happens, if there are open files - does the device gets mapping removed?
		# remove (if necessary) the device mapping
		if test -e "$DEV_MAPPER_DIR/$mnt_name"
		  then	"$CRYPTSETUP" luksClose "$mnt_name" || \
				error_msg 11 "could not remove the device mapper ($mnt_name) for device $1"
		 fi
		# try to remove the mountpoint - a failure is not important
		rmdir "$MNT_PARENT/$mnt_name" || true
		# set exitcode
		mountpoint -q "$MNT_PARENT/$mnt_name" && exit 1
		true
		;;
	create_crypto )
		# parameter: device keyfile
		test $# -ne 2 && error_msg 1 "wrong number of parameters"
		keyfile=$2
		test -e "$keyfile" || error_msg 2 "keyfile ($keyfile) not found"
		is_device_allowed "$1" || \
			error_msg 3 "this device ($1) is not listed in SCAN_DEVICES (see $CONF_FILE)"
		# read the passphrase from stdin
		# the iter-time is in milliseconds - keep it low for fast mounting
		cat "$keyfile" | \
			"$CRYPTSETUP" --cipher "$DEFAULT_CIPHER" --iter-time 2000 --batch-mode luksFormat "$1" || \
			error_msg 11 "failed to create the encrypted partition"
		name=$(get_device_mnt_name "$1")
		cat "$keyfile" | "$CRYPTSETUP" --batch-mode luksOpen "$1" "$name" || \
			error_msg 12 "failed to open the encrypted partition"
		# trash the passphrase in keyfile
		echo "0123456789abcdefghijklmnopqrstuvwxyz" > "$keyfile"
		# the disk cache surely prevents the previous line from being written, but we do it anyway ...
		echo "zyxwvutsrqponmlkjihgfedcba9876543210" > "$keyfile"
		rm "$keyfile"
		# complete in background
		(
			"$MKFS_DATA" "$DEV_MAPPER_DIR/$name" || \
				error_msg 13 "failed to create the encrypted filesystem"
			"$CRYPTSETUP" --batch-mode luksClose "$name" || \
				error_msg 14 "failed to close the encrypted mapped device"
		) </dev/null >/dev/null 2>/dev/null &
		true
		;;
	create_plain )
		# parameter: device
		test $# -ne 1 && error_msg 1 "wrong number of parameters for 'create_plain'"
		is_device_allowed "$1" || \
			error_msg 3 "this device ($1) is not listed in SCAN_DEVICES (see $CONF_FILE)"
		# complete in background
		(
			"$MKFS_DATA" "$1" || \
				error_msg 15 "failed to create the plaintext filesystem"
		) </dev/null >/dev/null 2>/dev/null &
		true
		;;
	get_device_mnt_name )
		# parameter: device
		test $# -ne 1 && error_msg 1 "wrong number of parameters"
		is_device_allowed "$1" || \
			error_msg 3 "this device ($1) is not listed in SCAN_DEVICES (see $CONF_FILE)"
		get_device_mnt_name "$1"
		;;
	get_device_uuid )
		# parameter: device
		test $# -ne 1 && error_msg 1 "wrong number of parameters"
		is_device_allowed "$1" || \
			error_msg 3 "this device ($1) is not listed in SCAN_DEVICES (see $CONF_FILE)"
		get_device_uuid "$1"
		;;
	is_config_partition )
		# parameter: device
		# returns exitcode 0 if the device contains a configuration
		test $# -ne 1 && error_msg 1 "wrong number of parameters"
		is_device_allowed "$1" || \
			error_msg 3 "this device ($1) is not listed in SCAN_DEVICES (see $CONF_FILE)"
		is_config=0
		tmp_dir=/tmp/$(basename $0)-$$-mnt
		mkdir -p "$tmp_dir"
		# error means "no config partition"
		if mount "$1" "$CONFIG_DIR"
			then	test -e "$CONFIG_DIR/$CONFIG_MARKER" && is_config=1
					umount "$CONFIG_DIR" || \
						error_msg 14 "unable to unmount configation partition after probing"
		  fi
		rmdir "$tmp_dir" || true
		# return 0 if $device is a config partition
		test "$is_config" -eq 1 && exit 0
		exit 1
		;;
	is_crypto_partition )
		# parameter: device
		# returns exitcode 0 if the device contains a luks header
		test $# -ne 1 && error_msg 1 "wrong number of parameters"
		is_device_allowed "$1" || \
			error_msg 3 "this device ($1) is not listed in SCAN_DEVICES (see $CONF_FILE)"
		is_luks_device "$1"
		;;
	is_plain_partition )
		# parameter: device
		# returns exitcode 0 if the device contains a readable filesystem
		test $# -ne 1 && error_msg 1 "wrong number of parameters"
		is_device_allowed "$1" || \
			error_msg 3 "this device ($1) is not listed in SCAN_DEVICES (see $CONF_FILE)"
		status=0
		tmp_dir=/tmp/$(basename $0)-$$-mnt
		mkdir -p "$tmp_dir"
		if mount "$1" "$tmp_dir" >/dev/null 2>/dev/null
			then	test ! -e "$tmp_dir/$CONFIG_MARKER" && status=1
					umount "$tmp_dir"
		  fi
		rmdir "$tmp_dir" || true
		test "$status" -eq 1 && exit 0
		exit 1
		;;
	trash_device )
		# parameter: device
		test $# -ne 1 && error_msg 1 "wrong number of parameters"
		is_device_allowed "$1" || \
			error_msg 3 "this device ($1) is not listed in SCAN_DEVICES (see $CONF_FILE)"
		dd if=/dev/urandom of="$1" bs=512 count=1 2>/dev/null
		;;
	diskinfo )
		# parameter: device
		test $# -ne 1 && error_msg 1 "wrong number of parameters"
		is_device_allowed "$1" || \
			error_msg 3 "this device ($1) is not listed in SCAN_DEVICES (see $CONF_FILE)"
		"$SFDISK" -L -q -l "$1"
		;;
	update_network )
		# parameter: none
		ip=
		# TODO: can we avoid to hard-code the filename ($CONFIG_DIR/ip) here?
		test -e "$CONFIG_DIR/ip" && ip=$(<"$CONFIG_DIR/ip")
		test -n "$z" && ifconfig "$NET_IFACE" "$ip"
		;;
	poweroff )
		# TODO: check configuration setting before
		"$POWEROFF"
		;;
	reboot )
		# TODO: check configuration setting before
		"$REBOOT"
		;;
	* )
		echo "[$(basename $0)] - unknown action: $ACTION" >&2
		echo "Syntax: $(basename $0) ACTION PARAMETERS"
		echo '	partition_disk			$device	$disk_layout'
		echo '	get_device_name			$device'
		echo '	get_device_uuid			$device'
		echo '	create_crypto			$device'
		echo '	mount				$device'
		echo '	umount				$name'
		echo '	create_config			$device'
		echo '	mount_config			$device'
		echo '	remount_config			{ ro | rw }'
		echo '	umount_config'
		echo '	is_config_partition		$device'
		echo '	is_plain_partition	$device'
		echo '	is_crypto_partition		$device'
		echo '	trash_device			$device'
		echo '	diskinfo			$device'
		echo '	update_network'
		echo '	poweroff'
		echo '	reboot'
		echo '	help'
		echo
		test "$ACTION" = "help" && exit 0
		# return error for any unknown/unspecified action
		exit 1
		;;
  esac

