Bump Standards-Version to 3.9.4
[grml-crypt.git] / grml-crypt
1 #!/bin/bash
2 # Filename:      grml-crypt
3 # Purpose:       Program to format, mount and unmount encrypted devices/files
4 # Authors:       Michael Gebetsroither <gebi@grml.org>
5 # Bug-Reports:   see http://grml.org/bugs/
6 # License:       This file is licensed under the GPL v2.
7 # Latest change: Don Jul 26 19:57:28 CEST 2007 [mika]
8 ################################################################################
9
10
11 ###
12 ### __INCLUDES
13 ###
14 . /etc/grml/sh-lib
15 #. /etc/grml/sysexits-sh
16
17
18
19 ###
20 ### __VARIABLES
21 ###
22
23 verbose_=0
24 DEV_MAPPER_="/dev/mapper"
25 CRYPTSETUP_="cryptsetup"
26 IS_IMAGE_='false'
27 SIZE_="10"
28 SIZE_SET_='false'
29 FSTYPE_="vfat"
30 TARGET_=""
31 VERIFY_PW_=""
32 MKFS_=""
33 DM_NAME_=""
34 DM_PATH_=""
35 ACTION_=""
36 DM_PREFIX_="grml-crypt_"
37 FORCE_='false'
38 FSCK_='false'
39 FSCK_EXTRA_OPTS_=""
40 ENTROPY_SOURCE_='/dev/urandom'
41 OPTIMIZED_MODE_SET_='false'
42 OPTIMIZING_LEVEL_=0
43 CIPHER_SIZE_="256"
44 CIPHER_="aes-cbc-essiv:sha256"
45 ITERATION_TIME_="1000"
46 ADDITIONAL_CRYPTSETUP_ARGS_=""
47 READONLY_SET_='false'
48 ADDITIONAL_MOUNT_ARGS_=""
49 BATCH_MODE_="--batch-mode"
50 PV_='/usr/bin/pv'
51
52 ###
53 ### __FUNCTIONS
54 ###
55
56 function printUsage
57 {
58   cat <<EOT
59 Usage: "$PROG_NAME__" [OPTIONS] action <device/file> [mountpoint]
60
61 $PROG_NAME__ is a wrapper around cryptsetup with LUKS support to format a device
62
63 OPTIONS:
64    -s         size of the loop-filesystem to create, in MB (default=$SIZE_)
65    -t         type of filesystem (default=$FSTYPE_)
66    -r         read only mode (fully supported only by start)
67    -z         insecure mode, using /dev/zero for most of the initialisation (INSECURE!)
68    -o         optimised initialisation mode (should be as secure as the default but faster)
69    -y         verifies the passphrase by asking for it twice
70    -f         force file overwriting in format mode and/or disable confirmation dialog
71    -F         only for action start: run fsck before mounting the filesystem.
72               Use fsck's -f option if given twice.
73    -X         Read next argument as a list of options to pass to fsck:
74               'grml-crypt -FF -X "-y -T" start /dev/ice' will run fsck with options -y and -T.
75    -m         additional arguments to mount
76    -v         verbose (show what is going on, v++)
77    -h         this help text
78
79 CRYPTSETUP FORMAT OPTIONS:
80    -S         cipher size, could be 128, 192 or 256 (default=$CIPHER_SIZE_)
81    -C         cipher, should be aes-plain for pre-2.6.10 (default=$CIPHER_)
82    -I         iteration time spend with PBKDF2 password  processing in seconds (default=$ITERATION_TIME_)
83    -A         additional arguments for cryptsetup (only supportet by format)
84
85 ACTIONS:
86    format  <device/file> [mountpoint]
87               Format a device or a file (is created with the given size if it
88               does not exist) with the given filesystem and mount it, if a
89               mountpoint was given.
90    start  <device/file> [mountpoint]
91               Mount the device/file in the mountpoint or to a default mountpoint.
92    stop   <mountpoint>
93               Umount the given mountpoint (umount, luksClose, losetup -d)
94
95 EOT
96
97   isGrml && cat <<EOT
98 NOTICE:
99    losetup does _NOT_ work on unionfs => grml-crypt with a filesystem image does ONLY
100    work if the image is on a tmpfs (eg. in /home/grml or /tmp).
101
102 EOT
103
104 }
105
106
107 function getDMName
108 {
109   device_="${1##*/}"
110
111   # first trying normal devicename
112   tmp_="${DM_PREFIX_}${device_}"
113   if [ ! -e "$tmp_" ]; then
114     echo "$tmp_"
115     return 0
116   fi
117
118   # second trying uuid of luks
119   #uuid_=`execute "$CRYPTSETUP_ luksUUID $1"`
120   #if [[ $? == 0 ]]; then
121   #  echo "$prefix_$uuid_"
122   #  return 0
123   #fi
124   warn "could not create device-mapper name for $1"
125   return 1
126 }
127
128
129 function formatDevice
130 {
131   type_="$1"  # could be donothing or init
132   ret_=0
133   local ddcmd_="dd if=/dev/zero of=$DM_PATH_ bs=1M &>/dev/null"
134   if [[ -x "$PV_" && $verbose_ -ge 3 ]] ; then
135     ddcmd_="dd if=/dev/zero bs=1M 2>/dev/null | $PV_ | dd of=$DM_PATH_ bs=1M &>/dev/null"
136   fi
137
138   args_="$VERIFY_PW_ $BATCH_MODE_ --key-size $CIPHER_SIZE_ --cipher $CIPHER_ --iter-time $ITERATION_TIME_ $ADDITIONAL_CRYPTSETUP_ARGS_"
139   #args_=`echo "$args_" |tr -s ' '`
140   execute "$CRYPTSETUP_ $args_ luksFormat $TARGET_" warn || return 1
141
142   execute "$CRYPTSETUP_ luksOpen $TARGET_ $DM_NAME_" warn \
143     "could not open $DM_PATH_ to create a filesystem on it!" || return 1
144   if [[ $type_ == 'init' && $OPTIMIZED_MODE_SET_ == 'true' ]]; then
145     echo "finishing optimised initialisation (this could take some time)"
146     # FIXME
147     execute "$ddcmd_" # || \
148     #  warn "could not finish optimised initialisation properly"
149     ret_=$?
150     # cutted out because of no space left on device error :(
151     #if [[ $ret_ != 0 ]]; then
152     #  execute "$CRYPTSETUP_ luksClose $DM_NAME_" warn
153     #  return 1
154     #fi
155   fi
156
157   execute "$MKFS_ $DM_PATH_ >/dev/null" warn
158   if [[ $? != 0 ]]; then
159     execute "$CRYPTSETUP_ luksClose $DM_NAME_"
160     warn "could not create filesystem on $DM_PATH_" 1
161     return 1
162   else
163     echo "Successully created $FSTYPE_ on encrypted $TARGET_"
164     return 0
165   fi
166 }
167
168
169 function actionStart
170 {
171   ret_=0
172
173   if [[ "$MOUNT_POINT_" == "" ]]; then
174     MOUNT_POINT_="/media/$DM_NAME_"
175   else
176     # error out if mountpoint was given but doesn't exist
177     if [ ! -d "$MOUNT_POINT_" ]; then
178       die "mountpoint $MOUNT_POINT_ does not exist"
179     fi
180   fi
181   # removed due to unionfs problem isLuks does not work with filesystem images
182   # without losetup
183   #$CRYPTSETUP_ isLuks $TARGET_ || die "$TARGET_ is not a luks partition"
184
185   # TARGET (is/should be) a filesystem image
186   if [ ! -b "$TARGET_" ]; then
187     notice "Operating on a file"
188     isExistent "$TARGET_" die "image does not exist"
189     TARGET_=`findNextFreeLoop` || die "could not find a free loop device"
190
191     # TARGET_ is now /dev/loop<x>
192     execute "losetup $TARGET_ $ORIG_TARGET_" die
193   fi
194   cargs_=""
195   $READONLY_SET_ && cargs_='--readonly'
196   execute "$CRYPTSETUP_ $cargs_ luksOpen $TARGET_ $DM_NAME_" warn || execute "losetup -d $TARGET_" || \
197     die "could not luksOpen $TARGET_"
198   if [[ "$FSCK_" == "true" ]] ; then
199     execute "fsck $FSCK_EXTRA_OPTS_ -C $DM_PATH_" || die "fsck failed on $DM_PATH_"
200   elif [[ "$FSCK_" == "trueforce" ]] ; then
201     execute "fsck -f $FSCK_EXTRA_OPTS_ -C $DM_PATH_" || die "fsck failed on $DM_PATH_"
202   fi
203   margs_=""
204   $READONLY_SET_ && margs_='-r'
205   # mountpoint was not given so we use the default one which we need to create first
206   if [ ! -d "$MOUNT_POINT_" ]; then
207     execute "mkdir -p '$MOUNT_POINT_'" || die "failed to create mountpoint $MOUNT_POINT_"
208   fi
209   udevadm settle
210   execute "mount $margs_ $ADDITIONAL_MOUNT_ARGS_ $DM_PATH_ $MOUNT_POINT_" die
211 }
212
213
214 function actionStop
215 {
216   mp_="$1"
217   ret_=0
218
219   isExistent "$mp_" die
220   tmp_=`realpath $mp_` || die "could not get realpath of $mp_"
221   dprint "realpath_=\"$tmp_\""
222
223   dm_path_=`mount |grep "$tmp_ "` || die "$tmp_ is not mounted"
224   dprint "dm_path_=\"$dm_path_\""
225   dm_path_=`echo $dm_path_ |awk '{print $1}'` || die "could not get devicemapper name for $tmp_"
226   dprint "dm_path_=\"$dm_path_\""
227
228   # check for symlinks
229   unset tmp_dm_path_
230   for dmapper in /dev/mapper/grml-crypt* ; do
231     link=$(readlink -f "$dmapper")
232     dprint "looping device mapper devices, dmapper=$dmapper => link=$link"
233     if [ "$link" = "$dm_path_" ] ; then
234       tmp_dm_path_="$dmapper"
235     fi
236   done
237
238   if [ -n "$tmp_dm_path_" ] ; then
239     dm_path_="$tmp_dm_path_"
240     unset tmp_dm_path_
241   fi
242
243   dm_name_="${dm_path_##*/}"
244   dprint "dm_name_=\"$dm_name_\""
245
246   dmsetup info $dm_name_ >/dev/null ||die "$dm_name_ is not active"
247   device_=`$CRYPTSETUP_ status $dm_name_ |awk '/device:/{print $2}'` || \
248     die "could not get underlying device of $dm_path_"
249   dprint "device_=\"$device_\""
250
251   execute "umount $dm_path_" die "could not unmount $device_"
252   if [[ "$MOUNT_POINT_" == "/media/$dm_name_" ]]; then
253     rmdir "$MOUNT_POINT_"
254   fi
255   execute "$CRYPTSETUP_ luksClose $dm_name_" die "could not close $dm_path_"
256   echo "$device_" |grep loop &>/dev/null && execute "losetup -d $device_" \
257     die "could not delete loop device $device_" || \
258     execute "losetup -d $device_ &>/dev/null" eprint "could not delete loop device $device_, \
259 this device possibly is not a loop device => maybe bogus error"
260   notice "$mp_ successfully unmountet/closed/deleted"
261 }
262
263 function yesDialog
264 {
265   msg_="$1"
266
267   echo "WARNING!" >&2
268   echo "========" >&2
269   echo -n "$msg_" >&2
270   echo -n " (type uppercase yes): " >&2
271   read input
272   if [[ $input == 'YES' ]]; then
273     return 0
274   fi
275
276   return 1
277 }
278
279 function actionFormat
280 {
281   IS_IMAGE_='false'
282   ret_=0
283   init_='init'
284   local ddcmd_
285
286   if (( $SIZE_ < 3 )); then
287     die "the minimum size of an encrypted luks partition should be 2"
288   fi
289
290   # TARGET (is/should be) a filesystem image
291   if [ ! -b "$TARGET_" ]; then
292     notice "Operating on a file"
293     IS_IMAGE_='true'
294     if [ -e "$TARGET_" ]; then
295       $FORCE_ || die "$TARGET_ does already exist"
296       warn "overwriting file $TARGET_"
297       init_='donothing'
298     else
299       echo -n "Initialising file with "
300       if [[ $OPTIMIZED_MODE_SET_ == 'true' ]]; then
301         echo "optimised SECURE mode"
302         execute "dd if=/dev/zero of=$TARGET_ bs=1M count=${SIZE_} &>/dev/null" \
303           die "could not initialise $TARGET_ with /dev/zero"
304       else
305         if [[ $ENTROPY_SOURCE_ == '/dev/zero' ]]; then
306           echo "INSERCURE mode"
307         else
308           echo "SECURE mode (taking /dev/urandom as source, this could take some time)"
309         fi
310         execute "dd if=$ENTROPY_SOURCE_ of=$TARGET_ bs=1M count=${SIZE_} &>/dev/null" ||\
311           die "could not initialise $TARGET_ with $ENTROPY_SOURCE_"
312       fi
313     fi
314
315     TARGET_=`findNextFreeLoop` || die "could not find a free loop device"
316
317     # TARGET_ is now /dev/loop<x>
318     execute "losetup $TARGET_ $ORIG_TARGET_" die
319     if [[ $OPTIMIZED_MODE_SET_ == 'true' || $ENTROPY_SOURCE_ == '/dev/zero' ]]; then
320       execute "dd if=/dev/urandom of=$TARGET_ bs=1M count=2 &>/dev/null" \
321       die "could not initialise the fist 2MB of $TARGET_ with /dev/urandom"
322     fi
323     formatDevice "$init_"
324     ret_=$?
325   else
326     $FORCE_ || (yesDialog "Are you shure you want to overwrite $TARGET_ ?" || die 'You are not sure')
327     notice 'Operating on a device'
328     echo -n 'Initialising device with '
329     if [[ $OPTIMIZED_MODE_SET_ == 'true' ]]; then
330       echo "optimised SECURE mode"
331       execute "dd if=/dev/urandom of=$TARGET_ bs=1M count=2 &>/dev/null" ||\
332         die "could not initialise the first 2MB of $TARGET_ with /dev/urandom"
333     elif [[ $ENTROPY_SOURCE_ != '/dev/zero' ]]; then
334       # default mode
335       echo "SECURE mode (taking $ENTROPY_SOURCE_ as source, this could take some time)"
336       ddcmd_="dd if=$ENTROPY_SOURCE_ of=$TARGET_ bs=1M &>/dev/null"
337       if [[ -x "$PV_" && $verbose_ -ge 3 ]] ; then
338         ddcmd_="dd if=$ENTROPY_SOURCE_ bs=1M 2>/dev/null | $PV_ | dd of=$TARGET_ bs=1M &>/dev/null"
339       fi
340       execute "$ddcmd_" # ||\
341         # skipped because "no space left on device" from dd
342         # die "could not initialise $TARGET_ with $ENTROPY_SOURCE_"
343     else
344       echo 'INSECURE mode (only initialising the fist 2MB with /dev/urandom)'
345       execute "dd if=/dev/urandom of=$TARGET_ bs=1M count=2 &>/dev/null" \
346         die "could not initialise the first 2MB of $TARGET_ with /dev/urandom"
347     fi
348
349     formatDevice "$init_"
350     ret_=$?
351   fi
352
353   # formatDevice was successfully
354   if (( $ret_ == 0 )); then
355     # a mountpoint was given (don't luksClose the device)
356     local mount_point_exists_='true'
357     test -d "$MOUNT_POINT_" || mount_point_exists_='false'
358
359     if [[ $MOUNT_POINT_ != "" && "$mount_point_exists_" == 'true' ]]; then
360       $READONLY_SET_ && margs_='-r'
361       execute "mount $margs_ $ADDITIONAL_MOUNT_ARGS_ $DM_PATH_ $MOUNT_POINT_" die
362     else
363       if [[ $MOUNT_POINT_ != "" ]]; then
364         $mount_point_exists_ || warn "mountpoint $MOUNT_POINT_ does not exist, not mounting. please use \"grml-crypt start $ORIG_TARGET_ <mountpoint>\" to start the device"
365       fi
366       execute "$CRYPTSETUP_ luksClose $DM_NAME_" warn
367       $IS_IMAGE_ && execute "losetup -d $TARGET_" warn
368     fi
369   else
370     execute "$CRYPTSETUP_ luksClose $DM_NAME_" warn
371     $IS_IMAGE_ && execute "losetup -d $TARGET_" warn
372   fi
373 }
374
375
376
377 ###
378 ### __MAIN
379 ###
380
381 while getopts "s:t:rzoyfFm:hvS:C:I:A:X:" opt; do
382   case "$opt" in
383     s) SIZE_="$OPTARG"; SIZE_SET_='true' ;;
384     t) FSTYPE_="$OPTARG" ;;
385     r) READONLY_SET_='true' ;;
386     z) let OPTIMIZING_LEVEL_=$OPTIMIZING_LEVEL_+1
387         ENTROPY_SOURCE_='/dev/zero'
388         warn 'initialising from INSECURE source /dev/zero' ;;
389     o) let OPTIMIZING_LEVEL_=$OPTIMIZING_LEVEL_+1
390         OPTIMIZED_MODE_SET_='true' ;;
391     y) VERIFY_PW_="--verify-passphrase" ;;
392     f) FORCE_='true' ;;
393     F) if [[ "$FSCK_" == "true" ]] ; then
394          FSCK_='trueforce'
395        else
396          FSCK_='true'
397        fi
398       ;;
399     X) FSCK_EXTRA_OPTS_="$OPTARG" ;;
400     m) ADDITIONAL_MOUNT_ARGS_="$OPTARG" ;;
401     h) printUsage; exit ;;
402     v) let verbose_=$verbose_+1 ;;
403     S) CIPHER_SIZE_="$OPTARG" ;;
404     C) CIPHER_="$OPTARG" ;;
405     I) ITERATION_TIME_="$OPTARG" ;;
406     A) ADDITIONAL_CRYPTSETUP_ARGS_="$OPTARG" ;;
407     ?) printUsage; exit 64 ;;
408   esac
409 done
410 shift $(($OPTIND - 1))  # set ARGV to the first not parsed commandline parameter
411 setVerbose $verbose_
412
413 checkRoot die "You have to be root to use this program"
414 disableSyslog
415
416 if [[ $1 == 'help' ]]; then
417   printUsage
418   exit 0
419 fi
420 if (( $# < 2 )); then
421   printUsage
422   die "wrong number of arguments ($#)" 1
423 fi
424 if (( $OPTIMIZING_LEVEL_ > 1 )); then
425   printUsage
426   die "please choose ONE initialisation methode"
427 fi
428 TARGET_="$2"
429
430 MKFS_="`which mkfs.$FSTYPE_`"
431 if [ $? != "0" ]; then
432   die "invalid filesystem type \"$FSTYPE_\"" 1
433 fi
434
435 # use batch-mode if available
436 $CRYPTSETUP_ $BATCH_MODE_ --help &>/dev/null;
437 ret_=$?
438 case "$ret_" in
439   0)  dprint "your cryptsetup understands --batch-mode" ;;
440   1)  BATCH_MODE_=""; notice "your cryptsetup does NOT understand --batch-mode, trying without" ;;
441   127)  die "could not execute cryptsetup" 127 ;;
442   *)  warn "problems executing $CRYPTSETUP_" $ret_
443 esac
444
445 DM_NAME_="`getDMName $TARGET_`"
446 DM_PATH_="$DEV_MAPPER_/$DM_NAME_"
447 ORIG_TARGET_="$TARGET_"
448 MOUNT_POINT_="$3"
449
450 case "$1" in
451   format) ACTION_='format'; actionFormat ;;
452   start)  ACTION_='start'; actionStart ;;
453   stop)  ACTION_='stop'; actionStop "$TARGET_" ;;
454   *)  printUsage ;;
455 esac
456
457 # END OF FILE
458 ################################################################################
459 # vim:foldmethod=marker tabstop=2 expandtab shiftwidth=2