#!/bin/sh # # Copyright (c) 02005 sense.lab # # 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 # set -eu # parse config file . /etc/cryptobox/cryptobox.conf ## configuration CERT_TEMP=/tmp/stunnel.pem ######## stuff ########## ALL_PARTITIONS=$(cat /proc/partitions | sed '1,2d; s/ */ /g; s/^ *//' | cut -d " " -f 4) ######################### function log_msg() { # the log file is not writable during boot - try before writing ... [ -w "$LOG_FILE" ] || return 0 echo >>"$LOG_FILE" echo "##### `date` #####" >>"$LOG_FILE" echo "$1" >>"$LOG_FILE" } function error_msg() # parameters: ExitCode ErrorMessage { echo "[`date`] - $2" | tee -a "$LOG_FILE" >&2 # print the execution stack - not usable with busybox # caller | sed 's/^/\t/' >&2 exit "$1" } function create_partitions() # Parameter: device { # TODO: allow different layouts # TODO: skip config partition if a configuration is already active local device="$1" # first partition size is 1 sector, second goes til end # sfdisk -n doesn't actually write (for testing purpose) echo -e "0,1,L \n,,L\n" | $SFDISK "$device" } function config_set_value() # parameters: SettingName [SettingValue] # read from stdin if SettingValue is not defined { mountpoint -q "$CONFIG_DIR" && mount -o rw,remount "$CONFIG_DIR" if [ $# -gt 1 ] then echo -n "$2" > "$CONFIG_DIR/$1" else cat - >"$CONFIG_DIR/$1" fi mountpoint -q "$CONFIG_DIR" && mount -o ro,remount "$CONFIG_DIR" } function config_get_value() # parameters: SettingName { # use mounted config, if it exists - otherwise use defaults local conf_dir if is_config_mounted then conf_dir=$CONFIG_DIR else conf_dir=$CONFIG_DEFAULTS_DIR fi [ -z "$1" ] && error_msg 1 "empty setting name" # check for existence - maybe use default values (for old releases without this setting) if [ ! -e "$conf_dir/$1" ] then case "$1" in # you may place default values for older versions here # for compatibility * ) error_msg 2 "unknown configuration value ($1)" # empty output ;; esac else echo -n $(cat "$conf_dir/$1") # this removes the trailing line break fi # always return without error true } function create_config() # Parameter: device { local device="$1" # create the new configuration filesystem if it is not static if is_config_mounted then log_msg "Using static configuration ..." else log_msg "Creating config filesystem ..." # filter output through 'tr' to replace tabs $MKFS_CONFIG "$device" | tr '\010' ' ' # mount the config partition rw log_msg "Mounting config partition ..." mount "$device" "$CONFIG_DIR" fi # create a marker to recognize a cryptobox partition date -I >"$CONFIG_DIR/$CONFIG_MARKER" log_msg "Copying configuration defaults ..." cp -a "$CONFIG_DEFAULTS_DIR/." "$CONFIG_DIR" log_msg "Copying temporary certificate file to config filesystem ..." # beware: the temp file should always be there - even after reboot - see "mount_config" cp -p "$CERT_TEMP" "$CERT_FILE" log_msg "Setting inital values ..." # beware: config_set_value remounts the config partition read-only config_set_value "ip" "$(get_current_ip)" # create database of readable names config_set_value "names.db" "" # reinitialise configuration log_msg "Unmounting config partition ..." umount_config log_msg "Reload configuration ..." mount_config } function get_current_ip() # not necessarily the same as configured (necessary for validation) { # filter the output of ifconfig and remove trailing line break echo -n $(ifconfig $NET_IFACE | grep "inet" | cut -d ":" -f2 | cut -d " " -f1) } function list_crypto_containers() { for a in $ALL_PARTITIONS do "$CRYPTSETUP" isLuks "/dev/$a" 2>/dev/null && echo "/dev/$a" done } function list_unused_partitions() { for a in $ALL_PARTITIONS do "$CRYPTSETUP" isLuks "/dev/$a" 2>/dev/null || echo "/dev/$a" done } function get_crypto_uuid() # Parameter: DEVICE { "$CRYPTSETUP" luksUUID "$1" } function get_crypto_name() # Parameter: DEVICE # return the readable name of the crypto container, it it is already defined # if undefined - return the uuid { local uuid=$($CRYPTSETUP luksUUID $1) local dbname=$(config_get_value "names.db" | grep "^$uuid:" | cut -d ":" -f 2-) if [ -z "$dbname" ] then echo -n "$uuid" else echo -n "$dbname" fi } function set_crypto_name() # Parameter: DEVICE NAME { local uuid=$($CRYPTSETUP luksUUID $1) # remove the old setting for this device and every possible entry with the same name (config_get_value 'names.db' | sed "/^$uuid:/d; /^[^:]*:$2$/d"; echo "$uuid:$2") | 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 log_msg "Creating crypto partition with the cipher $DEFAULT_CIPHER on $device" # the hash is ignored by luks # the iter-time specifies the time spent on PBKDF2 - one second is said to be sufficient # luksFormat floods the container with random noise automatically cat "$keyfile" | $CRYPTSETUP -c "$DEFAULT_CIPHER" luksFormat "$device" set_crypto_name "$device" "$name" local uuid=$(get_crypto_uuid "$device") # map the crypto container cat "$keyfile" | $CRYPTSETUP luksOpen "$device" "$uuid" # remove the passphrase-file as soon as possible dd if=/dev/zero of="$keyfile" bs=512 count=1 2>/dev/null rm "$keyfile" # filter output through 'tr' to replace tabs $MKFS_DATA "$CRYPTMAPPER_DIR/$uuid" | tr '\0101' ' ' # set user for samba mkdir -p "$MNT_PARENT/$name" mount "$CRYPTMAPPER_DIR/$uuid" "$MNT_PARENT/$name" chown $SAMBA_USER "$MNT_PARENT/$name" umount_crypto "$device" } function config_mount_test() # Parameter: device { local device=$1 local STATUS=0 mount "${device}" "$CONFIG_DIR" &>/dev/null || true is_config_mounted && STATUS=1 umount "$CONFIG_DIR" &>/dev/null || true # return code is the result of this expression test 1 -eq "$STATUS" } function is_config_mounted() { test -f "$CONFIG_DIR/$CONFIG_MARKER" } function is_crypto_mounted() # Parameter: DEVICE { local uuid=$(get_crypto_uuid $1) test -e "$CRYPTMAPPER_DIR/$uuid" } function is_init_running() { check_at_command_queue " init" } # check if a specified command is in an at-queue # Parameter: a regular expression of the commandline # Return: the command is part of an at-queue (0) or not (1) function check_at_command_queue() { # 1) get the available job numbers # 2) remove empty lines (especially the last one) # 3) check every associated command for the regexp at -l | cut -f 1 | while read jobnum do at -c $jobnum | sed '/^$/d' | tail -1 done | grep -q "$1" } function find_harddisk() # look for the harddisk to be partitioned { local device=$(get_available_disks | head -1) if [ -z "$device" ] ; then log_msg "no valid harddisk for initialisation found!" cat /proc/partitions >>"$LOG_FILE" # do not return with an error, to avoid a failing of the script ('break on error') # the caller of this function should handle an empty return string fi echo -n "$device" } function get_available_disks() # looks which allowed disks are at the moment connected with the cbox { for scan in $SCAN_DEVICES do for avail in $ALL_PARTITIONS do [ "$scan" = "$avail" ] && echo "$a" done done } function mount_config() { # error if dynamic configuration is active # return if static configuration is active if is_config_mounted && mountpoint -q "$CONFIG_DIR" then error_msg 3 "configuration directory ($CONFIG_DIR) is already mounted!" else is_config_mounted && return fi # look for a configuration partition # modify all scan_devices to get regular expressions like "^hda[0-9]*$" local scan_regex=$(echo "SCAN_DEVICES" | tr " " "\n" | sed 's/^/^/; s/$/[0-9]*$/') echo "$ALL_PARTITIONS" | grep "$scan_regex" | while read part do log_msg "Trying to load configuration from $part ..." if config_mount_test "$part" then log_msg "configuraton found on $part" mount "$part" "$CONFIG_DIR" # copy certificate to /tmp in case of re-initialization # /tmp should be writable, so tmpfs has to be mounted before (/etc/rcS.d) cp "$CERT_FILE" "$CERT_TEMP" return fi done log_msg "failed to locate config partition" return 1 } function umount_config() { is_config_mounted || return # only try to unmount, if it is not static (the config of a live-cd is always dynamic) if mountpoint -q "$CONFIG_DIR" then umount "$CONFIG_DIR" else true fi } function mount_crypto() # Parameter: DEVICE { local device=$1 [ -z "$device" ] && error_msg 4 'No valid harddisk found!' && return 1 is_crypto_mounted "$device" && echo "The crypto filesystem is already active!" && return local uuid=$(get_crypto_uuid $device) local name=$(get_crypto_name $device) # passphrase is read from stdin log_msg "Mounting crypto partition $name ($device)" $CRYPTSETUP luksOpen "$device" "$uuid" [ -e "$MNT_PARENT/$name" ] || mkdir -p "$MNT_PARENT/$name" if mount "$CRYPTMAPPER_DIR/$uuid" "$MNT_PARENT/$name" then log_msg "Mount succeeded - now starting samba ..." /etc/init.d/samba start return 0 else log_msg "Mount failed - removing the crypto device $uuid ..." $CRYPTSETUP luksClose "$uuid" return 1 fi } function umount_crypto() # Parameter: DEVICE { local device=$1 # do not break on error set +e # TODO: do not stop samba - or do it just temporarily if ps -e | grep -q " [sn]mbd$" then log_msg "Stopping samba ..." /etc/init.d/samba stop ps -e | grep -q " smbd$" && killall smbd ps -e | grep -q " nmbd$" && killall nmbd ps -e | grep -q " smbd$" && killall -9 smbd ps -e | grep -q " nmbd$" && killall -9 nmbd fi local uuid=$(get_crypto_uuid $device) local name=$(get_crypto_name $device) if mountpoint -q "$MNT_PARENT/$name" then log_msg "Unmounting crypto partition ..." umount "$MNT_PARENT/$name" rmdir "$MNT_PARENT/$name" fi if [ -e "$CRYPTMAPPER_DIR/$uuid" ] then log_msg "Removing dev-mapper ..." $CRYPTSETUP luksClose "$uuid" fi set -e } function box_purge() # removing just the first bytes from the harddisk should be enough { local device get_available_disks | while read a do log_msg "Purging $device ..." dd if=/dev/zero of=$device bs=1M count=1 log_msg " " done } function init_cryptobox() # this is only the first part of initialisation that takes no time - good for a smooth web interface { local device=$(find_harddisk) [ -z "$device" ] && log_msg 'No valid harddisk found!' && return 1 turn_off_all_crypto is_config_mounted && umount_config || true log_msg "Initializing config partition on $device ..." create_partitions "$device" # TODO: this should not be hard-coded create_config "${device}1" } function turn_off_all_crypto() { list_crypto_containers | while read a do is_crypto_mounted "$a" && umount_crypto "$a" done } ### main ### # set PATH because thttpd removes /sbin and /usr/sbin for cgis export PATH=/usr/sbin:/usr/bin:/sbin:/bin ACTION=help [ $# -gt 0 ] && ACTION="$1" && shift case "$ACTION" in config-up ) if mount_config then echo "Cryptobox configuration successfully loaded" else error_msg 3 "Could not find a configuration partition!" fi ;; config-down ) umount_config || error_msg 4 "Could not unmount configuration partition" ;; network-up ) kudzu -s -q --class network conf_ip=$(config_get_value "ip") log_msg "Configured $NET_IFACE for $conf_ip ..." ifconfig $NET_IFACE "$conf_ip" echo "Configured network interface for $NET_IFACE: $conf_ip" log_msg "Starting the firewall ..." "$FIREWALL_SCRIPT" start # start stunnel if [ -f "$CERT_FILE" ] then USE_CERT=$CERT_FILE else USE_CERT=$CERT_TEMP $MAKE_CERT_SCRIPT "$CERT_TEMP" >>"$LOG_FILE" 2>&1 fi log_msg "Starting stunnel ..." stunnel -p "$USE_CERT" -r localhost:80 -d 443 \ || echo "$USE_CERT not found - not starting stunnel" # this ping allows other hosts to get the IP of # the box, in case of misconfiguration ping -b -c 1 $(ifconfig $NET_IFACE | grep Bcast | cut -d ":" -f 3 | cut -d " " -f 1) &>/dev/null ;; network-down ) log_msg "Stopping the firewall ..." "$FIREWALL_SCRIPT" stop log_msg "Stopping stunnel ..." killall stunnel log_msg "Shutting the network interface down ..." ifconfig "$NET_IFACE" down ;; services-up ) # the mount point has to be writeable mount -t tmpfs tmpfs "$MNT_PARENT" # is something special necessary? ;; services-down ) umount "$MNT_PARENT" /etc/init.d/samba stop || true /etc/init.d/thttpd stop || true true ;; crypto-up ) [ $# -ne 1 ] && error_msg "invalid number of parameters for 'crypto-up'" mount_crypto "$1" ;; crypto-down ) [ $# -ne 1 ] && error_msg "invalid number of parameters for 'crypto-up'" umount_crypto "$1" ;; init ) init_cryptobox >"$LOG_FILE" 2>&1 ;; crypto-create ) # Parameter: DEVICE NAME [ $# -ne 2 ] && error_msg "invalid number of parameters for 'crypto-create'" # do it in the background to provide a smoother web interface # messages and errors get written to $LOG_FILE keyfile=/tmp/$(basename "$0")-passphrase-$(basename "$1") # read the password cat - >"$keyfile" # execute it in the background echo "'$0' crypto-create-bg '$1' '$2' '$keyfile' >'$LOG_FILE' 2>&1" | at now ;; crypto-create-bg ) create_crypto "$@" ;; crypto-list ) list_crypto_containers ;; crypto-list-unused ) list_unused_partitions ;; crypto-name ) # Parameter: DEVICE get_crypto_name "$1" ;; is_crypto_mounted ) [ $# -ne 1 ] && error_msg 10 "invalid number of parameters for 'is_crypto_mounted'" is_crypto_mounted "$1" ;; is_config_mounted ) is_config_mounted ;; is_init_running ) is_init_running ;; is_harddisk_available ) [ -z "$(find_harddisk)" ] && exit 1 exit 0 ;; update_ip_address ) # reconfigure the network interface to a new IP address # wait for 5 seconds to finish present http requests echo -n "sleep 5; ifconfig $NET_IFACE `config_get_value ip`" | at now ;; get_available_disks ) get_available_disks ;; get_current_ip ) get_current_ip ;; set_config ) [ $# -ne 2 ] && error_msg 7 "'set_config' requires two parameters" config_set_value "$1" "$2" ;; get_config ) [ $# -ne 1 ] && error_msg 6 "'get_config' requires exactly one parameter" config_get_value "$1" ;; diskinfo ) get_available_disks | while read a do $SFDISK -L -q -l "$a" done ;; box-purge ) log_msg "Cleaning the CryptoBox ..." turn_off_all_crypto "$0" config-down box_purge ;; poweroff ) log_msg "Turning off the CryptoBox ..." turn_off_all_crypto echo "poweroff" | at now ;; reboot ) log_msg "Rebooting the CryptoBox ..." turn_off_all_crypto echo "reboot" | at now ;; * ) echo "Syntax: `basename $0` ACTION [PARAMS]" echo " config-up - scan for configuration partition and mount it" echo " config-down - unmount configuration partition" echo " network-up - enable network interface" echo " network-down - disable network interface" echo " services-up - run some cryptobox specific daemons" echo " services-down - stop some cryptobox specific daemons" echo " crypto-up - mount crypto partition and start samba" echo " crypto-down - unmount crypto partition and stop samba" echo " box-init - initialize cryptobox (ALL data is LOST)" echo " box-init-fg - the first part of initialization" echo " box-init-bg - the last part of initialization (background)" echo " is_crypto_mounted - check, if crypto partition is mounted" echo " is_config_mounted - check, if configuration partition is mounted" echo " is_init_running - check, if initialization is ongoing" echo " is_harddisk_available - check, if there is a usable harddisk" echo " get_available_disks - shows all connected and allowed disks" echo " get_current_ip - get the current IP of the network interface" echo " update_ip_address - update the network interface after reconfiguration" echo " set_config NAME VALUE - change a configuration setting" echo " get_config NAME - retrieve a configuration setting" echo " diskinfo - show the partition table of the harddisk" echo " box-purge - destroy partitiontable of the harddisk (delete everything)" echo " poweroff - shutdown the cryptobox" echo " reboot - reboot the cryptobox" echo ;; esac