5969ed2efd545cd82f5fc368d22d19f2cc2f46b9
[platform/upstream/ecryptfs-utils.git] / src / utils / ecryptfs-setup-private
1 #!/bin/sh
2 # This script sets up an ecryptfs mount in a user's ~/Private
3 #
4 # Originally ecryptfs-setup-pam-wrapped.sh by Michael Halcrow, IBM
5 #
6 # Ported for use on Ubuntu by Dustin Kirkland <kirkland@ubuntu.com>
7 # Copyright (C) 2008 Canonical Ltd.
8 # Copyright (C) 2007-2008 International Business Machines
9 PRIVATE_DIR="Private"
10 WRAPPING_PASS="LOGIN"
11 ECRYPTFS_DIR="/home/.ecryptfs"
12 PW_ATTEMPTS=3
13 TEXTDOMAIN="ecryptfs-utils"
14 MESSAGE="$(gettext 'Enter your login passphrase')"
15 CIPHER="aes"
16 KEYBYTES="16"
17 FNEK=
18
19 # Zero out user-defined GREP_OPTIONS, such as --line-number
20 GREP_OPTIONS=
21
22 usage() {
23         echo "
24 Usage:
25
26 $0 [-f|--force] [-w|--wrapping] [--nopwcheck] [-n|--no-fnek]
27   [-u|--username USER] [-l|--loginpass LOGINPASS]
28   [-m|--mountpass MOUNTPASS]
29
30  -f, --force      Force overwriting of an existing setup
31  -w, --wrapping   Use an independent wrapping passphrase,
32                   different from the login passphrase
33  -n, --no-fnek    Do not encrypt filenames; If this flag is
34                   omitted, and the kernel supports filename
35                   encryption, then filenames will be encrypted
36  -u, --username   Username for encrypted private mountpoint,
37                   defaults to yourself
38  -l, --loginpass  Login/Wrapping passphrase for USER,
39                   used to wrap MOUNTPASS
40  --nopwcheck      Do not check the validity of the specified
41                   login password (useful for LDAP user accounts)
42  --noautomount    Setup this user such that the encrypted private
43                   directory is not automatically mounted on login
44  --noautoumount   Setup this user such that the encrypted private
45                   directory is not automatically unmounted at
46                   logout
47  -m, --mountpass  Passphrase for mounting the ecryptfs directory,
48                   defaults to randomly generated $KEYBYTES bytes
49  -b, --bootstrap  Bootstrap a new user's entire home directory
50                   Generates a random mount passphrase, which
51                   will be wrapped when the new login passphrase
52                   is set. SHOULD ONLY BE CALLED FROM 'adduser'.
53  --undo           Provide instructions on how to undo an
54                   encrypted private setup
55
56    Be sure to properly escape your parameters according to your
57    shell's special character nuances, and also surround the
58    parameters by double quotes, if necessary.
59 "
60         exit 1
61 }
62
63 undo_msg() {
64         echo "
65 In the event that you want to remove your eCryptfs Private Directory setup,
66 you will need to very carefully perform the following actions manually:
67
68  1. Obtain your Private directory mountpoint
69    $ PRIVATE=\`cat ~/.ecryptfs/Private.mnt 2>/dev/null || echo \$HOME/$PRIVATE_DIR\`
70  2. Ensure that you have moved all relevant data out of your \$PRIVATE directory
71  3. Unmount your encrypted private directory
72    $ ecryptfs-umount-private
73  4. Make your Private directory writable again
74    $ chmod 700 \$PRIVATE
75  5. Remove \$PRIVATE, ~/.Private, ~/.ecryptfs
76     Note: THIS IS VERY PERMANENT, BE VERY CAREFUL
77    $ rm -rf \$PRIVATE ~/.Private ~/.ecryptfs
78  6. Uninstall the utilities (this is specific to your Linux distribution)
79    $ sudo apt-get remove ecryptfs-utils libecryptfs0
80 "
81 }
82
83 error() {
84         echo "$(gettext 'ERROR: ')" "$@" 1>&2
85         exit 1
86 }
87
88 error_testing() {
89         rm -f "$1" >/dev/null
90         shift
91         /sbin/umount.ecryptfs_private >/dev/null
92         error "$@"
93         exit 1
94 }
95
96 random_passphrase () {
97         bytes=$1
98         # Pull $1 of random data from /dev/urandom,
99         # and convert to a string of hex digits
100         od -x -N $bytes --width=$bytes /dev/urandom | head -n 1 | sed "s/^0000000//" | sed "s/\s*//g"
101 }
102
103 filename_encryption_available() {
104         version=$(cat /sys/fs/ecryptfs/version 2>/dev/null)
105         [ -z "$version" ] && error "$(gettext 'Cannot get ecryptfs version, ecryptfs kernel module not loaded?')"
106         [ $(($version & 0x100)) -eq 0 ] && return 1
107         return 0
108 }
109
110 filename_encryption_available && FNEK="--fnek"
111
112 if [ ! -z "$SUDO_USER" ]; then
113         USER="$SUDO_USER"
114 fi
115
116 while [ ! -z "$1" ]; do
117         case "$1" in
118                 -u|--username)
119                         USER="$2"
120                         shift 2
121                 ;;
122                 -l|--loginpass)
123                         LOGINPASS="$2"
124                         shift 2
125                 ;;
126                 -m|--mountpass)
127                         MOUNTPASS="$2"
128                         shift 2
129                 ;;
130                 -w|--wrapping)
131                         WRAPPING_PASS="INDEPENDENT"
132                         MESSAGE="$(gettext 'Enter your wrapping passphrase')"
133                         shift 1
134                 ;;
135                 -f|--force)
136                         FORCE=1
137                         shift 1
138                 ;;
139                 --nopwcheck)
140                         NOPWCHECK=1
141                         shift 1
142                 ;;
143                 --noautomount)
144                         NOAUTOMOUNT=1
145                         shift 1
146                 ;;
147                 --noautoumount)
148                         NOAUTOUMOUNT=1
149                         shift 1
150                 ;;
151                 --undo)
152                         undo_msg
153                         exit 0
154                 ;;
155                 -b|--bootstrap)
156                         [ `whoami` = "root" ] || error "$(gettext 'You must be root to bootstrap encrypt a home directory')"
157                         BOOTSTRAP=1
158                         MOUNTPASS=`random_passphrase $KEYBYTES`
159                         RANDOM_MOUNTPASS=1
160                         shift 1
161                 ;;
162                 -n|--no-fnek)
163                         FNEK=
164                         shift 1
165                 ;;
166                 *)
167                         usage
168                 ;;
169         esac
170 done
171
172 # Prompt for the USER name, if not on the command line and not in the env
173 if [ -z "$USER" ]; then
174         while [ true ]; do
175                 echo -n "$(gettext 'Enter the username: ')"
176                 USER=`head -n1`
177                 echo
178                 if [ -z "$USER" ]; then
179                         echo "$(gettext 'ERROR: ')" "$(gettext 'You must provide a username')"
180                         continue
181                 else
182                         # Verify that the user exists
183                         if ! id "$USER" >/dev/null; then
184                                 echo "$(gettext 'ERROR: ')" "$(gettext 'User does not exist')" " [$USER]"
185                                 continue
186                         fi
187                         break
188                 fi
189         done
190 else
191         # Verify that the user exists
192         id "$USER" >/dev/null || error "$(gettext 'User does not exist')" "[$USER]"
193 fi
194
195 # Obtain USER's primary group
196 GROUP=$(id -g $USER)
197
198 # Check if the ecryptfs group exists, and user is member of ecryptfs group
199 if grep -qs "^ecryptfs:" /etc/group; then
200         if ! id "$USER" | grep -qs "\(ecryptfs\)"; then
201                error "$(gettext 'User needs to be a member of ecryptfs group')"
202         fi
203 fi
204
205 # Obtain the user's home directory
206 HOME=`getent passwd "$USER" | awk -F: '{print $6}'`
207 if [ ! -d "$HOME" ]; then
208         error "$(gettext 'User home directory does not exist')" "[$HOME]"
209 fi
210
211 if [ "$BOOTSTRAP" = "1" ]; then
212         # If we want to encrypt the entire homedir, we need the .ecryptfs
213         # config dir elsewhere, but linked into the homedir
214         mkdir -p -m 700 $ECRYPTFS_DIR/$USER/.ecryptfs 
215         ln -sf $ECRYPTFS_DIR/$USER/.ecryptfs $HOME/.ecryptfs
216         ln -sf $ECRYPTFS_DIR/$USER/.$PRIVATE_DIR $HOME/.$PRIVATE_DIR
217         MOUNTPOINT="$HOME"
218         CRYPTDIR="$ECRYPTFS_DIR/$USER/.$PRIVATE_DIR"
219 else
220         mkdir -p -m 700 $HOME/.ecryptfs
221         MOUNTPOINT="$HOME/$PRIVATE_DIR"
222         CRYPTDIR="$HOME/.$PRIVATE_DIR"
223 fi
224
225 # Check for previously setup private directory
226 if [ -s "$HOME/.ecryptfs/wrapped-passphrase" -a "$FORCE" != "1" ]; then
227         error "$(gettext 'wrapped-passphrase file already exists, use --force to overwrite.')"
228 fi
229 if [ -s "$HOME/.ecryptfs/$PRIVATE_DIR.sig" -a "$FORCE" != "1" ]; then
230         error "$PRIVATE_DIR.sig" "$(gettext 'file already exists, use --force to overwrite.')"
231 fi
232
233 # Check for active mounts
234 grep -qs "$MOUNTPOINT " /proc/mounts && error "[$MOUNTPOINT]" "$(gettext 'is already mounted')"
235 grep -qs "$CRYPTDIR " /proc/mounts && error "[$CRYPTDIR]" "$(gettext 'is already mounted')"
236
237 # Check that the mount point and encrypted directory are empty (skip symlinks).
238 # Perhaps one day we could provide a migration mode (using rsync or something),
239 # but this would be VERY hard to do safely.
240 count=`ls -Al "$MOUNTPOINT" 2>/dev/null | egrep -c "^[drwx-]{10}"`
241 if [ "$count" != "0" ]; then
242         error "$MOUNTPOINT" "$(gettext 'must be empty before proceeding')"
243 fi
244 count=`ls -Al "$CRYPTDIR" 2>/dev/null | egrep -c "^[dlrwx-]{10}"`
245 if [ "$count" != "0" ]; then
246         error "$CRYPTDIR" "$(gettext 'must be empty before proceeding')"
247 fi
248
249 stty_orig=`stty -g`
250 # Prompt for the LOGINPASS, if not on the command line and not in the env
251 if [ -z "$LOGINPASS" ] && ( [ "$BOOTSTRAP" != "1" ] || [ "$ECRYPTFS_MIGRATE" = "1" ] ); then
252         tries=0
253         while [ $tries -lt $PW_ATTEMPTS ]; do
254                 stty -echo
255                 echo -n "$MESSAGE [$USER]: "
256                 LOGINPASS=`head -n1`
257                 stty $stty_orig
258                 echo
259                 if [ $WRAPPING_PASS != "LOGIN" -o ! -x /sbin/unix_chkpwd ]; then
260                         # If we can't check the accuracy of the user's entered
261                         # passphrase, force them to type it twice (matching)
262                         stty -echo
263                         echo -n "$MESSAGE [$USER] (again): "
264                         LOGINPASS2=`head -n1`
265                         stty $stty_orig
266                         echo
267                         if [ "$LOGINPASS" != "$LOGINPASS2" ]; then
268                                 echo "$(gettext 'ERROR: ')" "$(gettext 'Wrapping passphrases must match')"
269                         else
270                                 break
271                         fi
272                         tries=$(($tries + 1))
273                         continue
274                 fi
275                 if [ -z "$LOGINPASS" ]; then
276                         echo "$(gettext 'ERROR: ')" "$(gettext 'You must provide a login passphrase')"
277                         tries=$(($tries + 1))
278                 else
279                         if [ "$NOPWCHECK" = "1" ]; then
280                                 echo "$(gettext 'INFO:')" "$(gettext 'Skipping password verification')"
281                                 break
282                         else
283                                 if printf "%s\0" "$LOGINPASS" | /sbin/unix_chkpwd "$USER" nullok; then
284                                         break
285                                 else
286                                         echo "$(gettext 'ERROR: ')" "$(gettext 'Your login passphrase is incorrect')"
287                                         tries=$(($tries + 1))
288                                 fi
289                         fi
290                 fi
291         done
292         if [ $tries -ge $PW_ATTEMPTS ]; then
293                 error "$(gettext 'Too many incorrect password attempts, exiting')"
294         fi
295 fi
296
297 # Prompt for the MOUNTPASS, if not on the command line and not in the env
298 if [ -z "$MOUNTPASS" ]; then
299         tries=0
300         while [ $tries -lt $PW_ATTEMPTS ]; do
301                 stty -echo
302                 echo -n "$(gettext 'Enter your mount passphrase [leave blank to generate one]: ')"
303                 MOUNTPASS=`head -n1`
304                 stty $stty_orig
305                 echo
306                 if [ -z "$MOUNTPASS" ]; then
307                         MOUNTPASS=`random_passphrase $KEYBYTES`
308                         RANDOM_MOUNTPASS=1
309                         break
310                 else
311                         stty -echo
312                         echo -n "$(gettext 'Enter your mount passphrase (again): ')"
313                         MOUNTPASS2=`head -n1`
314                         stty $stty_orig
315                         echo
316                         if [ "$MOUNTPASS" != "$MOUNTPASS2" ]; then
317                                 echo "$(gettext 'ERROR: ')" "$(gettext 'Mount passphrases do not match')"
318                                 tries=$(($tries + 1))
319                         else
320                                 break
321                         fi
322                 fi
323         done
324         if [ $tries -ge $PW_ATTEMPTS ]; then
325                 error "$(gettext 'Too many incorrect passphrase attempts, exiting')"
326         fi
327 fi
328
329 echo
330 echo "************************************************************************"
331 echo "$(gettext 'YOU SHOULD RECORD YOUR MOUNT PASSPHRASE AND STORE IT IN A SAFE LOCATION.')"
332 echo "  ecryptfs-unwrap-passphrase ~/.ecryptfs/wrapped-passphrase"
333 echo "$(gettext 'THIS WILL BE REQUIRED IF YOU NEED TO RECOVER YOUR DATA AT A LATER TIME.')"
334 echo "************************************************************************"
335 echo
336
337 ###############################################################################
338
339 # Setup private directory in home
340 mkdir -m 700 -p "$CRYPTDIR" || error "$(gettext 'Could not create crypt directory')" "[$CRYPTDIR]"
341 mkdir -m 700 -p "$MOUNTPOINT" || error "$(gettext 'Could not create mount directory')" "[$MOUNTPOINT]"
342 ln -sf /usr/share/ecryptfs-utils/ecryptfs-mount-private.txt "$MOUNTPOINT"/README.txt
343 ln -sf /usr/share/ecryptfs-utils/ecryptfs-mount-private.desktop "$MOUNTPOINT"/Access-Your-Private-Data.desktop
344 chmod 500 "$MOUNTPOINT"
345
346 # Setup ~/.ecryptfs directory
347 if [ "$NOAUTOMOUNT" = "1" ]; then
348         echo "$(gettext 'INFO:')" "$HOME/$PRIVATE_DIR" "$(gettext 'will not be mounted on login')"
349 else
350         touch $HOME/.ecryptfs/auto-mount || error "$(gettext 'Could not setup ecryptfs auto-mount')"
351 fi
352 if [ "$NOAUTOUMOUNT" = "1" ]; then
353         echo "$(gettext 'INFO:')" "$HOME/$PRIVATE_DIR" "$(gettext 'will not be unmounted on logout')"
354 else
355         touch $HOME/.ecryptfs/auto-umount || error "$(gettext 'Could not setup ecryptfs auto-umount')"
356 fi
357
358 if [ "$WRAPPING_PASS" = "LOGIN" ]; then
359         rm -f $HOME/.ecryptfs/wrapping-independent || error "$(gettext 'Could not remove ecryptfs wrapping-independent')"
360 else
361         touch $HOME/.ecryptfs/wrapping-independent || error "$(gettext 'Could not setup ecryptfs wrapping-independent')"
362 fi
363
364
365 # Backup any existing wrapped-passphrase or sig files; we DO NOT destroy this
366 timestamp=`date +%Y%m%d%H%M%S`
367 for i in "$HOME/.ecryptfs/wrapped-passphrase" "$HOME/.ecryptfs/$PRIVATE_DIR.sig"; do
368         if [ -s "$i" ]; then
369                 mv -f "$i" "$i.$timestamp" || error "(gettext 'Could not backup existing data')" "[$i]"
370         fi
371 done
372
373 # Setup wrapped-passphrase file
374 u=`umask`
375 umask 377
376 if [ -z "$LOGINPASS" ] && [ "$BOOTSTRAP" = "1" ]; then
377         # This will be wrapped by pam_ecryptfs's chauthtok as soon as the user
378         # chooses a password.  Until that happens (hopefully soon), standard
379         # file permissions (600) are all that's protecting it.  Write it to
380         # ramdisk, to keep it from leaking to the hard-drive.
381         temp=`mktemp /dev/shm/.ecryptfs-XXXXXX`
382         printf "%s" "$MOUNTPASS" > "$temp"
383         mv -f -T "$temp" "/dev/shm/.ecryptfs-$USER" || error "Could not create passphrase file"
384 else
385         printf "%s\n%s" "$MOUNTPASS" "$LOGINPASS" | ecryptfs-wrap-passphrase "$HOME/.ecryptfs/wrapped-passphrase" - || error "$(gettext 'Could not wrap passphrase')"
386 fi
387 umask $u
388
389 # Add the passphrase to current keyring
390 # On subsequent logins, this should be handled by "pam_ecryptfs.so unwrap"
391 response=`printf "%s" "$MOUNTPASS" | ecryptfs-add-passphrase $FNEK -`
392 if [ $? -ne 0 ]; then
393         error "$(gettext 'Could not add passphrase to the current keyring')"
394 fi
395 sig=`echo "$response" | grep "Inserted auth tok" | sed "s/^.*\[//" | sed "s/\].*$//"`
396 if ! echo "$sig" | egrep -qs "^[0-9a-fA-F]{$KEYBYTES,$KEYBYTES}$"; then
397         error "$(gettext 'Could not obtain the key signature')"
398 fi
399 temp=`mktemp`
400 echo "$sig" > "$temp" || error "$(gettext 'Could not create signature file')" "[$HOME/.ecryptfs/$PRIVATE_DIR.sig]"
401 mv "$temp" "$HOME/.ecryptfs/$PRIVATE_DIR.sig"
402 which restorecon 2>/dev/null && restorecon "$HOME/.ecryptfs/$PRIVATE_DIR.sig" > /dev/null 2>&1
403 temp=`mktemp`
404 echo "$MOUNTPOINT" > "$temp" || error "$(gettext 'Could not create mountpoint file')" "[$HOME/.ecryptfs/$PRIVATE_DIR.mnt]"
405 mv "$temp" "$HOME/.ecryptfs/$PRIVATE_DIR.mnt"
406 which restorecon 2>/dev/null && restorecon "$HOME/.ecryptfs/$PRIVATE_DIR.mnt" > /dev/null 2>&1
407
408 echo
409 echo "$(gettext 'Done configuring.')"
410 echo
411
412 # Skip the tests if we're in bootstrap mode, but exit with the encrypted
413 # homedir mounted
414 if [ "$BOOTSTRAP" = "1" ]; then
415         # Force the mount here, since the root user has the key loaded,
416         # and the calling 'adduser' is about to copy over /etc/skel
417         # NOTE: it is the responsibility of 'adduser' to unmount!
418         # And ensure that $USER owns the files/dirs we've created as root
419         chown $USER:$GROUP "$CRYPTDIR" /dev/shm/.ecryptfs-$USER
420         chown -R $USER:$GROUP $ECRYPTFS_DIR/$USER
421         chown -R $USER:$GROUP $MOUNTPOINT
422         if [ "$FNEK" = "--fnek" ]; then
423                 fnek_sig=`tail -n 1 "$HOME/.ecryptfs/$PRIVATE_DIR.sig"`
424                 sig=`head -n 1 "$HOME/.ecryptfs/$PRIVATE_DIR.sig"`
425                 sig_opt="ecryptfs_sig=$sig,ecryptfs_fnek_sig=$fnek_sig"
426         else
427                 sig_opt="ecryptfs_sig=$sig"
428         fi
429         # Do the mount, and provide some helpful symlinks
430         mount -i -t ecryptfs -o "rw,$sig_opt,ecryptfs_cipher=$CIPHER,ecryptfs_key_bytes=$KEYBYTES" "$CRYPTDIR" "$MOUNTPOINT" || error "Could not mount"
431         ln -sf $ECRYPTFS_DIR/$USER/.ecryptfs $MOUNTPOINT/.ecryptfs
432         ln -sf $ECRYPTFS_DIR/$USER/.$PRIVATE_DIR $MOUNTPOINT/.$PRIVATE_DIR
433         chown -R $USER:$GROUP $ECRYPTFS_DIR/$USER
434         chown -R $USER:$GROUP $MOUNTPOINT
435         exit 0
436 fi
437
438 # Now let's perform some basic mount/write/umount/read sanity testing...
439 echo "$(gettext 'Testing mount/write/umount/read...')"
440 printf "%s" "$MOUNTPASS" | ecryptfs-add-passphrase $FNEK -
441 /sbin/mount.ecryptfs_private || error "$(gettext 'Could not mount private ecryptfs directory')"
442 temp=`mktemp "$MOUNTPOINT/ecryptfs.test.XXXXXX"` || error_testing "$temp" "$(gettext 'Could not create empty file')"
443 random_data=`head -c 16000 /dev/urandom | od -x` || error_testing "$temp" "$(gettext 'Could not generate random data')"
444 echo "$random_data" > "$temp" || error_testing "$temp" "$(gettext 'Could not write encrypted file')"
445 md5sum1=`md5sum "$temp"` || error_testing "$temp" "$(gettext 'Could not read encrypted file')"
446 /sbin/umount.ecryptfs_private || error_testing "$temp" "$(gettext 'Could not unmount private ecryptfs directory')"
447 printf "%s" "$MOUNTPASS" | ecryptfs-add-passphrase $FNEK -
448 /sbin/mount.ecryptfs_private || error_testing "$temp" "$(gettext 'Could not mount private ecryptfs directory (2)')"
449 md5sum2=`md5sum "$temp"` || error_testing "$temp" "$(gettext 'Could not read encrypted file (2)')"
450 rm -f "$temp"
451 # Use ecryptfs-umount-private on the final run, to clear the used keys
452 # out of the keyring
453 ecryptfs-umount-private || error_testing "$temp" "$(gettext 'Could not unmount private ecryptfs directory (2)')"
454 if [ "$md5sum1" != "$md5sum2" ]; then
455         error "$(gettext 'Testing failed.')"
456 else
457         echo "$(gettext 'Testing succeeded.')"
458 fi
459
460 echo
461 echo "$(gettext 'Logout, and log back in to begin using your encrypted directory.')"
462 echo
463
464 exit 0