ttomb - coffin - secure lan file storage on a device (HTM) 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: