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