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