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