coffin

secure lan file storage on a device
git clone git://parazyd.org/coffin.git
Log | Files | Refs | Submodules | README | LICENSE

tomb (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: