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