#!/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 does EVERYTHING
# all other scripts are only frontends :)
#
# called by:
#	- some rc-scripts
#	- the web frontend cgi
#

# TODO: check permissions and owners of config files, directories and scripts before
# running cbox-root-actions.sh

set -eu


# default location of config file
CONF_FILE=/etc/cryptobox/cryptobox.conf

LIB_DIR=$(dirname "$0")

# to determine a nice default partition name
DEVICE_NAME_PREFIX="Disk #"

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

test ! -e "$CONF_FILE" && echo "Could not find the configuration file: $CONF_FILE" >&2 && exit 1

# parse config file
. "$CONF_FILE"

test ! -e "$CONF_FILE" && echo "Could not find the distribution specific configuration file: $CONF_FILE" >&2 && exit 1

# parse the distribution specific file
. "$DISTRIBUTION_CONF"

# check for writable log file
test -w "$LOG_FILE" || LOG_FILE=/tmp/$(basename "$LOG_FILE")

# retrieve configuration directory
CONFIG_DIR="$(getent passwd $CRYPTOBOX_USER | cut -d ':' -f 6)/config"
CONFIG_MARKER=cryptobox.marker

## configuration
ROOT_PERM_SCRIPT="$LIB_DIR/cryptobox_root_wrapper"
# ROOT_PERM_SCRIPT needs the MNT_PARENT setting
export MNT_PARENT="$(cd ~; pwd)/mnt"

######## stuff ##########

# all partitions with a trailing number
ALL_PARTITIONS=$(cat /proc/partitions | sed '1,2d; s/  */ /g; s/^ *//' | cut -d " " -f 4 | grep '[0-9]$')

#########################

function log_msg()
{
	# the log file is (maybe) not writable during boot - try
	# before writing ...
	test -w "$LOG_FILE" || return 0
	echo >>"$LOG_FILE"
	echo "##### `date` #####" >>"$LOG_FILE"
	echo "$1" >>"$LOG_FILE"
}


function error_msg()
# parameters: ExitCode ErrorMessage
{
	local all=$@
	test $# -ne 2 && error_msg 1 "*** invalid call of error_msg *** $all"
	echo "[`date`] - $2" | tee -a "$LOG_FILE" >&2
	# print the execution stack - not usable with busybox
	# caller | sed 's/^/\t/' >&2
	exit "$1"
}


# Parameter: device
function is_device_allowed() {
	# check for invalid characters and exit if one is found
	local device=$(echo "$1" | sed 's#[^a-zA-Z0-9_\-\./]##g')
	test "$1" = "$device" || return 1
	# remove leading "/dev/"
	device=$(echo "$device" | sed 's#^/dev/##')
	# return for empty name
	test -z "$device" && return 1
	for a in $ALL_PARTITIONS
	  do	echo "$device" | grep -q "^$a.*" && return 0
	 done
	# no matching device found - exit with error
	return 1
}

function config_set_value()
# parameters: SettingName [SettingValue]
# read from stdin if SettingValue is not defined
{
	if test $# -gt 1
		then	echo "$2" > "$CONFIG_DIR/$1"
		else	cat - >"$CONFIG_DIR/$1"
	  fi
}


function config_get_value()
# parameters: SettingName
{
	# use mounted config, if it exists - otherwise use defaults
	local conf_dir
	test -z "$1" && error_msg 1 "empty setting name"
	# check for existence - maybe use default values (even for old
	# releases that did not contain this setting)
	if test -e "$CONFIG_DIR/$1" 
	  then	cat "$CONFIG_DIR/$1"
	  elif test -e "$CONFIG_DEFAULTS_DIR/$1"
		  then	cat "$CONFIG_DEFAULTS_DIR/$1"
		  else	case "$1" in
					# you may place default values for older versions here
					# for compatibility
					* )
						error_msg 2 "unknown configuration value ($1)"
						;;
				  esac
	 fi
	return 0
}


function list_partitions_of_type()
# parameter: { config | crypto | plaindata | unused }
{
	local config=
	local crypto=
	local plaindata=
	local unused=
	for a in $ALL_PARTITIONS
		do	if "$ROOT_PERM_SCRIPT" is_crypto_partition "/dev/$a"
			   then	crypto="$crypto /dev/$a"
			   elif	"$ROOT_PERM_SCRIPT" is_config_partition "/dev/$a"
				   then	config="$config /dev/$a"
				   elif	"$ROOT_PERM_SCRIPT" is_plaindata_partition "/dev/$a"
					   then	plaindata="$plaindata /dev/$a"
					   else	unused="$unused /dev/$a"
			  fi
	  done
	case "$1" in
		config )
			echo "$config"
			;;
		crypto )
			echo "$crypto"
			;;
		plaindata )
			echo "$plaindata"
			;;
		unused )
			echo "$unused"
			;;
		* )
			error_msg 11 "wrong parameter ($1) for list_partition_types in $(basename $0)"
			;;
	  esac | tr " " "\n" | grep -v '^$'
	  return 0
}


# Parameter: DEVICE
function get_device_mnt_name() {
	"$ROOT_PERM_SCRIPT" get_device_mnt_name "$1"
}


# Parameter: DEVICE
function get_device_uuid() {
	"$ROOT_PERM_SCRIPT" get_device_uuid "$1"
}


# Parameter: DEVICE
# return the readable name of the crypto container, if it is already defined
# if undefined - return the uuid
function get_device_name() {
	local uuid=$(get_device_uuid "$1")
	local dbname=$(config_get_value "names.db" | grep "^$uuid:" | cut -d ":" -f 2-)
	# return dbname if it exists
	test -n "$dbname" && echo "$dbname" && return 0
	# find a nice name for the new partition
	local counter=1
	local test_name
	local test_uuid
	local test_result
	# try to find a name with the defined "prefix" followed by a number ...
	while true
	  do	test_name="$DEVICE_NAME_PREFIX$counter"
		if config_get_value "names.db" | grep -q ":$test_name$"
		  then	counter=$((counter+1))
		  else	# save it for next time
			set_device_name "$1" "$test_name"
			echo "$test_name"
			return 0
		 fi
	 done
}


function set_device_name()
# TODO: the implementation is quite ugly, but it works (tm)
# Parameter: DEVICE NAME
{
	local uuid=$(get_device_uuid "$1")
	# remove the old setting for this device and every possible entry with the same name
	local new_config=$(config_get_value 'names.db' | sed "/^$uuid:/d; /^[^:]*:$2$/d"; echo "$uuid:$2")
	echo "$new_config" | config_set_value "names.db"
}


function does_crypto_name_exist()
# Parameter: NAME
{
	config_get_value 'names.db' | grep -q "^[^:]*:$1$"
}


function create_crypto()
# Parameter: DEVICE NAME KEYFILE
# keyfile is necessary, to allow background execution via 'at'
{
	local device=$1
	local name=$2
	local keyfile=$3
	# otherwise the web interface will hang
	# passphrase may be passed via command line
	local key=$(<"$keyfile")
	# remove the passphrase-file as soon as possible
	dd if=/dev/zero of="$keyfile" bs=512 count=1 2>/dev/null
	rm "$keyfile"

	log_msg "Creating crypto partition with the cipher $DEFAULT_CIPHER on $device"
	echo "$key" | "$ROOT_PERM_SCRIPT" create_crypto "$device"

	set_crypto_name "$device" "$name"
}


function is_config_active() {
	test -f "$CONFIG_DIR/$CONFIG_MARKER"
}


# Parameter: DEVICE
function is_mounted() {
	local name=$(get_device_mnt_name "$1")
	test -n "$name" && mountpoint -q "$MNT_PARENT/$name"
}


# Parameter: DEVICE
function is_plain() {
	"$ROOT_PERM_SCRIPT" is_plain_partition "$1"
}


# Parameter: DEVICE
function is_encrypted() {
	"$ROOT_PERM_SCRIPT" is_crypto_partition "$1"
}


# list which allowed disks are at the moment connected with the cbox
function get_available_disks() {
	for scan in $SCAN_DEVICES
		do	for avail in $ALL_PARTITIONS
			  do	echo "$avail" | grep -q "^$scan[^/]*" && echo "/dev/$avail" 
			done
	 done
	return 0
}


# Parameter: DEVICE
function mount_crypto() {
	local device=$1
	test -z "$device" && error_msg 4 'No valid harddisk found!'
	is_mounted "$device" && echo "The crypto filesystem is already active!" && return
	# passphrase is read from stdin
	log_msg "Mounting a crypto partition from $device"
	"$ROOT_PERM_SCRIPT" mount "$device" >>"$LOG_FILE" 2>&1
}


function umount_partition() {
# Parameter: device
	local container=$(get_device_name "$1")
	"$ROOT_PERM_SCRIPT" umount "$1"
}


function box_purge()
# removing just the first bytes from the harddisk should be enough
# every harddisk will be overriden!
# this feature is only useful for validation
{
	# TODO: not ALL harddisks, please!
	get_available_disks | while read a
		do	log_msg "Purging $a ..."
			"$ROOT_PERM_SCRIPT" trash_device "$a"
	  done
}


function turn_off_all_containers() {
	# TODO - needs to be implemented
	return 0
}


### main ###

# set PATH because thttpd removes /sbin and /usr/sbin for cgis
export PATH=/usr/sbin:/usr/bin:/sbin:/bin


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

case "$ACTION" in
	crypto-up )
		test $# -ne 1 && error_msg 10 "invalid number of parameters for 'crypto-up'"
		is_device_allowed "$1" || error_msg 12 "invalid device: $1"
		mount_crypto "$1"
		;;
	crypto-down )
		test $# -ne 1 && error_msg 10 "invalid number of parameters for 'crypto-down'"
		is_device_allowed "$1" || error_msg 12 "invalid device: $1"
		umount_partition "$1"
		;;
	init )
		init_cryptobox </dev/null >>"$LOG_FILE" 2>&1
		;;
	list_container )
		test $# -ne 1 && error_msg 10 "invalid number of parameters for 'list_container'"
		case "$1" in
			config | unused | plaindata | crypto )
				list_partitions_of_type "$1"
				;;
			* )
				return 1
				;;
		  esac
		return 0
		;;
	get_device_name )
		# Parameter: DEVICE
		test $# -ne 1 && error_msg 10 "invalid number of parameters for 'get_device_name'"
		is_device_allowed "$1" || error_msg 12 "invalid device: $1"
		get_device_name "$1"
		;;
	set_device_name )
		# Parameter: DEVICE NAME
		test $# -ne 2 && error_msg 10 "invalid number of parameters for 'set_device_name'"
		is_device_allowed "$1" || error_msg 12 "invalid device: $1"
		set_device_name "$1" "$2"
		;;
	device_init )
		# Parameter: DEVICE [KEYFILE]
		test $# -lt 1 && error_msg 10 "invalid number of parameters for 'device_init' ($@)"
		test $# -gt 2 && error_msg 10 "invalid number of parameters for 'device_init' ($@)"
		if test $# -eq 2
		  then	test -z "$2" -o ! -e "$2" && error_msg 11 "invalid keyfile ($2) given for 'device_init'"
		 fi
		is_device_allowed "$1" || error_msg 12 "invalid device: $1"
		if test $# -eq 2
		  then	"$ROOT_PERM_SCRIPT" create_crypto "$1" "$2"
		  else	"$ROOT_PERM_SCRIPT" create_plain "$1"
		 fi
		true
		;;
	is_mounted )
		test $# -ne 1 && error_msg 10 "invalid number of parameters for 'is_mounted'"
		is_device_allowed "$1" || error_msg 12 "invalid device: $1"
		is_mounted "$1"
		;;
	is_encrypted )
		test $# -ne 1 && error_msg 10 "invalid number of parameters for 'is_encrypted'"
		is_device_allowed "$1" || error_msg 12 "invalid device: $1"
		is_encrypted "$1"
		;;
	is_plain )
		test $# -ne 1 && error_msg 10 "invalid number of parameters for 'is_plain'"
		is_device_allowed "$1" || error_msg 12 "invalid device: $1"
		is_plain "$1"
		;;
	check_config)
		is_config_active
		;;
	get_available_disks )
		get_available_disks
		;;
	set_config )
		test $# -ne 2 && error_msg 7 "'set_config' requires two parameters"
		config_set_value "$1" "$2"
		;;
	get_config )
		test $# -ne 1 && error_msg 6 "'get_config' requires exactly one parameter"
		config_get_value "$1"
		;;
	get_capacity_info )
		test $# -ne 1 && error_msg 6 "'get_capacity_info' requires exactly one parameter"
		is_device_allowed "$1" || error_msg 12 "invalid device: $1"
		is_mounted "$1" || error_msg 13 "the device is not mounted: $1"
		name=$(get_device_mnt_name "$1")
		df -h "$MNT_PARENT/$name" | tail -1
		;;
	diskinfo )
		get_available_disks | while read a
			do	"$ROOT_PERM_SCRIPT" diskinfo "$a"
		  done 2>/dev/null
		;;
	box-purge )
		log_msg "Cleaning the CryptoBox ..."
		turn_off_all_containers
		"$0" config-down
		box_purge >>"$LOG_FILE" 2>&1
		;;
	poweroff )
		log_msg "Shutting down the Cryptobox ..."
		turn_off_all_containers
		"$ROOT_PERM_SCRIPT" poweroff
		;;
	reboot )
		log_msg "Rebooting the Cryptobox ..."
		turn_off_all_containers
		"$ROOT_PERM_SCRIPT" reboot
		;;
	umount_all )
		log_msg "Unmounting all volumes ..."
		turn_off_all_containers
		;;
	* )
		echo "[$(basename $0)] - unknown action: $ACTION" >&2
		echo "Syntax: $(basename $0) ACTION [PARAMS]"
		echo "  crypto-up		- mount crypto partition"
		echo "  crypto-down		- unmount crypto partition"
		echo "	crypto-create	- a wrapper for 'crypto-create-bg'"
		echo "	crypto-create-bg	- create encrypted blockdevice and run mkfs"
		echo "  is_mounted	- check, if crypto partition is mounted"
		echo "	check_config	- check, if the configuration is usable"
		echo "  get_available_disks - shows all accessible disks"
		echo "  get_current_ip	- get the current IP of the network interface"
		echo "  set_config NAME VALUE	- change a configuration setting"
		echo "  get_config NAME	- retrieve a configuration setting"
		echo "  get_device_name DEVICE	- retrieve the human readable name of a partition"
		echo "  set_device_name DEVICE	- set the human readable name of a partition"
		echo "	device_init DEVICE KEYFILE	- initialize the filesystem of a partition (the keyfile just contains the passphrase)"
		echo "	get_capacity_info	- print the output of 'df' for the (mounted) partition"
		echo "  diskinfo		- show the partition table of the harddisk"
		echo "  box-purge		- destroy the partition tables of all harddisks (delete everything)"
		echo "	poweroff		- turn off the computer"
		echo "	reboot			- reboot the computer"
		echo
		;;
  esac

exit 0