#!/bin/zsh # # Tomb, the Crypto Undertaker # # A commandline tool to easily operate encryption of secret data # # Homepage on: [tomb.dyne.org](http://tomb.dyne.org) # {{{ License # Copyright (C) 2007-2013 Dyne.org Foundation # # Tomb is designed, written and maintained by Denis Roio # # With contributions by Anathema, Boyska and Hellekin O. Wolf. # # Testing and reviews are contributed by Dreamer, Shining, # Mancausoft, Asbesto Molesto and Nignux. # # Tomb's artwork is contributed by Jordi aka Mon Mort. #This source code is free software; you can redistribute it #and/or modify it under the terms of the GNU Public License #as published by the Free Software Foundation; either #version 3 of the License, or (at your option) any later #version. # #This source code is distributed in the hope that it will be #useful, but WITHOUT ANY WARRANTY; without even the implied #warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR #PURPOSE. Please refer to the GNU Public License for more #details. # #You should have received a copy of the GNU Public License #along with this source code; if not, write to: Free #Software Foundation, Inc., 675 Mass Ave, Cambridge, MA #02139, USA. # }}} - License # {{{ Global variables VERSION=1.3 DATE="May/2013" TOMBEXEC=$0 TOMBOPENEXEC="${TOMBEXEC}-open" typeset -a OLDARGS for arg in ${argv}; do OLDARGS+=($arg); done DD="dd" WIPE="rm -f" MKFS="mkfs.ext3 -q -F -j -L" STEGHIDE=1 MKTEMP=1 RESIZER=1 MOUNTOPTS="rw,noatime,nodev" typeset -A global_opts typeset -A opts typeset -h username typeset -h _uid typeset -h _gid typeset -h _tty # Set a sensible PATH PATH=/sbin:/bin:/usr/sbin:/usr/bin [[ "$TOMBEXEC" =~ "^/usr/local" ]] && PATH="/usr/local/bin:/usr/local/sbin:$PATH" # }}} # {{{ Safety functions _have_shm() { # Check availability of 1MB of SHM xxx "_have_shm 0 We need only 1 MB of RAM" [[ -k /dev/shm ]] || return 1 local -i SHM RAM SHM=$(df -k -B 4K -a -t tmpfs /dev/shm | awk '/\/dev\/shm/ { print $4; }') (( $? )) && return 1 xxx "_have_shm 1 SHM $SHM KB are available" RAM=$(awk '/MemFree/ { print $2 }' /proc/meminfo) xxx "_have_shm 2 RAM $RAM KB are free" (( $RAM >= 1024 )) && return 0 xxx "_have_shm 3 RAM $RAM KB left only :(" # Now we have more RAM than affected to SHM, so we can expect some for our little needs. # Does that work when SHM is disabled from kernel config? return 1 } # Create temporary directories with caution safe_dir() { # Try and create our temporary directory in RAM # Note that there's no warranty the underlying FS won't swap # every 5 seconds (e.g., ext3) local -i tries while (( $tries < 3 )) ; do tries+=1 if _have_shm; then xxx "safe_dir creating directory in RAM" if (( $MKTEMP )); then mktemp -d /dev/shm/$1.$$.XXXXXXX else dir="/dev/shm/$1.$$.$RANDOM$RANDOM" mkdir -m 0700 -p "$dir" print "$dir" fi return 0 else _warning "WARNING: we cannot ensure we're running in RAM." xxx "Wait a bit before retrying... (attempt $tries)" sync && sleep 0.5 fi done _warning "WARNING: no RAM available for me to run safely." return 1 } # Provide a random filename in shared memory safe_filename() { _have_shm || die "No access to shared memory on this system, sorry." (( $MKTEMP )) && \ mktemp -u /dev/shm/$1.$$.XXXXXXX || \ print "/dev/shm/$1.$$.$RANDOM$RANDOM" } # Check if swap is activated check_swap() { # Return 0 if NO swap is used, 1 if swap is used # Return 2 if swap(s) is(are) used, but ALL encrypted local swaps=$(awk '/partition/ { print $1 }' /proc/swaps 2>/dev/null) [[ -z "$swaps" ]] && return 0 # No swap partition is active no "An active swap partition is detected, this poses security risks." no "You can deactivate all swap partitions using the command:" no " swapoff -a" no "But if you want to proceed like this, use the -f (force) flag." die "Operation aborted." } # Ask user for a password ask_password() { # we use pinentry now # comes from gpg project and is much more secure # it also conveniently uses the right toolkit # pinentry has no custom icon setting # so we need to temporary modify the gtk theme if [ -r /usr/local/share/themes/tomb/gtk-2.0-key/gtkrc ]; then GTK2_RC=/usr/local/share/themes/tomb/gtk-2.0-key/gtkrc elif [ -r /usr/share/themes/tomb/gtk-2.0-key/gtkrc ]; then GTK2_RC=/usr/share/themes/tomb/gtk-2.0-key/gtkrc fi title="Insert tomb password" if [ $2 ]; then title="$2"; fi output=`cat </dev/null | tail -n +7 OPTION ttyname=$TTY OPTION lc-ctype=$LANG SETTITLE $title SETDESC $1 SETPROMPT Password: GETPIN EOF` if [[ `tail -n1 <<<$output` =~ ERR ]]; then return 1 fi head -n1 <<<$output | awk '/^D / { sub(/^D /, ""); print }' return 0 } # Drop privileges exec_as_user() { if ! [ $SUDO_USER ]; then exec $@[@] return $? fi xxx "exec_as_user '$SUDO_USER': ${(f)@}" sudo -u $SUDO_USER "${@[@]}" return $? } #Escalate privileges check_priv() { # save original user username=$USER if [ $UID != 0 ]; then xxx "Using sudo for root execution of 'tomb ${(f)OLDARGS}'" # check if sudo has a timestamp active sudok=false if ! option_is_set --sudo-pwd; then if [ $? != 0 ]; then # if not then ask a password cat </dev/null | awk '/^D / { sub(/^D /, ""); print }' | sudo -S -v OPTION ttyname=$TTY OPTION lc-ctype=$LANG SETTITLE Super user privileges required SETDESC Sudo execution of Tomb ${OLDARGS[@]} SETPROMPT Insert your USER password: GETPIN EOF fi else _verbose "Escalating privileges using sudo-pwd" sudo -S -v <<<`option_value --sudo-pwd` fi sudo "${TOMBEXEC}" -U ${UID} -G ${GID} -T ${TTY} "${(@)OLDARGS}" exit $? fi # are we root already # make sure necessary kernel modules are loaded modprobe dm_mod modprobe dm_crypt return 0 } # }}} # {{{ Commandline interaction usage() { cat <. EOF } # Check an option option_is_set() { # First argument, the commandline flag (i.e. "-s"). # Second (optional) argument: if "out", command will print it out 'set'/'unset' # (useful for if conditions). # Return 0 if is set, 1 otherwise [[ -n ${(k)opts[$1]} ]]; r=$? if [[ $2 == out ]]; then if [[ $r == 0 ]]; then echo 'set' else echo 'unset' fi fi return $r; } # Get an option value option_value() { # First argument, the commandline flag (i.e. "-s"). <<< ${opts[$1]} } # Messaging function with pretty coloring function _msg() { local command="print -P" local progname="$fg[magenta]${TOMBEXEC##*/}$reset_color" local message="$fg_bold[normal]$fg_no_bold[normal]${2}$reset_color" local -i returncode case "$1" in inline) command+=" -n"; pchars=" > "; pcolor="yellow" ;; message) pchars=" . "; pcolor="white"; message="$fg_no_bold[$pcolor]${2}$reset_color" ;; verbose) pchars="[D]"; pcolor="blue" ;; success) pchars="(*)"; pcolor="green"; message="$fg_no_bold[$pcolor]${2}$reset_color" ;; warning) pchars="[W]"; pcolor="yellow"; message="$fg_no_bold[$pcolor]${2}$reset_color" ;; failure) pchars="[E]"; pcolor="red"; message="$fg_no_bold[$pcolor]${2}$reset_color" returncode=1 ;; *) pchars="[F]"; pcolor="red" message="Developer oops! Usage: _msg MESSAGE_TYPE \"MESSAGE_CONTENT\"" returncode=127 ;; esac ${=command} "${progname} $fg_bold[$pcolor]$pchars$reset_color ${message}$color[reset_color]" >&2 return $returncode } function _message say() { local notice="message" [[ "$1" = "-n" ]] && shift && notice="inline" option_is_set -q || _msg "$notice" "$1" return 0 } alias act="_message -n" function _verbose xxx() { option_is_set -D && _msg verbose "$1" return 0 } function _success yes() { option_is_set -q || _msg success "$1" return 0 } function _warning no() { option_is_set -q || _msg warning "$1" return 1 } function _failure die() { typeset -i exitcode=${2:-1} option_is_set -q || _msg failure "$1" exit $exitcode } # Print out progress to inform GUI caller applications (--batch mode) progress() { # $1 is "what is progressing" # $2 is "percentage" # $3 is (eventually blank) status # Example: if creating a tomb, it could be sth like # progress create 0 filling with random data # progress create 40 generating key # progress keygen 0 please move the mouse # progress keygen 30 please move the mouse # progress keygen 60 please move the mouse # progress keygen 100 key generated # progress create 80 please enter password # progress create 90 formatting the tomb # progress create 100 tomb created successfully if ! option_is_set --batch; then return fi print "[m][P][$1][$2][$3]" >&2 } # Check what's installed check_bin() { # check for required programs for req in cryptsetup pinentry sudo gpg; do command -v $req >/dev/null || die "Cannot find $req. It's a requirement to use Tomb, please install it." 1 done export PATH=/sbin:/usr/sbin:$PATH # which dd command to use command -v dcfldd > /dev/null { test $? = 0 } && { DD="dcfldd statusinterval=1" } # which wipe command to use command -v wipe > /dev/null && WIPE="wipe -f -s" || WIPE="rm -f" # check for filesystem creation progs command -v mkfs.ext4 > /dev/null && \ MKFS="mkfs.ext4 -q -F -j -L" || \ MKFS="mkfs.ext3 -q -F -j -L" # check for mktemp command -v mktemp > /dev/null || MKTEMP=0 # check for steghide command -v steghide > /dev/null || STEGHIDE=0 # check for resize command -v e2fsck resize2fs > /dev/null || RESIZER=0 if which tomb-kdf-pbkdf2 &> /dev/null; then KDF_PBKDF2="tomb-kdf-pbkdf2" else local our_pbkdf2 our_pbkdf2="$(dirname $(readlink -f $TOMBEXEC))/kdf/tomb-kdf-pbkdf2" if which $our_pbkdf2 &> /dev/null; then KDF_PBKDF2=$our_pbkdf2 else KDF_PBKDF2= fi fi } # }}} - Commandline interaction # {{{ Key operations typeset -h tombkeydir # global used if key comes from stdin # This function retrieves a tomb key specified on commandline or one # laying nearby the tomb if found, or from stdin if the option was # selected. It also runs validity checks on the file. Callers should # always use drop_key() when done with all key operations. # On success returns 0 and prints out the full path to the key load_key() { # check if the key is set manually then use the one existing local tombdir="$1" local tombname="$2" if option_is_set -k ; then if [[ "`option_value -k`" == "-" ]]; then # take key from stdin tombkeydir=`safe_dir` cat > ${tombkeydir}/stdin.tmp tombkey=${tombkeydir}/stdin.tmp else # take key from a file tombkey=`option_value -k` fi else # guess key as lying besides the tomb tombkey=${tombdir}/${tombname}.tomb.key fi if [ -r "${tombkey}" ]; then _message "We'll use this key:" _message " `ls -lh ${tombkey}`" else return 1 fi # this does a check on the file header if ! is_valid_key ${tombkey}; then _warning "The key seems invalid, the application/pgp header is missing" return 1 fi print "$tombkey" return 0 } # This function asks the user for the password to use the key it tests # it against the return code of gpg on success returns 0 and prints # the password (be careful about where you save it!) ask_key_password() { tombkey="$1" local keyname=`basename $tombkey` _message "a password is required to use key ${keyname}" local passok=0 local tombpass="" if option_is_set --tomb-pwd; then tombpass=`option_value --tomb-pwd` else for c in 1 2 3; do if [ $c = 1 ]; then tombpass=`exec_as_user ${TOMBEXEC} askpass "Insert password to use key: $keyname"` else tombpass=`exec_as_user ${TOMBEXEC} askpass "Insert password to use key: $keyname (retry $c)"` fi if [[ $? != 0 ]]; then _warning "User aborted password dialog" return 1 fi get_lukskey "$tombpass" ${tombkey} >/dev/null if [ $? = 0 ]; then passok=1; _message "Password OK." break; fi done fi { test "$passok" = "1" } || { return 1 } print "$tombpass" unset $tombpass return 0 } # change tomb key password change_passwd() { _message "Commanded to change password for tomb key $1" if ! option_is_set -f && ! option_is_set --ignore-swap; then check_swap; fi local keyfile="$1" # $1 is the tomb key path # check the keyfile if ! [ -r $keyfile ]; then _warning "key not found: $keyfile" return 1 fi if ! is_valid_key $keyfile ; then _warning "file doesn't seems to be a tomb key: $keyfile" _warning "operation aborted." return 1 fi local tmpnewkey lukskey c tombpass tombpasstmp tmpnewkey=`safe_filename tombnew` lukskey=`safe_filename tombluks` _success "Changing password for $keyfile" tombpass=`ask_key_password $keyfile` { test $? = 0 } || { die "No valid password supplied" } get_lukskey "${tombpass}" ${keyfile} > ${lukskey}; drop_key { gen_key $lukskey > ${tmpnewkey} if ! is_valid_key $tmpnewkey; then die "Error: the newly generated keyfile does not seem valid" else # copy the new key as the original keyfile name cp "${tmpnewkey}" "${keyfile}" _success "Your passphrase was successfully updated." fi } always { _verbose "cleanup: $tmpnewkey $lukskey" # wipe all temp file ${=WIPE} "${tmpnewkey}" ${=WIPE} "${lukskey}" } return $? } # To be called after load_key() drop_key() { { test "$tombkeydir" = "" } && { return 0 } { test -r ${tombkeydir}/stdin.tmp } && { ${=WIPE} ${tombkeydir}/stdin.tmp; rmdir ${tombkeydir} } } #$1 is the keyfile we are checking is_valid_key() { # this header validity check is a virtuosism by Hellekin [[ `file =(awk '/^-+BEGIN/,0' $1) -bi` =~ application/pgp ]] return $? } # Gets a key file and a password, prints out the decoded contents to # be used directly by Luks as a cryptographic key get_lukskey() { # $1 is the password, $2 is the keyfile local tombpass=$1 local keyfile=$2 firstline=`head -n1 $keyfile` xxx "get_lukskey XXX $keyfile" if [[ $firstline =~ '^_KDF_' ]]; then _verbose "KDF: `cut -d_ -f 3 <<<$firstline`" case `cut -d_ -f 3 <<<$firstline` in pbkdf2sha1) if [[ -z $KDF_PBKDF2 ]]; then die "The tomb use kdf method 'pbkdf2', which is unsupported on your system" fi pbkdf2_param=`cut -d_ -f 4- <<<$firstline | tr '_' ' '` tombpass=$(${KDF_PBKDF2} ${=pbkdf2_param} 2> /dev/null <<<$tombpass) ;; *) _failure "No suitable program for KDF `cut -f 3 <<<$firstline`" unset tombpass return 1 ;; esac fi # fix for gpg 1.4.11 where the --status-* options don't work ;^/ gpgver=`gpg --version | awk '/^gpg/ {print $3}'` if [ "$gpgver" = "1.4.11" ]; then xxx "GnuPG is version 1.4.11 - adopting status fix" print ${tombpass} | \ gpg --batch --passphrase-fd 0 --no-tty --no-options -d "${keyfile}" unset tombpass ret=$? else # using status-file in gpg != 1.4.12 res=`safe_filename tomb.open` { test $? = 0 } || { unset tombpass; die "Fatal error creating temp file." } print ${tombpass} | \ gpg --batch --passphrase-fd 0 --no-tty --no-options --status-fd 2 \ --no-mdc-warning --no-permission-warning --no-secmem-warning \ -d "${keyfile}" 2>$res unset tombpass grep 'DECRYPTION_OKAY' $res ret=$?; rm -f $res fi xxx "get_lukskey returns $ret" return $ret } # takes care to encrypt a key # honored options: --kdf --tomb-pwd gen_key() { # $1 the lukskey to encrypt local lukskey=$1 # here user is prompted for key password local tombpass="" local tombpasstmp="" if ! option_is_set --tomb-pwd; then while true; do # 3 tries to write two times a matching password tombpass=`exec_as_user ${TOMBEXEC} askpass "Secure key for ${tombname}"` if [[ $? != 0 ]]; then die "User aborted" fi if [ -z $tombpass ]; then _warning "you set empty password, which is not possible" continue fi tombpasstmp=$tombpass tombpass=`exec_as_user ${TOMBEXEC} askpass "Secure key for ${tombname} (again)"` if [[ $? != 0 ]]; then die "User aborted" fi if [ "$tombpasstmp" = "$tombpass" ]; then break; fi unset tombpasstmp unset tombpass done else tombpass=`option_value --tomb-pwd` fi # KDF is a new key strenghtening technique against brute forcing # see: https://github.com/dyne/Tomb/issues/82 _verbose "KDF method chosen is: '`option_value --kdf`'" kdf_method=$(cut -d: -f1 <<<`option_value --kdf` ) case $kdf_method in pbkdf2) if [[ -z $KDF_PBKDF2 ]]; then die "The tomb use kdf method 'pbkdf2', which is unsupported on your system" fi # --kdf takes one parameter: iter time (on present machine) in seconds seconds=$(cut -d: -f2 -s <<<`option_value --kdf`) if [[ -z $seconds ]]; then seconds=1 fi local -i microseconds microseconds=$((seconds*1000000)) _verbose "Microseconds: $microseconds" pbkdf2_salt=`${KDF_PBKDF2}-gensalt` pbkdf2_iter=`${KDF_PBKDF2}-getiter $microseconds` # We use a length of 64bytes = 512bits (more than needed!?) tombpass=`${KDF_PBKDF2} $pbkdf2_salt $pbkdf2_iter 64 <<<"${tombpass}"` header="_KDF_pbkdf2sha1_${pbkdf2_salt}_${pbkdf2_iter}_64\n" ;; ""|null) header="" ;; *) _warning "KDF method non recognized" return 1 header="" ;; esac echo -n $header print "${tombpass}" \ | gpg --openpgp --batch --no-options --no-tty --passphrase-fd 0 2>/dev/null \ -o - -c -a ${lukskey} unset tombpass } # prints an array of ciphers available in gnupg (to encrypt keys) list_gnupg_ciphers() { # prints an error if GnuPG is not found which gpg > /dev/null || die "gpg (GnuPG) is not found, Tomb cannot function without it." ciphers=(`gpg --version | awk ' BEGIN { ciphers=0 } /^Cipher:/ { gsub(/,/,""); sub(/^Cipher:/,""); print; ciphers=1; next } /^Hash:/ { ciphers=0 } { if(ciphers==0) { next } else { gsub(/,/,""); print; } } '`) echo " ${ciphers}" return 1 } # Steganographic function to bury a key inside an image. bury_key() { tombkey=$1 imagefile=$2 file $tombkey | grep PGP > /dev/null if [ $? != 0 ]; then _warning "encode failed: $tombkey is not a tomb key" return 1 fi file $imagefile | grep JPEG > /dev/null if [ $? != 0 ]; then _warning "encode failed: $imagefile is not a jpeg image" return 1 fi _success "Encoding key $tombkey inside image $imagefile" _message "please choose a password for the encoding" # here user is prompted for key password for c in 1 2 3; do # 3 tries to write two times a matching password tombpass=`exec_as_user ${TOMBEXEC} askpass "Steg password for ${tombkey}"` tombpasstmp=$tombpass tombpass=`exec_as_user ${TOMBEXEC} askpass "Steg password for ${tombkey} (again)"` if [ "$tombpasstmp" = "$tombpass" ]; then break; fi unset tombpasstmp unset tombpass done if [ -z $tombpass ]; then _warning "passwords don't match, aborting operation." return 1 fi # Requires steghide to be installed awk ' /^-----/ {next} /^Version/ {next} {print $0}' ${tombkey} \ | steghide embed --embedfile - --coverfile ${imagefile} \ -p ${tombpass} -z 9 -e serpent cbc if [ $? != 0 ]; then _warning "encoding error: steghide reports problems" res=1 else _success "tomb key encoded succesfully into image ${imagefile}" res=0 fi unset tombpass return $res } # Steganographic function to exhume a key buries into an image exhume_key() { tombname=$1 imagefile=$2 res=1 file $imagefile | grep JPEG > /dev/null if [ $? != 0 ]; then _warning "encode failed: $imagefile is not a jpeg image" return 1 fi keyfile=${tombname%%\.*}.tomb.key if [[ -e "$keyfile" ]]; then _warning "Key file $keyfile already exist." return 1 fi _message "Trying to exhume a key out of image $imagefile" for c in 1 2 3; do if [ $c = 1 ]; then tombpass=`exec_as_user ${TOMBEXEC} askpass "Steg password for ${keyfile}"` else tombpass=`exec_as_user ${TOMBEXEC} askpass "Steg password for $keyfile (retry $c)"` fi # always steghide required steghide extract -sf ${imagefile} -p ${tombpass} -xf - \ | awk ' BEGIN { print "-----BEGIN PGP MESSAGE-----" } { print $0 } END { print "-----END PGP MESSAGE-----" }' > ${keyfile} if [ "`cat ${keyfile} | wc -l`" != "3" ]; then _success "${keyfile} succesfully decoded" res=0 break; fi done unset tombpass if [ $res != 0 ]; then _warning "nothing found." fi return $res } # }}} - Key handling # {{{ Create # This is a new way to create tombs which dissects the whole create_tomb() into 3 clear steps: # # * dig a .tomb (the large file) using /dev/random (takes some minutes at least) # # * forge a .key (the small file) using /dev/urandom (good entropy needed) # # * lock the .tomb file with the key, binding the key to the tomb (requires dm_crypt format) forge_key() { _message "Commanded to forge key $1" if ! [ $1 ]; then _warning "no key name specified for creation" return 1 fi # if swap is on, we remind the user about possible data leaks to disk if ! option_is_set -f && ! option_is_set --ignore-swap; then check_swap; fi # create the keyfile in tmpfs so that we leave less traces in RAM keytmp=`safe_dir tomb` (( $? )) && die "error creating temp dir" xxx "safe_dir at $keytmp" mount tmpfs "${keytmp}" -t tmpfs -o size=1m if [ $? != 0 ]; then _warning "cannot mount tmpfs filesystem in volatile memory" rm -r "${keytmp}" die "operation aborted." fi tombkey="$1" _message "this operation takes time, keep using this computer on other tasks," _message "once done you will be asked to choose a password for your tomb." _message "To make it faster you can move the mouse around." _message "If you are on a server, you can use an Entropy Generation Daemon." touch ${keytmp}/tomb.tmp chmod 0600 ${keytmp}/tomb.tmp random_source=/dev/random if option_is_set --use-urandom; then random_source=/dev/urandom fi xxx "Data dump using ${DD[1]} from $random_source" ${=DD} bs=1 count=256 if=$random_source of=${keytmp}/tomb.tmp if ! [ -r ${keytmp}/tomb.tmp ]; then _warning "cannot generate encryption key" umount ${keytmp} rm -r $keytmp die "operation aborted." fi _success "Choose the password of your key: ${tombkey}" _message "(you can also change it later using 'tomb passwd')" touch ${tombkey} chown ${_uid}:${_gid} ${tombkey} chmod 0600 ${tombkey} tombname="$tombkey" # the gen_key() function takes care of the new key's encryption gen_key ${keytmp}/tomb.tmp > ${tombkey} # this does a check on the file header if ! is_valid_key ${tombkey}; then _warning "The key does not seem to be valid" _warning "Dumping contents to screen:" cat ${tombkey} _warning "--" umount ${keytmp} rm -r $keytmp die "operation aborted." fi ${=WIPE} ${keytmp}/tomb.tmp # no need really, but anyway umount ${keytmp} rm -r ${keytmp} chown ${_uid}:${_gid} ${tombkey} _message "done forging $tombkey" _success "Your key is ready:" ls -lh ${tombkey} } # Dig a tomb, means that it will create an empty file to be formatted # as a loopback filesystem. Initially the file is filled with random data # taken from /dev/urandom which improves the tomb's overall security dig_tomb() { _message "Commanded to dig tomb $1" # if swap is on, we remind the user about possible data leaks to disk if ! option_is_set -f && ! option_is_set --ignore-swap; then check_swap; fi if ! [ $1 ]; then _warning "no tomb name specified for creation" return 1 fi tombfile=`basename $1` tombdir=`dirname $1` # make sure the file has a .tomb extension tombname=${tombfile%%\.*} tombfile=${tombname}.tomb # require the specification of the size of the tomb (-s) in MB tombsize="`option_value -s`" [ $tombsize ] || die "Size argument missing, use --size" [[ $tombsize != <-> ]] && die "Size argument is not an integer" [[ $tombsize -lt 10 ]] && die "Tombs can't be smaller than 10 megabytes" if [ -e ${tombdir}/${tombfile} ]; then _warning "A tomb exists already. I'm not digging here:" _warning " `ls -lh ${tombdir}/${tombfile}`" return 1 fi _success "Creating a new tomb in ${tombdir}/${tombfile}" _message "Generating ${tombfile} of ${tombsize}MiB" # we will first touch the file and set permissions: this way, even if interrupted, permissions are right touch ${tombdir}/${tombfile} chmod 0600 "${tombdir}/${tombfile}" chown $_uid:$_gid "${tombdir}/${tombfile}" xxx "Data dump using ${DD[1]} from /dev/urandom" ${=DD} if=/dev/urandom bs=1048576 count=${tombsize} of=${tombdir}/${tombfile} if [ $? = 0 -a -e ${tombdir}/${tombfile} ]; then _message " `ls -lh ${tombdir}/${tombfile}`" else die "Error creating the tomb ${tombdir}/${tombfile}, operation aborted." fi _success "Done digging $tombname" _message "your tomb is not yet ready, you need to forge a key and lock it:" _message "tomb forge ${tombname}.tomb.key" _message "tomb lock ${tombname}.tomb ${tombname}.tomb.key" } # this function locks a tomb with a key file # in fact LUKS formatting the loopback volume # it take arguments as the LUKS cipher to be used lock_tomb_with_key() { if ! [ $1 ]; then _warning "no tomb specified for locking" _warning "usage: tomb lock file.tomb file.tomb.key" return 1 fi tombfile=`basename $1` _message "Commanded to lock tomb ${tombfile}" tombdir=`dirname $1` # make sure the file has a .tomb extension tombname=${tombfile%%\.*} tombfile=${tombname}.tomb { test -f ${tombdir}/${tombfile} } || { die "There is no tomb here. You have to it dig first." return 1 } xxx "tomb found: ${tombdir}/${tombfile}" nstloop=`losetup -f` # get the number for next loopback device losetup -f ${tombdir}/${tombfile} # allocates the next loopback for our file xxx "loop mounted on ${nstloop}" _message "checking if the tomb is empty (we never step on somebody else's bones)" cryptsetup isLuks ${nstloop} if [ $? = 0 ]; then # is it a LUKS encrypted nest? then bail out and avoid reformatting it _warning "The tomb was already locked with another key" losetup -d ${nstloop} die "Operation aborted. I cannot lock an already locked tomb. Go dig a new one." else _message "fine, this tomb seems empty." fi # load key from options or file tombkey=`load_key ${tombdir} ${tombname}` { test $? = 0 } || { losetup -d $nstloop die "Aborting operations: error loading key $tombkey" } # make sure to call drop_key later # the encryption cipher for a tomb can be set at creation using -o if option_is_set -o; then cipher="`option_value -o`" else cipher="aes-cbc-essiv:sha256" fi _message "locking using cipher: $cipher" # get the pass from the user and check it tombpass=`ask_key_password "$tombkey"` { test $? = 0 } || { losetup -d ${nstloop} die "No valid password supplied" } _success "Locking ${tombfile} with ${tombkey}" _message "formatting Luks mapped device" get_lukskey "${tombpass}" ${tombkey} | \ cryptsetup --key-file - --batch-mode \ --cipher ${cipher} --key-size 256 --key-slot 0 \ luksFormat ${nstloop} if ! [ $? = 0 ]; then _warning "cryptsetup luksFormat returned an error" unset tombpass losetup -d $nstloop die "Operation aborted." fi get_lukskey "${tombpass}" ${tombkey} | \ cryptsetup --key-file - \ --cipher ${cipher} luksOpen ${nstloop} tomb.tmp if ! [ $? = 0 ]; then _warning "cryptsetup luksOpen returned an error" unset tombpass losetup -d $nstloop die "Operation aborted." fi # cleanup tombs unset tombpass drop_key # make sure all temp files are out _message "formatting your Tomb with Ext3/Ext4 filesystem" ${=MKFS} ${tombname} /dev/mapper/tomb.tmp if [ $? != 0 ]; then _warning "Tomb format returned an error" _warning "your tomb ${tombfile} may be corrupted." fi sync cryptsetup luksClose tomb.tmp losetup -d ${nstloop} _message "done locking $tombname using Luks dm-crypt ${create_cipher}" _success "Your tomb is ready in ${tombdir}/${tombfile} and secured with key ${tombkey}" } # backward compatibility create_tomb() { if ! [ $1 ]; then _warning "no tomb name specified for creation" return 1 fi dig_tomb ${=PARAM} { test $? = 0 } || { die "Failed to dig tomb, operation aborted." } tombfile=`basename $1` tombdir=`dirname $1` # make sure the file has a .tomb extension tombname=${tombfile%%\.*} tombfile=${tombname}.tomb forge_key ${tombfile}.key { test $? = 0 } || { die "Failed to forge key, operation aborted." } lock_tomb_with_key ${tombfile} -l ${tombfile}.key { test $? = 0 } || { die "Failed to lock tomb with key, operation aborted." } yes "Tomb $tombname succesfully created" ls -l ${tombfile}* } # }}} - Creation # {{{ Open # $1 = tombfile $2(optional) = mountpoint mount_tomb() { _message "Commanded to open tomb $1" if ! option_is_set -f && ! option_is_set --ignore-swap; then check_swap; fi if ! [ ${1} ]; then _warning "no tomb name specified for creation" return 1 fi # set up variables to be used # the full path is made with $tombdir/$tombfile local tombkey local tombfile local tombdir local tombname tombfile=`basename ${1}` tombdir=`dirname ${1}` # check file type (if its a Luks fs) file ${tombdir}/${tombfile} | grep -i 'luks encrypted file' 2>&1 >/dev/null if [ $? != 0 ]; then _warning "$1 is not a valid tomb file, operation aborted" return 1 fi tombname=${tombfile%%\.*} xxx "tomb found: ${tombdir}/${tombfile}" tombkey=`load_key ${tombdir} ${tombname}` { test $? = 0 } || { die "Aborting operations: error loading key $tombkey" } if [ "$2" = "" ]; then tombmount=/media/${tombfile} _message "mountpoint not specified, using default: $tombmount" else tombmount=$2 fi # check if its already open mount -l | grep "${tombfile}.*\[$tombname\]$" 2>&1 > /dev/null if [ $? = 0 ]; then _warning "$tombname is already open." _message "here below its status is reported:" list_tombs ${tombname} return 1 fi _success "Opening $tombfile on $tombmount" nstloop=`losetup -f` if [ $? = 255 ]; then die "too many tomb opened. Please close any of them to open another tomb" fi losetup -f ${tombdir}/${tombfile} cryptsetup isLuks ${nstloop} if [ $? != 0 ]; then # is it a LUKS encrypted nest? see cryptsetup(1) _warning "$tombfile is not a valid Luks encrypted storage file" return 1 fi say "this tomb is a valid LUKS encrypted device" luksdump="`cryptsetup luksDump ${nstloop}`" tombdump=(`print $luksdump | awk ' /^Cipher name/ {print $3} /^Cipher mode/ {print $3} /^Hash spec/ {print $3}'`) say "cipher is \"$tombdump[1]\" mode \"$tombdump[2]\" hash \"$tombdump[3]\"" slotwarn=`print $luksdump | awk ' BEGIN { zero=0 } /^Key slot 0/ { zero=1 } /^Key slot.*ENABLED/ { if(zero==1) print "WARN" }'` { test "$slotwarn" = "WARN" } && { _warning "Multiple key slots are enabled on this tomb. Beware: there can be a backdoor." } # save date of mount in minutes since 1970 mapdate=`date +%s` mapper="tomb.${tombname}.${mapdate}.`basename $nstloop`" keyname=`basename $tombkey | cut -d. -f1` tombpass=`ask_key_password $tombkey` { test $? = 0 } || { die "No valid password supplied" } get_lukskey "${tombpass}" ${tombkey} | \ cryptsetup --key-file - luksOpen ${nstloop} ${mapper} drop_key unset tombpass if ! [ -r /dev/mapper/${mapper} ]; then losetup -d ${nstloop} die "failure mounting the encrypted file" fi # array: [ cipher, keysize, loopdevice ] tombstat=(`cryptsetup status ${mapper} | awk ' /cipher:/ {print $2} /keysize:/ {print $2} /device:/ {print $2}'`) yes "Success unlocking tomb $tombname" xxx "key size is $tombstat[2] for cipher $tombstat[1]" _message "checking filesystem via $tombstat[3]" fsck -p -C0 /dev/mapper/${mapper} xxx "tomb engraved as $tombname" tune2fs -L ${tombname} /dev/mapper/${mapper} > /dev/null # we need root from here on mkdir -p $tombmount mount -o $MOUNTOPTS /dev/mapper/${mapper} ${tombmount} chown ${_uid}:${_gid} ${tombmount} chmod 0750 ${tombmount} _success "Success opening $tombfile on $fg_bold[white]$tombmount$fg_no_bold[white]" # print out when was opened the last time, by whom and where { test -r ${tombmount}/.last } && { tombtty="`cat ${tombmount}/.tty`" tombhost="`cat ${tombmount}/.host`" tombuid="`cat ${tombmount}/.uid`" tomblast="`cat ${tombmount}/.last`" tombuser=`awk -F: '/:'"$tombuid"':/ {print $1}' /etc/passwd` say "last visit by $fg_bold[white]$tombuser($tombuid)$fg_no_bold[white] from $fg_bold[white]$tombtty$fg_no_bold[white] on $fg_bold[white]$tombhost$fg_no_bold[white]" say "on date $fg_bold[white]`date --date @${tomblast} +%c`$fg_no_bold[white]" } # write down the UID and TTY that opened the tomb rm -f ${tombmount}/.uid echo ${_uid} > ${tombmount}/.uid rm -f ${tombmount}/.tty echo ${_tty} > ${tombmount}/.tty # also the hostname rm -f ${tombmount}/.host echo `hostname` > ${tombmount}/.host # and the "last time opened" information # in minutes since 1970, this is printed at next open rm -f ${tombmount}/.last echo "`date +%s`" > ${tombmount}/.last # human readable: date --date=@"`cat .last`" +%c # process bind-hooks (mount -o bind of directories) # and post-hooks (execute on open) if ! option_is_set -n ; then exec_safe_bind_hooks ${tombmount} exec_safe_post_hooks ${tombmount} open fi return 0 } # ## Hooks execution exec_safe_bind_hooks() { if [[ -n ${(k)opts[-o]} ]]; then MOUNTOPTS=${opts[-o]} fi local MOUNTPOINT="${1}" local ME=${SUDO_USER:-$(whoami)} local HOME=$(awk -v a="$ME" -F ':' '{if ($1 == a) print $6}' /etc/passwd 2>/dev/null) if [ $? -ne 0 ]; then _warning "how pitiful! A tomb, and no HOME" return 1 fi if [ -z "$MOUNTPOINT" -o ! -d "$MOUNTPOINT" ]; then _warning "cannot exec bind hooks without a mounted tomb." return 1 fi if ! [ -r "$MOUNTPOINT/bind-hooks" ]; then xxx "bind-hooks not found in $MOUNTPOINT" return 1 fi typeset -al mounted typeset -Al maps maps=($(<"$MOUNTPOINT/bind-hooks")) for dir in ${(k)maps}; do if [ "${dir[1]}" = "/" -o "${dir[1,2]}" = ".." ]; then _warning "bind-hooks map format: local/to/tomb local/to/\$HOME" continue fi if [ "${${maps[$dir]}[1]}" = "/" -o "${${maps[$dir]}[1,2]}" = ".." ]; then _warning "bind-hooks map format: local/to/tomb local/to/\$HOME. Rolling back" for dir in ${mounted}; do umount $dir; done return 1 fi if [ ! -r "$HOME/${maps[$dir]}" ]; then _warning "bind-hook target not existent, skipping $HOME/${maps[$dir]}" elif [ ! -r "$MOUNTPOINT/$dir" ]; then _warning "bind-hook source not found in tomb, skipping ${MOUNTPOINT}/${dir}" else mount -o bind,$MOUNTOPTS $MOUNTPOINT/$dir $HOME/${maps[$dir]} mounted+=("$HOME/${maps[$dir]}") fi done } # Post mount hooks exec_safe_post_hooks() { local mnt=$1 # first argument is where the tomb is mounted local ME=${SUDO_USER:-$(whoami)} if ! [ -x ${mnt}/post-hooks ]; then return; fi # if 'post-hooks' is found inside the tomb, check it: if it is an # executable, launch it as a user this might need a dialog for # security on what is being run, however we expect you know well # what is inside your tomb. this feature opens the possibility to # make encrypted executables. cat ${mnt}/post-hooks | head -n1 | grep '^#!/' if [ $? = 0 ]; then _success "post hooks found, executing as user $SUDO_USER" exec_as_user ${mnt}/post-hooks $2 fi } # }}} - Tomb open # {{{ List # list all tombs mounted in a readable format # $1 is optional, to specify a tomb list_tombs() { # list all open tombs mounted_tombs=(`list_tomb_mounts $1`) { test ${#mounted_tombs} = 0 } && { die "I can't see any ${1:-open} tomb, may they all rest in peace." } for t in ${mounted_tombs}; do mapper=`basename ${t[(ws:;:)1]}` tombname=${t[(ws:;:)5]} tombmount=${t[(ws:;:)2]} tombfs=${t[(ws:;:)3]} tombfsopts=${t[(ws:;:)4]} tombloop=${mapper[(ws:.:)4]} # calculate tomb size ts=`df -hP /dev/mapper/$mapper | awk "/mapper/"' { print $2 ";" $3 ";" $4 ";" $5 }'` tombtot=${ts[(ws:;:)1]} tombused=${ts[(ws:;:)2]} tombavail=${ts[(ws:;:)3]} tombpercent=${ts[(ws:;:)4]} tombp=${tombpercent%%%} tombsince=`date --date=@${mapper[(ws:.:)3]} +%c` # find out who opens it from where { test -r ${tombmount}/.tty } && { tombtty="`cat ${tombmount}/.tty`" tombhost="`cat ${tombmount}/.host`" tombuid="`cat ${tombmount}/.uid`" tombuser=`awk -F: '/:'"$tombuid"':/ {print $1}' /etc/passwd` } if option_is_set --get-mountpoint; then echo $tombmount continue fi # breaking up such strings is good for translation print -n "$fg[green]$tombname" print -n "$fg[white] open on " print -n "$fg_bold[white]$tombmount" print -n "$fg_no_bold[white] using " print "$fg_bold[white]$tombfs $tombfsopts" print -n "$fg_no_bold[green]$tombname" print -n "$fg_no_bold[white] open since " print "$fg_bold[white]$tombsince$fg_no_bold[white]" { test "$tombtty" = "" } || { print -n "$fg_no_bold[green]$tombname" print -n "$fg_no_bold[white] open by " print -n "$fg_bold[white]$tombuser" print -n "$fg_no_bold[white] from " print -n "$fg_bold[white]$tombtty" print -n "$fg_no_bold[white] on " print "$fg_bold[white]$tombhost" } print -n "$fg_no_bold[green]$tombname" print -n "$fg[white] size " print -n "$fg_bold[white]$tombtot" print -n "$fg_no_bold[white] of which " print -n "$fg_bold[white]$tombused" print -n "$fg_no_bold[white] used: " print -n "$fg_bold[white]$tombavail" print -n "$fg_no_bold[white] free (" print -n "$fg_bold[white]$tombpercent" print "$fg_no_bold[white] full)" if [[ ${tombp} -ge 90 ]]; then print -n "$fg_no_bold[green]$tombname" print "$fg_bold[red] Your tomb is almost full!" fi # now check hooks mounted_hooks=(`list_tomb_binds $tombname`) for h in ${mounted_hooks}; do print -n "$fg_no_bold[green]$tombname" print -n "$fg_no_bold[white] hooks " # print -n "$fg_bold[white]`basename ${h[(ws:;:)1]}`" # print -n "$fg_no_bold[white] on " print "$fg_bold[white]${h[(ws:;:)2]}$fg_no_bold[white]" done done } # print out an array of mounted tombs (internal use) # format is semi-colon separated list of attributes # if 1st arg is supplied, then list only that tomb # # String positions in the semicolon separated array: # # 1. full mapper path # # 2. mountpoint # # 3. filesystem type # # 4. mount options # # 5. tomb name list_tomb_mounts() { if [ "$1" = "" ]; then # list all open tombs mount -l \ | awk ' BEGIN { main="" } /^\/dev\/mapper\/tomb/ { if(main==$1) next; print $1 ";" $3 ";" $5 ";" $6 ";" $7 main=$1 } ' else # list a specific tomb mount -l \ | awk -vtomb="[$1]" ' BEGIN { main="" } /^\/dev\/mapper\/tomb/ { if($7!=tomb) next; if(main==$1) next; print $1 ";" $3 ";" $5 ";" $6 ";" $7 main=$1 } ' fi } # list_tomb_binds # print out an array of mounted bind hooks (internal use) # format is semi-colon separated list of attributes # needs an argument: name of tomb whose hooks belong list_tomb_binds() { if [ "$1" = "" ]; then _failure "internal error: list_tomb_binds called without argument."; fi # list bind hooks on util-linux 2.20 (Debian 7) mount -l \ | awk -vtomb="$1" ' BEGIN { main="" } /^\/dev\/mapper\/tomb/ { if($7!=tomb) next; if(main=="") { main=$1; next; } if(main==$1) print $1 ";" $3 ";" $5 ";" $6 ";" $7 } ' # list bind hooks on util-linux 2.17 (Debian 6) tombmount=`mount -l \ | awk -vtomb="$1" ' /^\/dev\/mapper\/tomb/ { if($7!=tomb) next; print $3; exit; }'` mount -l | grep "^$tombmount" \ | awk -vtomb="$1" ' /bind/ { print $1 ";" $3 ";" $5 ";" $6 ";" $7 }' } # }}} - Tomb list # {{{ Index and search # index files in all tombs for search # $1 is optional, to specify a tomb index_tombs() { { command -v updatedb > /dev/null } || { die "Cannot index tombs on this system: updatedb not installed" } mounted_tombs=(`list_tomb_mounts $1`) { test ${#mounted_tombs} = 0 } && { if [ $1 ]; then die "There seems to be no open tomb engraved as [$1]" else die "I can't see any open tomb, may they all rest in peace." fi } yes "Creating and updating search indexes" for t in ${mounted_tombs}; do mapper=`basename ${t[(ws:;:)1]}` tombname=${t[(ws:;:)5]} tombmount=${t[(ws:;:)2]} { test -r ${tombmount}/.noindex } && { say "skipping $tombname (.noindex found)" continue } say "indexing $tombname" updatedb -l 0 -o ${tombmount}/.updatedb -U ${tombmount} say "search index updated" done } search_tombs() { { command -v locate > /dev/null } || { die "Cannot index tombs on this system: updatedb not installed" } # list all open tombs mounted_tombs=(`list_tomb_mounts $1`) { test ${#mounted_tombs} = 0 } && { die "I can't see any open tomb, may they all rest in peace." } shift yes "Searching for: $fg_bold[white]${=@}$fg_no_bold[white]" for t in ${mounted_tombs}; do xxx "checking for index: ${t}" mapper=`basename ${t[(ws:;:)1]}` tombname=${t[(ws:;:)5]} tombmount=${t[(ws:;:)2]} if [ -r ${tombmount}/.updatedb ]; then say "Searching in tomb $tombname" locate -d ${tombmount}/.updatedb -e -i ${=@} say "Matches found: `locate -d ${tombmount}/.updatedb -e -i -c ${=@}`" else no "skipping tomb $tombname: not indexed" no "run 'tomb index' to create indexes" fi done } # }}} - Index and search # {{{ Resize # resize tomb file size resize_tomb() { _message "Commanded to resize tomb $1 to $opts[-s] megabytes" if ! [ $1 ]; then _failure "No tomb name specified for resizing" elif ! [ -r "$1" ]; then _failure "Cannot find $1" fi # $1 is the tomb file path local c tombpass tombkey local tombfile=`basename $1` local tombdir=`dirname $1` # make sure the file has a .tomb extension local tombname=${tombfile%%\.*} tombfile=${tombname}.tomb # load key from options or file tombkey=`load_key ${tombdir} ${tombname}` { test $? = 0 } || { die "Aborting operations: error loading key $tombkey" } # make sure to call drop_key later local tmp_resize=`safe_filename tmbrsz` local newtombsize=$opts[-s] local oldtombsize=`stat -c %s "$1" 2>/dev/null` local mounted_tomb=`mount -l | awk -vtomb="[$tombname]" '/^\/dev\/mapper\/tomb/ { if($7==tomb) print $1 }'` if [ "$mounted_tomb" ]; then _failure "the tomb $tombname is open, to resize it it needs to be close." fi # MB to bytes conversion newtombsize=`expr \( $newtombsize \* 1024 \) \* 1024 2> /dev/null` if ! [ "$newtombsize" ] ; then _failure "You must specify the new size of $tombname" elif [[ $newtombsize != <-> ]]; then _failure "Size is not an integer" elif [ "$newtombsize" -le "$oldtombsize" ]; then _failure "the new size must be greater then old tomb size." fi local delta=`expr $newtombsize \- $oldtombsize` act "Generating ${tombfile} of ${newtombsize}MiB" xxx "Data dump using ${DD[1]} from /dev/urandom" ${=DD} if=/dev/urandom bs=1048576 count=${tombsize} of="${tmp_resize}" if [ $? = 0 -a -e "${tmp_resize}" ]; then xxx "OK: `ls -lh ${tmp_resize}`" else _failure "Error creating the extra resize $tmp_resize, operation aborted." fi cat "${tmp_resize}" >> "$1" ${=WIPE} "${tmp_resize}" tombpass=`ask_key_password $tombkey` { test $? = 0 } || { die "No valid password supplied" } local nstloop=`losetup -f` if [ $? = 255 ]; then _failure "too many tomb opened. Please close any of them to open another tomb" fi losetup -f "$1" local mapdate=`date +%s` local mapper="tomb.${tombname}.${mapdate}.`basename $nstloop`" get_lukskey "${tombpass}" ${tombkey} | \ cryptsetup --key-file - luksOpen ${nstloop} ${mapper} unset tombpass drop_key # cleanup after load_key if ! [ -r /dev/mapper/${mapper} ]; then losetup -d ${nstloop} _failure "failure mounting the encrypted file" fi cryptsetup resize "${mapper}" if [ $? != 0 ]; then losetup -d ${nstloop} _failure "cryptsetup failed to resize $mapper" fi e2fsck -f /dev/mapper/${mapper} if [ $? != 0 ]; then losetup -d ${nstloop} _failure "e2fsck failed to check $mapper" fi resize2fs /dev/mapper/${mapper} if [ $? != 0 ]; then losetup -d ${nstloop} _failure "resize2fs failed to resize $mapper" fi sleep 1 # needs to settle a bit # close and free the loop device cryptsetup luksClose "${mapper}" losetup -d ${nstloop} return 0 } # }}} # {{{ Close umount_tomb() { local tombs how_many_tombs local pathmap mapper tombname tombmount loopdev local ans pidk pname if [ "$1" = "all" ]; then mounted_tombs=(`list_tomb_mounts`) else mounted_tombs=(`list_tomb_mounts $1`) fi { test ${#mounted_tombs} = 0 } && { _warning "There is no open tomb to be closed" return 1 } { test ${#mounted_tombs} -gt 1 } && { test "$1" = "" } && { _warning "Too many tombs mounted, please specify one (see tomb list)" _warning "or issue the command 'tomb close all' to close them all." return 1 } say "Tomb close $1" for t in ${mounted_tombs}; do mapper=`basename ${t[(ws:;:)1]}` tombname=${t[(ws:;:)5]} tombmount=${t[(ws:;:)2]} tombfs=${t[(ws:;:)3]} tombfsopts=${t[(ws:;:)4]} tombloop=${mapper[(ws:.:)4]} xxx "name: $tombname" xxx "mount: $tombmount" xxx "mapper: $mapper" { test -e "$mapper" } && { _warning "Tomb not found: $1" _warning "Please specify an existing tomb." return 0 } if [ $SLAM ]; then _success "Slamming tomb $tombname mounted on $tombmount" _message "Kill all processes busy inside the tomb" if ! slam_tomb "$tombmount"; then _warning "Cannot slam the tomb $tombname" return 1 fi else say "Closing tomb $tombname mounted on $tombmount" fi # check if there are binded dirs and close them bind_tombs=(`list_tomb_binds $tombname`) for b in ${bind_tombs}; do bind_mapper="${b[(ws:;:)1]}" bind_mount="${b[(ws:;:)2]}" _message "closing tomb bind hook: $bind_mount" umount $bind_mount if [[ $? != 0 ]]; then if [ $SLAM ]; then _success "Slamming tomb: killing all processes using this hook" slam_tomb "$bind_mount" if [[ $? == 1 ]]; then _warning "Cannot slam the bind hook $bind_mount" return 1 fi umount $bind_mount else _warning "Tomb bind hook $bind_mount is busy, cannot close tomb." fi fi done # Execute post-hooks for eventual cleanup if ! option_is_set -n ; then exec_safe_post_hooks ${tombmount%%/} close fi xxx "performing umount of $tombmount" umount ${tombmount} if ! [ $? = 0 ]; then _warning "Tomb is busy, cannot umount!" else # this means we used a "default" mount point { test "${tombmount}" = "/media/${tombname}.tomb" } && { rmdir ${tombmount} } fi cryptsetup luksClose $mapper { test $? = 0 } || { _warning "error occurred in cryptsetup luksClose ${mapper}" return 1 } losetup -d "/dev/$tombloop" _success "Tomb $tombname closed: your bones will rest in peace." done # loop across mounted tombs return 0 } # Kill all processes using the tomb slam_tomb() { # $1 = tomb mount point if [[ -z `fuser -m "$1" 2> /dev/null` ]]; then return 0 fi #Note: shells are NOT killed by INT or TERM, but they are killed by HUP for s in TERM HUP KILL; do xxx "Sending $s to processes inside the tomb:" if option_is_set -D; then ps -fp `fuser -m /media/a.tomb 2> /dev/null`| while read line; do xxx $line done fi fuser -s -m "$1" -k -M -$s if [[ -z `fuser -m "$1" 2> /dev/null` ]]; then return 0 fi if ! option_is_set -f; then sleep 3 fi done return 1 } # }}} - Tomb close # {{{ Main routine main() { local -A subcommands_opts ### Options configuration # Hi, dear developer! Are you trying to add a new subcommand, or # to add some options? Well, keep in mind that an option CAN'T # have differente meanings/behaviour in different subcommands. # For example, "-s" means "size" and accept an argument. If you are tempted to add # an option "-s" (that means, for example "silent", and doesn't accept an argument) # DON'T DO IT! # There are two reasons for that: # I. usability; user expect that "-s" is "size" # II. Option parsing WILL EXPLODE if you do this kind of bad things # (it will say "option defined more than once, and he's right") # # If you want to use the same option in multiple commands then # you can only use the non-abbreviated long-option version like: # -force and NOT -f main_opts=(q -quiet=q D -debug=D h -help=h v -version=v U: -uid=U G: -gid=G T: -tty=T -no-color -unsecure-dev-mode) subcommands_opts[__default]="" subcommands_opts[open]="f n -nohook=n k: -key=k o: -mount-options=o -ignore-swap -sudo-pwd: -tomb-pwd:" subcommands_opts[mount]=${subcommands_opts[open]} subcommands_opts[create]="f s: -size=s -force k: -key=k -ignore-swap -kdf: -sudo-pwd: -tomb-pwd: -use-urandom" subcommands_opts[forge]="f -ignore-swap k: -key=k -kdf: -use-urandom" subcommands_opts[dig]="f -ignore-swap s: -size=s" subcommands_opts[lock]="f -force -ignore-swap s: -size=s k: -key=k -sudo-pwd: -tomb-pwd:" subcommands_opts[passwd]="f -ignore-swap -kdf: -tomb-old-pwd: -tomb-pwd: " subcommands_opts[close]="-sudo-pwd:" subcommands_opts[help]="" subcommands_opts[slam]="" subcommands_opts[list]="-get-mountpoint" subcommands_opts[index]="" subcommands_opts[search]="" subcommands_opts[help]="" subcommands_opts[bury]="" subcommands_opts[exhume]="" subcommands_opts[decompose]="" subcommands_opts[recompose]="" subcommands_opts[install]="" subcommands_opts[askpass]="" subcommands_opts[mktemp]="" subcommands_opts[source]="" subcommands_opts[resize]="s: -size=s k: -key=k" subcommands_opts[check]="-ignore-swap" # subcommands_opts[translate]="" ### Detect subcommand local -aU every_opts #every_opts behave like a set; that is, an array with unique elements for optspec in $subcommands_opts$main_opts; do for opt in ${=optspec}; do every_opts+=${opt} done done local -a oldstar oldstar=($argv) #### detect early: useful for --optiion-parsing zparseopts -M -D -Adiscardme ${every_opts} if [[ -n ${(k)discardme[--option-parsing]} ]]; then echo $1 if [[ -n "$1" ]]; then return 1 fi return 0 fi unset discardme if ! zparseopts -M -E -D -Adiscardme ${every_opts}; then error "error parsing" return 127 fi unset discardme subcommand=$1 if [[ -z $subcommand ]]; then subcommand="__default" fi if [[ -z ${(k)subcommands_opts[$subcommand]} ]]; then _warning "There's no such command \"$subcommand\"." _failure "Please try -h for help" 127 # die "Subcommand '$subcommand' doesn't exist" 127 fi argv=(${oldstar}) unset oldstar ### Parsing global + command-specific options # zsh magic: ${=string} will split to multiple arguments when spaces occur set -A cmd_opts ${main_opts} ${=subcommands_opts[$subcommand]} # if there is no option, we don't need parsing if [[ -n $cmd_opts ]]; then zparseopts -M -E -D -Aopts ${cmd_opts} if [[ $? != 0 ]]; then _warning "Some error occurred during option processing." die "See \"tomb help\" for more info" 127 fi fi #build PARAM (array of arguments) and check if there are unrecognized options ok=0 PARAM=() for arg in $*; do if [[ $arg == '--' || $arg == '-' ]]; then ok=1 continue #it shouldnt be appended to PARAM elif [[ $arg[1] == '-' ]]; then if [[ $ok == 0 ]]; then die "unrecognized option $arg for subcommand $subcommand" 127 fi fi PARAM+=$arg done #first parameter actually is the subcommand: delete it and shift if [[ $subcommand != '__default' ]]; then PARAM[1]=() shift fi ### End parsing command-specific options if ! option_is_set --no-color; then autoload colors; colors fi if ! option_is_set --unsecure-dev-mode; then for opt in --sudo-pwd --tomb-pwd --use-urandom --tomb-old-pwd; do if option_is_set $opt; then die "You specified option $opt, which is DANGEROUS and should only be used for testing\nIf you really want so, add --unsecure-dev-mode" 127 fi done fi # when we run as root, we remember the original uid:gid # to set permissions for the calling user and drop privileges if option_is_set -U; then _uid="`option_value -U`"; fi if option_is_set -G; then _gid="`option_value -G`"; fi if option_is_set -T; then _tty="`option_value -T`"; fi xxx "Tomb command: $subcommand ${PARAM}" xxx "caller uid[$_uid] gid[$_gid] tty[$_tty]" case "$subcommand" in # new creation in three steps forge) check_priv forge_key ${=PARAM} ;; dig) dig_tomb ${=PARAM} ;; lock) check_priv lock_tomb_with_key ${=PARAM} ;; # backward compat create) check_priv create_tomb ${=PARAM} ;; mount|open) check_priv mount_tomb $PARAM[1] $PARAM[2] ;; umount|close|slam) check_priv [ "$subcommand" = "slam" ] && SLAM=1 umount_tomb $PARAM[1] ;; passwd) change_passwd $PARAM[1] ;; list) list_tombs $PARAM[1] ;; index) index_tombs $PARAM[1] ;; search) search_tombs ${=PARAM} ;; help) usage ;; bury) if [ "$STEGHIDE" = 0 ]; then _warning "steghide not installed. Cannot bury your key" return 1 fi bury_key $PARAM[1] $PARAM[2] ;; exhume) if [ "$STEGHIDE" = 0 ]; then _warning "steghide not installed. Cannot exhume your key" return 1 fi exhume_key $PARAM[1] $PARAM[2] ;; resize) if [ "$RESIZER" = 0 ]; then _warning "resize2fs not installed. Cannot resize your tomb." return 1 fi check_priv resize_tomb $PARAM[1] ;; # internal commands useful to developers 'source') return 0 ;; install) check_priv ; install_tomb ;; askpass) ask_password $PARAM[1] $PARAM[2] ;; mktemp) safe_dir $PARAM[1] ;; translate) generate_translatable_strings ;; __default) cat < EOF option_is_set -v && { cat <