tomb

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

tomb (91675B)


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