#!/bin/zsh # # Tomb, the Crypto Undertaker # # a tool to easily operate file encryption of private and secret data # # {{{ Copyleft (C) 2007-2013 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. # }}} # {{{ GLOBAL VARIABLES VERSION=1.3 DATE="Apr/2013" TOMBEXEC=$0 TOMBOPENEXEC="${TOMBEXEC}-open" typeset -a OLDARGS for arg in ${argv}; do OLDARGS+=($arg); done STEGHIDE=1 MKTEMP=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" # }}} # {{{ HELPER FUNCTIONS # {{{ OPTION PARSING # {{{ - Check an option option_is_set() { #First argument, the option (something like "-s") #Second (optional) argument: if it's "out", command will print it out 'set'/'unset' # This is 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 option (something like "-s") <<< ${opts[$1]} } # }}} # }}} # {{{ - Standard output message routines 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 } 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 BINARY DEPENDENCIES check_bin() { # check for required programs for req in pinentry sudo gpg; do which $req >/dev/null || die "Cannot find $req. Please install it." 1 done export PATH=/sbin:/usr/sbin:$PATH which cryptsetup > /dev/null && CRYPTSETUP=cryptsetup || die "Cryptsetup not found in $PATH." 1 # which dd command to use which dcfldd > /dev/null && DD=dcfldd || DD=dd # which wipe command to use which wipe > /dev/null && WIPE="wipe -f -s" || WIPE="rm -f" # check for filesystem creation progs which mkfs.ext4 > /dev/null && \ MKFS="mkfs.ext4 -q -F -j -L" || \ MKFS="mkfs.ext3 -q -F -j -L" # check for mktemp which mktemp > /dev/null || MKTEMP=0 # check for steghide which steghide > /dev/null || STEGHIDE=0 # resize suite check bin! which e2fsck > /dev/null || die "Cannot find e2fsck. Please install it." 1 which resize2fs > /dev/null || die "Cannot find resize2fs. Please install it." 1 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 } # }}} # {{{ - "SAFE" FUNCTIONS # {{{ - Create a directory with caution _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 } 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 # 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 /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 # sudo -n ${TOMBEXEC} &> /dev/null 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 return 0 } # }}} check_command() { #generic checks; useful for interaction, to check if there are problems #before wasting user's time if ! option_is_set --ignore-swap && ! option_is_set -f; then if ! check_swap; then error "Swap activated. Disable it with swapoff, or use --ignore-swap" exit 1 fi fi } usage() { cat <. EOF } # }}} # {{{ - I18N FUNCTIONS generate_translatable_strings() { cat <, 2013. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: Tomb $VERSION\n" "PO-Revision-Date: `date`\n" "Last-Translator: Denis Roio \n" "Language-Team: Tomb developers \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" # #: commandline help # msgid "" EOF usage | awk ' { print "\"" $0 "\"" }' cat < /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 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 } # }}} # {{{ - Decode Key decode_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 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 } list_gnupg_ciphers() { # prints an array of ciphers available in gnupg (to encrypt keys) # 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 } # }}} # }}} # {{{ - HOOK HELPERS # {{{ - Execute Bind Hooks 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 SUB-COMMANDS # 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 if [[ $DD = "dcfldd" ]]; then $DD bs=1 count=256 if=$random_source of=${keytmp}/tomb.tmp statusinterval=1 else $DD bs=1 count=256 if=$random_source of=${keytmp}/tomb.tmp fi 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, virtuosism by hellekin # [[ `file =(awk '/^-+BEGIN/,0' $1) -bi` =~ application/pgp ]] 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 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" 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}" tombsize_4k=`expr $tombsize \* 1024 / 4` _message "Generating ${tombfile} of ${tombsize}Mb (${tombsize_4k} blocks of 4Kb)" # 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}" $DD if=/dev/urandom bs=4k count=${tombsize_4k} 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 if ! [ -e ${tombdir}/${tombfile} ]; then die "There is no tomb here. You have to it dig first." return 1 fi 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 # check if the key is set manually then use the one existing if option_is_set -k ; then if [[ "`option_value -k`" == "-" ]]; then # take key from stdin local tombkeydir 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 to lock the tomb:" _message " `ls -lh ${tombkey}`" else losetup -d ${nstloop} die "No key found. Use the option -k to specify a key file." fi # this does a check on the file header, virtuosism by hellekin # [[ `file =(awk '/^-+BEGIN/,0' $1) -bi` =~ application/pgp ]] if ! is_valid_key ${tombkey}; then _warning "The key seems invalid, the application/pgp header is missing" losetup -d ${nstloop} die "Operation aborted." fi # 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" keyname=`basename $tombkey | cut -d. -f1` _message "a password is required to use key ${keyname}" local passok=0 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 losetup -d ${nstloop} die "User aborted" fi get_lukskey "${tombpass}" ${tombkey} >/dev/null if [ $? = 0 ]; then passok=1; _message "Password OK." break; fi done fi if [ "$passok" = "0" ]; then _warning "Password incorrect" losetup -d $nstloop die "Operation aborted." fi _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 unset tombpass _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}* } #internal use #$1 is the keyfile we are checking is_valid_key() { [[ `file =(awk '/^-+BEGIN/,0' $1) -bi` =~ application/pgp ]] return $? } #internal use #$1 is the password, $2 is the keyfile #will output the lukskey get_lukskey() { local tombpass=$1 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 print ${tombpass} | \ gpg --batch --passphrase-fd 0 --no-tty --no-options --status-fd 2 \ -d "${keyfile}" 2> /dev/null ret=$? xxx "gpg decryption returns $ret" unset tombpass return $ret } # internal use # $1 the lukskey to encrypt # honored options: --kdf --tomb-pwd gen_key() { 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 gpg --openpgp --batch --no-options --no-tty --passphrase-fd 0 2>/dev/null \ -o - -c -a ${lukskey} <<< "${tombpass}" unset tombpass } # }}} # {{{ - 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}" if option_is_set -k ; then if [[ "`option_value -k`" == "-" ]]; then # take key from stdin local tombkeydir 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}/${tombfile}.key fi if ! [ -r ${tombkey} ]; then _warning "key file not found: ${tombkey}" _warning "operation aborted." return 1 fi if ! [ $2 ]; then tombmount=/media/${tombfile} _message "mountpoint not specified, using default: $tombmount" elif ! [ -x $2 ]; then _warning "mountpoint $2 doesn't exist, operation aborted." return 1 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 on $tombmount" _message "here below its status is reported:" list_tombs ${tombname} return 1 fi _success "Opening $tombfile on $tombmount" # we need root from here on mkdir -p $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" $norm || rmdir $tombmount 2>/dev/null 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` _warning "Password is required for key ${keyname}" for c in 1 2 3; do if ! option_is_set --tomb-pwd; then tombpass=`exec_as_user ${TOMBEXEC} askpass "Open tomb ${keyname}"` if [[ $? != 0 ]]; then die "User aborted" fi else tombpass=`option_value --tomb-pwd` fi get_lukskey "${tombpass}" ${tombkey} | \ cryptsetup --key-file - luksOpen ${nstloop} ${mapper} local ret=$? unset tombpass if [[ $ret != 0 ]]; then if [[ $c = 3 ]] || option_is_set --tomb-pwd; then die "Wrong password: aborting" fi continue fi # if key was from stdin delete temp file and dir if [ $tombkeydir ]; then ${=WIPE} ${tombkey} rmdir $tombkeydir fi if [ -r /dev/mapper/${mapper} ]; then break; # password was correct fi done if ! [ -r /dev/mapper/${mapper} ]; then losetup -d ${nstloop} $norm || rmdir ${tombmount} 2>/dev/null 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 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 } # }}} # {{{ - Close # {{{ - Slam the door # 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 } # }}} # {{{ - Unmount Tomb umount_tomb() { local tombs how_many_tombs local pathmap mapper tombname tombmount loopdev local ans pidk pname if ! [ $1 ]; then tombs=`find /dev/mapper -name 'tomb.*'` how_many_tombs=`wc -w <<< "$tombs"` if [[ "$how_many_tombs" == "0" ]]; then _warning "There is no open tomb to be closed" return 1 elif [[ "$how_many_tombs" == "1" ]]; then #mapper=`find /dev/mapper -name 'tomb.*'` xxx "closing mapper $tombs" umount_tomb ${tombs} return 1 else _warning "Too many tombs mounted, please specify which to unmount:" ls /dev/mapper/tomb.* _warning "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 _success "Tombs are all closed, cemetery is quiet." return 0 fi for t in ${(f)tombs}; do umount_tomb ${t} done return 0 fi # tomb close argument deduction pathmap=`dirname "$1"` if [ "${pathmap}" = "/dev/mapper" ]; then mapper="$1" # argument is the mapper (or none which autofills mapper) tombname="${mapper[(ws:.:)2]}" tombmount=`mount -l | \ awk -vtomb="[$tombname]" '/^\/dev\/mapper\/tomb/ { if($7==tomb) print $3 } '` elif [ "$pathmap" = "." ]; then tombname="$1" # argument is the name mapper=`mount -l | \ awk -vtomb="[$tombname]" '/^\/dev\/mapper\/tomb/ { if($7==tomb) print $1 } '` tombmount=`mount -l | \ awk -vtomb="[$tombname]" '/^\/dev\/mapper\/tomb/ { if($7==tomb) print $3 } '` else tombmount="$1" # argument should be the mount mapper=`mount | awk -vmnt="$tombmount" '/^\/dev\/mapper\/tomb/ { if($3==mnt) print $1 }'` tombname="${mapper[(ws:.:)2]}" fi # avoid block when the same tomb is mounted, take only the first for tm in ${(f)tombmount}; do tombmount=${tm}; break; done xxx "tomb close argument: $1" xxx "name: $tombname" xxx "mount: $tombmount" xxx "mapper: $mapper" if ! [ -e "$mapper" ]; then _warning "Tomb not found: $1" _warning "Please specify an existing tomb." return 0 fi 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 _success "Closing tomb $tombname mounted on $tombmount" fi # check if there are binded dirs and close them tombmount_esc=`sed 's:\/:\\\/:g' <<< $tombmount ` unbind=`mount | awk "/^$tombmount_esc.*bind/"' { print $3 }'` for b in ${(f)unbind}; do hook="`basename $b`" _message "closing tomb hook: $hook" umount $b if [[ $? != 0 ]]; then if [ $SLAM ]; then _success "Slamming tomb: killing all processes using this hook" slam_tomb "$b" if [[ $? == 1 ]]; then _warning "Cannot slam the tomb $b" return 1 fi umount $b else _warning "Tomb hook is busy, cannot close tomb." return 1 fi fi done # Execute post-hooks for eventual cleanup if ! option_is_set -n ; then exec_safe_post_hooks ${tombmount%%/} close fi if [ $tombmount ]; then # tomb is actively mounted 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 if [ "${tombmount}" = "/media/${tombname}.tomb" ]; then rmdir ${tombmount} fi fi fi cryptsetup luksClose $mapper if ! [ $? = 0 ]; then _warning "error occurred in cryptsetup luksClose ${mapper}" return 1 fi loopdev=`cut -d '.' -f4 <<< "$mapper"` losetup -d "/dev/$loopdev" # kill the status tray widget if still present # this makes the widget disappear when closing tomb from cli awkmapper=`sed 's:\/:\\\/:g' <<< $mapper` statustray_pid=`ps ax | awk "/tomb-status $awkmapper/"' {print $1} '` if [ ${statustray_pid} ]; then kill ${statustray_pid} fi _success "Tomb $tombname closed: your bones will rest in peace." return 0 } # }}} # }}} # {{{ - Change Password # $1 is the tomb key path # 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" # 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" keyname=`basename $keyfile` if ! option_is_set --tomb-old-pwd; then while true; do tombpass=`exec_as_user ${TOMBEXEC} askpass "Type old password for ${keyname}" "Change tomb key password"` if [[ $? == 1 ]]; then die "User aborted" fi if get_lukskey "${tombpass}" ${keyfile} > ${lukskey}; then break fi done else tombpass=`option_value --tomb-old-pwd` if ! get_lukskey "${tombpass}" ${keyfile} > ${lukskey}; then die "Invalid old password" fi fi { 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 $? } # }}} # {{{ - Resize # resize tomb file size # $1 is tomb path 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 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 if option_is_set -k ; then if [[ "`option_value -k`" == "-" ]]; then # take key from stdin local tombkeydir 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}/${tombfile}.key fi if ! [ -r ${tombkey} ]; then _failure "key file not found: ${tombkey}" fi 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` local tombsize_4k=`expr $delta \/ 1024` tombsize_4k=`expr $tombsize_4k \/ 4 ` act "Generating ${tombfile} of ${newtombsize}Mb (${tombsize_4k} blocks of 4Kb)" "$DD" if=/dev/urandom bs=4k count=${tombsize_4k} 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}" 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`" _message "Password is required for key ${keyname}" for c in 1 2 3; do if [ $c = 1 ]; then tombpass=`exec_as_user ${TOMBEXEC} askpass ${keyname}` else tombpass=`exec_as_user ${TOMBEXEC} askpass "$keyname (retry $c)"` fi get_lukskey "${tombpass}" ${tombkey} | \ cryptsetup --key-file - luksOpen ${nstloop} ${mapper} unset tombpass if [ -r /dev/mapper/${mapper} ]; then break; # password was correct fi done 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 } # }}} # 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 # Positions in 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 } # 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 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 } ' } # {{{ - Index # 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 "sndexing $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." } yes "Searching for: $fg_bold[white]${=PARAM}$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 ${=PARAM} say "Matches found: `locate -d ${tombmount}/.updatedb -e -i -c ${=PARAM}`" else no "skipping tomb $tombname: not indexed" no "run 'tomb index' to create indexes" fi done } # {{{ - 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 } # }}} # {{{ - Status launch_status() { # calculates the correct arguments to launch tomb-status tray # applet; it takes the tomb name as an argument and can be # launched after a successful tomb mount. which tomb-status > /dev/null if [ $? != 0 ]; then die "Cannot find tomb-status binary, operation aborted." return 1 fi if ! [ $DISPLAY ]; then _warning "No active X display found, operation aborted." _warning "Status launches a graphical tray applet, you need X running." return 1 fi # check argument. use single tomb mounted if no argument # abort if more than one tomb is mounted. if ! [ $1 ]; then tombs=`find /dev/mapper -name 'tomb.*'` how_many_tombs=`wc -w <<< "$tombs"` if [[ "$how_many_tombs" == "0" ]]; then _warning "There is no open tomb, status cannot be launched" return 1 elif [[ "$how_many_tombs" == "1" ]]; then #mapper=`find /dev/mapper -name 'tomb.*'` tombname=`find /dev/mapper -name "tomb.*"` tombname=`basename $tombname | cut -d. -f2` _success "launching status for tomb $tombname" else _warning "Too many tombs mounted, please specify which one" list_tombs return 0 fi else # name was specified on command line tombname=$1 ls /dev/mapper | grep "^tomb.${tombname}.*" > /dev/null if [ $? != 0 ]; then _warning "Cannot find any tomb named $tombname being open, operation aborted." return 1 fi fi # finally we launch tombmap=`mount -l | awk "/\[${tombname}\]$/"' { print $1 } '` tombmount=`mount -l | awk "/\[${tombname}\]$/"' { print $3 } '` tomb-status $tombmap $tombname $tombmount &! return 0 } # }}} # {{{ - Install GUI # 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, sudo _message "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 _message "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="${TOMBOPENEXEC}" %U TryExec=tomb-open Icon=monmort.xpm Terminal=true Categories=Utility;Security;Archiving;Filesystem; MimeType=application/x-tomb-volume; X-AppInstall-Package=tomb EOF update-desktop-database _message "updating menus..." cat < /etc/menu/tomb ?package(tomb):command="tomb" icon="/usr/share/pixmaps/monmort.xpm" needs="text" \ section="Applications/Accessories" title="Tomb" hints="Crypto" \ hotkey="Tomb" EOF update-menus _message "updating mime info..." cat < /usr/share/mime-info/tomb.keys # actions for encrypted tomb storage application/x-tomb-volume: open="${TOMBOPENEXEC}" %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.key EOF cat < /usr/lib/mime/packages/tomb application/x-tomb-volume; tomb-open '%s'; priority=8 EOF update-mime _message "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=true mime-types=application/x-tomb-volume,application/x-tomb-key EOF _message "Tomb is now installed." } # }}} # }}} # {{{ MAIN COMMAND 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 -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[status]="" 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] ;; status) launch_status $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 encode_key $PARAM[1] $PARAM[2] ;; exhume) if [ "$STEGHIDE" = 0 ]; then _warning "steghide not installed. Cannot exhume your key" return 1 fi decode_key $PARAM[1] $PARAM[2] ;; resize) 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 ;; check) check_command $PARAM[1] ;; __default) cat < EOF option_is_set -v && { cat <