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