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