#!/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 # # TODO: check permissions and owners of config files, directories and scripts before # running cbox-root-actions.sh set -eu # define reasonable defaults USE_STUNNEL=0 EXEC_FIREWALL_RULES=0 SKIP_NETWORK_CONFIG=1 CONF_FILE=/etc/cryptobox/cryptobox.conf # read the default setting file, if it exists [ -e /etc/default/cryptobox ] && . /etc/default/cryptobox # parse config file . "$CONF_FILE" # check for writable log file [ -w "$LOG_FILE" ] || LOG_FILE=/tmp/$(basename "$LOG_FILE") ## 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 config_set_value() # parameters: SettingName [SettingValue] # read from stdin if SettingValue is not defined { [ "$USE_SEPERATE_CONFIG_PARTITION" = "1" ] && sudo "$ROOT_PERM_SCRIPT" remount_config rw if [ $# -gt 1 ] then echo -n "$2" > "$CONFIG_DIR/$1" else cat - >"$CONFIG_DIR/$1" fi [ "$USE_SEPERATE_CONFIG_PARTITION" = "1" ] && sudo "$ROOT_PERM_SCRIPT" remount_config ro } function config_get_value() # parameters: SettingName { # use mounted config, if it exists - otherwise use defaults local conf_dir if is_config_active 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 unload_config # create the new configuration filesystem if it is not static if [ "$USE_SEPERATE_CONFIG_PARTITION" != "1" ] then log_msg "Using static configuration ..." else log_msg "Creating config filesystem ..." sudo "$ROOT_PERM_SCRIPT" create_config "$device" log_msg "Mounting config partition ..." sudo "$ROOT_PERM_SCRIPT" mount_config "$device" sudo "$ROOT_PERM_SCRIPT" remount_config rw fi 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 "load_config" [ "$USE_STUNNEL" = 1 ] && 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" "" # create a marker to recognize a cryptobox partition # this should be the last step, to prevent a half-initialized state config_set_value "$CONFIG_MARKER" "$(date -I)" } 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_partitions_of_type() # parameter: { config | crypto | plaindata | unused } { local config= local crypto= local plaindata= local unused= for a in $ALL_PARTITIONS do if sudo "$ROOT_PERM_SCRIPT" is_crypto_partition "/dev/$a" then crypto="$crypto /dev/$a" elif sudo "$ROOT_PERM_SCRIPT" is_config_partition "/dev/$a" then config="$config /dev/$a" elif sudo "$ROOT_PERM_SCRIPT" is_plaindata_partition "/dev/$a" then plaindata="$plaindata /dev/$a" else unused="$unused /dev/$a" fi done case "$1" in config | crypto | plaindata | unused ) # dirty hack, but it works eval "echo \$$1" ;; * ) errot_msg "wrong parameter ($1) for list_partition_types in $(basename $0)" ;; esac | tr " " "\n" | grep -v '^$' } function get_crypto_uuid() # Parameter: DEVICE { sudo "$ROOT_PERM_SCRIPT" get_device_name "$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=$(get_crypto_uuid "$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() # TODO: the implementation is quite ugly, but it works (tm) # Parameter: DEVICE NAME { local uuid=$(get_crypto_uuid "$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' # TODO: check if the keyfile is still necessary for sudo -b { 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" | sudo "$ROOT_PERM_SCRIPT" create_crypto "$device" set_crypto_name "$device" "$name" } function is_config_active() { test -f "$CONFIG_DIR/$CONFIG_MARKER" } function is_crypto_mounted() # Parameter: DEVICE { local name=$(get_crypto_uuid "$1") [ -n "$name" ] && mountpoint -q "$MNT_PARENT/$name" } 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 "/dev/$avail" done done } function load_config() { unload_config local status=0 # look for a configuration partition [ "$USE_SEPERATE_CONFIG_PARTITION" = "1" ] && \ list_partitions_of_type config | while read part && [ "$status" = 0 ] do log_msg "Trying to load configuration from /dev/$part ..." if sudo "$ROOT_PERM_SCRIPT" is_config_partition "/dev/$part" then log_msg "configuraton found on $part" sudo "$ROOT_PERM_SCRIPT" mount_config "/dev/$part" status=1 fi done if is_config_active then # copy certificate to /tmp in case of re-initialization # /tmp should be writable, so tmpfs has to be mounted before (/etc/rcS.d) [ "$USE_STUNNEL" = 1 ] && cp "$CERT_FILE" "$CERT_TEMP" else log_msg "failed to locate config partition" return 1 fi true } function unload_config() { is_config_active || return # only try to unmount, if it is not static (the config of a live-cd is always dynamic) if [ "$USE_SEPERATE_CONFIG_PARTITION" = "1" ] then sudo "$ROOT_PERM_SCRIPT" umount_config 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 # passphrase is read from stdin log_msg "Mounting a crypto partition from $device" sudo "$ROOT_PERM_SCRIPT" mount "$device" >>"$LOG_FILE" 2>&1 } function umount_crypto() # Parameter: DEVICE { local device=$1 local uuid=$(get_crypto_uuid $device) sudo "$ROOT_PERM_SCRIPT" umount "$uuid" } function box_purge() # removing just the first bytes from the harddisk should be enough # every harddisk will be overriden! { # TODO: not ALL harddisks, please! get_available_disks | while read a do log_msg "Purging $a ..." sudo "$ROOT_PERM_SCRIPT" trash_device "$a" 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 unload_config || true log_msg "Partitioning the device ($device) ..." sudo "$ROOT_PERM_SCRIPT" partition_disk "$device" "0,1,L \n,,L\n" log_msg "Initializing config partition on ${device}1 ..." # 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 load_config then echo "Cryptobox configuration successfully loaded" else error_msg 0 "Could not find a configuration partition!" fi ;; config-down ) unload_config || error_msg 4 "Could not unmount configuration partition" ;; network-up ) if [ "$SKIP_NETWORK_CONFIG" != 1 ] then conf_ip=$(config_get_value "ip") log_msg "Configuring $NET_IFACE for $conf_ip ..." echo "Configuring network interface for $NET_IFACE: $conf_ip" "$IFCONFIG" "$NET_IFACE" "$conf_ip" fi if [ "$EXEC_FIREWALL_RULES" = 1 ] then log_msg "Starting the firewall ..." "$FIREWALL_SCRIPT" start fi if [ "$USE_STUNNEL" = 1 ] then # 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 # TODO: this could be dangerous - right? # this is necessary, to allow www-data to copy the certificate chown "$WEB_USER" "$CERT_TEMP" fi log_msg "Starting stunnel ..." stunnel -p "$USE_CERT" -r localhost:80 -d 443 \ || echo "$USE_CERT not found - not starting stunnel" fi ;; network-down ) if [ "$EXEC_FIREWALL_RULES" = 1 ] then log_msg "Stopping the firewall ..." "$FIREWALL_SCRIPT" stop fi if [ "$USE_STUNNEL" = 1 ] then log_msg "Stopping stunnel ..." # TODO: what about a pid? killall stunnel 2>/dev/null || true fi if [ "$SKIP_NETWORK_CONFIG" != 1 ] then log_msg "Shutting the network interface down ..." "$IFCONFIG" "$NET_IFACE" down fi ;; services-up ) # the mount point has to be writeable # this action is called as root - so we are allowed to umount # TODO: do this only for ro-filesystem # TODO: this way of mounting is evil if mountpoint -q "$MNT_PARENT" then true else mount -t tmpfs tmpfs "$MNT_PARENT" fi true ;; services-down ) # this action is called as root - so we are allowed to umount mountpoint -q "$MNT_PARENT" && umount "$MNT_PARENT" # TODO: we should not depend on samba and thttpd # /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-down'" 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_partitions_of_type crypto ;; crypto-list-unused ) list_partitions_of_type unused ;; 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_active ;; 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 if [ "$SKIP_NETWORK_CONFIG" != 1 ] then echo -n "sleep 5; sudo $ROOT_PERM_SCRIPT update_network" | at now fi ;; 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 sudo "$ROOT_PERM_SCRIPT" diskinfo "$a" done ;; box-purge ) log_msg "Cleaning the CryptoBox ..." turn_off_all_crypto "$0" config-down box_purge >>"$LOG_FILE" 2>&1 ;; 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" echo " crypto-down - unmount crypto partition" 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 all harddisks (delete everything)" echo " poweroff - shutdown the cryptobox" echo " reboot - reboot the cryptobox" echo ;; esac exit 0