#!/bin/zsh # # Tomb, the Crypto Undertaker # # a tool to easily operate file encryption of private and secret data # # Copyleft (C) 2007-2011 Denis Roio # # 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. VERSION=0.9.2 DATE=Feb/2011 # PATH=/usr/bin:/usr/sbin:/bin:/sbin # standard output message routines # it's always useful to wrap them, in case we change behaviour later notice() { if ! [ $QUIET ]; then echo "[*] $1" >&2; fi } act() { if ! [ $QUIET ]; then echo " . $1" >&2; fi } error() { if ! [ $QUIET ]; then echo "[!] $1" >&2; fi } func() { if [ $DEBUG ]; then echo "[D] $1" >&2; fi } # which dd command to use which dcfldd > /dev/null if [ $? = 0 ]; then DD="dcfldd" else DD=dd fi # which wipe command to use which wipe > /dev/null if [ $? = 0 ]; then WIPE=(wipe -f -s -q) else WIPE=(rm -f) fi # usb auto detect using dmesg # tested on ubuntu 10.04 - please test and patch on other systems if you can # TODO: use udev rules, see how archlinux folks document it - arch rox 8) # https://wiki.archlinux.org/index.php/System_Encryption_with_LUKS_for_dm-crypt # here we could modularize the choice of methods using function pointers, # so that they are configurable when calling tomb. ask_usbkey() { notice "Waiting 1 minute for a usb key to connect" echo -n " . please insert your usb key " exec_as_user notify-send -i monmort \ -u normal -h string:App:Tomb \ -h double:Version:${VERSION} \ -t 60 \ "Insert your USB KEY" \ "Tomb is waiting 1 minute for you to insert an external key." plugged=false c=0 while [ "$plugged" != "true" ]; do dmesg | tail -n 12 | grep -q 'new.*USB device' if [ $? = 0 ]; then plugged=true; fi echo -n "." sleep .5 c=`expr $c + 1` if [ $c -gt 15 ]; then echo error "timeout." export usbkey_mount=none return 1; fi done echo echo -n " . usb key inserted, attaching " c=0 attached=false while [ "$attached" != "true" ]; do dmesg | tail -n 12| grep -q 'Attached.*removable disk' if [ $? = 0 ]; then attached=true; fi echo -n "." sleep 1 c=`expr $c + 1` if [ $c -gt 15 ]; then echo error "timeout." export usbkey_mount=none return 1; fi done echo echo -n " . usb attached, opening " # get the first partition # usbpart=`dmesg |tail -n 12 | grep ' sd.:' |cut -d: -f2 |tr -d ' '` for i in $(seq 1 10); do usbpart=$(dmesg | tail -n 12 | sed '/ sd.:/!d;s/^.*: \(sd.[0-9]*\)/\1/') if [ -n "$usbpart" ]; then break elif [ $i -eq 10 ]; then error "timeout." return 1 else echo -n . sleep 1 fi done # # wait that is mounted (it automount is on) # c=0 # mounted=false # while [ "$mounted" != "true" ]; do # cat /proc/mounts | tail -n 2 | grep -q $usbpart # if [ $? = 0 ]; then mounted=true; fi # echo -n "." # sleep .5 # c=`expr $c + 1` # if [ $c -gt 30 ]; then # echo # error "timeout." # export usbkey_mount=none # return 1; # fi # done # # check where it is mounted # usbmount=`cat /proc/mounts | awk -v p=$usbpart '{ if( $1 == "/dev/" p) print $2 }'` # sleep 1 # mount the first partition on the usb key # mtmp=`tempfile -p tomb` # rm -f $mtmp # mkdir -p $mtmp mtmp=$(/bin/mktemp -d --tmpdir tomb.XXXXXXXXXXXX) mount /dev/$usbpart $mtmp if [ $? = 0 ]; then usbmount=$mtmp else error "cannot mount usbkey partition $usbmount" return 1 fi echo act "usb key mounted on $usbmount" export usbkey_mount=$usbmount return 0 } # we use pinentry now # comes from gpg project and is much more secure # it also conveniently uses the right toolkit ask_password() { # pinentry has no custom icon setting # so we need to temporary modify the gtk theme if [ -r ~/.gtkrc-2.0 ]; then cp ~/.gtkrc-2.0 ~/.gtkrc-2.0.tomb.bak fi cat <> ~/.gtkrc-2.0 pixmap_path "/usr/local/share/pixmaps" style "normal" { stock["gtk-dialog-authentication"] = {{"monmort.xpm"}} } widget "*" style "normal" EOF cat < /dev/null if [ $? = 0 ]; then sudo -u $SUDO_USER "${@[@]}" return $? else error "Tomb requires sudo. please install it." return 1 fi } # escalate privileges check_priv() { id | grep root > /dev/null if [ $? != 0 ]; then which gksu > /dev/null if [ $? = 0 ]; then func "Using gksu for root execution of 'tomb ${(f)ARGS}'" gksudo "tomb ${ARGS[@]}" exit $? fi which sudo > /dev/null if [ $? = 0 ]; then func "Using sudo for root execution of 'tomb ${(f)ARGS}'" # check if sudo has a timestamp active sudok=false sudo -n tomb 2> /dev/null if [ $? != 0 ]; then # if not then ask a password cat <&1 > /dev/null if [ $? != 0 ]; then notice "Tomb - simple commandline tool for encrypted storage" act "version $VERSION ($DATE) by Jaromil @ dyne.org" fi echo $@ | grep '\-D' 2>&1 > /dev/null if [ $? = 0 ]; then echo "[D] invoked with args \"${(f)@}\" " echo "[D] running on `date`" fi ARGS=$@[@] OPTS=`getopt -o hvqDs:k: -n 'tomb' -- "$@"` while true; do case "$1" in -h) act "" notice "Syntax: tomb [options] command [file] [mountpoint]" act "" notice "Commands:" act "create create a new encrypted storage FILE and keys" act "open open an existing tomb FILE on MOUNTPOINT" act "close closes the tomb on MOUNTPOINT" act "bury hide a tomb key inside a jpeg image" act "exhume extract an hidden tomb key from a jpeg image" act "" notice "Options:" act "-s size of the storage file when creating one (MB)" act "-k path to the key to use for decryption" act "" act "-h print this help" act "-v version information for this tool" act "-q run quietly without printing informations" act "-D print debugging information at runtime" echo; exit 2 ;; -v) # print out the GPL license in this file act "" cat $0 | awk ' BEGIN { license=0 } /^# This source/ { license=1 } { if(license==1) print " " $0 } /MA 02139, USA.$/ { license=0 } ' act "" exit 0 ;; -q) QUIET=1; shift 1 ;; -D) DEBUG=1; shift 1 ;; -s) SIZE=$2; shift 2 ;; -k) KEY=$2; shift 2 ;; --) shift; break ;; *) CMD=$1; FILE=$2; MOUNT=$3; # compat with old args CMD2=${2}; CMD3=${3}; break ;; esac done if ! [ $CMD ]; then error "first argument missing, use -h for help" tomb-notify exit 0 fi func "Tomb called: $CMD $CMD2 $CMD3" create_tomb() { # make sure the file has a .tomb extension FILE="${FILE%\.*}.tomb" if [ -e "$FILE" ]; then error "$FILE exists already. I'm not digging here." return 1 fi notice "Creating a new tomb" if [ -z $SIZE ]; then if [ $MOUNT ]; then SIZE=$MOUNT else act "No size specified, summoning the Tomb Undertaker to guide us in the creation." tomb-open &! return 0 fi fi SIZE_4k=`expr $SIZE \* 1000 / 4` act "Generating ${FILE} of ${SIZE}Mb (${SIZE_4k} blocks of 4Kb)" $DD if=/dev/urandom bs=4k count=${SIZE_4k} of=${FILE} if [ $? = 0 -a -e ${FILE} ]; then act "OK: `ls -lh ${FILE}`" else error "Error creating the tomb ${FILE}, operation aborted." exit 1 fi modprobe dm-crypt modprobe aes-i586 nstloop=`losetup -f` # get the number for next loopback device losetup -f ${FILE} # allocates the next loopback for our file # create the keyfile in tmpfs so that we leave less traces in RAM keytmp=`tempfile -p tomb` rm -f $keytmp mkdir -p $keytmp mount tmpfs ${keytmp} -t tmpfs -o size=1m if [ $? != 0 ]; then error "cannot mount tmpfs filesystem in volatile memory" error "operation aborted." losetup -d $nstloop rm -r $keytmp exit 1 fi act "Generating secret key..." act "this operation takes time, keep using this computer on other tasks," act "once done you will be asked to choose a password for your tomb." touch ${keytmp}/tomb.tmp chmod 0600 ${keytmp}/tomb.tmp $DD bs=1 count=256 if=/dev/random of=${keytmp}/tomb.tmp if ! [ -r ${keytmp}/tomb.tmp ]; then error "cannot generate encryption key, operation aborted." umount ${keytmp} losetup -d $nstloop rm -r $keytmp exit 1 fi notice "Setup your secret key file ${FILE}.gpg" tomb-notify "The Tomb key is being forged:" "please set your password." # 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 tomb -q askpass ${FILE}` tombpasstmp=$tombpass tombpass=`exec_as_user tomb -q askpass "${FILE} (again)"` if [ "$tombpasstmp" = "$tombpass" ]; then break; fi unset tombpasstmp unset tombpass done if [ -z $tombpass ]; then error "passwords don't match, aborting operation" umount ${keytmp} losetup -d $nstloop rm -r $keytmp exit 1 fi echo "${tombpass}" | gpg --batch --no-options --no-tty --passphrase-fd 0 \ -o "${FILE}.gpg" -c -a ${keytmp}/tomb.tmp if [ $? = 2 ]; then error "setting password failed: gnupg returns 2" umount ${keytmp} losetup -d $nstloop rm -r $keytmp exit 1 fi act "formatting Luks mapped device" # we use aes-cbc-essiv with sha256 # for security, performance and compatibility cryptsetup --batch-mode \ --cipher aes-cbc-essiv:sha256 --key-size 256 \ luksFormat ${nstloop} ${keytmp}/tomb.tmp if ! [ $? = 0 ]; then act "operation aborted." exit 0 fi cryptsetup --key-file ${keytmp}/tomb.tmp --cipher aes luksOpen ${nstloop} tomb.tmp ${WIPE[@]} ${keytmp}/tomb.tmp umount ${keytmp} rm -r ${keytmp} # cryptsetup luksDump ${nstloop} act "formatting your Tomb with Ext4 filesystem" mkfs.ext4 -q -F -j -L "${FILE%%.*}" /dev/mapper/tomb.tmp if [ $? = 0 ]; then act "OK, encrypted storage succesfully formatted" else act "error formatting Tomb" fi sync cryptsetup luksClose tomb.tmp losetup -d ${nstloop} notice "done creating $FILE encrypted storage (using Luks dm-crypt AES/SHA256)" tomb-notify "The Tomb is ready!" "We will now open your new Tomb for the first time." notice "Your tomb is ready on ${FILE} and secured with key ${FILE}.gpg" act "Would you like to save the key on an external usb device?" act "This is recommended for safety:" act "Always keep the key in a different place than the door!" act "If you answer yes, you'll need a USB KEY now: (y/n)" tomb-notify "Tomb has forged a key." "Would you like to save it on USB?" echo -n " > " read -q if [ $? = 0 ]; then ask_usbkey if ! [ -e ${usbkey_mount} ]; then error "cannot save the key in a separate place, move it yourself later." else mkdir -m 0700 -p ${usbkey_mount}/.tomb cp -v ${FILE}.gpg ${usbkey_mount}/.tomb/ chmod -R go-rwx ${usbkey_mount}/.tomb umount ${usbkey_mount} unset usbkey_mount notice "Key ${FILE}.gpg succesfully saved on your USB" act "now we proceed opening your new tomb" KEY=${FILE}.gpg CMD2=${FILE} CMD3=/media/${FILE} mount_tomb ${FILE} ${WIPE[@]} ${FILE}.gpg fi else # kept besides (deprecated behaviour) act "now we proceed opening your new tomb" KEY=${FILE}.gpg unset CMD2 unset CMD3 mount_tomb ${FILE} fi } mount_tomb() { if ! [ $CMD2 ]; then error "need an argument, operation aborted." return 1 elif [ -r $CMD2 ]; then tombfile=`basename $CMD2` else # try also adding a .tomb extension tombfile=${tombfile%%\.*}.tomb if ! [ -r $tombfile ]; then error "cannot find a tomb named $CMD2" return 1 fi fi tombdir=`dirname $CMD2` file ${tombdir}/${tombfile} | grep -i 'luks encrypted.*cbc-essiv' 2>&1 >/dev/null if [ $? != 0 ]; then error "$CMD2 is not a valid tomb file, operation aborted" tomb-notify "Not a tomb." "$CMD2 doesn't seems a real tomb." return 1 fi tombname=${tombfile%%\.*} act "mounting tomb named $tombname" if [ $KEY ]; then tombkey="`basename $KEY`" tombkeypath="$KEY" act "tomb key specified manually, using: $tombkeypath" else tombkey=${tombfile}.gpg if [ -r $tombkey ]; then tombkeypath=$tombkey elif [ -r "$tombdir/$tombkey" ]; then tombkeypath="$tombdir/$tombkey" else error "encryption key ${enc_key} not found on disk" error "use -k option to specify which key to use" error "provide a usb key now, or press ctrl-c to abort" notice "please insert your USB KEY" ask_usbkey # returns usbkey_mount, now check if the key is there if [ -r ${usbkey_mount}/.tomb/${tombkey} ]; then tombkeypath=${usbkey_mount}/.tomb/${tombkey} notice "key found on ${tombkeypath}" else error "key is missing, try to locate $tombkey in your files." error "operation aborted" return 1 fi fi fi if ! [ $CMD3 ]; then tombmount=/media/${tombfile} act "mountpoint not specified, using default: $tombmount" elif ! [ -x $CMD3 ]; then error "mountpoint $CMD3 doesn't exist, operation aborted." if [ -n "$usbkey_mount" ]; then umount $usbkey_mount rmdir $usbkey_mount unset usbkey_mount fi return 1 else tombmount=$CMD3 fi notice "mounting $tombfile on mountpoint $tombmount" # we need root from here on local norm=$(test -d $tombmount) $norm || mkdir -p $tombmount nstloop=`losetup -f` losetup -f ${tombdir}/${tombfile} act "check for a valid LUKS encrypted device" cryptsetup isLuks ${nstloop} if [ $? != 0 ]; then # is it a LUKS encrypted nest? see cryptsetup(1) error "$tombfile is not a valid Luks encrypted storage file" $norm || rmdir $tombmount 2>/dev/null return 1 fi modprobe dm-crypt modprobe aes-i586 # save date of mount in minutes since 1970 mapdate="`date +%s`" mapdate="`echo ${mapdate}/60 | bc -l | cut -d. -f1`" mapper="tomb.${tombname}.${mapdate}.`basename $nstloop`" notice "Password is required for key ${tombkey}" keyname=`basename $tombkey | cut -d. -f1` for c in 1 2 3; do if [ $c = 1 ]; then tombpass=`exec_as_user tomb -q askpass ${keyname}` else tombpass=`exec_as_user tomb -q askpass "$keyname (retry $c)"` fi echo "${tombpass}" \ | gpg --batch --passphrase-fd 0 --no-tty --no-options \ -d "${tombkeypath}" \ | cryptsetup --key-file - luksOpen ${nstloop} ${mapper} unset tombpass if [ -r /dev/mapper/${mapper} ]; then break; # password was correct fi done if [ -r ${usbkey_mount}/.tomb/${tombkey} ]; then umount ${usbkey_mount} rmdir ${usbkey_mount} unset usbkey_mount fi if ! [ -r /dev/mapper/${mapper} ]; then error "failure mounting the encrypted file" losetup -d ${nstloop} $norm || rmdir ${tombmount} 2>/dev/null return 1 fi act "encrypted storage filesystem check" fsck -p -C0 /dev/mapper/${mapper} act "tomb engraved as $tombname" tune2fs -L ${tombname} /dev/mapper/${mapper} > /dev/null mount -o rw,noatime,nodev /dev/mapper/${mapper} ${tombmount} # Ensure the user can write the disk - 10x Hellekin :) ME=${SUDO_USER:-$(whoami)} chmod 0750 ${tombmount} chown $(id -u $ME):$(id -g $ME) ${tombmount} notice "encrypted storage $tombfile succesfully mounted on $tombmount" # exec_bind_hooks ${tombmount} exec_safe_bind_hooks ${tombmount} exec_post_hooks ${tombmount} open return 0 } encode_key() { tombkey=$CMD2 imagefile=$CMD3 file $tombkey | grep PGP > /dev/null if [ $? != 0 ]; then error "encode failed: $tombkey is not a tomb key" return 1 fi file $imagefile | grep JPEG > /dev/null if [ $? != 0 ]; then error "encode failed: $imagefile is not a jpeg image" return 1 fi notice "Encoding key $tombkey inside image $imagefile" act "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 tomb -q askpass ${FILE}` tombpasstmp=$tombpass tombpass=`exec_as_user tomb -q askpass "${FILE} (again)"` if [ "$tombpasstmp" = "$tombpass" ]; then break; fi unset tombpasstmp unset tombpass done if [ -z $tombpass ]; then error "passwords don't match, aborting operation." return 1 fi awk ' /^-----/ {next} /^Version/ {next} /^Comment/ {next} {print $0}' ${tombkey} \ | steghide embed --embedfile - --coverfile ${imagefile} \ -p ${tombpass} -z 9 -e serpent cbc if [ $? != 0 ]; then error "encoding error: steghide reports problems" res=1 else notice "tomb key encoded succesfully into image ${imagefile}" res=0 fi unset tombpass return $res } decode_key() { tombname=$CMD2 imagefile=$CMD3 res=1 file $imagefile | grep JPEG > /dev/null if [ $? != 0 ]; then error "encode failed: $imagefile is not a jpeg image" return 1 fi tombfile=${tombname%%\.*}.tomb.gpg notice "Decoding a key out of image $imagefile" for c in 1 2 3; do if [ $c = 1 ]; then tombpass=`exec_as_user tomb -q askpass ${keyname}` else tombpass=`exec_as_user tomb -q askpass "$keyname (retry $c)"` fi steghide extract -sf ${imagefile} -p ${tombpass} -xf - \ | awk ' BEGIN { print "-----BEGIN PGP MESSAGE-----" print "Version: GnuPG v1.4.10 (GNU/Linux)" } { print $0 } END { print "-----END PGP MESSAGE-----" }' > ${tombfile} if [ "`cat ${tombfile} | wc -l`" != "3" ]; then act "${tombfile} succesfully decoded" res=0 break; fi done unset tombpass if [ $res != 0 ]; then error "nothing found." fi return $res } exec_bind_hooks() { mnt=$1 # first argument is where the tomb is mounted if ! [ -r ${mnt}/bind-hooks ]; then return; fi # if 'bind-hooks' is found inside the tomb, parse it # every line contains two strings: # the first is a directory existing inside the tomb # the second is the place where it should be mounted (-o bind) hook=`cat ${mnt}/bind-hooks | awk ' /^#/ { next } { if($1 && $2) print "mount -o bind \${mnt}/" $1 " " $2 "; " } '` # restore $HOME for the calling user HOME=/home/${SUDO_USER} act "bind hooks found, mounting directories as requested" # execute the mount commands eval $hook } # FIXME: this should sanitize pathes! exec_safe_bind_hooks() { local MOUNTPOINT="${1}" local ME=${SUDO_USER:-$(whoami)} local HOME=$(grep $ME /etc/passwd | sed "s/^${ME}:.*:.*:.*:.*:\([\/a-z]*\):.*$/\1/" 2>/dev/null) if [ $? -ne 0 ]; then error "how pitiful! A tomb, and no HOME" return 1 fi if [ -z "$MOUNTPOINT" -o ! -d "$MOUNTPOINT" ]; then error "cannot exec bind hooks without a mounted tomb." return 1 fi if [ ! -r "$MOUNTPOINT/bind-hooks" ]; then func "cannot read bind-hooks." return fi typeset -al created typeset -al mounted typeset -Al maps maps=($(<"$MOUNTPOINT/bind-hooks")) for dir in ${(k)maps}; do if [ "${dir[1]}" = "/" -o "${dir[1,2]}" = ".." ]; then error "bind-hooks map format: local/to/tomb local/to/\$HOME" continue fi if [ "${${maps[$dir]}[1]}" = "/" -o "${${maps[$dir]}[1,2]}" = ".." ]; then error "bind-hooks map format: local/to/tomb local/to/\$HOME. Rolling back" for dir in ${mounted}; do umount $dir; done for dir in ${created}; do rmdir $dir; done return 1 fi if [ ! -d "$HOME/${maps[$dir]}" ]; then notice "creating $HOME/${maps[$dir]}" mkdir -p $HOME/${maps[$dir]} created+=("$HOME/${maps[$dir]}") fi mount --bind $MOUNTPOINT/$dir $HOME/${maps[$dir]} mounted+=("$HOME/${maps[$dir]}") done } exec_post_hooks() { mnt=$1 # first argument is where the tomb is mounted 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 act "post hooks found, executing as user $SUDO_USER" exec_as_user ${mnt}/post-hooks $2 fi } get_arg_tomb() { # set up variables to be used by caller: # tombfile - filename without path # tombdir - directory where the tomb is # tombname - name of the tomb (filename without extension) # the full path is made with $tombdir/$tombfile if [ -z $1 ]; then error "internal: get_arg_tomb called without argument" return 1 fi # make sure there is a .tomb extension arg=${1%%\.*}.tomb if ! [ -r ${arg} ]; then error "file not found: $arg" return 1 fi tombfile=`basename $arg` tombdir=`dirname $arg` file ${tombdir}/${tombfile} | grep -i 'luks encrypted file' 2>&1 >/dev/null if [ $? != 0 ]; then error "$arg is not a valid tomb file, operation aborted" tomb-notify "Not a tomb." "$arg doesn't seems a real tomb." return 1 fi tombname=${tombfile%%\.*} return 0 } backup_tomb() { # FIXME - duplicity asks passwords too often # using duplicity which duplicity > /dev/null if [ $? != 0 ]; then error "duplicity not found, can't operate backup" return 1 fi if [ -z $CMD3 ]; then error "backup command needs 2 arguments: tomb and destination url" error "please refer to tomb(1) and duplicity(1) manuals for more information" return 1 fi # is it a tomb? get_arg_tomb ${CMD2} if [ $? != 0 ]; then error "there is no tomb to backup, operation aborted." return 1 fi # is it a url? echo "${CMD3}" | grep -i -e '^.*:\/\/.*' 2>&1 > /dev/null if ! [ $? = 0 ]; then error "second argument is not a valid duplicity url." error "read the tomb(1) and duplicity(1) manual for more information" return 1 fi bckurl=${CMD3} # is it ssh? protocol="`expr substr $bckurl 1 3`" act "backup over protocol $protocol" if [ "$protocol" = "ssh" ]; then act "ssh connection requires a password" FTP_PASSWORD="`exec_as_user tomb askpass $bckurl`" dupopts="--ssh-askpass" # TODO verify ssh access before duplicity does # since it blocks the thing retrying 5 times and such crap # i.e. try ssh true to sshurl="`echo $bckurl | sed -e 's/ssh:\/\///'`" # --no-print-statistics fi # duplicity works only on directories # so we create a directory in tmpfs and bind the tomb inside it # during backup the encrypted tomb will be exposed # TODO: check that the tomb is not mounted and, if mounted # remount it read-only so it doesn't gets modified during bck bckname=${tombname}.bck mkdir -p /dev/shm/${bckname} if [ $? != 0 ]; then error "cannot generate a temporary backup directory in /dev/shm, operation aborted." return 1 fi bcktmpdir=/dev/shm/${bckname} # mmm, maybe we should mount our own tmpfs? we need root anyway for mount -o bind # if we reach to eliminate this mount trick and upload only one file with duplicity # then this function doesn't needs to be root to work. touch ${bcktmpdir}/${tombfile} mount -o bind ${tombdir}/${tombfile} ${bcktmpdir}/${tombfile} bcklast=`exec_as_user duplicity \ ${(s: :)dupopts} \ collection-status ${bckurl} \ | awk '/^Last full backup date:/ { print $5 }'` # we detect if backup already exists or not so we can handle # password prompt (choosing a password for full, inserting for incr) if [ "$bcklast" = "none" ]; then notice "Creating a backup of tomb $tombname on url $bckurl" exec_as_user FTP_PASSWORD="$FTP_PASSWORD" duplicity ${(s: :)dupopts} \ full ${bcktmpdir} ${bckurl} else notice "Updating a backup of tomb $tombname on url $bckurl" exec_as_user FTP_PASSWORD="$FTP_PASSWORD" duplicity ${(s: :)dupopts} \ incr ${bcktmpdir} ${bckurl} fi unset FTP_PASSWORD if [ $? != 0 ]; then error "duplicity reported error, operation aborted" umount ${bcktmpdir}/${tombfile} return 1 fi notice "Operation successful." umount ${bcktmpdir}/${tombfile} return 0 } umount_tomb() { if ! [ $1 ]; then how_many_tombs="`find /dev/mapper -name 'tomb.*' | wc -w`" if [ "$how_many_tombs" = "0" ]; then error "there is no open tomb to be closed" return 1 elif [ "$how_many_tombs" = "1" ]; then mapper=`find /dev/mapper -name 'tomb.*'` else error "too many tombs mounted, please specify which to unmount:" ls /dev/mapper/tomb.* error "or issue the command 'tomb close all' to clos'em all." return 1 fi fi if [ "$1" = "all" ]; then tombs=`find /dev/mapper -name 'tomb.*'` if ! [ $tombs ]; then error "Tombs are all closed, cemetery is quiet." return 1 fi for t in ${(f)tombs}; do umount_tomb ${t} done return 0 fi if [ -r "$1" ]; then # accepts relative and absolute path mapper="$1" elif [ -r /dev/mapper/${1} ]; then mapper=/dev/mapper/${1} else error "tomb not found: $1" error "please specify an existing /dev/mapper/tomb.*" tomb-notify "Tomb was already closed." "Undertaker will rest in peace." return 0 fi basemap=`basename $mapper` tombname=`echo ${basemap} | cut -d. -f2` tombmount=`mount | grep $mapper | awk '{print $3}'` # check if there are binded dirs and close them first mount | grep "${tombmount}" 2>/dev/null | grep -v loop 2>&1 > /dev/null if [ $? = 0 ]; then act "closing bind hooks for tomb $tombname " unbind=`mount | grep ${tombmount} | grep -v loop | awk ' { print "umount " $3 "; " } '` eval $unbind func "umount binded dirs:" func "$unbind" fi # Execute post-hooks for eventual cleanup exec_post_hooks ${tombmount} close act "closing tomb $tombname on dm-crypt $basemap" mount | grep $mapper 2>&1 >/dev/null if [ $? = 0 ]; then # still mounted umount ${mapper} if ! [ $? = 0 ]; then tomb-notify "Tomb '$tombname' is too busy." \ "Close all applications and file managers, then try again." return 1 fi fi cryptsetup luksClose $basemap if ! [ $? = 0 ]; then error "error occurred in cryptsetup luksClose ${basemap}" return 1 fi losetup -d "/dev/`echo $basemap | cut -d. -f4`" notice "crypt storage ${mapper} unmounted" tomb-notify "Tomb closed: $tombname" "Your bones will Rest In Peace." return 0 } # install mime-types, bells and whistles for the desktop # see http://developers.sun.com/solaris/articles/integrating_gnome.html # and freedesktop specs install_tomb() { # TODO: distro package deps (for binary) # debian: zsh, cryptsetup, libgtk2.0-0, libnotify-bin act "updating mimetypes..." cat < /tmp/dyne-tomb.xml Tomb encrypted volume Tomb crypto key EOF xdg-mime install /tmp/dyne-tomb.xml xdg-icon-resource install --context mimetypes --size 32 monmort.xpm monmort xdg-icon-resource install --size 32 monmort.xpm dyne-monmort rm /tmp/dyne-tomb.xml act "updating desktop..." cat < /usr/share/applications/tomb.desktop [Desktop Entry] Version=1.0 Type=Application Name=Tomb crypto undertaker GenericName=Crypto undertaker Comment=Keep your bones safe Exec=tomb-open %U TryExec=tomb-open Icon=monmort.xpm Terminal=false Categories=Utility;Security;Archiving;Filesystem; MimeType=application/x-tomb-volume; X-AppInstall-Package=tomb EOF update-desktop-database act "updating menus..." cat < /etc/menu/tomb ?package(tomb):command="tomb" icon="/usr/share/pixmaps/monmort.xpm" needs="cryptsetup" \ section="Applications/Accessories" title="Tomb" hints="Crypto" \ hotkey="Tomb" EOF update-menus act "updating mime info..." cat < /usr/share/mime-info/tomb.keys # actions for encrypted tomb storage application/x-tomb-volume: open=tomb-open %f view=tomb-open %f icon-filename=monmort.xpm short_list_application_ids_for_novice_user_level=tomb EOF cat < /usr/share/mime-info/tomb.mime # mime type for encrypted tomb storage application/x-tomb-volume ext: tomb application/x-tomb-key ext: tomb.gpg EOF cat < /usr/lib/mime/packages/tomb application/x-tomb-volume; tomb-open '%s'; priority=8 EOF update-mime act "updating application entry..." cat < /usr/share/application-registry/tomb.applications tomb command=tomb-open name=Tomb - Crypto Undertaker can_open_multiple_files=false expects_uris=false requires_terminal=false mime-types=application/x-tomb-volume,application/x-tomb-key EOF act "Tomb is now installed." } case "$CMD" in create) check_priv ; create_tomb ;; mount) check_priv ; mount_tomb ;; open) check_priv ; mount_tomb ;; umount) check_priv ; umount_tomb ${CMD2} ;; unmount) check_priv ; umount_tomb ${CMD2} ;; close) check_priv ; umount_tomb ${CMD2} ;; bury) encode_key ${CMD2} ${CMD3} ;; exhume) decode_key ${CMD2} ;; backup) check_priv ; backup_tomb ${CMD2} ${CMD3} ;; install) check_priv ; install_tomb ;; askpass) ask_password $CMD2 $CMD3 ;; status) tomb-status ;; notify) tomb-notify $CMD2 $CMD3 ;; *) error "command \"$CMD\" not recognized" act "try -h for help" return 1 ;; esac # return codes from called functions # return $?