ttomb - coffin - secure lan file storage on a device
 (HTM) git clone git://parazyd.org/coffin.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
 (DIR) README
 (DIR) LICENSE
       ---
       ttomb (93885B)
       ---
            1 #!/bin/zsh
            2 #
            3 # Tomb, the Crypto Undertaker
            4 #
            5 # A commandline tool to easily operate encryption of secret data
            6 #
            7 
            8 # {{{ License
            9 
           10 # Copyright (C) 2007-2016 Dyne.org Foundation
           11 #
           12 # Tomb is designed, written and maintained by Denis Roio <jaromil@dyne.org>
           13 #
           14 # With contributions by Anathema, Boyska, Hellekin O. Wolf and GDrooid
           15 #
           16 # Gettext internationalization and Spanish translation is contributed by
           17 # GDrooid, French translation by Hellekin, Russian translation by fsLeg,
           18 # German translation by x3nu.
           19 #
           20 # Testing, reviews and documentation are contributed by Dreamer, Shining
           21 # the Translucent, Mancausoft, Asbesto Molesto, Nignux, Vlax, The Grugq,
           22 # Reiven, GDrooid, Alphazo, Brian May, TheJH, fsLeg, JoelMon and the
           23 # Linux Action Show!
           24 #
           25 # Tomb's artwork is contributed by Jordi aka Mon Mort and Logan VanCuren.
           26 #
           27 # Cryptsetup was developed by Christophe Saout and Clemens Fruhwirth.
           28 
           29 # This source code is free software; you can redistribute it and/or
           30 # modify it under the terms of the GNU Public License as published by
           31 # the Free Software Foundation; either version 3 of the License, or
           32 # (at your option) any later version.
           33 #
           34 # This source code is distributed in the hope that it will be useful,
           35 # but WITHOUT ANY WARRANTY; without even the implied warranty of
           36 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  Please refer
           37 # to the GNU Public License for more details.
           38 #
           39 # You should have received a copy of the GNU Public License along with
           40 # this source code; if not, write to: Free Software Foundation, Inc.,
           41 # 675 Mass Ave, Cambridge, MA 02139, USA.
           42 
           43 # }}} - License
           44 
           45 # {{{ Global variables
           46 
           47 typeset VERSION="2.2"
           48 typeset DATE="Dec/2015"
           49 typeset TOMBEXEC=$0
           50 typeset TMPPREFIX=${TMPPREFIX:-/tmp}
           51 export PATH="/usr/local/share/coffin/bin:$PATH"
           52 # TODO: configure which tmp dir to use from a cli flag
           53 
           54 # Tomb is using some global variables set by the shell:
           55 # TMPPREFIX, UID, GID, PATH, TTY, USERNAME
           56 # You can grep 'global variable' to see where they are used.
           57 
           58 # Keep a reference of the original command line arguments
           59 typeset -a OLDARGS
           60 for arg in "${(@)argv}"; do OLDARGS+=("$arg"); done
           61 
           62 # Special command requirements
           63 typeset -a DD WIPE PINENTRY
           64 DD=(dd)
           65 WIPE=(rm -f)
           66 PINENTRY=(pinentry)
           67 
           68 # load zsh regex module
           69 zmodload zsh/regex
           70 zmodload zsh/mapfile
           71 zmodload -F zsh/stat b:zstat
           72 
           73 # make sure variables aren't exported
           74 unsetopt allexport
           75 
           76 # Flag optional commands if available (see _ensure_dependencies())
           77 typeset -i KDF=1
           78 typeset -i STEGHIDE=1
           79 typeset -i RESIZER=1
           80 typeset -i SWISH=1
           81 typeset -i QRENCODE=1
           82 
           83 # Default mount options
           84 typeset      MOUNTOPTS="rw,noatime,nodev"
           85 
           86 # Makes glob matching case insensitive
           87 unsetopt CASE_MATCH
           88 
           89 typeset -AH OPTS              # Command line options (see main())
           90 
           91 # Command context (see _whoami())
           92 typeset -H  _USER             # Running username
           93 typeset -Hi _UID              # Running user identifier
           94 typeset -Hi _GID              # Running user group identifier
           95 typeset -H  _TTY              # Connected input terminal
           96 
           97 # Tomb context (see _plot())
           98 typeset -H TOMBPATH           # Full path to the tomb
           99 typeset -H TOMBDIR            # Directory where the tomb is
          100 typeset -H TOMBFILE           # File name of the tomb
          101 typeset -H TOMBNAME           # Name of the tomb
          102 
          103 # Tomb secrets
          104 typeset -H TOMBKEY            # Encrypted key contents (see forge_key(), recover_key())
          105 typeset -H TOMBKEYFILE        # Key file               (ditto)
          106 typeset -H TOMBSECRET         # Raw deciphered key     (see forge_key(), gpg_decrypt())
          107 typeset -H TOMBPASSWORD       # Raw tomb passphrase    (see gen_key(), ask_key_password())
          108 typeset -H TOMBTMP            # Filename of secure temp just created (see _tmp_create())
          109 
          110 typeset -aH TOMBTMPFILES      # Keep track of temporary files
          111 typeset -aH TOMBLOOPDEVS      # Keep track of used loop devices
          112 
          113 # Make sure sbin is in PATH (man zshparam)
          114 path+=( /sbin /usr/sbin )
          115 
          116 # For gettext
          117 export TEXTDOMAIN=tomb
          118 
          119 # }}}
          120 
          121 # {{{ Safety functions
          122 
          123 # Wrap sudo with a more visible message
          124 _sudo() {
          125     local sudo_eng="[sudo] Enter password for user ::1 user:: to gain superuser privileges"
          126     local msg="$(gettext -s "$sudo_eng")"
          127     msg=${(S)msg//::1*::/$USER}
          128     sudo -p "
          129 $msg
          130 
          131 " ${@}
          132 }
          133 
          134 # Cleanup anything sensitive before exiting.
          135 _endgame() {
          136 
          137     # Prepare some random material to overwrite vars
          138     local rr="$RANDOM"
          139     while [[ ${#rr} -lt 500 ]]; do
          140         rr+="$RANDOM"
          141     done
          142 
          143     # Ensure no information is left in unallocated memory
          144     TOMBPATH="$rr";      unset TOMBPATH
          145     TOMBDIR="$rr";       unset TOMBDIR
          146     TOMBFILE="$rr";      unset TOMBFILE
          147     TOMBNAME="$rr";      unset TOMBNAME
          148     TOMBKEY="$rr";       unset TOMBKEY
          149     TOMBKEYFILE="$rr";   unset TOMBKEYFILE
          150     TOMBSECRET="$rr";    unset TOMBSECRET
          151     TOMBPASSWORD="$rr";  unset TOMBPASSWORD
          152 
          153     # Clear temporary files
          154     for f in $TOMBTMPFILES; do
          155         ${=WIPE} "$f"
          156     done
          157     unset TOMBTMPFILES
          158 
          159     # Detach loop devices
          160     for l in $TOMBLOOPDEVS; do
          161         _sudo losetup -d "$l"
          162     done
          163     unset TOMBLOOPDEVS
          164 
          165 }
          166 
          167 # Trap functions for the _endgame event
          168 TRAPINT()  { _endgame INT   }
          169 TRAPEXIT() { _endgame EXIT  }
          170 TRAPHUP()  { _endgame HUP   }
          171 TRAPQUIT() { _endgame QUIT  }
          172 TRAPABRT() { _endgame ABORT }
          173 TRAPKILL() { _endgame KILL  }
          174 TRAPPIPE() { _endgame PIPE  }
          175 TRAPTERM() { _endgame TERM  }
          176 TRAPSTOP() { _endgame STOP  }
          177 
          178 _cat() { local -a _arr;
          179     # read file using mapfile, newline fix
          180     _arr=("${(f@)${mapfile[${1}]%$ā€™\nā€™}}"); print "$_arr"
          181 }
          182 
          183 _is_found() {
          184     # returns 0 if binary is found in path
          185     [[ "$1" = "" ]] && return 1
          186     command -v "$1" 1>/dev/null 2>/dev/null
          187     return $?
          188 }
          189 
          190 # Identify the running user
          191 # Set global variables _UID, _GID, _TTY, and _USER, either from the
          192 # command line, -U, -G, -T, respectively, or from the environment.
          193 # Also update USERNAME and HOME to maintain consistency.
          194 _whoami() {
          195 
          196     # Set username from UID or environment
          197     _USER=$SUDO_USER
          198     [[ "$_USER" = "" ]] && { _USER=$USERNAME }
          199     [[ "$_USER" = "" ]] && { _USER=$(id -u)  }
          200     [[ "$_USER" = "" ]] && {
          201         _failure "Failing to identify the user who is calling us" }
          202 
          203     # Get GID from option -G or the environment
          204     option_is_set -G \
          205         && _GID=$(option_value -G) || _GID=$(id -g $_USER)
          206 
          207     # Get UID from option -U or the environment
          208     option_is_set -U \
          209         && _UID=$(option_value -U) || _UID=$(id -u $_USER)
          210 
          211     _verbose "Identified caller: ::1 username:: (::2 UID:::::3  GID::)" $_USER $_UID $_GID
          212 
          213     # Update USERNAME accordingly if possible
          214     [[ $EUID == 0 && $_USER != $USERNAME ]] && {
          215         _verbose "Updating USERNAME from '::1 USERNAME::' to '::2 _USER::')" $USERNAME $_USER
          216         USERNAME=$_USER
          217     }
          218 
          219     # Force HOME to _USER's HOME if necessary
          220     local home=$(awk -F: "/^$_USER:/ { print \$6 }" /etc/passwd 2>/dev/null)
          221     [[ $home == $HOME ]] || {
          222         _verbose "Updating HOME to match user's: ::1 home:: (was ::2 HOME::)" \
          223             $home $HOME
          224         HOME=$home }
          225 
          226     # Get connecting TTY from option -T or the environment
          227     option_is_set -T && _TTY=$(option_value -T)
          228     [[ -z $_TTY ]]   && _TTY=$TTY
          229 
          230 }
          231 
          232 # Define sepulture's plot (setup tomb-related arguments)
          233 # Synopsis: _plot /path/to/the.tomb
          234 # Set TOMB{PATH,DIR,FILE,NAME}
          235 _plot() {
          236 
          237     # We set global variables
          238     typeset -g TOMBPATH TOMBDIR TOMBFILE TOMBNAME
          239 
          240     TOMBPATH="$1"
          241 
          242     TOMBDIR=$(dirname $TOMBPATH)
          243 
          244     TOMBFILE=$(basename $TOMBPATH)
          245 
          246     # The tomb name is TOMBFILE without an extension.
          247     # It can start with dots: ..foo.tomb -> ..foo
          248     TOMBNAME="${TOMBFILE%\.[^\.]*}"
          249     [[ -z $TOMBNAME ]] && {
          250         _failure "Tomb won't work without a TOMBNAME." }
          251 
          252 }
          253 
          254 # Provide a random filename in shared memory
          255 _tmp_create() {
          256     [[ -d "$TMPPREFIX" ]] || {
          257         # we create the tempdir with the sticky bit on
          258         _sudo mkdir -m 1777 "$TMPPREFIX"
          259         [[ $? == 0 ]] || _failure "Fatal error creating the temporary directory: ::1 temp dir::" "$TMPPREFIX"
          260     }
          261 
          262     # We're going to add one more $RANDOM for each time someone complains
          263     # about this being too weak of a random.
          264     tfile="${TMPPREFIX}/$RANDOM$RANDOM$RANDOM$RANDOM"   # Temporary file
          265     umask 066
          266     [[ $? == 0 ]] || {
          267         _failure "Fatal error setting the permission umask for temporary files" }
          268 
          269     [[ -r "$tfile" ]] && {
          270         _failure "Someone is messing up with us trying to hijack temporary files." }
          271 
          272     touch "$tfile"
          273     [[ $? == 0 ]] || {
          274         _failure "Fatal error creating a temporary file: ::1 temp file::" "$tfile" }
          275 
          276     _verbose "Created tempfile: ::1 temp file::" "$tfile"
          277     TOMBTMP="$tfile"
          278     TOMBTMPFILES+=("$tfile")
          279 
          280     return 0
          281 }
          282 
          283 # Check if a *block* device is encrypted
          284 # Synopsis: _is_encrypted_block /path/to/block/device
          285 # Return 0 if it is an encrypted block device
          286 _is_encrypted_block() {
          287     local    b=$1 # Path to a block device
          288     local    s="" # lsblk option -s (if available)
          289 
          290     # Issue #163
          291     # lsblk --inverse appeared in util-linux 2.22
          292     # but --version is not consistent...
          293     lsblk --help | grep -Fq -- --inverse
          294     [[ $? -eq 0 ]] && s="--inverse"
          295 
          296     sudo lsblk $s -o type -n $b 2>/dev/null \
          297         | egrep -q '^crypt$'
          298 
          299     return $?
          300 }
          301 
          302 # Check if swap is activated
          303 # Return 0 if NO swap is used, 1 if swap is used.
          304 # Return 1 if any of the swaps is not encrypted.
          305 # Return 2 if swap(s) is(are) used, but ALL encrypted.
          306 # Use _check_swap in functions. It will call this function and
          307 # exit if unsafe swap is present.
          308 _ensure_safe_swap() {
          309 
          310     local -i r=1    # Return code: 0 no swap, 1 unsafe swap, 2 encrypted
          311     local -a swaps  # List of swap partitions
          312     local    bone is_crypt
          313 
          314     swaps="$(awk '/^\// { print $1 }' /proc/swaps 2>/dev/null)"
          315     [[ -z "$swaps" ]] && return 0 # No swap partition is active
          316 
          317     _message "An active swap partition is detected..."
          318     for s in $=swaps; do
          319         { _is_encrypted_block $s } && { r=2 } || {
          320             # We're dealing with unencrypted stuff.
          321             # Maybe it lives on an encrypted filesystem anyway.
          322             # @todo: verify it's actually on an encrypted FS (see #163 and !189)
          323             # Well, no: bail out.
          324             r=1; break
          325         }
          326     done
          327 
          328     if [[ $r -eq 2 ]]; then
          329         _success "All your swaps are belong to crypt. Good."
          330     else
          331         _warning "This poses a security risk."
          332         _warning "You can deactivate all swap partitions using the command:"
          333         _warning " swapoff -a"
          334         _warning "[#163] I may not detect plain swaps on an encrypted volume."
          335         _warning "But if you want to proceed like this, use the -f (force) flag."
          336     fi
          337     return $r
          338 
          339 }
          340 
          341 # Wrapper to allow encrypted swap and remind the user about possible
          342 # data leaks to disk if swap is on, which shouldn't be ignored. It could
          343 # be run once in main(), but as swap evolves, it's better to run it
          344 # whenever swap may be needed.
          345 # Exit if unencrypted swap is active on the system.
          346 _check_swap() {
          347     if ! option_is_set -f && ! option_is_set --ignore-swap; then
          348         _ensure_safe_swap
          349         case $? in
          350             0|2)     # No, or encrypted swap
          351                 return 0
          352                 ;;
          353             *)       # Unencrypted swap
          354                 _failure "Operation aborted."
          355                 ;;
          356         esac
          357     fi
          358 }
          359 
          360 # Ask user for a password
          361 # Wraps around the pinentry command, from the GnuPG project, as it
          362 # provides better security and conveniently use the right toolkit.
          363 ask_password() {
          364 
          365     local description="$1"
          366     local title="${2:-Enter tomb password.}"
          367     local output
          368     local password
          369     local gtkrc
          370     local theme
          371 
          372     # Distributions have broken wrappers for pinentry: they do
          373     # implement fallback, but they disrupt the output somehow.  We are
          374     # better off relying on less intermediaries, so we implement our
          375     # own fallback mechanisms. Pinentry supported: curses, gtk-2, qt4
          376     # and x11.
          377 
          378     # make sure LANG is set, default to C
          379     LANG=${LANG:-C}
          380 
          381     _verbose "asking password with tty=$TTY lc-ctype=$LANG"
          382 
          383     if [[ "$DISPLAY" = "" ]]; then
          384 
          385         if _is_found "pinentry-curses"; then
          386             _verbose "using pinentry-curses"
          387             output=`cat <<EOF | pinentry-curses
          388 OPTION ttyname=$TTY
          389 OPTION lc-ctype=$LANG
          390 SETTITLE $title
          391 SETDESC $description
          392 SETPROMPT Password:
          393 GETPIN
          394 EOF`
          395         else
          396             _failure "Cannot find pinentry-curses and no DISPLAY detected."
          397         fi
          398 
          399     else # a DISPLAY is found to be active
          400 
          401         # customized gtk2 dialog with a skull (if extras are installed)
          402         if _is_found "pinentry-gtk-2"; then
          403             _verbose "using pinentry-gtk2"
          404 
          405             gtkrc=""
          406             theme=/share/themes/tomb/gtk-2.0-key/gtkrc
          407             for i in /usr/local /usr; do
          408                 [[ -r $i/$theme ]] && {
          409                     gtkrc="$i/$theme"
          410                     break
          411                 }
          412             done
          413             [[ "$gtkrc" = "" ]] || {
          414                 gtkrc_old="$GTK2_RC_FILES"
          415                 export GTK2_RC_FILES="$gtkrc"
          416             }
          417             output=`cat <<EOF | pinentry-gtk-2
          418 OPTION ttyname=$TTY
          419 OPTION lc-ctype=$LANG
          420 SETTITLE $title
          421 SETDESC $description
          422 SETPROMPT Password:
          423 GETPIN
          424 EOF`
          425             [[ "$gtkrc" = "" ]] || export GTK2_RC_FILES="$gtkrc_old"
          426 
          427             # TODO QT4 customization of dialog
          428         elif _is_found "pinentry-qt4"; then
          429             _verbose "using pinentry-qt4"
          430 
          431             output=`cat <<EOF | pinentry-qt4
          432 OPTION ttyname=$TTY
          433 OPTION lc-ctype=$LANG
          434 SETTITLE $title
          435 SETDESC $description
          436 SETPROMPT Password:
          437 GETPIN
          438 EOF`
          439 
          440             # TODO X11 customization of dialog
          441         elif _is_found "pinentry-x11"; then
          442             _verbose "using pinentry-x11"
          443 
          444             output=`cat <<EOF | pinentry-x11
          445 OPTION ttyname=$TTY
          446 OPTION lc-ctype=$LANG
          447 SETTITLE $title
          448 SETDESC $description
          449 SETPROMPT Password:
          450 GETPIN
          451 EOF`
          452 
          453         else
          454 
          455             if _is_found "pinentry-curses"; then
          456                 _verbose "using pinentry-curses"
          457 
          458                 _warning "Detected DISPLAY, but only pinentry-curses is found."
          459                 output=`cat <<EOF | pinentry-curses
          460 OPTION ttyname=$TTY
          461 OPTION lc-ctype=$LANG
          462 SETTITLE $title
          463 SETDESC $description
          464 SETPROMPT Password:
          465 GETPIN
          466 EOF`
          467             else
          468                 _failure "Cannot find any pinentry: impossible to ask for password."
          469             fi
          470 
          471         fi
          472 
          473     fi # end of DISPLAY block
          474 
          475     # parse the pinentry output
          476     for i in ${(f)output}; do
          477         [[ "$i" =~ "^ERR.*" ]] && {
          478             _warning "Pinentry error: ::1 error::" ${i[(w)3]}
          479             print "canceled"
          480             return 1 }
          481 
          482         # here the password is found
          483         [[ "$i" =~ "^D .*" ]] && password="${i##D }"
          484     done
          485 
          486     [[ "$password" = "" ]] && {
          487         _warning "Empty password"
          488         print "empty"
          489         return 1 }
          490 
          491     print "$password"
          492     return 0
          493 }
          494 
          495 
          496 
          497 # Check if a filename is a valid tomb
          498 is_valid_tomb() {
          499     _verbose "is_valid_tomb ::1 tomb file::" $1
          500 
          501     # First argument must be the path to a tomb
          502     [[ -z "$1" ]] && {
          503         _failure "Tomb file is missing from arguments." }
          504 
          505     _fail=0
          506     # Tomb file must be a readable, writable, non-empty regular file.
          507     [[ ! -w "$1" ]] && {
          508         _warning "Tomb file is not writable: ::1 tomb file::" $1
          509         _fail=1
          510     }
          511     [[ ! -f "$1" ]] && {
          512         _warning "Tomb file is not a regular file: ::1 tomb file::" $1
          513         _fail=1
          514     }
          515     [[ ! -s "$1" ]] && {
          516         _warning "Tomb file is empty (zero length): ::1 tomb file::" $1
          517         _fail=1
          518     }
          519 
          520     _uid="`zstat +uid $1`"
          521     [[ "$_uid"  = "$UID" ]] || {
          522         _user="`zstat -s +uid $1`"
          523         _warning "Tomb file is owned by another user: ::1 tomb owner::" $_user
          524     }
          525     [[ $_fail = 1 ]] && {
          526         _failure "Tomb command failed: ::1 command name::" $subcommand
          527     }
          528 
          529     # TODO: split the rest of that function out.
          530     # We already have a valid tomb, now we're checking
          531     # whether we can alter it.
          532 
          533     # Tomb file may be a LUKS FS (or we are creating it)
          534     [[ "`file $1`" =~ "luks encrypted file" ]] || {
          535         _warning "File is not yet a tomb: ::1 tomb file::" $1 }
          536 
          537     _plot $1     # Set TOMB{PATH,DIR,FILE,NAME}
          538 
          539     # Tomb already mounted (or we cannot alter it)
          540     [[ "`mount -l`" -regex-match "${TOMBFILE}.*\[$TOMBNAME\]$" ]] && {
          541         _failure "Tomb is currently in use: ::1 tomb name::" $TOMBNAME
          542     }
          543 
          544     _message "Valid tomb file found: ::1 tomb path::" $TOMBPATH
          545 
          546     return 0
          547 }
          548 
          549 # $1 is the tomb file to be lomounted
          550 lo_mount() {
          551     tpath="$1"
          552 
          553     # check if we have support for loop mounting
          554     _nstloop=`_sudo losetup -f`
          555     [[ $? = 0 ]] || {
          556         _warning "Loop mount of volumes is not possible on this machine, this error"
          557         _warning "often occurs on VPS and kernels that don't provide the loop module."
          558         _warning "It is impossible to use Tomb on this machine at this conditions."
          559         _failure "Operation aborted."
          560     }
          561 
          562     _sudo losetup -f "$tpath" # allocates the next loopback for our file
          563 
          564     TOMBLOOPDEVS+=("$_nstloop") # add to array of lodevs used
          565 
          566     return 0
          567 }
          568 
          569 # print out latest loopback mounted
          570 lo_new() { print - "${TOMBLOOPDEVS[${#TOMBLOOPDEVS}]}" }
          571 
          572 # $1 is the path to the lodev to be preserved after quit
          573 lo_preserve() {
          574     _verbose "lo_preserve on ::1 path::" $1
          575     # remove the lodev from the tomb_lodevs array
          576     TOMBLOOPDEVS=("${(@)TOMBLOOPDEVS:#$1}")
          577 }
          578 
          579 # eventually used for debugging
          580 dump_secrets() {
          581     print "TOMBPATH: $TOMBPATH"
          582     print "TOMBNAME: $TOMBNAME"
          583 
          584     print "TOMBKEY len: ${#TOMBKEY}"
          585     print "TOMBKEYFILE: $TOMBKEYFILE"
          586     print "TOMBSECRET len: ${#TOMBSECRET}"
          587     print "TOMBPASSWORD: $TOMBPASSWORD"
          588 
          589     print "TOMBTMPFILES: ${(@)TOMBTMPFILES}"
          590     print "TOMBLOOPDEVS: ${(@)TOMBLOOPDEVS}"
          591 }
          592 
          593 # }}}
          594 
          595 # {{{ Commandline interaction
          596 
          597 usage() {
          598     _print "Syntax: tomb [options] command [arguments]"
          599     _print "\000"
          600     _print "Commands:"
          601     _print "\000"
          602     _print " // Creation:"
          603     _print " dig     create a new empty TOMB file of size -s in MiB"
          604     _print " forge   create a new KEY file and set its password"
          605     _print " lock    installs a lock on a TOMB to use it with KEY"
          606     _print "\000"
          607     _print " // Operations on tombs:"
          608     _print " open    open an existing TOMB (-k KEY file or - for stdin)"
          609     _print " index   update the search indexes of tombs"
          610     _print " search  looks for filenames matching text patterns"
          611     _print " list    list of open TOMBs and information on them"
          612     _print " close   close a specific TOMB (or 'all')"
          613     _print " slam    slam a TOMB killing all programs using it"
          614     [[ $RESIZER == 1 ]] && {
          615         _print " resize  resize a TOMB to a new size -s (can only grow)"
          616     }
          617     _print "\000"
          618     _print " // Operations on keys:"
          619     _print " passwd  change the password of a KEY (needs old pass)"
          620     _print " setkey  change the KEY locking a TOMB (needs old key and pass)"
          621     _print "\000"
          622     [[ $QRENCODE == 1 ]] && {
          623         _print " // Backup on paper:"
          624         _print " engrave makes a QR code of a KEY to be saved on paper"
          625     }
          626     _print "\000"
          627     [[ $STEGHIDE == 1 ]] && {
          628         _print " // Steganography:"
          629         _print " bury    hide a KEY inside a JPEG image (for use with -k)"
          630         _print " exhume  extract a KEY from a JPEG image (prints to stdout)"
          631     }
          632     _print "\000"
          633     _print "Options:"
          634     _print "\000"
          635     _print " -s     size of the tomb file when creating/resizing one (in MiB)"
          636     _print " -k     path to the key to be used ('-k -' to read from stdin)"
          637     _print " -n     don't process the hooks found in tomb"
          638     _print " -o     options passed to commands: open, lock, forge (see man)"
          639     _print " -f     force operation (i.e. even if swap is active)"
          640     [[ $KDF == 1 ]] && {
          641         _print " --kdf  forge keys armored against dictionary attacks"
          642     }
          643 
          644     _print "\000"
          645     _print " -h     print this help"
          646     _print " -v     print version, license and list of available ciphers"
          647     _print " -q     run quietly without printing informations"
          648     _print " -D     print debugging information at runtime"
          649     _print "\000"
          650     _print "For more informations on Tomb read the manual: man tomb"
          651     _print "Please report bugs on <http://github.com/dyne/tomb/issues>."
          652 }
          653 
          654 
          655 # Check whether a commandline option is set.
          656 #
          657 # Synopsis: option_is_set -flag [out]
          658 #
          659 # First argument is the commandline flag (e.g., "-s").
          660 # If the second argument is present and set to 'out', print out the
          661 # result: either 'set' or 'unset' (useful for if conditions).
          662 #
          663 # Return 0 if is set, 1 otherwise
          664 option_is_set() {
          665     local -i r   # the return code (0 = set, 1 = unset)
          666 
          667     [[ -n ${(k)OPTS[$1]} ]];
          668     r=$?
          669 
          670     [[ $2 == "out" ]] && {
          671         [[ $r == 0 ]] && { print 'set' } || { print 'unset' }
          672     }
          673 
          674     return $r;
          675 }
          676 
          677 # Print the option value matching the given flag
          678 # Unique argument is the commandline flag (e.g., "-s").
          679 option_value() {
          680     print -n - "${OPTS[$1]}"
          681 }
          682 
          683 # Messaging function with pretty coloring
          684 function _msg() {
          685     local msg="$2"
          686     command -v gettext 1>/dev/null 2>/dev/null && msg="$(gettext -s "$2")"
          687     for i in $(seq 3 ${#});
          688     do
          689         msg=${(S)msg//::$(($i - 2))*::/$*[$i]}
          690     done
          691 
          692     local command="print -P"
          693     local progname="$fg[magenta]${TOMBEXEC##*/}$reset_color"
          694     local message="$fg_bold[normal]$fg_no_bold[normal]$msg$reset_color"
          695     local -i returncode
          696 
          697     case "$1" in
          698         inline)
          699             command+=" -n"; pchars=" > "; pcolor="yellow"
          700             ;;
          701         message)
          702             pchars=" . "; pcolor="white"; message="$fg_no_bold[$pcolor]$msg$reset_color"
          703             ;;
          704         verbose)
          705             pchars="[D]"; pcolor="blue"
          706             ;;
          707         success)
          708             pchars="(*)"; pcolor="green"; message="$fg_no_bold[$pcolor]$msg$reset_color"
          709             ;;
          710         warning)
          711             pchars="[W]"; pcolor="yellow"; message="$fg_no_bold[$pcolor]$msg$reset_color"
          712             ;;
          713         failure)
          714             pchars="[E]"; pcolor="red"; message="$fg_no_bold[$pcolor]$msg$reset_color"
          715             returncode=1
          716             ;;
          717         print)
          718             progname=""
          719             ;;
          720         *)
          721             pchars="[F]"; pcolor="red"
          722             message="Developer oops!  Usage: _msg MESSAGE_TYPE \"MESSAGE_CONTENT\""
          723             returncode=127
          724             ;;
          725     esac
          726     ${=command} "${progname} $fg_bold[$pcolor]$pchars$reset_color ${message}$color[reset_color]" >&2
          727     return $returncode
          728 }
          729 
          730 function _message say() {
          731     local notice="message"
          732     [[ "$1" = "-n" ]] && shift && notice="inline"
          733     option_is_set -q || _msg "$notice" $@
          734     return 0
          735 }
          736 
          737 function _verbose xxx() {
          738     option_is_set -D && _msg verbose $@
          739     return 0
          740 }
          741 
          742 function _success yes() {
          743     option_is_set -q || _msg success $@
          744     return 0
          745 }
          746 
          747 function _warning  no() {
          748     option_is_set -q || _msg warning $@
          749     return 1
          750 }
          751 
          752 function _failure die() {
          753     typeset -i exitcode=${exitv:-1}
          754     option_is_set -q || _msg failure $@
          755     # be sure we forget the secrets we were told
          756     exit $exitcode
          757 }
          758 
          759 function _print() {
          760     option_is_set -q || _msg print $@
          761     return 0
          762 }
          763 
          764 _list_optional_tools() {
          765     typeset -a _deps
          766     _deps=(gettext dcfldd wipe steghide)
          767     _deps+=(resize2fs tomb-kdb-pbkdf2 qrencode swish-e unoconv)
          768     for d in $_deps; do
          769         _print "`which $d`"
          770     done
          771     return 0
          772 }
          773 
          774 
          775 # Check program dependencies
          776 #
          777 # Tomb depends on system utilities that must be present, and other
          778 # functionality that can be provided by various programs according to
          779 # what's available on the system.  If some required commands are
          780 # missing, bail out.
          781 _ensure_dependencies() {
          782 
          783     # Check for required programs
          784     for req in cryptsetup pinentry sudo gpg mkfs.ext4 e2fsck; do
          785         command -v $req 1>/dev/null 2>/dev/null || {
          786             _failure "Missing required dependency ::1 command::.  Please install it." $req }
          787     done
          788 
          789     # Ensure system binaries are available in the PATH
          790     path+=(/sbin /usr/sbin) # zsh magic
          791 
          792     # Which dd command to use
          793     command -v dcfldd 1>/dev/null 2>/dev/null && DD=(dcfldd statusinterval=1)
          794 
          795     # Which wipe command to use
          796     command -v wipe 1>/dev/null 2>/dev/null && WIPE=(wipe -f -s)
          797 
          798     # Check for steghide
          799     command -v steghide 1>/dev/null 2>/dev/null || STEGHIDE=0
          800     # Check for resize
          801     command -v resize2fs 1>/dev/null 2>/dev/null || RESIZER=0
          802     # Check for KDF auxiliary tools
          803     command -v tomb-kdb-pbkdf2 1>/dev/null 2>/dev/null || KDF=0
          804     # Check for Swish-E file content indexer
          805     command -v swish-e 1>/dev/null 2>/dev/null || SWISH=0
          806     # Check for QREncode for paper backups of keys
          807     command -v qrencode 1>/dev/null 2>/dev/null || QRENCODE=0
          808 }
          809 
          810 # }}} - Commandline interaction
          811 
          812 # {{{ Key operations
          813 
          814 # $1 is the encrypted key contents we are checking
          815 is_valid_key() {
          816     local key="$1"       # Unique argument is an encrypted key to test
          817 
          818     _verbose "is_valid_key"
          819 
          820     [[ -z $key ]] && key=$TOMBKEY
          821     [[ "$key" = "cleartext" ]] && {
          822         { option_is_set --unsafe } || {
          823             _warning "cleartext key from stdin selected: this is unsafe."
          824             exitv=127 _failure "please use --unsafe if you really want to do this."
          825         }
          826         _warning "received key in cleartext from stdin (unsafe mode)"
          827         return 0 }
          828 
          829     [[ -z $key ]] && {
          830         _warning "is_valid_key() called without an argument."
          831         return 1
          832     }
          833 
          834     # If the key file is an image don't check file header
          835     [[ -r $TOMBKEYFILE ]] \
          836         && [[ $(file $TOMBKEYFILE) =~ "JP.G" ]] \
          837         && {
          838         _message "Key is an image, it might be valid."
          839         return 0 }
          840 
          841     [[ $key =~ "BEGIN PGP" ]] && {
          842         _message "Key is valid."
          843         return 0 }
          844 
          845     return 1
          846 }
          847 
          848 # $1 is a string containing an encrypted key
          849 _tomb_key_recover recover_key() {
          850     local key="${1}"    # Unique argument is an encrypted key
          851 
          852     _warning "Attempting key recovery."
          853 
          854     _head="${key[(f)1]}" # take the first line
          855 
          856     TOMBKEY=""        # Reset global variable
          857 
          858     [[ $_head =~ "^_KDF_" ]] && TOMBKEY+="$_head\n"
          859 
          860     TOMBKEY+="-----BEGIN PGP MESSAGE-----\n"
          861     TOMBKEY+="$key\n"
          862     TOMBKEY+="-----END PGP MESSAGE-----\n"
          863 
          864     return 0
          865 }
          866 
          867 # Retrieve the tomb key from the file specified from the command line,
          868 # or from stdin if -k - was selected.  Run validity checks on the
          869 # file.  On success, return 0 and print out the full path of the key.
          870 # Set global variables TOMBKEY and TOMBKEYFILE.
          871 _load_key() {
          872     local keyfile="$1"    # Unique argument is an optional keyfile
          873 
          874     [[ -z $keyfile ]] && keyfile=$(option_value -k)
          875     [[ -z $keyfile ]] && {
          876         _failure "This operation requires a key file to be specified using the -k option." }
          877 
          878     if [[ $keyfile == "-" ]]; then
          879         _verbose "load_key reading from stdin."
          880         _message "Waiting for the key to be piped from stdin... "
          881         TOMBKEYFILE=stdin
          882         TOMBKEY=$(cat)
          883     elif [[ $keyfile == "cleartext" ]]; then
          884         _verbose "load_key reading SECRET from stdin"
          885         _message "Waiting for the key to be piped from stdin... "
          886         TOMBKEYFILE=cleartext
          887         TOMBKEY=cleartext
          888         TOMBSECRET=$(cat)
          889     else
          890         _verbose "load_key argument: ::1 key file::" $keyfile
          891         [[ -r $keyfile ]] || _failure "Key not found, specify one using -k."
          892         TOMBKEYFILE=$keyfile
          893         TOMBKEY="${mapfile[$TOMBKEYFILE]}"
          894     fi
          895 
          896     _verbose "load_key: ::1 key::" $TOMBKEYFILE
          897 
          898     [[ "$TOMBKEY" = "" ]] && {
          899         # something went wrong, there is no key to load
          900         # this occurs especially when piping from stdin and aborted
          901         _failure "Key not found, specify one using -k."
          902     }
          903 
          904     is_valid_key $TOMBKEY || {
          905         _warning "The key seems invalid or its format is not known by this version of Tomb."
          906         _tomb_key_recover $TOMBKEY
          907     }
          908 
          909     # Declared TOMBKEYFILE (path)
          910     # Declared TOMBKEY (contents)
          911 
          912     return 0
          913 }
          914 
          915 # takes two args just like get_lukskey
          916 # prints out the decrypted content
          917 # contains tweaks for different gpg versions
          918 gpg_decrypt() {
          919     # fix for gpg 1.4.11 where the --status-* options don't work ;^/
          920     local gpgver=$(gpg --version --no-permission-warning | awk '/^gpg/ {print $3}')
          921     local gpgpass="$1\n$TOMBKEY"
          922     local gpgstatus
          923 
          924     [[ $gpgver == "1.4.11" ]] && {
          925         _verbose "GnuPG is version 1.4.11 - adopting status fix."
          926 
          927         TOMBSECRET=`print - "$gpgpass" | \
          928             gpg --batch --passphrase-fd 0 --no-tty --no-options`
          929         ret=$?
          930         unset gpgpass
          931 
          932     } || { # using status-file in gpg != 1.4.11
          933 
          934         TOMBSECRET=`print - "$gpgpass" | \
          935             gpg --batch --passphrase-fd 0 --no-tty --no-options \
          936             --status-fd 2 --no-mdc-warning --no-permission-warning \
          937             --no-secmem-warning` |& grep GNUPG: \
          938             | read -r -d'\n' gpgstatus
          939 
          940         unset gpgpass
          941 
          942         ret=1
          943 
          944         [[ "${gpgstatus}" =~ "DECRYPTION_OKAY" ]] && { ret=0 }
          945 
          946 
          947     }
          948     return $ret
          949 
          950 }
          951 
          952 
          953 # Gets a key file and a password, prints out the decoded contents to
          954 # be used directly by Luks as a cryptographic key
          955 get_lukskey() {
          956     # $1 is the password
          957     _verbose "get_lukskey"
          958 
          959     _password="$1"
          960 
          961 
          962     firstline="${TOMBKEY[(f)1]}"
          963 
          964     # key is KDF encoded
          965     if [[ $firstline =~ '^_KDF_' ]]; then
          966         kdf_hash="${firstline[(ws:_:)2]}"
          967         _verbose "KDF: ::1 kdf::" "$kdf_hash"
          968         case "$kdf_hash" in
          969             "pbkdf2sha1")
          970                 kdf_salt="${firstline[(ws:_:)3]}"
          971                 kdf_ic="${firstline[(ws:_:)4]}"
          972                 kdf_len="${firstline[(ws:_:)5]}"
          973                 _message "Unlocking KDF key protection ($kdf_hash)"
          974                 _verbose "KDF salt: $kdf_salt"
          975                 _verbose "KDF ic: $kdf_ic"
          976                 _verbose "KDF len: $kdf_len"
          977                 _password=$(tomb-kdb-pbkdf2 $kdf_salt $kdf_ic $kdf_len 2>/dev/null <<<$_password)
          978                 ;;
          979             *)
          980                 _failure "No suitable program for KDF ::1 program::." $pbkdf_hash
          981                 unset _password
          982                 return 1
          983                 ;;
          984         esac
          985 
          986         # key needs to be exhumed from an image
          987     elif [[ -r $TOMBKEYFILE && $(file $TOMBKEYFILE) =~ "JP.G" ]]; then
          988 
          989         exhume_key $TOMBKEYFILE "$_password"
          990 
          991     fi
          992 
          993     gpg_decrypt "$_password" # Save decrypted contents into $TOMBSECRET
          994 
          995     ret="$?"
          996 
          997     _verbose "get_lukskey returns ::1::" $ret
          998     return $ret
          999 }
         1000 
         1001 # This function asks the user for the password to use the key it tests
         1002 # it against the return code of gpg on success returns 0 and saves
         1003 # the password in the global variable $TOMBPASSWORD
         1004 ask_key_password() {
         1005     [[ -z "$TOMBKEYFILE" ]] && {
         1006         _failure "Internal error: ask_key_password() called before _load_key()." }
         1007 
         1008     [[ "$TOMBKEYFILE" = "cleartext" ]] && {
         1009         _verbose "no password needed, using secret bytes from stdin"
         1010         return 0 }
         1011 
         1012     _message "A password is required to use key ::1 key::" $TOMBKEYFILE
         1013     passok=0
         1014     tombpass=""
         1015     if [[ "$1" = "" ]]; then
         1016 
         1017         for c in 1 2 3; do
         1018             if [[ $c == 1 ]]; then
         1019                 tombpass=$(ask_password "Insert password to: $TOMBKEYFILE")
         1020             else
         1021                 tombpass=$(ask_password "Insert password to: $TOMBKEYFILE (attempt $c)")
         1022             fi
         1023             [[ $? = 0 ]] || {
         1024                 _warning "User aborted password dialog."
         1025                 return 1
         1026             }
         1027 
         1028             get_lukskey "$tombpass"
         1029 
         1030             [[ $? = 0 ]] && {
         1031                 passok=1; _message "Password OK."
         1032                 break;
         1033             }
         1034         done
         1035 
         1036     else
         1037         # if a second argument is present then the password is already known
         1038         tombpass="$1"
         1039         _verbose "ask_key_password with tombpass: ::1 tomb pass::" $tombpass
         1040 
         1041         get_lukskey "$tombpass"
         1042 
         1043         [[ $? = 0 ]] && {
         1044             passok=1; _message "Password OK."
         1045         }
         1046 
         1047     fi
         1048     [[ $passok == 1 ]] || return 1
         1049 
         1050     TOMBPASSWORD=$tombpass
         1051     return 0
         1052 }
         1053 
         1054 # call cryptsetup with arguments using the currently known secret
         1055 # echo flags eliminate newline and disable escape (BSD_ECHO)
         1056 _cryptsetup() {
         1057     print -R -n - "$TOMBSECRET" | _sudo cryptsetup --key-file - ${=@}
         1058     return $?
         1059 }
         1060 
         1061 # change tomb key password
         1062 change_passwd() {
         1063     local tmpnewkey lukskey c tombpass tombpasstmp
         1064 
         1065     _check_swap  # Ensure swap is secure, if any
         1066     _load_key    # Try loading key from option -k and set TOMBKEYFILE
         1067 
         1068     _message "Commanded to change password for tomb key ::1 key::" $TOMBKEYFILE
         1069 
         1070     _tmp_create
         1071     tmpnewkey=$TOMBTMP
         1072 
         1073     if option_is_set --tomb-old-pwd; then
         1074         local tomboldpwd="`option_value --tomb-old-pwd`"
         1075         _verbose "tomb-old-pwd = ::1 old pass::" $tomboldpwd
         1076         ask_key_password "$tomboldpwd"
         1077     else
         1078         ask_key_password
         1079     fi
         1080     [[ $? == 0 ]] || _failure "No valid password supplied."
         1081 
         1082     _success "Changing password for ::1 key file::" $TOMBKEYFILE
         1083 
         1084     # Here $TOMBSECRET contains the key material in clear
         1085 
         1086     { option_is_set --tomb-pwd } && {
         1087         local tombpwd="`option_value --tomb-pwd`"
         1088         _verbose "tomb-pwd = ::1 new pass::" $tombpwd
         1089         gen_key "$tombpwd" >> "$tmpnewkey"
         1090     } || {
         1091         gen_key >> "$tmpnewkey"
         1092     }
         1093 
         1094     { is_valid_key "${mapfile[$tmpnewkey]}" } || {
         1095         _failure "Error: the newly generated keyfile does not seem valid." }
         1096 
         1097     # Copy the new key as the original keyfile name
         1098     cp -f "${tmpnewkey}" $TOMBKEYFILE
         1099     _success "Your passphrase was successfully updated."
         1100 
         1101     return 0
         1102 }
         1103 
         1104 
         1105 # takes care to encrypt a key
         1106 # honored options: --kdf  --tomb-pwd -o
         1107 gen_key() {
         1108     # $1 the password to use; if not set ask user
         1109     # -o is the --cipher-algo to use (string taken by GnuPG)
         1110     local algopt="`option_value -o`"
         1111     local algo="${algopt:-AES256}"
         1112     # here user is prompted for key password
         1113     tombpass=""
         1114     tombpasstmp=""
         1115 
         1116     if [ "$1" = "" ]; then
         1117         while true; do
         1118             # 3 tries to write two times a matching password
         1119             tombpass=`ask_password "Type the new password to secure your key"`
         1120             if [[ $? != 0 ]]; then
         1121                 _failure "User aborted."
         1122             fi
         1123             if [ -z $tombpass ]; then
         1124                 _failure "You set empty password, which is not possible."
         1125             fi
         1126             tombpasstmp=$tombpass
         1127             tombpass=`ask_password "Type the new password to secure your key (again)"`
         1128             if [[ $? != 0 ]]; then
         1129                 _failure "User aborted."
         1130             fi
         1131             if [ "$tombpasstmp" = "$tombpass" ]; then
         1132                 break;
         1133             fi
         1134             unset tombpasstmp
         1135             unset tombpass
         1136         done
         1137     else
         1138         tombpass="$1"
         1139         _verbose "gen_key takes tombpass from CLI argument: ::1 tomb pass::" $tombpass
         1140     fi
         1141 
         1142     header=""
         1143     [[ $KDF == 1 ]] && {
         1144         { option_is_set --kdf } && {
         1145             # KDF is a new key strenghtening technique against brute forcing
         1146             # see: https://github.com/dyne/Tomb/issues/82
         1147             itertime="`option_value --kdf`"
         1148             # removing support of floating points because they can't be type checked well
         1149             if [[ "$itertime" != <-> ]]; then
         1150                 unset tombpass
         1151                 unset tombpasstmp
         1152                 _error "Wrong argument for --kdf: must be an integer number (iteration seconds)."
         1153                 _error "Depending on the speed of machines using this tomb, use 1 to 10, or more"
         1154                 return 1
         1155             fi
         1156             # --kdf takes one parameter: iter time (on present machine) in seconds
         1157             local -i microseconds
         1158             microseconds=$(( itertime * 1000000 ))
         1159             _success "Using KDF, iteration time: ::1 microseconds::" $microseconds
         1160             _message "generating salt"
         1161             pbkdf2_salt=`tomb-kdb-pbkdf2-gensalt`
         1162             _message "calculating iterations"
         1163             pbkdf2_iter=`tomb-kdb-pbkdf2-getiter $microseconds`
         1164             _message "encoding the password"
         1165             # We use a length of 64bytes = 512bits (more than needed!?)
         1166             tombpass=`tomb-kdb-pbkdf2 $pbkdf2_salt $pbkdf2_iter 64 <<<"${tombpass}"`
         1167 
         1168             header="_KDF_pbkdf2sha1_${pbkdf2_salt}_${pbkdf2_iter}_64\n"
         1169         }
         1170     }
         1171 
         1172 
         1173     print $header
         1174 
         1175     # TODO: check result of gpg operation
         1176     cat <<EOF | gpg --openpgp --force-mdc --cipher-algo ${algo} \
         1177         --batch --no-options --no-tty --passphrase-fd 0 --status-fd 2 \
         1178         -o - -c -a
         1179 ${tombpass}
         1180 $TOMBSECRET
         1181 EOF
         1182     # print -n "${tombpass}" \
         1183     #     | gpg --openpgp --force-mdc --cipher-algo ${algo} \
         1184     #     --batch --no-options --no-tty --passphrase-fd 0 --status-fd 2 \
         1185     #     -o - -c -a ${lukskey}
         1186 
         1187     TOMBPASSWORD="$tombpass"    # Set global variable
         1188     unset tombpass
         1189     unset tombpasstmp
         1190 }
         1191 
         1192 # prints an array of ciphers available in gnupg (to encrypt keys)
         1193 list_gnupg_ciphers() {
         1194     # prints an error if GnuPG is not found
         1195     which gpg 2>/dev/null || _failure "gpg (GnuPG) is not found, Tomb cannot function without it."
         1196 
         1197     ciphers=(`gpg --version | awk '
         1198 BEGIN { ciphers=0 }
         1199 /^Cipher:/ { gsub(/,/,""); sub(/^Cipher:/,""); print; ciphers=1; next }
         1200 /^Hash:/ { ciphers=0 }
         1201 { if(ciphers==0) { next } else { gsub(/,/,""); print; } }
         1202 '`)
         1203     print " ${ciphers}"
         1204     return 1
         1205 }
         1206 
         1207 # Steganographic function to bury a key inside an image.
         1208 # Requires steghide(1) to be installed
         1209 bury_key() {
         1210 
         1211     _load_key    # Try loading key from option -k and set TOMBKEY
         1212 
         1213     imagefile=$PARAM
         1214 
         1215     [[ "`file $imagefile`" =~ "JPEG" ]] || {
         1216         _warning "Encode failed: ::1 image file:: is not a jpeg image." $imagefile
         1217         return 1
         1218     }
         1219 
         1220     _success "Encoding key ::1 tomb key:: inside image ::2 image file::" $TOMBKEY $imagefile
         1221     _message "Please confirm the key password for the encoding"
         1222     # We ask the password and test if it is the same encoding the
         1223     # base key, to insure that the same password is used for the
         1224     # encryption and the steganography. This is a standard enforced
         1225     # by Tomb, but it isn't strictly necessary (and having different
         1226     # password would enhance security). Nevertheless here we prefer
         1227     # usability.
         1228 
         1229     { option_is_set --tomb-pwd } && {
         1230         local tombpwd="`option_value --tomb-pwd`"
         1231         _verbose "tomb-pwd = ::1 tomb pass::" $tombpwd
         1232         ask_key_password "$tombpwd"
         1233     } || {
         1234         ask_key_password
         1235     }
         1236     [[ $? != 0 ]] && {
         1237         _warning "Wrong password supplied."
         1238         _failure "You shall not bury a key whose password is unknown to you." }
         1239 
         1240     # We omit armor strings since having them as constants can give
         1241     # ground to effective attacks on steganography
         1242     print - "$TOMBKEY" | awk '
         1243 /^-----/ {next}
         1244 /^Version/ {next}
         1245 {print $0}' \
         1246     | steghide embed --embedfile - --coverfile ${imagefile} \
         1247     -p $TOMBPASSWORD -z 9 -e serpent cbc
         1248     if [ $? != 0 ]; then
         1249         _warning "Encoding error: steghide reports problems."
         1250         res=1
         1251     else
         1252         _success "Tomb key encoded succesfully into image ::1 image file::" $imagefile
         1253         res=0
         1254     fi
         1255 
         1256     return $res
         1257 }
         1258 
         1259 # mandatory 1st arg: the image file where key is supposed to be
         1260 # optional 2nd arg: the password to use (same as key, internal use)
         1261 # optional 3rd arg: the key where to save the result (- for stdout)
         1262 exhume_key() {
         1263     [[ "$1" = "" ]] && {
         1264         _failure "Exhume failed, no image specified" }
         1265 
         1266     local imagefile="$1"  # The image file where to look for the key
         1267     local tombpass="$2"   # (Optional) the password to use (internal use)
         1268     local destkey="$3"    # (Optional) the key file where to save the
         1269     # result (- for stdout)
         1270     local r=1             # Return code (default: fail)
         1271 
         1272     # Ensure the image file is a readable JPEG
         1273     [[ ! -r $imagefile ]] && {
         1274         _failure "Exhume failed, image file not found: ::1 image file::" "${imagefile:-none}" }
         1275     [[ ! $(file "$imagefile") =~ "JP.G" ]] && {
         1276         _failure "Exhume failed: ::1 image file:: is not a jpeg image." $imagefile }
         1277 
         1278     # When a password is passed as argument then always print out
         1279     # the exhumed key on stdout without further checks (internal use)
         1280     [[ -n "$tombpass" ]] && {
         1281         TOMBKEY=$(steghide extract -sf $imagefile -p $tombpass -xf -)
         1282         [[ $? != 0 ]] && {
         1283             _failure "Wrong password or no steganographic key found" }
         1284 
         1285         recover_key $TOMBKEY
         1286 
         1287         return 0
         1288     }
         1289 
         1290     # Ensure we have a valid destination for the key
         1291     [[ -z $destkey ]] && { option_is_set -k } && destkey=$(option_value -k)
         1292     [[ -z $destkey ]] && {
         1293         destkey="-" # No key was specified: fallback to stdout
         1294         _message "printing exhumed key on stdout" }
         1295 
         1296     # Bail out if destination exists, unless -f (force) was passed
         1297     [[ $destkey != "-" && -s $destkey ]] && {
         1298         _warning "File exists: ::1 tomb key::" $destkey
         1299         { option_is_set -f } && {
         1300             _warning "Use of --force selected: overwriting."
         1301             rm -f $destkey
         1302         } || {
         1303             _warning "Make explicit use of --force to overwrite."
         1304             _failure "Refusing to overwrite file. Operation aborted." }
         1305     }
         1306 
         1307     _message "Trying to exhume a key out of image ::1 image file::" $imagefile
         1308     { option_is_set --tomb-pwd } && {
         1309         tombpass=$(option_value --tomb-pwd)
         1310         _verbose "tomb-pwd = ::1 tomb pass::" $tombpass
         1311     } || {
         1312         [[ -n $TOMBPASSWORD ]] && tombpass=$TOMBPASSWORD
         1313     } || {
         1314         tombpass=$(ask_password "Insert password to exhume key from $imagefile")
         1315         [[ $? != 0 ]] && {
         1316             _warning "User aborted password dialog."
         1317             return 1
         1318         }
         1319     }
         1320 
         1321     # Extract the key from the image
         1322     steghide extract -sf $imagefile -p ${tombpass} -xf $destkey
         1323     r=$?
         1324 
         1325     # Report to the user
         1326     [[ "$destkey" = "-" ]] && destkey="stdout"
         1327     [[ $r == 0 ]] && {
         1328         _success "Key succesfully exhumed to ::1 key::." $destkey
         1329     } || {
         1330         _warning "Nothing found in ::1 image file::" $imagefile
         1331     }
         1332 
         1333     return $r
         1334 }
         1335 
         1336 # Produces a printable image of the key contents so a backup on paper
         1337 # can be made and hidden in books etc.
         1338 engrave_key() {
         1339 
         1340     _load_key    # Try loading key from option -k and set TOMBKEYFILE
         1341 
         1342     local keyname=$(basename $TOMBKEYFILE)
         1343     local pngname="$keyname.qr.png"
         1344 
         1345     _success "Rendering a printable QRCode for key: ::1 tomb key file::" $TOMBKEYFILE
         1346     # we omit armor strings to save space
         1347     awk '/^-----/ {next}; /^Version/ {next}; {print $0}' $TOMBKEYFILE \
         1348         | qrencode --size 4 --level H --casesensitive -o $pngname
         1349     [[ $? != 0 ]] && {
         1350         _failure "QREncode reported an error." }
         1351 
         1352     _success "Operation successful:"
         1353     # TODO: only if verbose and/or not silent
         1354     ls -lh $pngname
         1355     file $pngname
         1356 }
         1357 
         1358 # }}} - Key handling
         1359 
         1360 # {{{ Create
         1361 
         1362 # Since version 1.5.3, tomb creation is a three-step process that replaces create_tomb():
         1363 #
         1364 # * dig a .tomb (the large file) using /dev/urandom (takes some minutes at least)
         1365 #
         1366 # * forge a .key (the small file) using /dev/random (good entropy needed)
         1367 #
         1368 # * lock the .tomb file with the key, binding the key to the tomb (requires dm_crypt format)
         1369 
         1370 # Step one - Dig a tomb
         1371 #
         1372 # Synopsis: dig_tomb /path/to/tomb -s sizemebibytes
         1373 #
         1374 # It will create an empty file to be formatted as a loopback
         1375 # filesystem.  Initially the file is filled with random data taken
         1376 # from /dev/urandom to improve overall tomb's security and prevent
         1377 # some attacks aiming at detecting how much data is in the tomb, or
         1378 # which blocks in the filesystem contain that data.
         1379 
         1380 dig_tomb() {
         1381     local    tombpath="$1"    # Path to tomb
         1382     # Require the specification of the size of the tomb (-s) in MiB
         1383     local -i tombsize=$(option_value -s)
         1384 
         1385     _message "Commanded to dig tomb ::1 tomb path::" $tombpath
         1386 
         1387     [[ -n "$tombpath"   ]] || _failure "Missing path to tomb"
         1388     [[ -n "$tombsize"   ]] || _failure "Size argument missing, use -s"
         1389     [[ $tombsize == <-> ]] || _failure "Size must be an integer (mebibytes)"
         1390     [[ $tombsize -ge 10 ]] || _failure "Tombs can't be smaller than 10 mebibytes"
         1391 
         1392     _plot $tombpath          # Set TOMB{PATH,DIR,FILE,NAME}
         1393 
         1394     [[ -e $TOMBPATH ]] && {
         1395         _warning "A tomb exists already. I'm not digging here:"
         1396         ls -lh $TOMBPATH
         1397         return 1
         1398     }
         1399 
         1400     _success "Creating a new tomb in ::1 tomb path::" $TOMBPATH
         1401 
         1402     _message "Generating ::1 tomb file:: of ::2 size::MiB" $TOMBFILE $tombsize
         1403 
         1404     # Ensure that file permissions are safe even if interrupted
         1405     touch $TOMBPATH
         1406     [[ $? = 0 ]] || {
         1407         _warning "Error creating the tomb ::1 tomb path::" $TOMBPATH
         1408         _failure "Operation aborted."
         1409     }
         1410     chmod 0600 $TOMBPATH
         1411 
         1412     _verbose "Data dump using ::1:: from /dev/urandom" ${DD[1]}
         1413     ${=DD} if=/dev/urandom bs=1048576 count=$tombsize of=$TOMBPATH
         1414 
         1415     [[ $? == 0 && -e $TOMBPATH ]] && {
         1416         ls -lh $TOMBPATH
         1417     } || {
         1418         _warning "Error creating the tomb ::1 tomb path::" $TOMBPATH
         1419         _failure "Operation aborted."
         1420     }
         1421 
         1422     _success "Done digging ::1 tomb name::" $TOMBNAME
         1423     _message "Your tomb is not yet ready, you need to forge a key and lock it:"
         1424     _message "tomb forge ::1 tomb path::.key" $TOMBPATH
         1425     _message "tomb lock ::1 tomb path:: -k ::1 tomb path::.key" $TOMBPATH
         1426 
         1427     return 0
         1428 }
         1429 
         1430 # Step two -- Create a detached key to lock a tomb with
         1431 #
         1432 # Synopsis: forge_key [destkey|-k destkey] [-o cipher]
         1433 #
         1434 # Arguments:
         1435 # -k                path to destination keyfile
         1436 # -o                Use an alternate algorithm
         1437 #
         1438 forge_key() {
         1439     # can be specified both as simple argument or using -k
         1440     local destkey="$1"
         1441     { option_is_set -k } && { destkey=$(option_value -k) }
         1442 
         1443     local algo="AES256"  # Default encryption algorithm
         1444 
         1445     [[ -z "$destkey" ]] && {
         1446         _failure "A filename needs to be specified using -k to forge a new key." }
         1447 
         1448 #    _message "Commanded to forge key ::1 key::" $destkey
         1449 
         1450     _check_swap # Ensure the available memory is safe to use
         1451 
         1452     # Ensure GnuPG won't exit with an error before first run
         1453     [[ -r $HOME/.gnupg/pubring.gpg ]] || {
         1454         mkdir -m 0700 $HOME/.gnupg
         1455         touch $HOME/.gnupg/pubring.gpg }
         1456 
         1457     # Do not overwrite any files accidentally
         1458     [[ -r "$destkey" ]] && {
         1459         ls -lh $destkey
         1460         _failure "Forging this key would overwrite an existing file. Operation aborted." }
         1461 
         1462     touch $destkey
         1463     [[ $? == 0 ]] || {
         1464         _warning "Cannot generate encryption key."
         1465         _failure "Operation aborted." }
         1466     chmod 0600 $destkey
         1467 
         1468     # Update algorithm if it was passed on the command line with -o
         1469     { option_is_set -o } && algopt="$(option_value -o)"
         1470     [[ -n "$algopt" ]] && algo=$algopt
         1471 
         1472     _message "Commanded to forge key ::1 key:: with cipher algorithm ::2 algorithm::" \
         1473         $destkey $algo
         1474 
         1475     [[ $KDF == 1 ]] && {
         1476         _message "Using KDF to protect the key password (`option_value --kdf` rounds)"
         1477     }
         1478 
         1479     TOMBKEYFILE="$destkey"    # Set global variable
         1480 
         1481     _warning "This operation takes time, keep using this computer on other tasks,"
         1482     _warning "once done you will be asked to choose a password for your tomb."
         1483     _warning "To make it faster you can move the mouse around."
         1484     _warning "If you are on a server, you can use an Entropy Generation Daemon."
         1485 
         1486     # Use /dev/random as the entropy source, unless --use-urandom is specified
         1487     local random_source=/dev/random
         1488     { option_is_set --use-urandom } && random_source=/dev/urandom
         1489 
         1490     _verbose "Data dump using ::1:: from ::2 source::" ${DD[1]} $random_source
         1491     TOMBSECRET=$(${=DD} bs=1 count=256 if=$random_source)
         1492     [[ $? == 0 ]] || {
         1493         _warning "Cannot generate encryption key."
         1494         _failure "Operation aborted." }
         1495 
         1496     # Here the global variable TOMBSECRET contains the naked secret
         1497 
         1498     _success "Choose the  password of your key: ::1 tomb key::" $TOMBKEYFILE
         1499     _message "(You can also change it later using 'tomb passwd'.)"
         1500     # _user_file $TOMBKEYFILE
         1501 
         1502     tombname="$TOMBKEYFILE" # XXX ???
         1503     # the gen_key() function takes care of the new key's encryption
         1504     { option_is_set --tomb-pwd } && {
         1505         local tombpwd="`option_value --tomb-pwd`"
         1506         _verbose "tomb-pwd = ::1 new pass::" $tombpwd
         1507         gen_key "$tombpwd" >> $TOMBKEYFILE
         1508     } || {
         1509         gen_key >> $TOMBKEYFILE
         1510     }
         1511 
         1512     # load the key contents (set global variable)
         1513     TOMBKEY="${mapfile[$TOMBKEYFILE]}"
         1514 
         1515     # this does a check on the file header
         1516     is_valid_key $TOMBKEY || {
         1517         _warning "The key does not seem to be valid."
         1518         _warning "Dumping contents to screen:"
         1519         print "${mapfile[$TOMBKEY]}"
         1520         _warning "--"
         1521         _sudo umount ${keytmp}
         1522         rm -r $keytmp
         1523         _failure "Operation aborted."
         1524     }
         1525 
         1526     _message "Done forging ::1 key file::" $TOMBKEYFILE
         1527     _success "Your key is ready:"
         1528     ls -lh $TOMBKEYFILE
         1529 }
         1530 
         1531 # Step three -- Lock tomb
         1532 #
         1533 # Synopsis: tomb_lock file.tomb file.tomb.key [-o cipher]
         1534 #
         1535 # Lock the given tomb with the given key file, in fact formatting the
         1536 # loopback volume as a LUKS device.
         1537 # Default cipher 'aes-xts-plain64:sha256'can be overridden with -o
         1538 lock_tomb_with_key() {
         1539     # old default was aes-cbc-essiv:sha256
         1540     # Override with -o
         1541     # for more alternatives refer to cryptsetup(8)
         1542     local cipher="aes-xts-plain64:sha256"
         1543 
         1544     local tombpath="$1"      # First argument is the path to the tomb
         1545 
         1546     [[ -n $tombpath ]] || {
         1547         _warning "No tomb specified for locking."
         1548         _warning "Usage: tomb lock file.tomb file.tomb.key"
         1549         return 1
         1550     }
         1551 
         1552     _plot $tombpath
         1553 
         1554     _message "Commanded to lock tomb ::1 tomb file::" $TOMBFILE
         1555 
         1556     [[ -f $TOMBPATH ]] || {
         1557         _failure "There is no tomb here. You have to dig it first." }
         1558 
         1559     _verbose "Tomb found: ::1 tomb path::" $TOMBPATH
         1560 
         1561     lo_mount $TOMBPATH
         1562     nstloop=`lo_new`
         1563 
         1564     _verbose "Loop mounted on ::1 mount point::" $nstloop
         1565 
         1566     _message "Checking if the tomb is empty (we never step on somebody else's bones)."
         1567     _sudo cryptsetup isLuks ${nstloop}
         1568     if [ $? = 0 ]; then
         1569         # is it a LUKS encrypted nest? then bail out and avoid reformatting it
         1570         _warning "The tomb was already locked with another key."
         1571         _failure "Operation aborted. I cannot lock an already locked tomb. Go dig a new one."
         1572     else
         1573         _message "Fine, this tomb seems empty."
         1574     fi
         1575 
         1576     _load_key    # Try loading key from option -k and set TOMBKEYFILE
         1577 
         1578     # the encryption cipher for a tomb can be set when locking using -c
         1579     { option_is_set -o } && algopt="$(option_value -o)"
         1580     [[ -n "$algopt" ]] && cipher=$algopt
         1581     _message "Locking using cipher: ::1 cipher::" $cipher
         1582 
         1583     # get the pass from the user and check it
         1584     if option_is_set --tomb-pwd; then
         1585         tomb_pwd="`option_value --tomb-pwd`"
         1586         _verbose "tomb-pwd = ::1 tomb pass::" $tomb_pwd
         1587         ask_key_password "$tomb_pwd"
         1588     else
         1589         ask_key_password
         1590     fi
         1591     [[ $? == 0 ]] || _failure "No valid password supplied."
         1592 
         1593     _success "Locking ::1 tomb file:: with ::2 tomb key file::" $TOMBFILE $TOMBKEYFILE
         1594 
         1595     _message "Formatting Luks mapped device."
         1596     _cryptsetup --batch-mode \
         1597         --cipher ${cipher} --key-size 256 --key-slot 0 \
         1598         luksFormat ${nstloop}
         1599     [[ $? == 0 ]] || {
         1600         _warning "cryptsetup luksFormat returned an error."
         1601         _failure "Operation aborted." }
         1602 
         1603     _cryptsetup --cipher ${cipher} luksOpen ${nstloop} tomb.tmp
         1604     [[ $? == 0 ]] || {
         1605         _warning "cryptsetup luksOpen returned an error."
         1606         _failure "Operation aborted." }
         1607 
         1608     _message "Formatting your Tomb with Ext3/Ext4 filesystem."
         1609     _sudo mkfs.ext4 -q -F -j -L $TOMBNAME /dev/mapper/tomb.tmp
         1610 
         1611     [[ $? == 0 ]] || {
         1612         _warning "Tomb format returned an error."
         1613         _warning "Your tomb ::1 tomb file:: may be corrupted." $TOMBFILE }
         1614 
         1615     # Sync
         1616     _sudo cryptsetup luksClose tomb.tmp
         1617 
         1618     _message "Done locking ::1 tomb name:: using Luks dm-crypt ::2 cipher::" $TOMBNAME $cipher
         1619     _success "Your tomb is ready in ::1 tomb path:: and secured with key ::2 tomb key::" \
         1620         $TOMBPATH $TOMBKEYFILE
         1621 
         1622 }
         1623 
         1624 # This function changes the key that locks a tomb
         1625 change_tomb_key() {
         1626     local tombkey="$1"      # Path to the tomb's key file
         1627     local tombpath="$2"     # Path to the tomb
         1628 
         1629     _message "Commanded to reset key for tomb ::1 tomb path::" $tombpath
         1630 
         1631     [[ -z "$tombpath" ]] && {
         1632         _warning "Command 'setkey' needs two arguments: the old key file and the tomb."
         1633         _warning "I.e:  tomb -k new.tomb.key old.tomb.key secret.tomb"
         1634         _failure "Execution aborted."
         1635     }
         1636 
         1637     _check_swap
         1638 
         1639     # this also calls _plot()
         1640     is_valid_tomb $tombpath
         1641 
         1642     lo_mount $TOMBPATH
         1643     nstloop=`lo_new`
         1644     _sudo cryptsetup isLuks ${nstloop}
         1645     # is it a LUKS encrypted nest? we check one more time
         1646     [[ $? == 0 ]] || {
         1647         _failure "Not a valid LUKS encrypted volume: ::1 volume::" $TOMBPATH }
         1648 
         1649     _load_key $tombkey    # Try loading given key and set TOMBKEY and
         1650     # TOMBKEYFILE
         1651     local oldkey=$TOMBKEY
         1652     local oldkeyfile=$TOMBKEYFILE
         1653 
         1654     # we have everything, prepare to mount
         1655     _success "Changing lock on tomb ::1 tomb name::" $TOMBNAME
         1656     _message "Old key: ::1 old key::" $oldkeyfile
         1657 
         1658     # render the mapper
         1659     mapdate=`date +%s`
         1660     # save date of mount in minutes since 1970
         1661     mapper="tomb.$TOMBNAME.$mapdate.$(basename $nstloop)"
         1662 
         1663     # load the old key
         1664     if option_is_set --tomb-old-pwd; then
         1665         tomb_old_pwd="`option_value --tomb-old-pwd`"
         1666         _verbose "tomb-old-pwd = ::1 old pass::" $tomb_old_pwd
         1667         ask_key_password "$tomb_old_pwd"
         1668     else
         1669         ask_key_password
         1670     fi
         1671     [[ $? == 0 ]] || {
         1672         _failure "No valid password supplied for the old key." }
         1673     old_secret=$TOMBSECRET
         1674 
         1675     # luksOpen the tomb (not really mounting, just on the loopback)
         1676     print -R -n - "$old_secret" | _sudo cryptsetup --key-file - \
         1677         luksOpen ${nstloop} ${mapper}
         1678     [[ $? == 0 ]] || _failure "Unexpected error in luksOpen."
         1679 
         1680     _load_key # Try loading new key from option -k and set TOMBKEYFILE
         1681 
         1682     _message "New key: ::1 key file::" $TOMBKEYFILE
         1683 
         1684     if option_is_set --tomb-pwd; then
         1685         tomb_new_pwd="`option_value --tomb-pwd`"
         1686         _verbose "tomb-pwd = ::1 tomb pass::" $tomb_new_pwd
         1687         ask_key_password "$tomb_new_pwd"
         1688     else
         1689         ask_key_password
         1690     fi
         1691     [[ $? == 0 ]] || {
         1692         _failure "No valid password supplied for the new key." }
         1693 
         1694     _tmp_create
         1695     tmpnewkey=$TOMBTMP
         1696     print -R -n - "$TOMBSECRET" >> $tmpnewkey
         1697 
         1698     print -R -n - "$old_secret" | _sudo cryptsetup --key-file - \
         1699         luksChangeKey "$nstloop" "$tmpnewkey"
         1700 
         1701     [[ $? == 0 ]] || _failure "Unexpected error in luksChangeKey."
         1702 
         1703     _sudo cryptsetup luksClose "${mapper}" || _failure "Unexpected error in luksClose."
         1704 
         1705     _success "Succesfully changed key for tomb: ::1 tomb file::" $TOMBFILE
         1706     _message "The new key is: ::1 new key::" $TOMBKEYFILE
         1707 
         1708     return 0
         1709 }
         1710 
         1711 # }}} - Creation
         1712 
         1713 # {{{ Open
         1714 
         1715 # $1 = tombfile $2(optional) = mountpoint
         1716 mount_tomb() {
         1717     local tombpath="$1"    # First argument is the path to the tomb
         1718     [[ -n "$tombpath" ]] || _failure "No tomb name specified for opening."
         1719 
         1720     _message "Commanded to open tomb ::1 tomb name::" $tombpath
         1721 
         1722     _check_swap
         1723 
         1724     # this also calls _plot()
         1725     is_valid_tomb $tombpath
         1726 
         1727     _load_key # Try loading new key from option -k and set TOMBKEYFILE
         1728 
         1729     tombmount="$2"
         1730     [[ "$tombmount" = "" ]] && {
         1731         tombmount=/media/$TOMBNAME
         1732         [[ -d /media ]] || { # no /media found, adopting /run/media/$USER (udisks2 compat)
         1733             tombmount=/run/media/$_USER/$TOMBNAME
         1734         }
         1735         _message "Mountpoint not specified, using default: ::1 mount point::" $tombmount
         1736     }
         1737 
         1738     _success "Opening ::1 tomb file:: on ::2 mount point::" $TOMBNAME $tombmount
         1739 
         1740     lo_mount $TOMBPATH
         1741     nstloop=`lo_new`
         1742 
         1743     _sudo cryptsetup isLuks ${nstloop} || {
         1744         # is it a LUKS encrypted nest? see cryptsetup(1)
         1745         _failure "::1 tomb file:: is not a valid Luks encrypted storage file." $TOMBFILE }
         1746 
         1747     _message "This tomb is a valid LUKS encrypted device."
         1748 
         1749     luksdump="`_sudo cryptsetup luksDump ${nstloop}`"
         1750     tombdump=(`print $luksdump | awk '
         1751         /^Cipher name/ {print $3}
         1752         /^Cipher mode/ {print $3}
         1753         /^Hash spec/   {print $3}'`)
         1754     _message "Cipher is \"::1 cipher::\" mode \"::2 mode::\" hash \"::3 hash::\"" $tombdump[1] $tombdump[2] $tombdump[3]
         1755 
         1756     slotwarn=`print $luksdump | awk '
         1757         BEGIN { zero=0 }
         1758         /^Key slot 0/ { zero=1 }
         1759         /^Key slot.*ENABLED/ { if(zero==1) print "WARN" }'`
         1760     [[ "$slotwarn" == "WARN" ]] && {
         1761         _warning "Multiple key slots are enabled on this tomb. Beware: there can be a backdoor." }
         1762 
         1763     # save date of mount in minutes since 1970
         1764     mapdate=`date +%s`
         1765 
         1766     mapper="tomb.$TOMBNAME.$mapdate.$(basename $nstloop)"
         1767 
         1768     _verbose "dev mapper device: ::1 mapper::" $mapper
         1769     _verbose "Tomb key: ::1 key file::" $TOMBKEYFILE
         1770 
         1771     # take the name only, strip extensions
         1772     _verbose "Tomb name: ::1 tomb name:: (to be engraved)" $TOMBNAME
         1773 
         1774     { option_is_set --tomb-pwd } && {
         1775         tomb_pwd="`option_value --tomb-pwd`"
         1776         _verbose "tomb-pwd = ::1 tomb pass::" $tomb_pwd
         1777         ask_key_password "$tomb_pwd"
         1778     } || {
         1779         ask_key_password
         1780     }
         1781     [[ $? == 0 ]] || _failure "No valid password supplied."
         1782 
         1783     _cryptsetup luksOpen ${nstloop} ${mapper}
         1784     [[ $? = 0 ]] || {
         1785         _failure "Failure mounting the encrypted file." }
         1786 
         1787     # preserve the loopdev after exit
         1788     lo_preserve "$nstloop"
         1789 
         1790     # array: [ cipher, keysize, loopdevice ]
         1791     tombstat=(`_sudo cryptsetup status ${mapper} | awk '
         1792     /cipher:/  {print $2}
         1793     /keysize:/ {print $2}
         1794     /device:/  {print $2}'`)
         1795     _success "Success unlocking tomb ::1 tomb name::" $TOMBNAME
         1796     _verbose "Key size is ::1 size:: for cipher ::2 cipher::" $tombstat[2] $tombstat[1]
         1797 
         1798     _message "Checking filesystem via ::1::" $tombstat[3]
         1799     _sudo fsck -p -C0 /dev/mapper/${mapper}
         1800     _verbose "Tomb engraved as ::1 tomb name::" $TOMBNAME
         1801     _sudo tune2fs -L $TOMBNAME /dev/mapper/${mapper} > /dev/null
         1802 
         1803     # we need root from here on
         1804     _sudo mkdir -p $tombmount
         1805 
         1806     # Default mount options are overridden with the -o switch
         1807     { option_is_set -o } && {
         1808         local oldmountopts=$MOUNTOPTS
         1809         MOUNTOPTS="$(option_value -o)" }
         1810 
         1811     # TODO: safety check MOUNTOPTS
         1812     # safe_mount_options && \
         1813     _sudo mount -o $MOUNTOPTS /dev/mapper/${mapper} ${tombmount}
         1814     # Clean up if the mount failed
         1815     [[ $? == 0 ]] || {
         1816         _warning "Error mounting ::1 mapper:: on ::2 tombmount::" $mapper $tombmount
         1817         [[ $oldmountopts != $MOUNTOPTS ]] && \
         1818           _warning "Are mount options '::1 mount options::' valid?" $MOUNTOPTS
         1819         # TODO: move cleanup to _endgame()
         1820         [[ -d $tombmount ]] && _sudo rmdir $tombmount
         1821         [[ -e /dev/mapper/$mapper ]] && _sudo cryptsetup luksClose $mapper
         1822         # The loop is taken care of in _endgame()
         1823         _failure "Cannot mount ::1 tomb name::" $TOMBNAME
         1824     }
         1825 
         1826     _sudo chown $UID:$GID ${tombmount}
         1827     _sudo chmod 0711 ${tombmount}
         1828 
         1829     _success "Success opening ::1 tomb file:: on ::2 mount point::" $TOMBFILE $tombmount
         1830 
         1831     local tombtty tombhost tombuid tombuser
         1832 
         1833     # print out when it was opened the last time, by whom and where
         1834     [[ -r ${tombmount}/.last ]] && {
         1835         tombsince=$(_cat ${tombmount}/.last)
         1836         tombsince=$(date --date=@$tombsince +%c)
         1837         tombtty=$(_cat ${tombmount}/.tty)
         1838         tombhost=$(_cat ${tombmount}/.host)
         1839         tomblast=$(_cat ${tombmount}/.last)
         1840         tombuid=$(_cat ${tombmount}/.uid | tr -d ' ')
         1841 
         1842         tombuser=$(getent passwd $tombuid)
         1843         tombuser=${tombuser[(ws@:@)1]}
         1844 
         1845         _message "Last visit by ::1 user::(::2 tomb build::) from ::3 tty:: on ::4 host::" $tombuser $tombuid $tombtty $tombhost
         1846         _message "on date ::1 date::" $tombsince
         1847     }
         1848     # write down the UID and TTY that opened the tomb
         1849     rm -f ${tombmount}/.uid
         1850     print $_UID > ${tombmount}/.uid
         1851     rm -f ${tombmount}/.tty
         1852     print $_TTY > ${tombmount}/.tty
         1853     # also the hostname
         1854     rm -f ${tombmount}/.host
         1855     hostname > ${tombmount}/.host
         1856     # and the "last time opened" information
         1857     # in minutes since 1970, this is printed at next open
         1858     rm -f ${tombmount}/.last
         1859     date +%s > ${tombmount}/.last
         1860     # human readable: date --date=@"`cat .last`" +%c
         1861 
         1862 
         1863     # process bind-hooks (mount -o bind of directories)
         1864     # and post-hooks (execute on open)
         1865     { option_is_set -n } || {
         1866         exec_safe_bind_hooks ${tombmount}
         1867         exec_safe_post_hooks ${tombmount} open }
         1868 
         1869     return 0
         1870 }
         1871 
         1872 ## HOOKS EXECUTION
         1873 #
         1874 # Execution of code inside a tomb may present a security risk, e.g.,
         1875 # if the tomb is shared or compromised, an attacker could embed
         1876 # malicious code.  When in doubt, open the tomb with the -n switch in
         1877 # order to skip this feature and verify the files mount-hooks and
         1878 # bind-hooks inside the tomb yourself before letting them run.
         1879 
         1880 # Mount files and directories from the tomb to the current user's HOME.
         1881 #
         1882 # Synopsis: exec_safe_bind_hooks /path/to/mounted/tomb
         1883 #
         1884 # This can be a security risk if you share tombs with untrusted people.
         1885 # In that case, use the -n switch to turn off this feature.
         1886 exec_safe_bind_hooks() {
         1887     local mnt="$1"   # First argument is the mount point of the tomb
         1888 
         1889     # Default mount options are overridden with the -o switch
         1890     [[ -n ${(k)OPTS[-o]} ]] && MOUNTOPTS=${OPTS[-o]}
         1891 
         1892     # No HOME set? Note: this should never happen again.
         1893     [[ -z $HOME ]] && {
         1894         _warning "How pitiful!  A tomb, and no HOME."
         1895         return 1 }
         1896 
         1897     [[ -z $mnt || ! -d $mnt ]] && {
         1898         _warning "Cannot exec bind hooks without a mounted tomb."
         1899         return 1 }
         1900 
         1901     [[ -r "$mnt/bind-hooks" ]] || {
         1902         _verbose "bind-hooks not found in ::1 mount point::" $mnt
         1903         return 1 }
         1904 
         1905     typeset -Al maps        # Maps of files and directories to mount
         1906     typeset -al mounted     # Track already mounted files and directories
         1907 
         1908     # better parsing for bind hooks checks for two separated words on
         1909     # each line, using zsh word separator array subscript
         1910     _bindhooks="${mapfile[${mnt}/bind-hooks]}"
         1911     for h in ${(f)_bindhooks}; do
         1912         s="${h[(w)1]}"
         1913         d="${h[(w)2]}"
         1914         [[ "$s" = "" ]] && { _warning "bind-hooks file is broken"; return 1 }
         1915         [[ "$d" = "" ]] && { _warning "bind-hooks file is broken"; return 1 }
         1916         maps+=($s $d)
         1917         _verbose "bind-hook found: $s -> $d"
         1918     done
         1919     unset _bindhooks
         1920 
         1921     for dir in ${(k)maps}; do
         1922         [[ "${dir[1]}" == "/" || "${dir[1,2]}" == ".." ]] && {
         1923             _warning "bind-hooks map format: local/to/tomb local/to/\$HOME"
         1924             continue }
         1925 
         1926         [[ "${${maps[$dir]}[1]}" == "/" || "${${maps[$dir]}[1,2]}" == ".." ]] && {
         1927             _warning "bind-hooks map format: local/to/tomb local/to/\$HOME.  Rolling back"
         1928             for dir in ${mounted}; do _sudo umount $dir; done
         1929             return 1 }
         1930 
         1931         if [[ ! -r "$HOME/${maps[$dir]}" ]]; then
         1932             _warning "bind-hook target not existent, skipping ::1 home::/::2 subdir::" $HOME ${maps[$dir]}
         1933         elif [[ ! -r "$mnt/$dir" ]]; then
         1934             _warning "bind-hook source not found in tomb, skipping ::1 mount point::/::2 subdir::" $mnt $dir
         1935         else
         1936             _sudo mount -o bind,$MOUNTOPTS $mnt/$dir $HOME/${maps[$dir]} \
         1937                 && mounted+=("$HOME/${maps[$dir]}")
         1938         fi
         1939     done
         1940 }
         1941 
         1942 # Execute automated actions configured in the tomb.
         1943 #
         1944 # Synopsis: exec_safe_post_hooks /path/to/mounted/tomb [open|close]
         1945 #
         1946 # If an executable file named 'post-hooks' is found inside the tomb,
         1947 # run it as a user.  This might need a dialog for security on what is
         1948 # being run, however we expect you know well what is inside your tomb.
         1949 # If you're mounting an untrusted tomb, be safe and use the -n switch
         1950 # to verify what it would run if you let it.  This feature opens the
         1951 # possibility to make encrypted executables.
         1952 exec_safe_post_hooks() {
         1953     local mnt=$1     # First argument is where the tomb is mounted
         1954     local act=$2     # Either 'open' or 'close'
         1955 
         1956     # Only run if post-hooks has the executable bit set
         1957     [[ -x $mnt/post-hooks ]] || return
         1958 
         1959     # If the file starts with a shebang, run it.
         1960     cat $mnt/post-hooks | head -n1 | grep '^#!\s*/' &> /dev/null
         1961     [[ $? == 0 ]] && {
         1962         _success "Post hooks found, executing as user ::1 user name::." $USERNAME
         1963         $mnt/post-hooks $act $mnt
         1964     }
         1965 }
         1966 
         1967 # }}} - Tomb open
         1968 
         1969 # {{{ List
         1970 
         1971 # list all tombs mounted in a readable format
         1972 # $1 is optional, to specify a tomb
         1973 list_tombs() {
         1974 
         1975     local tombname tombmount tombfs tombfsopts tombloop
         1976     local ts tombtot tombused tombavail tombpercent tombp tombsince
         1977     local tombtty tombhost tombuid tombuser
         1978     # list all open tombs
         1979     mounted_tombs=(`list_tomb_mounts $1`)
         1980     [[ ${#mounted_tombs} == 0 ]] && {
         1981         _failure "I can't see any ::1 status:: tomb, may they all rest in peace." ${1:-open} }
         1982 
         1983     for t in ${mounted_tombs}; do
         1984         mapper=`basename ${t[(ws:;:)1]}`
         1985         tombname=${t[(ws:;:)5]}
         1986         tombmount=${t[(ws:;:)2]}
         1987         tombfs=${t[(ws:;:)3]}
         1988         tombfsopts=${t[(ws:;:)4]}
         1989         tombloop=${mapper[(ws:.:)4]}
         1990 
         1991         # calculate tomb size
         1992         ts=`df -hP /dev/mapper/$mapper |
         1993 awk "/mapper/"' { print $2 ";" $3 ";" $4 ";" $5 }'`
         1994         tombtot=${ts[(ws:;:)1]}
         1995         tombused=${ts[(ws:;:)2]}
         1996         tombavail=${ts[(ws:;:)3]}
         1997         tombpercent=${ts[(ws:;:)4]}
         1998         tombp=${tombpercent%%%}
         1999 
         2000         # obsolete way to get the last open date from /dev/mapper
         2001         # which doesn't work when tomb filename contain dots
         2002         # tombsince=`date --date=@${mapper[(ws:.:)3]} +%c`
         2003 
         2004         # find out who opens it from where
         2005         [[ -r ${tombmount}/.tty ]] && {
         2006             tombsince=$(_cat ${tombmount}/.last)
         2007             tombsince=$(date --date=@$tombsince +%c)
         2008             tombtty=$(_cat ${tombmount}/.tty)
         2009             tombhost=$(_cat ${tombmount}/.host)
         2010             tombuid=$(_cat ${tombmount}/.uid | tr -d ' ')
         2011 
         2012             tombuser=$(getent passwd $tombuid)
         2013             tombuser=${tombuser[(ws@:@)1]}
         2014         }
         2015 
         2016         { option_is_set --get-mountpoint } && { print $tombmount; continue }
         2017 
         2018         _message "::1 tombname:: open on ::2 tombmount:: using ::3 tombfsopts::" \
         2019             $tombname $tombmount $tombfsopts
         2020 
         2021         _verbose "::1 tombname:: /dev/::2 tombloop:: device mounted (detach with losetup -d)" $tombname $tombloop
         2022 
         2023         _message "::1 tombname:: open since ::2 tombsince::" $tombname $tombsince
         2024 
         2025         [[ -z "$tombtty" ]] || {
         2026             _message "::1 tombname:: open by ::2 tombuser:: from ::3 tombtty:: on ::4 tombhost::" \
         2027                 $tombname $tombuser $tombtty $tombhost
         2028         }
         2029 
         2030         _message "::1 tombname:: size ::2 tombtot:: of which ::3 tombused:: (::5 tombpercent::%) is used: ::4 tombavail:: free " \
         2031             $tombname $tombtot $tombused $tombavail $tombpercent
         2032 
         2033         [[ ${tombp} -ge 90 ]] && {
         2034             _warning "::1 tombname:: warning: your tomb is almost full!" $tombname
         2035         }
         2036 
         2037         # Now check hooks
         2038         mounted_hooks=(`list_tomb_binds $tombname $tombmount`)
         2039         for h in ${mounted_hooks}; do
         2040             _message "::1 tombname:: hooks ::2 hookname:: on ::3 hookdest::" \
         2041                 $tombname "`basename ${h[(ws:;:)1]}`" ${h[(ws:;:)2]}
         2042         done
         2043     done
         2044 }
         2045 
         2046 
         2047 # Print out an array of mounted tombs (internal use)
         2048 # Format is semi-colon separated list of attributes
         2049 # if 1st arg is supplied, then list only that tomb
         2050 #
         2051 # String positions in the semicolon separated array:
         2052 #
         2053 # 1. full mapper path
         2054 #
         2055 # 2. mountpoint
         2056 #
         2057 # 3. filesystem type
         2058 #
         2059 # 4. mount options
         2060 #
         2061 # 5. tomb name
         2062 list_tomb_mounts() {
         2063     [[ -z "$1" ]] && {
         2064         # list all open tombs
         2065         mount -l \
         2066             | awk '
         2067 BEGIN { main="" }
         2068 /^\/dev\/mapper\/tomb/ {
         2069   if(main==$1) next;
         2070   print $1 ";" $3 ";" $5 ";" $6 ";" $7
         2071   main=$1
         2072 }
         2073 '
         2074     } || {
         2075         # list a specific tomb
         2076         mount -l \
         2077             | awk -vtomb="[$1]" '
         2078 BEGIN { main="" }
         2079 /^\/dev\/mapper\/tomb/ {
         2080   if($7!=tomb) next;
         2081   if(main==$1) next;
         2082   print $1 ";" $3 ";" $5 ";" $6 ";" $7
         2083   main=$1
         2084 }
         2085 '
         2086     }
         2087 }
         2088 
         2089 # list_tomb_binds
         2090 # print out an array of mounted bind hooks (internal use)
         2091 # format is semi-colon separated list of attributes
         2092 # needs two arguments: name of tomb whose hooks belong
         2093 #                      mount tomb
         2094 list_tomb_binds() {
         2095     [[ -z "$2" ]] && {
         2096         _failure "Internal error: list_tomb_binds called without argument." }
         2097 
         2098     # OK well, prepare for some insanity: parsing the mount table on GNU/Linux
         2099     # is like combing a Wookie while he is riding a speedbike down a valley.
         2100 
         2101     typeset -A tombs
         2102     typeset -a binds
         2103     for t in "${(f)$(mount -l | grep '/dev/mapper/tomb.*]$')}"; do
         2104         len="${(w)#t}"
         2105         [[ "${t[(w)$len]}" = "$1" ]] || continue
         2106         tombs+=( ${t[(w)1]} ${t[(w)$len]} )
         2107 
         2108     done
         2109 
         2110     for m in ${(k)tombs}; do
         2111         for p in "${(f)$(cat /proc/mounts):s/\\040(deleted)/}"; do
         2112             # Debian's kernel appends a '\040(deleted)' to the mountpoint in /proc/mounts
         2113             # so if we parse the string as-is then this will break the parsing. How nice of them!
         2114             # Some bugs related to this are more than 10yrs old. Such Debian! Much stable! Very parsing!
         2115             # Bug #711183  umount parser for /proc/mounts broken on stale nfs mount (gets renamed to "/mnt/point (deleted)")
         2116             # Bug #711184  mount should not stat mountpoints on mount
         2117             # Bug #711187  linux-image-3.2.0-4-amd64: kernel should not rename mountpoint if nfs server is dead/unreachable
         2118             [[ "${p[(w)1]}" = "$m" ]] && {
         2119                 [[ "${(q)p[(w)2]}" != "${(q)2}" ]] && {
         2120                     # Our output format:
         2121                     # mapper;mountpoint;fs;flags;name
         2122                     binds+=("$m;${(q)p[(w)2]};${p[(w)3]};${p[(w)4]};${tombs[$m]}") }
         2123             }
         2124         done
         2125     done
         2126 
         2127     # print the results out line by line
         2128     for b in $binds; do print - "$b"; done
         2129 }
         2130 
         2131 # }}} - Tomb list
         2132 
         2133 # {{{ Index and search
         2134 
         2135 # index files in all tombs for search
         2136 # $1 is optional, to specify a tomb
         2137 index_tombs() {
         2138     { command -v updatedb 1>/dev/null 2>/dev/null } || {
         2139         _failure "Cannot index tombs on this system: updatedb (mlocate) not installed." }
         2140 
         2141     updatedbver=`updatedb --version | grep '^updatedb'`
         2142     [[ "$updatedbver" =~ "GNU findutils" ]] && {
         2143         _warning "Cannot use GNU findutils for index/search commands." }
         2144     [[ "$updatedbver" =~ "mlocate" ]] || {
         2145         _failure "Index command needs 'mlocate' to be installed." }
         2146 
         2147     _verbose "$updatedbver"
         2148 
         2149     mounted_tombs=(`list_tomb_mounts $1`)
         2150     [[ ${#mounted_tombs} == 0 ]] && {
         2151         # Considering one tomb
         2152         [[ -n "$1" ]] && {
         2153             _failure "There seems to be no open tomb engraved as [::1::]" $1 }
         2154         # Or more
         2155         _failure "I can't see any open tomb, may they all rest in peace." }
         2156 
         2157     _success "Creating and updating search indexes."
         2158 
         2159     # start the LibreOffice document converter if installed
         2160     { command -v unoconv 1>/dev/null 2>/dev/null } && {
         2161         unoconv -l 2>/dev/null &
         2162         _verbose "unoconv listener launched."
         2163         sleep 1 }
         2164 
         2165     for t in ${mounted_tombs}; do
         2166         mapper=`basename ${t[(ws:;:)1]}`
         2167         tombname=${t[(ws:;:)5]}
         2168         tombmount=${t[(ws:;:)2]}
         2169         [[ -r ${tombmount}/.noindex ]] && {
         2170             _message "Skipping ::1 tomb name:: (.noindex found)." $tombname
         2171             continue }
         2172         _message "Indexing ::1 tomb name:: filenames..." $tombname
         2173         updatedb -l 0 -o ${tombmount}/.updatedb -U ${tombmount}
         2174 
         2175         # here we use swish to index file contents
         2176         [[ $SWISH == 1 ]] && {
         2177             _message "Indexing ::1 tomb name:: contents..." $tombname
         2178             rm -f ${tombmount}/.swishrc
         2179             _message "Generating a new swish-e configuration file: ::1 swish conf::" ${tombmount}/.swishrc
         2180             cat <<EOF > ${tombmount}/.swishrc
         2181 # index directives
         2182 DefaultContents TXT*
         2183 IndexDir $tombmount
         2184 IndexFile $tombmount/.swish
         2185 # exclude images
         2186 FileRules filename regex /\.jp.?g/i
         2187 FileRules filename regex /\.png/i
         2188 FileRules filename regex /\.gif/i
         2189 FileRules filename regex /\.tiff/i
         2190 FileRules filename regex /\.svg/i
         2191 FileRules filename regex /\.xcf/i
         2192 FileRules filename regex /\.eps/i
         2193 FileRules filename regex /\.ttf/i
         2194 # exclude audio
         2195 FileRules filename regex /\.mp3/i
         2196 FileRules filename regex /\.ogg/i
         2197 FileRules filename regex /\.wav/i
         2198 FileRules filename regex /\.mod/i
         2199 FileRules filename regex /\.xm/i
         2200 # exclude video
         2201 FileRules filename regex /\.mp4/i
         2202 FileRules filename regex /\.avi/i
         2203 FileRules filename regex /\.ogv/i
         2204 FileRules filename regex /\.ogm/i
         2205 FileRules filename regex /\.mkv/i
         2206 FileRules filename regex /\.mov/i
         2207 FileRules filename regex /\.flv/i
         2208 FileRules filename regex /\.webm/i
         2209 # exclude system
         2210 FileRules filename is ok
         2211 FileRules filename is lock
         2212 FileRules filename is control
         2213 FileRules filename is status
         2214 FileRules filename is proc
         2215 FileRules filename is sys
         2216 FileRules filename is supervise
         2217 FileRules filename regex /\.asc$/i
         2218 FileRules filename regex /\.gpg$/i
         2219 # pdf and postscript
         2220 FileFilter .pdf pdftotext "'%p' -"
         2221 FileFilter .ps  ps2txt "'%p' -"
         2222 # compressed files
         2223 FileFilterMatch lesspipe "%p" /\.tgz$/i
         2224 FileFilterMatch lesspipe "%p" /\.zip$/i
         2225 FileFilterMatch lesspipe "%p" /\.gz$/i
         2226 FileFilterMatch lesspipe "%p" /\.bz2$/i
         2227 FileFilterMatch lesspipe "%p" /\.Z$/
         2228 # spreadsheets
         2229 FileFilterMatch unoconv "-d spreadsheet -f csv --stdout %P" /\.xls.*/i
         2230 FileFilterMatch unoconv "-d spreadsheet -f csv --stdout %P" /\.xlt.*/i
         2231 FileFilter .ods unoconv "-d spreadsheet -f csv --stdout %P"
         2232 FileFilter .ots unoconv "-d spreadsheet -f csv --stdout %P"
         2233 FileFilter .dbf unoconv "-d spreadsheet -f csv --stdout %P"
         2234 FileFilter .dif unoconv "-d spreadsheet -f csv --stdout %P"
         2235 FileFilter .uos unoconv "-d spreadsheet -f csv --stdout %P"
         2236 FileFilter .sxc unoconv "-d spreadsheet -f csv --stdout %P"
         2237 # word documents
         2238 FileFilterMatch unoconv "-d document -f txt --stdout %P" /\.doc.*/i
         2239 FileFilterMatch unoconv "-d document -f txt --stdout %P" /\.odt.*/i
         2240 FileFilterMatch unoconv "-d document -f txt --stdout %P" /\.rtf.*/i
         2241 FileFilterMatch unoconv "-d document -f txt --stdout %P" /\.tex$/i
         2242 # native html support
         2243 IndexContents HTML* .htm .html .shtml
         2244 IndexContents XML*  .xml
         2245 EOF
         2246 
         2247             swish-e -c ${tombmount}/.swishrc -S fs -v3
         2248         }
         2249         _message "Search index updated."
         2250     done
         2251 }
         2252 
         2253 search_tombs() {
         2254     { command -v locate 1>/dev/null 2>/dev/null } || {
         2255         _failure "Cannot index tombs on this system: updatedb (mlocate) not installed." }
         2256 
         2257     updatedbver=`updatedb --version | grep '^updatedb'`
         2258     [[ "$updatedbver" =~ "GNU findutils" ]] && {
         2259         _warning "Cannot use GNU findutils for index/search commands." }
         2260     [[ "$updatedbver" =~ "mlocate" ]] || {
         2261         _failure "Index command needs 'mlocate' to be installed." }
         2262 
         2263     _verbose "$updatedbver"
         2264 
         2265     # list all open tombs
         2266     mounted_tombs=(`list_tomb_mounts`)
         2267     [[ ${#mounted_tombs} == 0 ]] && {
         2268         _failure "I can't see any open tomb, may they all rest in peace." }
         2269 
         2270     _success "Searching for: ::1::" ${(f)@}
         2271     for t in ${mounted_tombs}; do
         2272         _verbose "Checking for index: ::1::" ${t}
         2273         mapper=`basename ${t[(ws:;:)1]}`
         2274         tombname=${t[(ws:;:)5]}
         2275         tombmount=${t[(ws:;:)2]}
         2276         [[ -r ${tombmount}/.updatedb ]] && {
         2277             # Use mlocate to search hits on filenames
         2278             _message "Searching filenames in tomb ::1 tomb name::" $tombname
         2279             locate -d ${tombmount}/.updatedb -e -i "${(f)@}"
         2280             _message "Matches found: ::1 matches::" \
         2281                 $(locate -d ${tombmount}/.updatedb -e -i -c ${(f)@})
         2282 
         2283             # Use swish-e to search over contents
         2284             [[ $SWISH == 1 && -r $tombmount/.swish ]] && {
         2285                 _message "Searching contents in tomb ::1 tomb name::" $tombname
         2286                 swish-e -w ${=@} -f $tombmount/.swish -H0 }
         2287         } || {
         2288             _warning "Skipping tomb ::1 tomb name::: not indexed." $tombname
         2289             _warning "Run 'tomb index' to create indexes." }
         2290     done
         2291     _message "Search completed."
         2292 }
         2293 
         2294 # }}} - Index and search
         2295 
         2296 # {{{ Resize
         2297 
         2298 # resize tomb file size
         2299 resize_tomb() {
         2300     local tombpath="$1"    # First argument is the path to the tomb
         2301 
         2302     _message "Commanded to resize tomb ::1 tomb name:: to ::2 size:: mebibytes." $1 $OPTS[-s]
         2303 
         2304     [[ -z "$tombpath" ]] && _failure "No tomb name specified for resizing."
         2305     [[ ! -r $tombpath ]] && _failure "Cannot find ::1::" $tombpath
         2306 
         2307     newtombsize="`option_value -s`"
         2308     [[ -z "$newtombsize" ]] && {
         2309         _failure "Aborting operations: new size was not specified, use -s" }
         2310 
         2311     # this also calls _plot()
         2312     is_valid_tomb $tombpath
         2313 
         2314     _load_key # Try loading new key from option -k and set TOMBKEYFILE
         2315 
         2316     local oldtombsize=$(( `stat -c %s "$TOMBPATH" 2>/dev/null` / 1048576 ))
         2317     local mounted_tomb=`mount -l |
         2318         awk -vtomb="[$TOMBNAME]" '/^\/dev\/mapper\/tomb/ { if($7==tomb) print $1 }'`
         2319 
         2320     # Tomb must not be open
         2321     [[ -z "$mounted_tomb" ]] || {
         2322         _failure "Please close the tomb ::1 tomb name:: before trying to resize it." $TOMBNAME }
         2323     # New tomb size must be specified
         2324     [[ -n "$newtombsize" ]] || {
         2325         _failure "You must specify the new size of ::1 tomb name::" $TOMBNAME }
         2326     # New tomb size must be an integer
         2327     [[ $newtombsize == <-> ]] || _failure "Size is not an integer."
         2328 
         2329     # Tombs can only grow in size
         2330     if [[ "$newtombsize" -gt "$oldtombsize" ]]; then
         2331 
         2332         delta="$(( $newtombsize - $oldtombsize ))"
         2333 
         2334         _message "Generating ::1 tomb file:: of ::2 size::MiB" $TOMBFILE $newtombsize
         2335 
         2336         _verbose "Data dump using ::1:: from /dev/urandom" ${DD[1]}
         2337         ${=DD} if=/dev/urandom bs=1048576 count=${delta} >> $TOMBPATH
         2338         [[ $? == 0 ]] || {
         2339             _failure "Error creating the extra resize ::1 size::, operation aborted." \
         2340                      $tmp_resize }
         2341 
         2342     # If same size this allows to re-launch resize if pinentry expires
         2343     # so that it will continue resizing without appending more space.
         2344     # Resizing the partition to the file size cannot harm data anyway.
         2345     elif [[ "$newtombsize" = "$oldtombsize" ]]; then
         2346         _message "Tomb seems resized already, operating filesystem stretch"
         2347     else
         2348         _failure "The new size must be greater then old tomb size."
         2349     fi
         2350 
         2351     { option_is_set --tomb-pwd } && {
         2352         tomb_pwd="`option_value --tomb-pwd`"
         2353         _verbose "tomb-pwd = ::1 tomb pass::" $tomb_pwd
         2354         ask_key_password "$tomb_pwd"
         2355     } || {
         2356         ask_key_password
         2357     }
         2358     [[ $? == 0 ]] || _failure "No valid password supplied."
         2359 
         2360     lo_mount "$TOMBPATH"
         2361     nstloop=`lo_new`
         2362 
         2363     mapdate=`date +%s`
         2364     mapper="tomb.$TOMBNAME.$mapdate.$(basename $nstloop)"
         2365 
         2366     _message "opening tomb"
         2367     _cryptsetup luksOpen ${nstloop} ${mapper} || {
         2368         _failure "Failure mounting the encrypted file." }
         2369 
         2370     _sudo cryptsetup resize "${mapper}" || {
         2371         _failure "cryptsetup failed to resize ::1 mapper::" $mapper }
         2372 
         2373     _sudo e2fsck -p -f /dev/mapper/${mapper} || {
         2374         _failure "e2fsck failed to check ::1 mapper::" $mapper }
         2375 
         2376     _sudo resize2fs /dev/mapper/${mapper} || {
         2377         _failure "resize2fs failed to resize ::1 mapper::" $mapper }
         2378 
         2379     # close and free the loop device
         2380     _sudo cryptsetup luksClose "${mapper}"
         2381 
         2382     return 0
         2383 }
         2384 
         2385 # }}}
         2386 
         2387 # {{{ Close
         2388 
         2389 umount_tomb() {
         2390     local tombs how_many_tombs
         2391     local pathmap mapper tombname tombmount loopdev
         2392     local ans pidk pname
         2393 
         2394     if [ "$1" = "all" ]; then
         2395         mounted_tombs=(`list_tomb_mounts`)
         2396     else
         2397         mounted_tombs=(`list_tomb_mounts $1`)
         2398     fi
         2399 
         2400     [[ ${#mounted_tombs} == 0 ]] && {
         2401         _failure "There is no open tomb to be closed." }
         2402 
         2403     [[ ${#mounted_tombs} -gt 1 && -z "$1" ]] && {
         2404         _warning "Too many tombs mounted, please specify one (see tomb list)"
         2405         _warning "or issue the command 'tomb close all' to close them all."
         2406         _failure "Operation aborted." }
         2407 
         2408     for t in ${mounted_tombs}; do
         2409         mapper=`basename ${t[(ws:;:)1]}`
         2410 
         2411         # strip square parens from tombname
         2412         tombname=${t[(ws:;:)5]}
         2413         tombmount=${t[(ws:;:)2]}
         2414         tombfs=${t[(ws:;:)3]}
         2415         tombfsopts=${t[(ws:;:)4]}
         2416         tombloop=${mapper[(ws:.:)4]}
         2417 
         2418         _verbose "Name: ::1 tomb name::" $tombname
         2419         _verbose "Mount: ::1 mount point::" $tombmount
         2420         _verbose "Mapper: ::1 mapper::" $mapper
         2421 
         2422         [[ -e "$mapper" ]] && {
         2423             _warning "Tomb not found: ::1 tomb file::" $1
         2424             _warning "Please specify an existing tomb."
         2425             return 0 }
         2426 
         2427         [[ -n $SLAM ]] && {
         2428             _success "Slamming tomb ::1 tomb name:: mounted on ::2 mount point::" \
         2429                 $tombname $tombmount
         2430             _message "Kill all processes busy inside the tomb."
         2431             { slam_tomb "$tombmount" } || {
         2432                 _failure "Cannot slam the tomb ::1 tomb name::" $tombname }
         2433         } || {
         2434             _message "Closing tomb ::1 tomb name:: mounted on ::2 mount point::" \
         2435                 $tombname $tombmount }
         2436 
         2437         # check if there are binded dirs and close them
         2438         bind_tombs=(`list_tomb_binds $tombname $tombmount`)
         2439         for b in ${bind_tombs}; do
         2440             bind_mapper="${b[(ws:;:)1]}"
         2441             bind_mount="${b[(ws:;:)2]}"
         2442             _message "Closing tomb bind hook: ::1 hook::" $bind_mount
         2443             _sudo umount "`print - ${bind_mount}`" || {
         2444                 [[ -n $SLAM ]] && {
         2445                     _success "Slamming tomb: killing all processes using this hook."
         2446                     slam_tomb "`print - ${bind_mount}`" || _failure "Cannot slam the bind hook ::1 hook::" $bind_mount
         2447                     umount "`print - ${bind_mount}`" || _failure "Cannot slam the bind hook ::1 hook::" $bind_mount
         2448                 } || {
         2449                     _failure "Tomb bind hook ::1 hook:: is busy, cannot close tomb." $bind_mount
         2450                 }
         2451             }
         2452         done
         2453 
         2454         # Execute post-hooks for eventual cleanup
         2455         { option_is_set -n } || {
         2456             exec_safe_post_hooks ${tombmount%%/} close }
         2457 
         2458         _verbose "Performing umount of ::1 mount point::" $tombmount
         2459         _sudo umount ${tombmount}
         2460         [[ $? = 0 ]] || { _failure "Tomb is busy, cannot umount!" }
         2461 
         2462         # If we used a default mountpoint and is now empty, delete it
         2463         tombname_regex=${tombname//\[/}
         2464         tombname_regex=${tombname_regex//\]/}
         2465 
         2466         [[ "$tombmount" -regex-match "[/run]?/media[/$_USER]?/$tombname_regex" ]] && {
         2467             _sudo rmdir $tombmount }
         2468 
         2469         _sudo cryptsetup luksClose $mapper
         2470         [[ $? == 0 ]] || {
         2471             _failure "Error occurred in cryptsetup luksClose ::1 mapper::" $mapper }
         2472 
         2473         # Normally the loopback device is detached when unused
         2474         [[ -e "/dev/$tombloop" ]] && _sudo losetup -d "/dev/$tombloop"
         2475         [[ $? = 0 ]] || {
         2476             _verbose "/dev/$tombloop was already closed." }
         2477 
         2478         _success "Tomb ::1 tomb name:: closed: your bones will rest in peace." $tombname
         2479 
         2480     done # loop across mounted tombs
         2481 
         2482     return 0
         2483 }
         2484 
         2485 # Kill all processes using the tomb
         2486 slam_tomb() {
         2487     # $1 = tomb mount point
         2488     if [[ -z `fuser -m "$1" 2>/dev/null` ]]; then
         2489         return 0
         2490     fi
         2491     #Note: shells are NOT killed by INT or TERM, but they are killed by HUP
         2492     for s in TERM HUP KILL; do
         2493         _verbose "Sending ::1:: to processes inside the tomb:" $s
         2494         if option_is_set -D; then
         2495             ps -fp `fuser -m /media/a.tomb 2>/dev/null`|
         2496             while read line; do
         2497                 _verbose $line
         2498             done
         2499         fi
         2500         fuser -s -m "$1" -k -M -$s
         2501         if [[ -z `fuser -m "$1" 2>/dev/null` ]]; then
         2502             return 0
         2503         fi
         2504         if ! option_is_set -f; then
         2505             sleep 3
         2506         fi
         2507     done
         2508     return 1
         2509 }
         2510 
         2511 # }}} - Tomb close
         2512 
         2513 # {{{ Main routine
         2514 
         2515 main() {
         2516 
         2517     _ensure_dependencies  # Check dependencies are present or bail out
         2518 
         2519     local -A subcommands_opts
         2520     ### Options configuration
         2521     #
         2522     # Hi, dear developer!  Are you trying to add a new subcommand, or
         2523     # to add some options?  Well, keep in mind that option names are
         2524     # global: they cannot bear a different meaning or behaviour across
         2525     # subcommands.  The only exception is "-o" which means: "options
         2526     # passed to the local subcommand", and thus can bear a different
         2527     # meaning for different subcommands.
         2528     #
         2529     # For example, "-s" means "size" and accepts one argument. If you
         2530     # are tempted to add an alternate option "-s" (e.g., to mean
         2531     # "silent", and that doesn't accept any argument) DON'T DO IT!
         2532     #
         2533     # There are two reasons for that:
         2534     #    I. Usability; users expect that "-s" is "size"
         2535     #   II. Option parsing WILL EXPLODE if you do this kind of bad
         2536     #       things (it will complain: "option defined more than once")
         2537     #
         2538     # If you want to use the same option in multiple commands then you
         2539     # can only use the non-abbreviated long-option version like:
         2540     # -force and NOT -f
         2541     #
         2542     main_opts=(q -quiet=q D -debug=D h -help=h v -version=v f -force=f -tmp: U: G: T: -no-color -unsafe)
         2543     subcommands_opts[__default]=""
         2544     # -o in open and mount is used to pass alternate mount options
         2545     subcommands_opts[open]="n -nohook=n k: -kdf: o: -ignore-swap -tomb-pwd: "
         2546     subcommands_opts[mount]=${subcommands_opts[open]}
         2547 
         2548     subcommands_opts[create]="" # deprecated, will issue warning
         2549 
         2550     # -o in forge and lock is used to pass an alternate cipher.
         2551     subcommands_opts[forge]="-ignore-swap k: -kdf: o: -tomb-pwd: -use-urandom "
         2552     subcommands_opts[dig]="-ignore-swap s: -size=s "
         2553     subcommands_opts[lock]="-ignore-swap k: -kdf: o: -tomb-pwd: "
         2554     subcommands_opts[setkey]="k: -ignore-swap -kdf: -tomb-old-pwd: -tomb-pwd: "
         2555     subcommands_opts[engrave]="k: "
         2556 
         2557     subcommands_opts[passwd]="k: -ignore-swap -kdf: -tomb-old-pwd: -tomb-pwd: "
         2558     subcommands_opts[close]=""
         2559     subcommands_opts[help]=""
         2560     subcommands_opts[slam]=""
         2561     subcommands_opts[list]="-get-mountpoint "
         2562 
         2563     subcommands_opts[index]=""
         2564     subcommands_opts[search]=""
         2565 
         2566     subcommands_opts[help]=""
         2567     subcommands_opts[bury]="k: -tomb-pwd: "
         2568     subcommands_opts[exhume]="k: -tomb-pwd: "
         2569     # subcommands_opts[decompose]=""
         2570     # subcommands_opts[recompose]=""
         2571     # subcommands_opts[install]=""
         2572     subcommands_opts[askpass]=""
         2573     subcommands_opts[source]=""
         2574     subcommands_opts[resize]="-ignore-swap s: -size=s k: -tomb-pwd: "
         2575     subcommands_opts[check]="-ignore-swap "
         2576     #    subcommands_opts[translate]=""
         2577 
         2578     ### Detect subcommand
         2579     local -aU every_opts #every_opts behave like a set; that is, an array with unique elements
         2580     for optspec in $subcommands_opts$main_opts; do
         2581         for opt in ${=optspec}; do
         2582             every_opts+=${opt}
         2583         done
         2584     done
         2585     local -a oldstar
         2586     oldstar=("${(@)argv}")
         2587     #### detect early: useful for --option-parsing
         2588     zparseopts -M -D -Adiscardme ${every_opts}
         2589     if [[ -n ${(k)discardme[--option-parsing]} ]]; then
         2590         print $1
         2591         if [[ -n "$1" ]]; then
         2592             return 1
         2593         fi
         2594         return 0
         2595     fi
         2596     unset discardme
         2597     if ! zparseopts -M -E -D -Adiscardme ${every_opts}; then
         2598         _failure "Error parsing."
         2599         return 127
         2600     fi
         2601     unset discardme
         2602     subcommand=$1
         2603     if [[ -z $subcommand ]]; then
         2604         subcommand="__default"
         2605     fi
         2606 
         2607     if [[ -z ${(k)subcommands_opts[$subcommand]} ]]; then
         2608         _warning "There's no such command \"::1 subcommand::\"." $subcommand
         2609         exitv=127 _failure "Please try -h for help."
         2610     fi
         2611     argv=("${(@)oldstar}")
         2612     unset oldstar
         2613 
         2614     ### Parsing global + command-specific options
         2615     # zsh magic: ${=string} will split to multiple arguments when spaces occur
         2616     set -A cmd_opts ${main_opts} ${=subcommands_opts[$subcommand]}
         2617     # if there is no option, we don't need parsing
         2618     if [[ -n $cmd_opts ]]; then
         2619         zparseopts -M -E -D -AOPTS ${cmd_opts}
         2620         if [[ $? != 0 ]]; then
         2621             _warning "Some error occurred during option processing."
         2622             exitv=127 _failure "See \"tomb help\" for more info."
         2623         fi
         2624     fi
         2625     #build PARAM (array of arguments) and check if there are unrecognized options
         2626     ok=0
         2627     PARAM=()
         2628     for arg in $*; do
         2629         if [[ $arg == '--' || $arg == '-' ]]; then
         2630             ok=1
         2631             continue #it shouldn't be appended to PARAM
         2632         elif [[ $arg[1] == '-'  ]]; then
         2633             if [[ $ok == 0 ]]; then
         2634                 exitv=127 _failure "Unrecognized option ::1 arg:: for subcommand ::2 subcommand::" $arg $subcommand
         2635             fi
         2636         fi
         2637         PARAM+=$arg
         2638     done
         2639     # First parameter actually is the subcommand: delete it and shift
         2640     [[ $subcommand != '__default' ]] && { PARAM[1]=(); shift }
         2641 
         2642     ### End parsing command-specific options
         2643 
         2644     # Use colors unless told not to
         2645     { ! option_is_set --no-color } && { autoload -Uz colors && colors }
         2646     # Some options are only available during insecure mode
         2647     { ! option_is_set --unsafe } && {
         2648         for opt in --tomb-pwd --use-urandom --tomb-old-pwd; do
         2649             { option_is_set $opt } && {
         2650                 exitv=127 _failure "You specified option ::1 option::, which is DANGEROUS and should only be used for testing\nIf you really want so, add --unsafe" $opt }
         2651         done
         2652     }
         2653     # read -t or --tmp flags to set a custom temporary directory
         2654     option_is_set --tmp && TMPPREFIX=$(option_value --tmp)
         2655 
         2656 
         2657     # When we run as root, we remember the original uid:gid to set
         2658     # permissions for the calling user and drop privileges
         2659     _whoami # Reset _UID, _GID, _TTY
         2660 
         2661     [[ "$PARAM" == "" ]] && {
         2662         _verbose "Tomb command: ::1 subcommand::" $subcommand
         2663     } || {
         2664         _verbose "Tomb command: ::1 subcommand:: ::2 param::" $subcommand $PARAM
         2665     }
         2666 
         2667     [[ -z $_UID ]] || {
         2668         _verbose "Caller: uid[::1 uid::], gid[::2 gid::], tty[::3 tty::]." \
         2669             $_UID $_GID $_TTY
         2670     }
         2671 
         2672     _verbose "Temporary directory: $TMPPREFIX"
         2673 
         2674     # Process subcommand
         2675     case "$subcommand" in
         2676 
         2677         # USAGE
         2678         help)
         2679             usage
         2680             ;;
         2681 
         2682         # DEPRECATION notice (leave here as 'create' is still present in old docs)
         2683         create)
         2684             _warning "The create command is deprecated, please use dig, forge and lock instead."
         2685             _warning "For more informations see Tomb's manual page (man tomb)."
         2686             _failure "Operation aborted."
         2687             ;;
         2688 
         2689         # CREATE Step 1: dig -s NN file.tomb
         2690         dig)
         2691             dig_tomb ${=PARAM}
         2692             ;;
         2693 
         2694         # CREATE Step 2: forge file.tomb.key
         2695         forge)
         2696             forge_key ${=PARAM}
         2697             ;;
         2698 
         2699         # CREATE Step 2: lock -k file.tomb.key file.tomb
         2700         lock)
         2701             lock_tomb_with_key ${=PARAM}
         2702             ;;
         2703 
         2704         # Open the tomb
         2705         mount|open)
         2706             mount_tomb ${=PARAM}
         2707             ;;
         2708 
         2709         # Close the tomb
         2710         # `slam` is used to force closing.
         2711         umount|close|slam)
         2712             [[ "$subcommand" ==  "slam" ]] && SLAM=1
         2713             umount_tomb $PARAM[1]
         2714             ;;
         2715 
         2716         # Grow tomb's size
         2717         resize)
         2718             [[ $RESIZER == 0 ]] && {
         2719                 _failure "Resize2fs not installed: cannot resize tombs." }
         2720             resize_tomb $PARAM[1]
         2721             ;;
         2722 
         2723         ## Contents manipulation
         2724 
         2725         # Index tomb contents
         2726         index)
         2727             index_tombs $PARAM[1]
         2728             ;;
         2729 
         2730         # List tombs
         2731         list)
         2732             list_tombs $PARAM[1]
         2733             ;;
         2734 
         2735         # Search tomb contents
         2736         search)
         2737             search_tombs ${=PARAM}
         2738             ;;
         2739 
         2740         ## Locking operations
         2741 
         2742         # Export key to QR Code
         2743         engrave)
         2744             [[ $QRENCODE == 0 ]] && {
         2745                 _failure "QREncode not installed: cannot engrave keys on paper." }
         2746             engrave_key ${=PARAM}
         2747             ;;
         2748 
         2749         # Change password on existing key
         2750         passwd)
         2751             change_passwd $PARAM[1]
         2752             ;;
         2753 
         2754         # Change tomb key
         2755         setkey)
         2756             change_tomb_key ${=PARAM}
         2757             ;;
         2758 
         2759         # STEGANOGRAPHY: hide key inside an image
         2760         bury)
         2761             [[ $STEGHIDE == 0 ]] && {
         2762                 _failure "Steghide not installed: cannot bury keys into images." }
         2763             bury_key $PARAM[1]
         2764             ;;
         2765 
         2766         # STEGANOGRAPHY: read key hidden in an image
         2767         exhume)
         2768             [[ $STEGHIDE == 0 ]] && {
         2769                 _failure "Steghide not installed: cannot exhume keys from images." }
         2770             exhume_key $PARAM[1]
         2771             ;;
         2772 
         2773         ## Internal commands useful to developers
         2774 
         2775         # Make tomb functions available to the calling shell or script
         2776         'source')   return 0 ;;
         2777 
         2778         # Ask user for a password interactively
         2779         askpass)    ask_password $PARAM[1] $PARAM[2] ;;
         2780 
         2781         # Default operation: presentation, or version information with -v
         2782         __default)
         2783             _print "Tomb ::1 version:: - a strong and gentle undertaker for your secrets" $VERSION
         2784             _print "\000"
         2785             _print " Copyright (C) 2007-2015 Dyne.org Foundation, License GNU GPL v3+"
         2786             _print " This is free software: you are free to change and redistribute it"
         2787             _print " For the latest sourcecode go to <http://dyne.org/software/tomb>"
         2788             _print "\000"
         2789             option_is_set -v && {
         2790                 local langwas=$LANG
         2791                 LANG=en
         2792                 _print " This source code is distributed in the hope that it will be useful,"
         2793                 _print " but WITHOUT ANY WARRANTY; without even the implied warranty of"
         2794                 _print " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
         2795                 LANG=$langwas
         2796                 _print " When in need please refer to <http://dyne.org/support>."
         2797                 _print "\000"
         2798                 _print "System utils:"
         2799                 _print "\000"
         2800                 cat <<EOF
         2801   `sudo -V | head -n1`
         2802   `cryptsetup --version`
         2803   `pinentry --version`
         2804   `gpg --version | head -n1` - key forging algorithms (GnuPG symmetric ciphers):
         2805   `list_gnupg_ciphers`
         2806 EOF
         2807                 _print "\000"
         2808                 _print "Optional utils:"
         2809                 _print "\000"
         2810                 _list_optional_tools version
         2811                 return 0
         2812             }
         2813             usage
         2814             ;;
         2815 
         2816         # Reject unknown command and suggest help
         2817         *)
         2818             _warning "Command \"::1 subcommand::\" not recognized." $subcommand
         2819             _message "Try -h for help."
         2820             return 1
         2821             ;;
         2822     esac
         2823     return $?
         2824 }
         2825 
         2826 # }}}
         2827 
         2828 # {{{ Run
         2829 
         2830 main "$@" || exit $?   # Prevent `source tomb source` from exiting
         2831 
         2832 # }}}
         2833 
         2834 # -*- tab-width: 4; indent-tabs-mode:nil; -*-
         2835 # vim: set shiftwidth=4 expandtab: