#!/bin/sh ### ### File: makeuser ### ### Copyright (c) 2001-2004 Apple Computer, Inc. All Rights Reserved. ### ### Authors: Soren Spies (SS) ### Brian Latimer (BDL) ### ### History: ### 1.0 2001/09/21 SS Initial implementation ### 2.0 2002/10/10 BDL Overhaul for 10.2 compatibility/modified functionality ### 2.1 2003/04/18 BDL Minor updates to help text, password handling ### 2.2 2003/05/01 BDL Update to naprivs switch verification; and "sanitized" for external release ### 2.3 2003/06/11 BDL Need to define NetInfo "sharedDir" key, to allow AFP guest access ### 2.4 2003/08/28 BDL Brute-force approach for defining NetInfo keys, due to locked media issues ### 2.5 2003/09/10 BDL Change switches to be all lowercase (not mixed-case) ### 2.6 2003/09/13 BDL Enforce execution as root ### 2.7 2003/09/17 BDL Delimit 'basename' ref's, to allow for spaces in path ### 2.8 2003/09/19 BDL Additional provisions for spaces in other entries ('eval' execution) ### 3.0 2003/11/09 BDL Significant update for Mac OS X 10.3 Panther (GID provisions, etc.) ### 3.1 2003/12/04 BDL Merged Mac OS X 10.2 Jaguar functionality back in - checks version of ### system on target drive for conditional processing ### 3.2 2003/12/09 BDL Under Panther, consider short name matching an existing group name, and ### fail if it matches (must then override group, or choose another short name) ### 3.3 2003/12/22 BDL Touch .SetupRegComplete, as well - to avoid MiniBuddy launch ### 3.4 2004/01/28 BDL Updated system version processing (use last-known type for newer OS's); ### reordering of root execution enforcement; cleanup of usage text ### 4.0 2004/02/06 BDL Implemented shadow password processing, as an alternative to "-cryptpass" ### 4.1 2004/02/13 BDL Allow both -shadowpass and -cryptpass - use whichever is most appropriate; ### Evolution build environment execution provisions ### 4.2 2004/02/24 BDL If unrecognized switch, echo it to the user, to allow for easier debugging ### Also, accept "-picture" == "-loginpic", for old Evolution compatibility ### 4.3 2004/03/17 BDL Minor switch updates ### 4.4 2004/05/20 BDL New processing for optional user-specific templates: If a user-specific template for the ### specified short name exists, copy its content to newly-created user's home, as well ### ### Description: ### Will create a user with the specified name & properties, copying appropriate ### User Template information into a user home directory hierarchy, configuring ### custom data as defined, etc. ### ### Note that the newly-create user's home is created from a copy of the standard User Template data: ### '/System/Library/User Template/English.lproj' and '.../Non_localized' ### but if it is found, also user-specific template data from: ### '/System/Library/User Template/Custom_user/{username}' ### ### Can operate on local (active) NetInfo database; or, if alternate NIDB selection ### (non-boot drive, etc.) is desired, use "-raw" and/or "-sysroot" parameters ### ### By default, will also disable Setup Assistant (i.e. we are performing this ### action as an alternative to running MacBuddy). ### ### ### To obtain a shadow password value (only valid for post-10.2 systems), perform the following: ### 1) Within the 'Accounts' prefpane, specify a password for a given user ### 2) Within Terminal: nicl . -read users/{username} generateduid ### Result: "generateduid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" ### 3) Within Terminal: cat /private/var/db/shadow/hash/{above uid value} ### ### The result (contents of that file) will be the 104-character shadowpass value needed. ### ### To obtain a (less-secure) crypted password value, for the '-cryptpass' parameter: ### 1) Within Terminal: openssl passwd ### 2) Enter the desired password ### 2) Verify the desired password ### ### The result will be the 13-character cryptpass value needed. ### # If executing within Evolution environment, doScript destination volume is always the first argument if [ "$DTDOSCRIPT" ]; then SYSROOT=$1 shift # Otherwise, assume local volume (can override with -sysroot parameter) else SYSROOT="/" fi NIDB="private/var/db/netinfo/local.nidb" FIRSTUID=${FIRSTUID:-501} # we increment beyond existing users below TEMPLATE_ENGLISH="System/Library/User Template/English.lproj" TEMPLATE_NONLOC="System/Library/User Template/Non_localized" TEMPLATE_USER="System/Library/User Template/Custom_user" SHADOWDIR="private/var/db/shadow/" NULLSHADOW="31D6CFE0D16AE931B73C59D7E0C089C0AAD3B435B51404EEAAD3B435B51404EEDA39A3EE5E6B4B0D3255BFEF95601890AFD80709" SYSTEMVERSION="System/Library/CoreServices/SystemVersion" APPLESETUPDONE="private/var/db/.AppleSetupDone" SETUPREGCOMPLETE="Library/Receipts/.SetupRegComplete" usage() { echo "makeuser - scripted user creation under Mac OS X 10.2 or later" echo "Usage: `basename \"$0\"` -user -realname [ -password | -cryptpass ] []" echo " e.g. makeuser -user teacher -realname 'School Teacher' -cryptpass 'LEbsPcSWgMCAE' -sysroot '/Volumes/Other HD/' -admin -deskpic '/Library/Desktop Pictures/Aqua Graphite.jpg' " echo "" echo " and are mandatory" echo "" echo " should be created manually, extracted, and specified as input" echo " (see contents of /private/var/db/shadow/hash/{generateduid} for value)" echo " can be generated with 'openssl passwd'" echo " (this is the only valid password option for 10.2.x systems)" echo " * If neither option specified, user will be created with NULL password" echo "" echo " are any of:" echo " -admin - give user administrative capabilities" echo " -hint - set password hint to the given string" echo " -loginpic

- set user's login picture to the given path" echo " -deskpic

- set user's desktop picture to the given path" echo "" echo " and, not as widely used:" echo " -naprivs - set Remote Desktop privs ('naprivs' key) to the given value" echo " -preserveSA - Setup Assistant should still run on first boot" echo " -preserveRA - Registration Asst. should still run upon first admin login" echo " -pwchange - user's password change field set to value" echo " -pwexpire - user's password expire field set to value" echo " -group - override default group with group name provided" echo " -shell - override default shell with shell at the provided path" echo " -raw - edit netinfo files directly (default when -sysroot != /)" echo " -sysroot - modify the system rooted on the given volume (e.g. '/Volumes/Server HD/')" echo "" } die() { [ "$#" -gt 0 ] && echo "`basename \"$0\"`: ERROR -" "$@" >&2 exit 1 } # If no parameters, throw usage and quit [ $# = 0 ] && usage >&2 && exit 1 unset user realname shadowpass cryptpass unset authority passwdval customuid shadowval unset ADMINUSER HINT LOGINPIC DESKTOPPIC unset GROUP NAPRIVS LEAVEASD LEAVEREG unset PWCHANGE PWEXPIRE SHELL unset RAW unset theVers sysVers while [[ $1 = -* ]]; do case "$1" in -h | -help) usage; exit 0;; -user) shift; user="$1" [ -z "$user" ] || [[ "$user" = -* ]] && usage >&2 && die "need user";; -realname) shift; realname="$1" [ -z "$realname" ] || [[ "$realname" = -* ]] && usage >&2 && die "need realname";; -password | -shadowpass) shift; shadowpass="$1" [ -z "$shadowpass" ] || [[ "$shadowpass" = -* ]] && usage >&2 && die "need shadowpass value (for no passwd, don't specify switch)";; -cryptpass) shift; cryptpass="$1" [ -z "$cryptpass" ] || [[ "$cryptpass" = -* ]] && usage >&2 && die "need cryptpass value (for no passwd, don't specify switch)";; -admin) ADMINUSER=1;; -hint) shift; HINT="$1" [ -z "$HINT" ] || [[ "$HINT" = -* ]] && usage >&2 && die "need password hint";; -loginpic | -picture) shift; LOGINPIC="$1" [ -z "$LOGINPIC" ] || [[ "$LOGINPIC" = -* ]] && usage >&2 && die "need path to login picture";; -deskpic) shift; DESKTOPPIC="$1" [ -z "$DESKTOPPIC" ] || [[ "$DESKTOPPIC" = -* ]] && usage >&2 && die "need path to desktop pic";; -group) shift; GROUP="$1" [ -z "$GROUP" ] || [[ "$GROUP" = -* ]] && usage >&2 && die "need alternate groupname";; -naprivs) shift; NAPRIVS="$1" [ -z "$NAPRIVS" ] && usage >&2 && die "need naprivs value";; -preserveSA) LEAVEASD=1;; -preserveMB) LEAVEASD=1;; # in case "MacBuddy" is stuck in our head -preserveRA) LEAVEREG=1;; -pwchange) shift; PWCHANGE="$1" echo "WARNING: unknown exactly how system uses 'change' field" >&2 [ -z "$PWCHANGE" ] || [[ "$PWCHANGE" = -* ]] && usage >&2 die "need password change value";; -pwexpire) shift; PWEXPIRE="$1" echo "WARNING: unknown exactly how system uses 'expire' field" >&2 [ -z "$PWEXPIRE" ] || [[ "$PWEXPIRE" = -* ]] && usage >&2 die "need password expire value";; -shell) shift; SHELL="$1" [ -z "$SHELL" ] || [[ "$SHELL" = -* ]] && usage >&2 && die "need shell";; -raw) RAW=1;; -sysroot) shift; SYSROOT="$1" [ -z "$SYSROOT" ] || [[ "$SYSROOT" = -* ]] && usage >&2 && die "need sysroot";; *) usage >&2; die "'$1' switch not recognized";; esac shift done # Ensure we are running this script as root (to allow access to all NetInfo interactions) if [ "`whoami`" != "root" ] ; then die "script must be run as root" exit fi # Check for presence of required parameters, then begin other verifications if [ -z "$user" ] || [ -z "$realname" ]; then usage >&2 die " and are required" fi if echo "$user"|/usr/bin/perl -ne "die if /[^a-zA-Z0-9]/" > /dev/null; then echo "WARNING: user names with special characters are a Very Bad Idea" >&2 fi ## Sleep due to a "Restricted Access" bug if nicl is called to often, too quickly ## /bin/sleep 2 # Provisions for alternate nidb selection [ -d "$SYSROOT" ] || die "$SYSROOT doesn't seem to exist" if [ "$RAW" -o "$SYSROOT" != "/" ]; then if [ "$SYSROOT" = / ]; then echo "WARNING: -raw is dangerous to a normally-booted system" >&2 else [[ "$SYSROOT" = */ ]] && SYSROOT=`echo "$SYSROOT"|sed 's!/$!!'` fi df "$SYSROOT"|/usr/bin/perl -ne "die if /$SYSROOT\Z/" >/dev/null || die "$SYSROOT not a mountpoint" niclcmd="eval nicl -raw \"$SYSROOT/$NIDB\"" # Odd provision for spaces in volume name else niclcmd="eval nicl -raw /var/db/netinfo/local.nidb" # 'eval' this, as well (to ensure common behavior below) fi echo "`basename \"$0\"`: niclcmd is '$niclcmd'" >&2 # Determine system version of target drive theVers="`defaults read \"$SYSROOT/$SYSTEMVERSION\" ProductVersion `" case $theVers in 10.0* | 10.1*) die "Unsupported OS version: '$theVers'" ;; 10.2*) sysVers="Jaguar"; if [ -z "$SHELL" ]; then SHELL="/bin/tcsh" fi;; 10.3*) sysVers="Panther"; if [ -z "$SHELL" ]; then SHELL="/bin/bash" fi;; *) sysVers="Panther"; echo "WARNING: Later OS version encountered; $sysVers-style user will be created" >&2 ; if [ -z "$SHELL" ]; then SHELL="/bin/bash" fi;; esac # Verify basic password validity (character length, format) if [ "$shadowpass" ] && [ `echo -n $shadowpass | wc -m` != 104 ]; then die "Shadow password must be exactly 104 characters" elif [ "$shadowpass" ] && [ `echo -n "$shadowpass" | /usr/bin/perl -ne "die if /[^A-F0-9]/"` > /dev/null ]; then die "Shadow password must be comprised of only uppercase, hexadecimal digits" elif [ "$cryptpass" ] && [ `echo -n $cryptpass | wc -m` != 13 ]; then die "Crypted password must be exactly 13 characters" fi # Sanity check the password(s) provided against the system version of the target # # If Shadowpass and Cryptpass are both supplied, just take whichever is appropriate for the target system if [ "$shadowpass" ] && [ "$cryptpass" ]; then if [ "$sysVers" == "Jaguar" ]; then shadowpass="" # cryptpass will be used; blank out shadowpass echo "WARNING: Both shadow and crypt passwords supplied - Mac OS X 10.2.x can only use crypted passwords" >&2 else cryptpass="" # shadowpass will be used; blank out cryptpass echo "WARNING: Both shadow and crypt passwords supplied - using shadow password for enhanced security" >&2 fi # Shadow password valid ONLY for post-Jaguar implementations; throw error if attempted on Jaguar elif [ "$sysVers" == "Jaguar" ] && [ "$shadowpass" ]; then usage >&2 die "Shadow password not valid for Mac OS X 10.2.x systems; provide -cryptpass instead" # For post-Jaguar systems, crypted passwords are allowed, but not recommended; provide warning if provided elif [ "$sysVers" != "Jaguar" ] && [ "$cryptpass" ]; then echo "WARNING: Mac OS X versions after 10.2.x should use shadow passwords; these are more secure than crypted passwords" >&2 fi # Basic input looks good; report what we intend to do echo "Creating $sysVers-style user under $theVers ..." # Find first available UID after $FIRSTUID ### NOTE: ### Specification of a given uid is not allowed - use 'modifyUID' after user creation, if desired uid=$FIRSTUID while $niclcmd -read /users/uid=$uid > /dev/null 2>&1; do uid=$((uid+1)) done # See if this username already exists $niclcmd -read /users/"$user" > /dev/null 2>&1 && die "user '$user' already exists" # If a different default group was specified, then determined GID for this group if [ "$GROUP" ]; then gid=`$niclcmd -read /groups/"$GROUP" gid | awk '{print $2}'` # Else, under Jaguar, set group to "staff" elif [ "$sysVers" == "Jaguar" ]; then GROUP="staff" gid=`$niclcmd -read /groups/"$GROUP" gid | awk '{print $2}'` # Else, under Panther, create a new group with the same name & ID as this user elif [ "$sysVers" == "Panther" ]; then GROUP=$user # ... as long as this username does not match an existing GROUP name $niclcmd -read /groups/"$GROUP" > /dev/null 2>&1 && die "group '$GROUP' already exists; either override group, or choose another short name" gid=$uid # Now, actually create the group record within NetInfo $niclcmd -create /groups/"$GROUP" || die "creating group" $niclcmd -create /groups/"$GROUP" users "" || die "setting group users" $niclcmd -create /groups/"$GROUP" passwd "\"*\"" || die "setting group passwd" $niclcmd -create /groups/"$GROUP" gid "\"$gid\"" || die "setting group gid" fi # Ensure gid is set correctly, corresponding to group name [ -z "$gid" ] && die "couldn't find GID for $GROUP" # Everything else is good; actually create the user record within NetInfo $niclcmd -create /users/"$user" || die "creating user" ### File system interactions ### # Determine home directory, and create (if it doesn't already exist) dir=/Users/"$user" userdir="${SYSROOT}$dir" if [ -d "$userdir" ]; then echo "WARNING: User home directory $userdir already exists" >&2 else mkdir "$userdir" || die "creating home directory '$userdir' " fi ### TO DO: Handle language preference (don't always blindly take English) ditto -rsrc "$SYSROOT/$TEMPLATE_ENGLISH" "$userdir" ditto -rsrc "$SYSROOT/$TEMPLATE_NONLOC" "$userdir" # If user-specific template exists, then copy this content, as well if [ -d "$SYSROOT/$TEMPLATE_USER/$user" ]; then ditto -rsrc "$SYSROOT/$TEMPLATE_USER/$user" "$userdir" ## rm -rf "$SYSROOT/$TEMPLATE_USER/$user" fi # Set "default" key for desktop picture (no longer dependent on displayID) if [ "$DESKTOPPIC" ]; then defaults write "$userdir/Library/Preferences/"com.apple.desktop \ "{ \ Background = { \ default = { \ ImageFilePath = \"$DESKTOPPIC\"; \ Placement = Crop; \ }; \ }; \ }" fi # Set owner and group on everything in home directory hierarchy chown -R $uid "$userdir" || die "setting owner of '$userdir' to $uid" chgrp -R $gid "$userdir" || die "setting group of '$userdir' to $gid" # Setup password provisions, based on type of password entered if [ "$cryptpass" ] || [ "$sysVers" == "Jaguar" ]; then # Cryptpass is only valid option - and, no need for generateduid authority=";basic;" passwdval="$cryptpass" # even if blank... else authority=";ShadowHash;" passwdval="********" # flag to system, to reference ...shadow/hash/{genuid} file # Create a custom UID for this user ### NOTE: Final 12 chars will be the ethernet addr of the CREATING machine; not target, as would be expected customuid=`uuidgen` || die "creating generated uid" # Create ...shadow/hash/ directory to hold uid file (if it doesn't already exist) if ! [ -d "$SYSROOT/$SHADOWDIR/hash/" ]; then mkdir -p "$SYSROOT/$SHADOWDIR/hash/" || die "creating ...shadow/hash/ directory" fi # If no password provided, set shadow password to value representing NULL entry if [ -z "$shadowpass" ]; then shadowval="$NULLSHADOW" else shadowval="$shadowpass" fi # Populate uid-based shadowhash file with the shadow password echo -n $shadowval > "$SYSROOT/$SHADOWDIR/hash/$customuid" || die "cannot write to $customuid file" # Finally, ensure correct ownership and permissions on this entire shadowhash area chown -R root:wheel "$SYSROOT/$SHADOWDIR" || die "setting ownership of ...shadow/hash/ area" chmod -R go-rwx "$SYSROOT/$SHADOWDIR" || die "setting mode of ...shadow/hash/ area" # Ensure non-readable by any other than root fi ### Additional NetInfo interactions ### # Define all standard user keys, roughly in the order they are defined by the standard API $niclcmd -create /users/"$user" shell "\"$SHELL\"" || die "setting shell" $niclcmd -create /users/"$user" home "\"$dir\"" || die "setting home" $niclcmd -create /users/"$user" gid "\"$gid\"" || die "setting gid" $niclcmd -create /users/"$user" authentication_authority "\"$authority\"" || die "setting authentication authority" $niclcmd -create /users/"$user" passwd "\"$passwdval\"" || die "setting passwd value" if [ "$shadowval" ]; then # cryptpass not specified; system capable of shadow passwords $niclcmd -create /users/"$user" generateduid "\"$customuid\"" || die "setting generateduid" fi $niclcmd -create /users/"$user" realname "\"$realname\"" || die "setting realname" $niclcmd -create /users/"$user" hint "\"$HINT\"" || die "setting hint" $niclcmd -create /users/"$user" sharedDir Public || die "setting sharedDir" $niclcmd -create /users/"$user" uid "\"$uid\"" || die "setting uid" $niclcmd -create /users/"$user" _writers_passwd "\"$user\"" || die "setting _writers_passwd" $niclcmd -create /users/"$user" _writers_tim_passwd "\"$user\"" || die "setting _writers_tim_passwd" $niclcmd -create /users/"$user" _writers_picture "\"$user\"" || die "setting _writers_picture" $niclcmd -create /users/"$user" _writers_hint "\"$user\"" || die "setting _writers_hint" if [ "$sysVers" != "Jaguar" ]; then $niclcmd -create /users/"$user" _writers_realname "\"$user\"" || die "setting _writers_realname" fi # Add entries for optional parameters, if supplied if [ "$LOGINPIC" ]; then $niclcmd -create /users/"$user" picture "\"$LOGINPIC\"" || die "adding loginpic" fi if [ "$NAPRIVS" ]; then $niclcmd -create /users/"$user" naprivs "\"$NAPRIVS\"" || die "adding naprivs" fi if [ "$PWCHANGE" ]; then $niclcmd -create /users/"$user" change "\"$PWCHANGE\"" || die "adding pw change value" fi if [ "$PWEXPIRE" ]; then $niclcmd -create /users/"$user" expire "\"$PWEXPIRE\"" || die "adding pw expire value" fi # If "-admin" specified, add the user "$user" to the groups needed if [ "$ADMINUSER" ]; then $niclcmd -append /groups/admin users "$user" || die "adding user to admin group" if [ "$sysVers" != "Jaguar" ]; then $niclcmd -append /groups/appserverusr users "$user" || die "adding user to appserverusr group" $niclcmd -append /groups/appserveradm users "$user" || die "adding user to appserveradm group" fi fi # Suppress the launch of Setup Assistant, and/or Registration Assistant, unless we specify otherwise if [ -z "$LEAVEASD" ]; then touch "$SYSROOT/$APPLESETUPDONE" || die "adding $APPLESETUPDONE" else if [ -f "$SYSROOT/$APPLESETUPDONE" ]; then echo "WARNING: $APPLESETUPDONE already exists; Setup Assistant will not run">&2 fi fi if [ -z "$LEAVEREG" ]; then touch "$SYSROOT/$SETUPREGCOMPLETE" || die "adding $SETUPREGCOMPLETE" else if [ -f "$SYSROOT/$SETUPREGCOMPLETE" ]; then echo "WARNING: $SETUPREGCOMPLETE already exists; Registration Assistant will not run">&2 fi fi # Finally, echo our results if [ "$shadowpass" ]; then echo "`basename \"$0\"`: Made user '$user' named '$realname', uid $uid, gid $gid, and shadow password" elif [ "$cryptpass" ]; then echo "`basename \"$0\"`: Made user '$user' named '$realname', uid $uid, gid $gid, and crypted password '$cryptpass'" else echo "`basename \"$0\"`: Made user '$user' named '$realname', uid $uid, gid $gid, and NULL password" fi # Th-th-th-that's all, folks!