Files
PureDarwin/scripts/pd_injectuser
2009-12-14 01:32:36 +01:00

475 lines
11 KiB
Bash
Executable File

#!/bin/sh
#
# Copyright (c) 2007-2008 The PureDarwin Project.
# All rights reserved.
#
# @LICENSE_HEADER_START@
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# @LICENSE_HEADER_END@
#
#
# aladin <aladin@puredarwin.org>
#
#
# Add users without dscl in Darwin 9
# based on pd_adduser
#
#
# Changelog
#
# 20080321 - See `hg log' (mercurial) from now - aladin
# 20081211 - Adding shell choice - aladin
# 20081209 - License header update - aladin
# 20080831 - Minor updates - aladin
# 20080827 - Minor updates
# typo fixed - aladin
# 20080817 - <user>.state file added
# Minor updates - aladin
# 20080808 - Initial version - aladin
#
# Ensure root exclusivity
if [ "$UID" -ne 0 ]
then
echo "You must be root in order to use $(basename $0)"
exit 1
fi
#
# Set up variables
#
OPENSSL="/usr/bin/openssl"
GREP="/usr/bin/grep"
HEAD="/usr/bin/head"
SORT="/usr/bin/sort"
XXD="/usr/bin/xxd"
AWK="/usr/bin/awk"
# Mount devfs (e.g., /dev/null, /dev/random, etc.. are needed)
/sbin/mount_devfs devfs /dev
# Default target volume
MOUNT=/
if [ "$1" != "" ]
then
# Ensure "target" exists
if [ ! -e "$1" ]
then
echo "Unknown volume name: $1"
exit -1
fi
# Set new target
MOUNT="$1"
fi
echo "Target volume name: $MOUNT"
echo ""
# User existence
EXISTS=0
# Usage
#
# ./pd_injectuser target default_username
# $0 $1 $2
T_USERNAME=$2
#
# Interactive
#
# Prompt for username (if no arg specified above)
if [ "$T_USERNAME" = "" ]; then
# Infinite loop until username is not blank
while [ 1 ]; do
printf "Username: "
read T_USERNAME;
if [ ! "$T_USERNAME" = "" ]; then
break
else
echo "Username must not be blank, try again."
fi
done
else
T_DEFAULT="TRUE"
echo "Entering default mode"
fi
# Check whether user already exists
if [ -e ${MOUNT}/private/var/db/dslocal/nodes/Default/users/${T_USERNAME}.plist ]; then
echo "${T_USERNAME} already exists"
if [ "$T_DEFAULT" = "" ]; then
printf "Overwrite? y[n]: "
read CHOICE
else
echo "Overwrite forced by default mode"
CHOICE="y"
fi
if [ "$CHOICE" = "y" ]; then
EXISTS=1
else
echo "Aborted."
exit 1
fi
fi
# Prompt for realname (if no arg specified above)
if [ "$T_DEFAULT" = "" ]; then
printf "Realname: "
read T_REALNAME
else
T_REALNAME="$T_USERNAME"
fi
# Generating a random Universally Unique IDentifier (UUID)
#
# ie for user: e0d36085-8a80-238b-abfb-c537c7b36378
# ie for root: FFFFEEEE-DDDD-CCCC-BBBB-AAAA00000000
#
T_UUID="` $OPENSSL rand 4 | $XXD -p -u`-`$OPENSSL rand 2 |
$XXD -p -u`-`$OPENSSL rand 2 | $XXD -p -u`-`$OPENSSL rand 2 |
$XXD -p -u`-`$OPENSSL rand 6 | $XXD -p -u`"
# Find max uid
M_UID=`cat ${MOUNT}/var/db/dslocal/nodes/Default/users/*.plist |
$GREP -v generateduid | $GREP -A2 uid |
$GREP string | $AWK -F ">|<" ' {printf $3"\n"}' |
$SORT -rg | $HEAD -n1`
if [ "$T_USERNAME" == "root" ]; then
# echo "Warning: Security consequences involved"
M_UID=0
T_UUID="FFFFEEEE-DDDD-CCCC-BBBB-AAAA00000000"
else
if [ $M_UID -lt 1000 ]; then
M_UID=1000
else
M_UID=`expr $M_UID + 1`
fi
fi
T_UID_DEFAULT=$M_UID
T_GID_DEFAULT=$M_UID
if [ "$T_DEFAULT" = "" ]; then
printf "UID (default: $T_UID_DEFAULT): "
read T_UID
printf "GID (default: $T_GID_DEFAULT): "
read T_GID
fi
if [ "$T_UID" = "" ]; then
T_UID=${T_UID_DEFAULT}
fi
#if [ "$T_UID" = "0" ]; then
# echo "Warning: Security consequences involved"
#fi
#if [ "$T_GID" = "" ]; then
# T_GID=${T_GID_DEFAULT}
#fi
if [ "$T_GID" = "0" ]; then
echo "Warning: Security consequences involved"
fi
if [ "$T_USERNAME" == "root" ]; then
T_HOMEDIR_DEFAULT="/var/$T_USERNAME"
else
T_HOMEDIR_DEFAULT="/Users/$T_USERNAME"
fi
# Default shell
T_SHELL_DEFAULT=/bin/zsh
#SHELL_VALID=0
if [ "$T_DEFAULT" = "" ]; then
# Prompt for default shell
echo "Available shells:"
echo "`cat $MOUNT/etc/shells | $GREP bin`"
# Infinite loop until a valid shell is entered
while [ 1 ]; do
printf "Shell (default: $T_SHELL_DEFAULT): "
read T_SHELL;
if [ ! "$T_SHELL" = "" ]; then
$GREP $T_SHELL $MOUNT/etc/shells > /dev/null 2> /dev/null
if [ $? -eq 0 ]; then
break
else
echo "Insert valid shell, try again."
fi
else
break
fi
done
fi
# Default shell
if [ "$T_SHELL" = "" ]; then
T_SHELL=$T_SHELL_DEFAULT
fi
# Prompt for home directory
if [ "$T_DEFAULT" = "" ]; then
printf "Home directory (default: $T_HOMEDIR_DEFAULT): "
read T_HOMEDIR;
fi
# Default home directory
if [ "$T_HOMEDIR" = "" ]; then
T_HOMEDIR=$T_HOMEDIR_DEFAULT
fi
# Check whether home directory already exists
if [ -e "${MOUNT}${T_HOMEDIR}" ]; then
echo "${MOUNT}${T_HOMEDIR} already exists"
#exit1
fi
# Prompt for admin group
if [ "$T_DEFAULT" = "" ]; then
printf "Add ${T_USERNAME} to the admin group? [y]n: "
read CHOICE;
if [ ! "$CHOICE" = "n" ]; then
ADMIN_USER=1
fi
else
echo "Granted (Admin) by default mode"
ADMIN_USER=1
fi
# Disabling tty output
stty -echo
if [ "$T_DEFAULT" = "" ]; then
# Infinite loop until password was entered the same twice
while [ 1 ]; do
printf "Password: "
read T_PASSWORD;
echo ""
printf "Verifying - Password: "
read V_PASSWORD;
echo ""
if [ "$T_PASSWORD" = "$V_PASSWORD" ]; then
break
else
echo "Verify failure"
fi
done
else
T_PASSWORD="$T_USERNAME"
fi
# Enabling tty output
stty echo
# Display summary
echo ""
echo "Generated UUID: ${T_UUID}"
echo "Username: ${T_USERNAME}"
echo "Realname: ${T_REALNAME}"
echo "Password: ********"
echo "UID: ${T_UID}"
if [ "$ADMIN_USER" == "1" ]; then
echo "GID: 80, ${T_GID}"
else
echo "GID: ${T_GID}"
fi
echo "Shell: ${T_SHELL}"
echo "Home: ${T_HOMEDIR}"
echo ""
if [ "$T_DEFAULT" = "" ]; then
printf "Add [y]n: "
read CHOICE
if [ "$CHOICE" = "n" ]; then
echo "Aborted."
exit 0
fi
fi
#
# Create user plist file
#
USER_PLIST="${MOUNT}/var/db/dslocal/nodes/Default/users/${T_USERNAME}.plist"
cat >$USER_PLIST<<EOOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>authentication_authority</key>
<array>
<string>;ShadowHash;</string>
</array>
<key>generateduid</key>
<array>
<string>${T_UUID}</string>
</array>
<key>gid</key>
<array>
<string>${T_GID}</string>
</array>
<key>home</key>
<array>
<string>/Users/${T_USERNAME}</string>
</array>
<key>name</key>
<array>
<string>${T_USERNAME}</string>
</array>
<key>passwd</key>
<array>
<string>********</string>
</array>
<key>realname</key>
<array>
<string>${T_REALNAME}</string>
</array>
<key>shell</key>
<array>
<string>${T_SHELL}</string>
</array>
<key>uid</key>
<array>
<string>${T_UID}</string>
</array>
</dict>
</plist>
EOOF
#
# Create shadow hash file (620 bytes)
#
# 1240 hexadecimal characters:
# ( 64 + 40 + 64 ) + ( 8 + 40 ) + ( 1024 )
#
mkdir -p ${MOUNT}/private/var/db/shadow/hash
# Init character pointer
i=0
# 168 hexadecimal characters
#
# 64 characters for the LANMAN password (require "Windows sharing" enabled)
# 40 characters for the "unsalted" SHA1 hash
# 64 characters for md5 hash, at least it's a power of 2 (2^6)
#
while (( i++ < 168 ))
do
printf "0" >> ${MOUNT}/private/var/db/shadow/hash/${T_UUID}
done
# Hexadecimal salt (ie: FC2B0C2E)
T_HEX_SALT=`$OPENSSL rand 4 | $XXD -p -u`
# Binary salt (reverted from hex, plain hexdump style)
T_BIN_SALT=`printf ${T_HEX_SALT} | $XXD -r -u -p`
# Generated sha1 hash with provided password and binary salt
T_SHA1_HASH=`printf "%s%s" ${T_BIN_SALT} ${T_PASSWORD} | $OPENSSL dgst -sha1 | tr ' a-z' ' A-Z'`
# 8 characters for the hexadecimal salt
printf ${T_HEX_SALT} >> ${MOUNT}/private/var/db/shadow/hash/${T_UUID}
# 40 characters for the sha1 hash
printf ${T_SHA1_HASH} >> ${MOUNT}/private/var/db/shadow/hash/${T_UUID}
# iJump the hash!
i=$[$i+48]
# 1024 characters for "unknow reason (future big hash?)", at least it's another power of 2 (2^10)
while (( i++ < 1241 ))
do
printf "0" >> ${MOUNT}/private/var/db/shadow/hash/${T_UUID}
done
#
# Create shadow hash file .state (not mandatory)
#
T_CREATION_DATE=`date "+%Y-%m-%dT%H:%M:%SZ"`
sleep 1
T_LASTLOGIN_DATE=`date "+%Y-%m-%dT%H:%M:%SZ"`
cat >${MOUNT}/private/var/db/shadow/hash/${T_UUID}.state<<EOOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CreationDate</key>
<date>${T_CREATION_DATE}</date>
<key>FailedLoginCount</key>
<integer>0</integer>
<key>LastLoginDate</key>
<date>${T_LASTLOGIN_DATE}</date>
<key>NewPasswordRequired</key>
<integer>0</integer>
</dict>
</plist>
EOOF
# Add to admin group
if [ "$ADMIN_USER" = "1" ];
then
$GREP "<string>${T_USERNAME}</string>" "${MOUNT}/private/var/db/dslocal/nodes/Default/groups/admin.plist"
if [ $? -eq 1 ];
then
cp ${MOUNT}/private/var/db/dslocal/nodes/Default/groups/admin.plist ${MOUNT}/private/var/db/dslocal/nodes/Default/groups/admin.plist.origin
cat ${MOUNT}/private/var/db/dslocal/nodes/Default/groups/admin.plist.origin |
sed "s/<string>root<\/string>/<string>root<\/string><string>${T_USERNAME}<\/string>/" > ${MOUNT}/private/var/db/dslocal/nodes/Default/groups/admin.plist
# FIXME missing \n\t\t as ...>root</string>\n\t\t<string>${T_US...
# Alternative
#perl -pi -e "s|<string>root</string>|<string>root</string>\n\t\t<string>${T_USERNAME}</string>|g" ${MOUNT}/private/var/db/dslocal/nodes/Default/groups/admin.plist
else
echo "${T_USERNAME} is already found in admin group"
fi
fi
# Create and populate home directory
mkdir -p ${MOUNT}${T_HOMEDIR}
chown ${T_UID}:${T_GID} ${MOUNT}${T_HOMEDIR}
# Auto reload DirectoryService in order to avoid reboot to make effect
if [ "$MOUNT" = "/" ]; then
launchctl stop com.apple.DirectoryServices
fi
echo "User ${T_USERNAME} created"
# Symetry: restoring state
/sbin/umount -f /dev