tomb

the crypto undertaker
git clone git://parazyd.org/tomb.git
Log | Files | Refs | README | LICENSE

tomb (102333B)


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