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