#!/bin/sh
# This script sets up an ecryptfs mount in a user's ~/Private
#
# Originally ecryptfs-setup-pam-wrapped.sh by Michael Halcrow, IBM
#
# Ported for use on Ubuntu by Dustin Kirkland <kirkland@canonical.com>
# Copyright (C) 2008 Canonical Ltd.
# Copyright (C) 2007-2008 International Business Machines
PRIVATE_DIR="Private"
WRAPPING_PASS="LOGIN"
PW_ATTEMPTS=3
MESSAGE="Enter your login passphrase"
CIPHER="aes"
KEYBYTES="16"
FNEK=

# Zero out user-defined GREP_OPTIONS, such as --line-number
GREP_OPTIONS=

usage() {
    echo
    echo "Usage:"
    echo "  $0 [-f|--force] [-w|--wrapping] [--nopwcheck] [-n|--no-fnek]"
    echo "  [-u|--username USER] [-l|--loginpass LOGINPASS]"
    echo "  [-m|--mountpass MOUNTPASS]"
    echo
    echo " -f, --force      Force overwriting of an existing setup"
    echo " -w, --wrapping   Use an independent wrapping passphrase,"
    echo "                  different from the login passphrase"
    echo " -n, --no-fnek    Do not encrypt filenames; If this flag is"
    echo "                  omitted, and the kernel supports filename"
    echo "                  encryption, then filenames will be encrypted"
    echo " -u, --username   Username for encrypted private mountpoint,"
    echo "                  defaults to yourself"
    echo " -l, --loginpass  Login/Wrapping passphrase for USER,"
    echo "                  used to wrap MOUNTPASS"
    echo " --nopwcheck      Do not check the validity of the specified"
    echo "                  login password (useful for LDAP user accounts)"
    echo " --noautomount    Setup this user such that the encrypted private"
    echo "                  directory is not automatically mounted on login"
    echo " --noautoumount   Setup this user such that the encrypted private"
    echo "                  directory is not automatically unmounted at"
    echo "                  logout"
    echo " -m, --mountpass  Passphrase for mounting the ecryptfs directory,"
    echo "                  defaults to randomly generated $KEYBYTES bytes"
    echo " -b, --bootstrap  Bootstrap a new user's entire home directory"
    echo "                  Generates a random mount passphrase, which"
    echo "            will be wrapped when the new login passphrase"
    echo "            is set. SHOULD ONLY BE CALLED FROM 'adduser'."
    echo " --undo           Provide instructions on how to undo an"
    echo "                  encrypted private setup"
    echo
    echo "   Be sure to properly escape your parameters according to your"
    echo "   shell's special character nuances, and also surround the"
    echo "   parameters by double quotes, if necessary."
    echo
    exit 1
}

undo_msg() {
    echo "
In the event that you want to remove your eCryptfs Private Directory setup,
you will need to very carefully perform the following actions manually:

 1. Obtain your Private directory mountpoint
   $ PRIVATE=\`cat ~/.ecryptfs/Private.mnt 2>/dev/null || echo \$HOME/Private\`
 2. Ensure that you have moved all relevant data out of your \$PRIVATE directory
 3. Unmount your encrypted private directory
   $ ecryptfs-umount-private
 4. Make your Private directory writable again
   $ chmod 700 \$PRIVATE
 5. Remove \$PRIVATE, ~/.Private, ~/.ecryptfs
    Note: THIS IS VERY PERMANENT, BE VERY CAREFUL
   $ rm -rf \$PRIVATE ~/.Private ~/.ecryptfs
 6. Uninstall the utilities (this is specific to your Linux distribution)
   $ sudo apt-get remove ecryptfs-utils libecryptfs0
"
}

error() {
    echo "ERROR: $1" 1>&2
    exit 1
}

error_testing() {
    rm -f "$1" >/dev/null
    /sbin/umount.ecryptfs_private >/dev/null
    error "$2"
    exit 1
}

random_passphrase () {
    bytes=$1
    # Pull $1 of random data from /dev/urandom,
    # and convert to a string of hex digits
    od -x -N $bytes --width=$bytes /dev/urandom | head -n 1 | sed "s/^0000000//" | sed "s/\s*//g"
}

filename_encryption_available() {
    version=$(cat /sys/fs/ecryptfs/version 2>/dev/null)
    [ -z "$version" ] && error "Can't get ecryptfs version, ecryptfs kernel module not loaded?"
    [ $(($version & 0x100)) -eq 0 ] && return 1
    return 0
}

filename_encryption_available && FNEK="--fnek"

if [ ! -z "$SUDO_USER" ]; then
    USER="$SUDO_USER"
fi

while [ ! -z "$1" ]; do
    case "$1" in
        -u|--username)
                        USER="$2"
            shift 2
        ;;
        -l|--loginpass)
            LOGINPASS="$2"
            shift 2
        ;;
        -m|--mountpass)
            MOUNTPASS="$2"
            shift 2
        ;;
        -w|--wrapping)
            WRAPPING_PASS="INDEPENDENT"
            MESSAGE="Enter your wrapping passphrase"
            shift 1
        ;;
        -f|--force)
            FORCE=1
            shift 1
        ;;
        --nopwcheck)
            NOPWCHECK=1
            shift 1
        ;;
        --noautomount)
            NOAUTOMOUNT=1
            shift 1
        ;;
        --noautoumount)
            NOAUTOUMOUNT=1
            shift 1
        ;;
        --undo)
            undo_msg
            exit 0
        ;;
        -b|--bootstrap)
            [ `whoami` = "root" ] || error "You must be root to bootstrap encrypt a home directory"
            BOOTSTRAP=1
            MOUNTPASS=`random_passphrase $KEYBYTES`
            RANDOM_MOUNTPASS=1
            shift 1
        ;;
        -n|--no-fnek)
            FNEK=
            shift 1
        ;;
        *)
            usage
        ;;
    esac
done

# Prompt for the USER name, if not on the command line and not in the env
if [ -z "$USER" ]; then
    while [ true ]; do
        echo -n "Enter the username: "
        USER=`head -n1`
        echo
        if [ -z "$USER" ]; then
            echo "ERROR: You must provide a username"
            continue
        else
            # Verify that the user exists
            if ! id "$USER" >/dev/null; then
                echo "ERROR: User [$USER] does not exist"
                continue
            fi
            break
        fi
    done
else
    # Verify that the user exists
    id "$USER" >/dev/null || error "User [$USER] does not exist"
fi

# Check if user is member of ecryptfs group
if ! groups "$USER" | sed  -e 's| |\n|g' | grep -q '^ecryptfs$'; then
       error "User needs to be a member of ecryptfs group"
fi

# Obtain the user's home directory
HOME=`getent passwd "$USER" | awk -F: '{print $6}'`
if [ ! -d "$HOME" ]; then
    error "User home directory [$HOME] does not exist"
fi

if [ "$BOOTSTRAP" = "1" ]; then
    # If we want to encrypt the entire homedir, we need the .ecryptfs
    # config dir elsewhere, but linked into the homedir
    mkdir -p -m 700 /var/lib/ecryptfs/$USER
    ln -sf /var/lib/ecryptfs/$USER $HOME/.ecryptfs
    MOUNTPOINT="$HOME"
else
    mkdir -m 700 $HOME/.ecryptfs
    MOUNTPOINT="$HOME/$PRIVATE_DIR"
fi

# Check for previously setup private directory
if [ -s "$HOME/.ecryptfs/wrapped-passphrase" -a "$FORCE" != "1" ]; then
    error "wrapped-passphrase file already exists, use --force to overwrite."
fi
if [ -s "$HOME/.ecryptfs/$PRIVATE_DIR.sig" -a "$FORCE" != "1" ]; then
    error "$PRIVATE_DIR.sig file already exists, use --force to overwrite."
fi

# Check for active mounts
CRYPTDIR="$HOME/.$PRIVATE_DIR"
grep -qs "$MOUNTPOINT " /proc/mounts && error "[$MOUNTPOINT] is already mounted"
grep -qs "$CRYPTDIR " /proc/mounts && error "[$CRYPTDIR] is already mounted"

# Check that the mount point and encrypted directory are empty (skip symlinks).
# Perhaps one day we could provide a migration mode (using rsync or something),
# but this would be VERY hard to do safely.
count=`ls -Al "$MOUNTPOINT" 2>/dev/null | egrep -c "^[drwx-]{10}"`
if [ "$count" != "0" ]; then
    error "$MOUNTPOINT must be empty before proceeding"
fi
count=`ls -Al "$CRYPTDIR" 2>/dev/null | egrep -c "^[dlrwx-]{10}"`
if [ "$count" != "0" ]; then
    error "$CRYPTDIR must be empty before proceeding"
fi

stty_orig=`stty -g`
# Prompt for the LOGINPASS, if not on the command line and not in the env
if [ -z "$LOGINPASS" ] && [ "$BOOTSTRAP" != "1" ]; then
    tries=0
    while [ $tries -lt $PW_ATTEMPTS ]; do
        stty -echo
        echo -n "$MESSAGE: "
        LOGINPASS=`head -n1`
        stty $stty_orig
        echo
        if [ $WRAPPING_PASS != "LOGIN" -o ! -x /sbin/unix_chkpwd ]; then
            # If we can't check the accuracy of the user's entered
            # passphrase, force them to type it twice (matching)
            stty -echo
            echo -n "$MESSAGE (again): "
            LOGINPASS2=`head -n1`
            stty $stty_orig
            echo
            if [ "$LOGINPASS" != "$LOGINPASS2" ]; then
                echo "ERROR: Wrapping passphrases must match"
            else
                break
            fi
            tries=$(($tries + 1))
            continue
        fi
        if [ -z "$LOGINPASS" ]; then
            echo "ERROR: You must provide a login passphrase"
            tries=$(($tries + 1))
        else
            if [ "$NOPWCHECK" = "1" ]; then
                echo "INFO: Skipping password verification"
                break
            else
                if printf "%s\0" "$LOGINPASS" | /sbin/unix_chkpwd "$USER" nullok; then
                    break
                else
                    echo "ERROR: Your login passphrase is incorrect"
                    tries=$(($tries + 1))
                fi
            fi
        fi
    done
    if [ $tries -ge $PW_ATTEMPTS ]; then
        echo "ERROR: Too many incorrect password attempts, exiting"
        exit 1
    fi
fi

# Prompt for the MOUNTPASS, if not on the command line and not in the env
if [ -z "$MOUNTPASS" ]; then
    tries=0
    while [ $tries -lt $PW_ATTEMPTS ]; do
        stty -echo
        echo -n "Enter your mount passphrase [leave blank to generate one]: "
        MOUNTPASS=`head -n1`
        stty $stty_orig
        echo
        if [ -z "$MOUNTPASS" ]; then
            MOUNTPASS=`random_passphrase $KEYBYTES`
            RANDOM_MOUNTPASS=1
            break
        else
            stty -echo
            echo -n "Enter your mount passphrase (again): "
            MOUNTPASS2=`head -n1`
            stty $stty_orig
            echo
            if [ "$MOUNTPASS" != "$MOUNTPASS2" ]; then
                echo "ERROR: Mount passphrases do not match"
                tries=$(($tries + 1))
            else
                break
            fi
        fi
    done
    if [ $tries -ge $PW_ATTEMPTS ]; then
        echo "ERROR: Too many incorrect passphrase attempts, exiting"
        exit 1
    fi
fi

#echo
#echo "Using username [$USER]"
#echo "Using mount passphrase [$MOUNTPASS]"
#echo "Using login passphrase [$LOGINPASS]"
#echo "Using mount point [$MOUNTPOINT]"
#echo "Using encrypted dir [$CRYPTDIR]"
#echo
#echo "This script will attempt to set up your system to mount"
#echo "$MOUNTPOINT with eCryptfs automatically on login,"
#echo "using your login passphrase."
echo
echo "************************************************************************"
if [ "$RANDOM_MOUNTPASS" = "1" ]; then
    echo "YOU SHOULD RECORD THIS MOUNT PASSPHRASE AND STORE IN A SAFE LOCATION:"
    echo "$MOUNTPASS"
else
    echo "YOU SHOULD RECORD YOUR MOUNT PASSPHRASE AND STORE IN A SAFE LOCATION:"
fi
echo "THIS WILL BE REQUIRED IF YOU NEED TO RECOVER YOUR DATA AT A LATER TIME."
echo "************************************************************************"
echo

###############################################################################

# Setup private directory in home
mkdir -m 700 -p "$CRYPTDIR" || error "Could not create crypt directory [$CRYPTDIR]"
mkdir -m 700 -p "$MOUNTPOINT" || error "Could not create mount directory [$MOUNTPOINT]"
ln -sf /usr/share/ecryptfs-utils/ecryptfs-mount-private.txt "$MOUNTPOINT"/README.txt
ln -sf /usr/share/ecryptfs-utils/ecryptfs-mount-private.desktop "$MOUNTPOINT"/Access-Your-Private-Data.desktop
chmod 500 "$MOUNTPOINT"

# Setup ~/.ecryptfs directory
if [ "$NOAUTOMOUNT" = "1" ]; then
    echo "INFO: $HOME/$PRIVATE_DIR will not be mounted on login"
else
    touch $HOME/.ecryptfs/auto-mount || error "Could not setup ecryptfs auto-mount"
fi
if [ "$NOAUTOUMOUNT" = "1" ]; then
    echo "INFO: $HOME/$PRIVATE_DIR will not be unmounted on logout"
else
    touch $HOME/.ecryptfs/auto-umount || error "Could not setup ecryptfs auto-umount"
fi

if [ "$WRAPPING_PASS" = "LOGIN" ]; then
    rm -f $HOME/.ecryptfs/wrapping-independent || error "Could not remove ecryptfs wrapping-independent"
else
    touch $HOME/.ecryptfs/wrapping-independent || error "Could not setup ecryptfs wrapping-independent"
fi


# Backup any existing wrapped-passphrase or sig files; we DO NOT destroy this
timestamp=`date +%Y%m%d%H%M%S`
for i in "$HOME/.ecryptfs/wrapped-passphrase" "$HOME/.ecryptfs/$PRIVATE_DIR.sig"; do
    if [ -s "$i" ]; then
        mv -f "$i" "$i.$timestamp" || error "Could not backup existing data [$i]"
    fi
done

# Setup wrapped-passphrase file
u=`umask`
umask 377
if [ "$BOOTSTRAP" = "1" ]; then
    # This will be wrapped by pam_ecryptfs's chauthtok as soon as the user
    # chooses a password.  Until that happens (hopefully soon), standard
    # file permissions (600) are all that's protecting it.  Write it to
    # ramdisk, to keep it from leaking to the hard-drive.
    temp=`mktemp /dev/shm/.ecryptfs-XXXXXX`
    printf "%s" "$MOUNTPASS" > "$temp"
    mv "$temp" "/dev/shm/.ecryptfs-$USER"
else
    printf "%s\n%s" "$MOUNTPASS" "$LOGINPASS" | ecryptfs-wrap-passphrase "$HOME/.ecryptfs/wrapped-passphrase" - || error "Could not wrap passphrase"
fi
umask $u

# Add the passphrase to current keyring
# On subsequent logins, this should be handled by "pam_ecryptfs.so unwrap"
response=`printf "%s" "$MOUNTPASS" | ecryptfs-add-passphrase $FNEK -`
if [ $? -ne 0 ]; then
    error "Could not add passphrase to the current keyring"
fi
sig=`echo "$response" | grep "Inserted auth tok" | sed "s/^.*\[//" | sed "s/\].*$//"`
if ! echo "$sig" | egrep -qs "^[0-9a-fA-F]{$KEYBYTES,$KEYBYTES}$"; then
    error "Could not obtain the key signature"
fi
temp=`mktemp`
echo "$sig" > "$temp" || error "Could not create signature file [$HOME/.ecryptfs/$PRIVATE_DIR.sig]"
mv "$temp" "$HOME/.ecryptfs/$PRIVATE_DIR.sig"
temp=`mktemp`
echo "$MOUNTPOINT" > "$temp" || error "Could not create mountpoint file [$HOME/.ecryptfs/$PRIVATE_DIR.mnt]"
mv "$temp" "$HOME/.ecryptfs/$PRIVATE_DIR.mnt"

echo
echo "Done configuring."
echo

# Skip the tests if we're in bootstrap mode, but exit with the encrypted
# homedir mounted
if [ "$BOOTSTRAP" = "1" ]; then
    # Force the mount here, since the root user has the key loaded,
    # and the calling 'adduser' is about to copy over /etc/skel
    # NOTE: it is the responsibility of 'adduser' to unmount!
    # And ensure that $USER owns the files/dirs we've created as root
    chown $USER:$USER "$CRYPTDIR" /dev/shm/.ecryptfs-$USER
    if [ "$FNEK" = "--fnek" ]; then
        fnek_sig=`tail -n 1 "$HOME/.ecryptfs/$PRIVATE_DIR.sig"`
        sig=`head -n 1 "$HOME/.ecryptfs/$PRIVATE_DIR.sig"`
        sig_opt="ecryptfs_sig=$sig,ecryptfs_fnek_sig=$fnek_sig"
    else
        sig_opt="ecryptfs_sig=$sig"
    fi
    mount -i -t ecryptfs -o "rw,$sig_opt,ecryptfs_cipher=$CIPHER,ecryptfs_key_bytes=$KEYBYTES" "$CRYPTDIR" "$MOUNTPOINT" || error "Could not mount"
    ln -sf /var/lib/ecryptfs/$USER $MOUNTPOINT/.ecryptfs
    for i in auto-mount \
         auto-umount \
         $PRIVATE_DIR.mnt \
         $PRIVATE_DIR.sig \
         wrapped-passphrase;
    do
        [ -e $HOME/.ecryptfs/$i ] && chown $USER:$USER $MOUNTPOINT/.ecryptfs/$i
    done
    chown $USER:$USER /var/lib/ecryptfs/$USER
    chown -h $USER:$USER $MOUNTPOINT/.ecryptfs
    exit 0
fi

# Now let's perform some basic mount/write/umount/read sanity testing...
echo "Testing mount/write/umount/read..."
/sbin/mount.ecryptfs_private || error "Could not mount private ecryptfs directory"
temp=`mktemp "$MOUNTPOINT/ecryptfs.test.XXXXXX"` || error_testing "$temp" "Could not create empty file"
random_data=`head -c 16000 /dev/urandom | od -x` || error_testing "$temp" "Could not generate random data"
echo "$random_data" > "$temp" || error_testing "$temp" "Could not write encrypted file"
md5sum1=`md5sum "$temp"` || error_testing "$temp" "Could not read encrypted file"
/sbin/umount.ecryptfs_private || error_testing "$temp" "Could not unmount private ecryptfs directory"
/sbin/mount.ecryptfs_private || error_testing "$temp" "Could not mount private ecryptfs directory (2)"
md5sum2=`md5sum "$temp"` || error_testing "$temp" "Could not read encrypted file (2)"
rm -f "$temp"
# Use ecryptfs-umount-private on the final run, to clear the used keys
# out of the keyring
ecryptfs-umount-private || error_testing "$temp" "Could not unmount private ecryptfs directory (2)"
if [ "$md5sum1" != "$md5sum2" ]; then
    error "Testing failed."
else
    echo "Testing succeeded."
fi

echo
echo "Logout, and log back in to begin using your encrypted directory."
echo
exit 0