#!/sbin/sh # # Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. # . /lib/svc/share/smf_include.sh # # Establish PATH for non-built in commands # export PATH=/sbin:/usr/bin:/usr/sbin # Usage USAGE="Usage: $0 { start | unconfigure }" TMP_DIR=/system/volatile ETC_SHADOW=/etc/shadow TMP_SHADOW="${TMP_DIR}/shadow.$$" ETC_SUDOERS_DIR=/etc/sudoers.d ETC_SUDOERS_FILE="${ETC_SUDOERS_DIR}/svc-system-config-user" ETC_AUTO_HOME=/etc/auto_home TMP_AUTO_HOME="${TMP_DIR}/auto_home.$$" ETC_USER_ATTR=/etc/user_attr # property group definitions # configuration of user account PG_USER_ACCOUNT="user_account" # unconfiguration of user account PG_CONFIGURED_USER_ACCOUNT="configured_user" # configuration of root account PG_ROOT_ACCOUNT="root_account" # directory containing initial user profile files ETC_SKEL=/etc/skel # initial user profile files DOT_PROFILE=".profile" DOT_BASHRC=".bashrc" INITIAL_DOT_PROFILE="$ETC_SKEL/$DOT_PROFILE" INITIAL_DOT_BASHRC="$ETC_SKEL/$DOT_BASHRC" # user account properties # login name PROP_USER_LOGIN="$PG_USER_ACCOUNT/login" # password PROP_USER_PASSWORD="$PG_USER_ACCOUNT/password" # description (usually user's full name) PROP_USER_DESCRIPTION="$PG_USER_ACCOUNT/description" # full pathname of the program used as the user's shell on login PROP_USER_SHELL="$PG_USER_ACCOUNT/shell" # UID PROP_USER_UID="$PG_USER_ACCOUNT/uid" # GID PROP_USER_GID="$PG_USER_ACCOUNT/gid" # type (role, normal) - see user_attr(4) PROP_USER_TYPE="$PG_USER_ACCOUNT/type" # profiles PROP_USER_PROFILES="$PG_USER_ACCOUNT/profiles" # roles PROP_USER_ROLES="$PG_USER_ACCOUNT/roles" # sudoers entry PROP_USER_SUDOERS="$PG_USER_ACCOUNT/sudoers" # /etc/auto_home entry PROP_USER_AUTOHOME="$PG_USER_ACCOUNT/autohome" # user SSH keys PROP_USER_SSHKEYS="$PG_USER_ACCOUNT/ssh_public_keys" # expiration date for a login PROP_USER_EXPIRE="$PG_USER_ACCOUNT/expire" # name of home directory ZFS dataset PROP_USER_HOME_ZFS_FS="$PG_USER_ACCOUNT/home_zfs_dataset" # home directory mountpoint PROP_USER_HOME_MOUNTPOINT="$PG_USER_ACCOUNT/home_mountpoint" # configured user account properties - utilized during unconfiguration # login PROP_CONFIGURED_USER_LOGIN="$PG_CONFIGURED_USER_ACCOUNT/login" # root account properties # password PROP_ROOT_PASSWORD="$PG_ROOT_ACCOUNT/password" # type (e.g. role) - see user_attr(4) PROP_ROOT_TYPE="$PG_ROOT_ACCOUNT/type" # expiration date for a login PROP_ROOT_EXPIRE="$PG_ROOT_ACCOUNT/expire" # root SSH keys PROP_ROOT_SSHKEYS="$PG_ROOT_ACCOUNT/ssh_public_keys" # default value for unconfigured properties SMF_UNCONFIGURED_VALUE="" # # Binary tested for determining if we are in ROZR zone booted in ready-only # mode. # ROZR_TEST_BINARY="/sbin/sh" # ROZR error message ROZR_ERR_MSG="\nDetected ROZR zone booted in read-only mode. System Configuration not permitted.\n" # Package used to determine if "sudo" is installed on the system PKG_SUDO="pkg:/security/sudo" # Indicates whether root password was successfully configured. typeset root_passwd_is_configured=false # # check_rozr_and_abort() # # Description: # If we are in ROZR non-global zone booted in read-only mode, # log the error message and drop service into maintenance mode. # # Parameters: # $1 - error message emitted if ROZR zone booted in read-only mode # is detected. # check_rozr_and_abort() { typeset err_msg="$1" # # Check if specified binary is writable. It is if ROZR zone is booted # in writable mode. If that file is read-only, emit error message # and drop service into maintenance mode. # if [[ ! -w "$ROZR_TEST_BINARY" ]] ; then print -u1 "$err_msg" | smf_console exit $SMF_EXIT_ERR_FATAL fi } # # enter_maintenance_mode() # # Description: # Provides user with information that configuration of user or root # account failed and drops service into maintenance mode. # # Parameters: # $1 - failed account - defaults to user. # # Returns: exits with $SMF_EXIT_ERR_FATAL # enter_maintenance_mode() { typeset account_type="$1" if [[ "$account_type" != "root" ]] ; then account_type="user" fi if $root_passwd_is_configured ; then root_passwd_msg="the configured" else root_passwd_msg="an empty" fi print -u1 "\n$SMF_FMRI failed to configure $account_type" \ "account." | smf_console print -u1 "Entering single user mode, as login to the fully booted" \ "system may not be possible." | smf_console print -u1 "Once provided with the sulogin(1m) prompt, you can log in" \ "as root with $root_passwd_msg password." | smf_console # # Drop service into maintenance mode. As a result of that, system # will boot into single user mode, since following services have # 'require_all' dependency upon this smf service: # - svc:/milestone/single-user:default # - svc:/system/console-login:default # exit $SMF_EXIT_ERR_FATAL } # # get_smf_prop() # # Description: # Retrieve value of SMF property. # For 'astring' type of property, take care of removing quoting backslashes, # since according to svcprop(1) man page, shell metacharacters # (';', '&', '(', ')', '|', '^', '<', '>', newline, space, tab, backslash, # '"', single-quote, '`') are quoted by backslashes (\). # # Parameters: # $1 - SMF property name # # Returns: # 0 - property was configured in SC manifest # 1 - property was not configured in SC manifest # get_smf_prop() { typeset prop_name="$1" typeset prop_value typeset prop_type # # If property is not set for service instance (which means it was not # defined in SC manifest), return with 'unconfigured' value. # svcprop -Cq -p "$prop_name" $SMF_FMRI if (( $? != 0 )) ; then print -u1 $SMF_UNCONFIGURED_VALUE return 1 fi # # retrieve property. # prop_value=$(svcprop -p "$prop_name" $SMF_FMRI) if (( $? != 0 )) ; then print -u2 "Failed to obtain value of <$prop_name> property" \ "which is suspicious, defaulting to" \ "<$SMF_UNCONFIGURED_VALUE>." print -u1 $SMF_UNCONFIGURED_VALUE return 1 fi # For 'astring' type, remove backslashes from quoted metacharacters. prop_type=$(svccfg -s $SMF_FMRI listprop "$prop_name" | nawk '{ print $2 }') if [[ $prop_type == "astring" ]] ; then prop_value=$(print $prop_value | sed -e 's/\\\(.\)/\1/g') if (( $? != 0 )) ; then print -u2 "Failed when trying to remove '\' from" \ "<$prop_name> property, defaulting to" \ "<$SMF_UNCONFIGURED_VALUE>." print -u1 $SMF_UNCONFIGURED_VALUE return 1 fi # # Since according to svcprop(1) man page empty ASCII string # value is presented as a pair of double quotes (""), we need # to check for this combination and replace it # with empty string. # [[ "$prop_value" == '""' ]] && prop_value="" fi print -u1 "$prop_value" return 0 } # # set_smf_prop() # # Description: # Set value of SMF property. # # Parameters: # $1 - SMF service # $1 - property name # $1 - property value # # Returns: # Aborts with SMF_EXIT_ERR_FATAL in case of failure # set_smf_prop() { typeset svc_name="$1" typeset prop_name="$2" typeset prop_value="$3" svccfg -s $svc_name setprop $prop_name = $prop_value if (( $? != 0 )) ; then print -u2 "svccfg(1M) failed to set <$prop_name> property" \ "to <$prop_value> for <$svc_name> smf service, aborting." exit $SMF_EXIT_ERR_FATAL fi # # Refresh service, so that configuration is propagated to running smf # snapshot. # svcadm refresh $svc_name if (( $? != 0 )) ; then print -u2 "svcadm(1M) failed to refresh" \ "<$svc_name> smf service, aborting." exit $SMF_EXIT_ERR_FATAL fi } # # configure_account_type() # # Description: # set 'type' of user account - needs to be done separately, since # useradd -K type= is not supported - see useradd(1M) man page # # Parameters: # $1 - login # $2 - account type # # Returns: # aborts with $SMF_EXIT_ERR_FATAL in case of failure # configure_account_type() { typeset account="$1" typeset type="$2" typeset cmd_mod="usermod" # # If account is a role, use rolemod(1m) to set its type, otherwise # use usermod(1m). # account_type=$(userattr type $account) if [[ $? == 0 && "$account_type" == "role" ]] ; then print -u1 " <$account> is a role, using rolemod(1m) to set" \ "its type." cmd_mod="rolemod" fi $cmd_mod -S files -K type="$type" "$account" if (( $? != 0 )) ; then print -u2 "Failed to configure <$account> account as type" \ "<$type>, aborting." enter_maintenance_mode $account fi } # # set_expiration_date() # # Description: # sets expiration date for account, if SMF property is set to "0" (zero) # user is forced to change the password at next login # # Parameters: # $1 - login # $2 - expiration date # # Returns: # aborts with $SMF_EXIT_ERR_FATAL in case of failure # set_expiration_date() { typeset account="$1" typeset expire="$2" if [[ "$expire" == "0" ]] ; then print -u1 " User will be prompted to change password for"\ "account <$account> at the next login." passwd -f "$account" if (( $? != 0 )) ; then print -u2 "Calling passwd(1) -f failed for user" \ "<$account>, aborting." enter_maintenance_mode $account fi else usermod -S files -e "$expire" "$account" if (( $? != 0 )) ; then print -u2 "Failed to set expiration date to" \ "<$expire> for account <$account>, aborting." enter_maintenance_mode $account fi fi } # # create_initial_user_profile() # # Description: # Creates initial user's profile by copying .profile and .bashrc # (in case bash is used as user's shell) from /etc/skel/ directory # # Parameters: # $1 - account # $2 - group ID # $3 - home directory # $4 - shell # # Returns: # aborts with $SMF_EXIT_ERR_FATAL in case of failure # create_initial_user_profile() { typeset account="$1" typeset gid="$2" typeset home_dir="$3" typeset user_shell="$4" typeset skel_profiles="$INITIAL_DOT_PROFILE" # # Copy initial profile for bash(1) only if bash(1) was selected # as a user shell. # if [[ "$user_shell" == ~(E)bash$ ]] ; then print -u1 " bash(1) selected as a shell for <$account>" \ "account, copying initial bash profile" \ "$INITIAL_DOT_BASHRC to home directory." skel_profiles="$skel_profiles $INITIAL_DOT_BASHRC" fi # # Copy initial profiles to the home directory. # Set desired permissions and ownership for created files. # for f in $skel_profiles; do typeset home_profile="${home_dir}/$(basename $f)" cp $f "$home_profile" if (( $? != 0 )) ; then print -u2 "Failed to copy $f to $home_profile," \ "aborting." enter_maintenance_mode fi chmod 0644 "$home_profile" if (( $? != 0 )) ; then print -u2 "Failed to change permissions to 0644" \ "for $home_profile, aborting." enter_maintenance_mode fi chown $account:$gid "$home_profile" if (( $? != 0 )) ; then print -u2 "Failed to set ownership to $account:$gid" \ "for $home_profile, aborting." enter_maintenance_mode fi done # # Adjust ownership also for home directory. # chown $account:$gid "$home_dir" if (( $? != 0 )) ; then print -u2 "Failed to set ownership to $account:$gid" \ "for $home_dir, aborting." enter_maintenance_mode fi } # # set_autohome() # # Description: # Adds entry into /etc/auto_home file # # Parameters: # $1 - login name # $2 - string representing homedir mapping # # Returns: # aborts with $SMF_EXIT_ERR_FATAL in case of failure # set_autohome() { typeset user=$1 typeset homedir_map=$2 # create temporary file cp -p $ETC_AUTO_HOME $TMP_AUTO_HOME if (( $? != 0 )) ; then print -u2 "Failed to create temporary file $TMP_AUTO_HOME," \ "aborting." enter_maintenance_mode fi # # Read TMP_AUTO_HOME file and add homedir mapping entry right before # line starting with "+auto_home" string (representing auto_home # NIS map). Remove any existing user entries. # nawk '{ if ( $1 == "+auto_home" ) printf "%s\t%s\n", login, autohome if ( $1 != login ) print }' login="$user" autohome="$homedir_map" \ $ETC_AUTO_HOME > $TMP_AUTO_HOME if (( $? != 0 )) ; then print -u2 "Failed to add entry into $ETC_AUTO_HOME, aborting." enter_maintenance_mode fi # move temporary file to the final location mv $TMP_AUTO_HOME $ETC_AUTO_HOME if (( $? != 0 )) ; then print -u2 "Failed to move temporary file $TMP_AUTO_HOME" \ "to $ETC_AUTO_HOME, aborting." enter_maintenance_mode fi } # # get_zfs_mountpoint() # # Description: # Obtains ZFS dataset mountpoint and prints it to stdout. If mountpoint # can't be determined, empty string is printed. # # Parameters: # $1 - name of ZFS dataset # # Returns: # 0 - Mountpoint successfully obtained # 1 - Mountpoint can't be determined # get_zfs_mountpoint() { typeset zfs_mnt=$(zfs get -H -o value mountpoint "$1") if (( $? != 0 )) ; then print "" return 1 else print "$zfs_mnt" return 0 fi } # # zfs_dataset_mounted() # # Description: # Check if particular ZFS dataset is mounted # # Parameters: # $1 - name of ZFS dataset # # Returns: # 0 - ZFS dataset is mounted # 1 - ZFS dataset is not mounted # zfs_dataset_mounted() { typeset mounted_prop=$(zfs get -H -o value mounted "$1") if [[ "$mounted_prop" == "yes" ]] ; then return 0 else return 1 fi } # # zfs_dataset_exists() # # Description: # Check if particular ZFS dataset exists # # Parameters: # $1 - name of ZFS dataset # # Returns: # 0 - ZFS dataset exists # 1 - ZFS dataset does not exist # zfs_dataset_exists() { zfs list "$1" > /dev/null 2>&1 return $? } # # verify_home_zfs_parents() # # Description: # Verify that parents of given home ZFS dataset exist. If one or more # parent is missing, log error message and drop service into maintenance # mode. # # Parameters: # $1 - name of home ZFS dataset # $2 - value of true indicates default case (name of home ZFS dataset # was not explicitly configured in system configuration profile). # verify_home_zfs_parents() { typeset home_zfs_ds="$1" typeset is_default_case="$2" typeset zfs_ds_parent=$(dirname "$home_zfs_ds") # If there is no parent, there is nothing to check, so return. [[ -z "$zfs_ds_parent" ]] && return # If parent of ZFS dataset exists, return. zfs_dataset_exists "$zfs_ds_parent" && return # # Since at least one parent is missing, log error message. It clarifies # the failure and provides user with instructions how to recover. # # Emit first part of error message. print -u1 "\nService failed to create home directory ZFS dataset" print -u1 "for initial user, because following parent ZFS datasets" print -u1 "are missing (were not created during installation):\n" # List all missing parents. while [[ -n "$zfs_ds_parent" ]] ; do zfs_dataset_exists "$zfs_ds_parent" && break || print -u1 " $zfs_ds_parent" zfs_ds_parent=$(dirname "$zfs_ds_parent") done # Vertical separator print -u1 # # In default case (home ZFS dataset not specified in SC profile), # user is also provided with instructions how to create missing ZFS # datasets. # We do not display those details in non-default case, as it is not # possible to reliably determine mountpoints fitting initial # user's intent. # if $is_default_case ; then # Continue with second part of error message. print -u1 "To recover from the failure, create parent dataset(s) manually" print -u1 "on command line with:\n" # # Display instructions for creating missing datasets. # In default case, only following two datasets may be missing: # /export # /export/home # Handle them separately, as instructions to create them # are different due to /export/home inheriting # mountpoint from /export. # zfs_ds_export_home=$(dirname "$home_zfs_ds") zfs_ds_export=$(dirname "$zfs_ds_export_home") zfs_dataset_exists "$zfs_ds_export" || print -u1 " zfs create -o mountpoint=/export $zfs_ds_export" zfs_dataset_exists "$zfs_ds_export_home" || print -u1 " zfs create $zfs_ds_export_home" # Vertical separator print -u1 # Continue with error message. print -u1 "then clear the service using 'svcadm clear config-user'." else # Continue with error message. print -u1 "To recover from the failure, create parent dataset(s) manually" print -u1 "on command line, then clear the service using 'svcadm clear config-user'." fi # Finish error message. print -u1 "Alternatively, reinstall affected system(s), specifying" print -u1 "appropriate entries for those ZFS datasets." print -u1 "See the ai_manifest(4) man page for more information" print -u1 "and refer to /usr/share/auto_install/manifest/default.xml" print -u1 "as an example." # Drop service into maintenance mode. enter_maintenance_mode } # # unmount_zfs_dataset_chain() # # Description: # Unmounts given ZFS dataset along with its parents. Preserve # the mountpoint if it was temporary one and remove persistent one, # so that filesystem/local is later able to mount ZFS datasets. # Temporary mountpoints are preserved, as their removal may be # perceived as undesired effect of reconfiguration process. # # Tolerate potential unmount failures, as ZFS dataset (or its children) may # be currently in use. For instance if there is an user logged in with home # directory residing on child of to-be-unmounted ZFS dataset. # # Parameters: # $1 - name of home ZFS dataset # unmount_zfs_dataset_chain() { typeset zfs_ds="$1" # Stop at root ZFS dataset while [[ "$zfs_ds" == ~(E)/ ]] ; do # # If ZFS dataset is not mounted, there is no need to unmount # it. And its persistent mountpoint was removed in previous # step as a cascade effect of "rmdir -p". # if ! zfs_dataset_mounted "$zfs_ds" ; then zfs_ds=${zfs_ds%/*} continue fi print -u1 " Unmounting <$zfs_ds> ZFS dataset." zfs unmount "$zfs_ds" if (( $? != 0 )) ; then print -u1 " Could not unmount <$zfs_ds> ZFS dataset," \ "likely because its child is currently in use." print -u1 " Leaving it and its ancestors mounted." break fi # Remove persistent mountpoint of just unmounted ZFS dataset. typeset mntp=$(get_zfs_mountpoint "$zfs_ds") if (( $? == 0 )) ; then # # do not check return code from rmdir - we know # it might fail due to the fact that some # of subdirectories might not be empty # print -u1 " Removing <$mntp> ZFS mountpoint." rmdir -ps "$mntp" else print -u1 " Could not determine mountpoint" \ "for ZFS dataset <$zfs_ds>, skipping its" \ "removal." fi # Proceed with parent dataset. zfs_ds=${zfs_ds%/*} done } # # create_user_account() # # Description: # creates user account # # Returns: # aborts with $SMF_EXIT_ERR_FATAL in case of failure # create_user_account() { typeset login_name typeset uid typeset gid typeset shell typeset roles typeset home_zfs_fs typeset home_mntpoint typeset home_dir typeset desc typeset profiles typeset account_type typeset sudoers typeset password typeset expire typeset autohome # CLI options for useradd(1M) typeset useradd_opt="-S files" # # Indicator that default value is to be used for home ZFS dataset. # Initially assume that home ZFS dataset was explicitly specified # in related System Configuration profile. # typeset is_default_home_zfs_fs=false # # User account can't be created if login is not provided. # Do not treat it as fatal error, just log it # login_name=$(get_smf_prop $PROP_USER_LOGIN) if [[ -z "$login_name" ]]; then print -u1 " Login name not provided, user account" \ "will not be created." return fi # # If user account already exists, do not proceed with the # configuration. Only creating user account from scratch # is supported. Thus messing with existing configuration could # produce undetermined results. # # Also, Trusted Extensions rely on that not being treated as an error, # since user account configuration may be inherited from global zone # which may not be known at the time SC profile is generated. # grep "^${login_name}:" $ETC_SHADOW > /dev/null 2>&1 if (( $? == 0 )) ; then print -u1 " Login <$login_name> already exists, skipping" \ "user account configuration." return fi # get UID. If not provided, let useradd(1M) fill in the default uid=$(get_smf_prop $PROP_USER_UID) (( $? == 0 )) && (( $uid > 99 )) && useradd_opt="$useradd_opt -u $uid" # get GID. If not provided, use 10 (staff) as a default gid=$(get_smf_prop $PROP_USER_GID) (( $? != 0 )) || (( $gid == 0)) && gid=10 useradd_opt="$useradd_opt -g $gid" # get user's shell. If not provided, let useradd(1M) fill in the default shell=$(get_smf_prop $PROP_USER_SHELL) [[ -n "$shell" ]] && useradd_opt="$useradd_opt -s $shell" # get list of comma separated roles roles=$(get_smf_prop $PROP_USER_ROLES) [[ -n "$roles" ]] && useradd_opt="$useradd_opt -R $roles" # set user labels for Trusted Extensions if (smf_is_system_labeled); then if (smf_is_nonglobalzone); then min_label=$(plabel|atohexlabel) clearance=$min_label else min_label=admin_low clearance=admin_high fi useradd_opt="$useradd_opt -K min_label=$min_label -K clearance=$clearance" fi # # Construct name of home directory ZFS dataset. # # If its name is not specified in SC profile, create home ZFS dataset # as a child of 'root pool' ZFS dataset - the one mounted on # / within a zone (global or non-global). # In particular, home ZFS dataset will be created as: # # /export/home/ # # If not explicitly configured in SC profile, its mountpoint will be # inherited from parent dataset (/export/home) # and will be in form of /export/home/. # home_zfs_fs=$(get_smf_prop $PROP_USER_HOME_ZFS_FS) if [[ -z "$home_zfs_fs" ]] ; then is_default_home_zfs_fs=true # # Determine name of 'root pool' ZFS dataset from root ZFS # dataset (mounted on '/'). # Root ZFS dataset has following format: # - global zone: /ROOT/ # - non-global zone: /rpool/ROOT/ # root_ds=$(df -k / | nawk 'NR == 2 {print $1}') if [[ -z "$root_ds" ]] ; then print -u2 "Could not obtain name of root ZFS dataset," \ "aborting." enter_maintenance_mode fi print -u1 " <$root_ds> detected as ZFS root dataset." # # Determine 'root pool' dataset from 'root' dataset # by stripping '/ROOT/' suffix. # root_pool_ds=${root_ds%/ROOT/*} if [[ -z "$root_pool_ds" ]] ; then print -u2 "Could not determine name of root pool ZFS" \ "dataset, aborting." enter_maintenance_mode fi print -u1 " <$root_pool_ds> detected as ZFS root pool dataset." # # Root pool dataset determined, create home dataset in form # of /export/home/. # home_zfs_fs="${root_pool_ds}/export/home/$login_name" fi # # get home directory mountpoint # home_mntpoint=$(get_smf_prop $PROP_USER_HOME_MOUNTPOINT) # # Configure ZFS dataset for user's home directory # print -u1 " Creating user home directory on <$home_zfs_fs> ZFS" \ "dataset." # # Create ZFS dataset if it does not already exist. # if ! zfs_dataset_exists "$home_zfs_fs" ; then # # First verify that parents of to-be-created home ZFS # dataset exist. If they don't exist, inform user and # enter maintenance mode. # verify_home_zfs_parents "$home_zfs_fs" "$is_default_home_zfs_fs" # # set also mountpoint if provided, otherwise let zfs # inherit the mountpoint from parent dataset # if [[ -n "$home_mntpoint" ]] ; then zfs create -o mountpoint="$home_mntpoint" \ "$home_zfs_fs" else # # Unmount all parents of to-be-created ZFS dataset, # as they may be currently mounted on temporary # mountpoints in which case 'zfs create' would fail # to mount dataset created with inherited mountpoint. # unmount_zfs_dataset_chain ${home_zfs_fs%/*} zfs create "$home_zfs_fs" fi if (( $? != 0 )) ; then print -u2 "Failed to create ZFS dataset" \ "<$home_zfs_fs>, aborting." enter_maintenance_mode fi else # # If ZFS mountpoint is not explicitly configured, go with # existing ZFS mountpoint. If mountpoint can't be determined # or if it is 'legacy', default to '/export/home/. # if [[ -z "$home_mntpoint" ]] ; then zfs_mntpoint=$(get_zfs_mountpoint "$home_zfs_fs") if (( $? != 0 )) ; then home_mntpoint="/export/home/$login_name" print -u1 " Could not determine mountpoint" \ "for ZFS dataset <$home_zfs_fs>," \ "<$home_mntpoint> will be used." elif [[ "$zfs_mntpoint" == "legacy" ]] ; then home_mntpoint="/export/home/$login_name" print -u1 " ZFS dataset <$home_zfs_fs>," \ "uses legacy mountpoint, it will be set" \ "to <$home_mntpoint> instead." fi fi if [[ -n "$home_mntpoint" ]] ; then print -u1 " ZFS dataset <$home_zfs_fs> exists, only" \ "ZFS mountpoint will be set to <$home_mntpoint>." zfs set mountpoint="$home_mntpoint" "$home_zfs_fs" if (( $? != 0 )) ; then print -u2 "Failed to set mountpoint to" \ "<$home_mntpoint> for ZFS dataset" \ "<$home_zfs_fs>, aborting." enter_maintenance_mode fi fi # # Unmount home ZFS dataset along with its parents to make sure # they are not mounted on temporary mountpoints. # Home ZFS dataset is mounted on its persistent mountpoint # in the next step. # unmount_zfs_dataset_chain "$home_zfs_fs" # If not already mounted, mount home ZFS dataset now. if zfs_dataset_mounted "$home_zfs_fs" ; then print -u1 " ZFS dataset <$home_zfs_fs> is mounted." else print -u1 " Mounting ZFS dataset <$home_zfs_fs>." zfs mount "$home_zfs_fs" if (( $? != 0 )) ; then print -u2 "Could not mount ZFS dataset" \ "<$home_zfs_fs>, aborting." enter_maintenance_mode fi fi fi # # now when ZFS dataset has been configured, use its mountpoint # as user's home directory # home_mntpoint=$(get_zfs_mountpoint "$home_zfs_fs") if (( $? != 0 )) ; then print -u2 "Could not determine mountpoint for ZFS dataset" \ "<$home_zfs_fs>, aborting." enter_maintenance_mode fi print -u1 " Home mountpoint: $home_mntpoint" # set permissions to 0755 for home directory chmod 0755 "$home_mntpoint" if (( $? != 0 )) ; then print -u2 "Failed to change permissions to 0755 for" \ "${home_mntpoint} directory, aborting." enter_maintenance_mode fi # # Create user account by means of useradd(1M). As far as home # directory path is concerned, it can be either maintained in passwd(4) # database or in the auto_home map (in which case automounter mounts # home directory on demand on /home/ mountpoint). # # Desired scenario is selected via autohome smf property. # If autohome property is set to empty string, let useradd(1m) create # user account without dependency on automounter. Otherwise, instruct # useradd(1m) to create entry in /etc/auto_home file. In default # case (autohome property not specified) it is created in form of # # localhost: # # Home directory is prepended with 'localhost' hostname in order to # avoid hardcoding current hostname in /etc/auto_home file. That would # cause problems in scenarios when hostname is not permanent (obtained # from DHCP) or is to be reconfigured (e.g. when non-global zone # is created as a clone of existing one). # autohome=$(get_smf_prop $PROP_USER_AUTOHOME) if [[ $? == 0 && -z "$autohome" ]]; then home_dir="$home_mntpoint" else home_dir="localhost:$home_mntpoint" fi print -u1 " Home directory: $home_dir" print -u1 " Calling useradd(1M) to create user account." print -u1 " cmd: useradd $useradd_opt -d $home_dir $login_name" useradd $useradd_opt -d "$home_dir" $login_name typeset -i ret=$? if [[ $ret != 0 ]] ; then print -u2 -f "%s%d%s\n" \ "useradd(1M) failed to create user account, (ret=" \ $ret \ "), aborting." enter_maintenance_mode fi # set description for user account (usually full user name) desc=$(get_smf_prop $PROP_USER_DESCRIPTION) if [[ -n "$desc" ]] ; then print -u1 " Setting description to <$desc> for account" \ "<$login_name>." usermod -S files -c "$desc" "$login_name" if (( $? != 0 )) ; then print -u2 "Failed to set description to <$desc> for" \ "<$login_name> account, aborting." enter_maintenance_mode fi fi # assign profiles to user account profiles=$(get_smf_prop $PROP_USER_PROFILES) if [[ -n "$profiles" ]] ; then print -u1 " Assigning profiles <$profiles> to user account" \ "<$login_name>." usermod -S files -P "$profiles" "$login_name" if (( $? != 0 )) ; then print -u2 "Failed to assign profiles <$profiles> to" \ "<$login_name> account, aborting." enter_maintenance_mode fi fi # set type of user account account_type=$(get_smf_prop $PROP_USER_TYPE) if [[ -n "$account_type" ]] ; then print -u1 " Configuring <$login_name> account as type" \ "<$account_type>." configure_account_type "$login_name" "$account_type" fi # # Set 'lock_after_retries' to 'no'. # That prevents locking user account after the count of failed logins # equals or exceeds the allowed number of retries as defined by RETRIES # in /etc/default/login. # print -u1 " Preventing locking user account when number of allowed" \ "login attempts is exceeded." usermod -S files -K lock_after_retries=no "$login_name" if (( $? != 0 )) ; then print -u2 "usermod -S files -K lock_after_retries=no command" \ "failed, aborting." enter_maintenance_mode fi # if provided, set password for created user password=$(get_smf_prop $PROP_USER_PASSWORD) if (( $? == 0 )); then print -u1 " Setting password for user <$login_name>." passwd -p "$password" "$login_name" fi # # configure expiration date # # if required, forces the user to change password at the next login by # expiring the password # expire=$(get_smf_prop $PROP_USER_EXPIRE) if [[ -n "$expire" ]] ; then print -u1 " Setting expire date to <$expire> for user" \ "<$login_name>." set_expiration_date "$login_name" "$expire" fi # # Configure sudoers entry, if provided, and if the sudo # directory structure that is needed exists. # if [ -d "$ETC_SUDOERS_DIR" ] ; then sudoers=$(get_smf_prop $PROP_USER_SUDOERS) if [[ -n "$sudoers" ]] ; then print -u1 " Creating <$ETC_SUDOERS_FILE> file." # Make sure that the created file is not publicly # readable by setting the umask to the correct # permissions before creating it. OLDUMASK=$(umask) umask 0226 print "$login_name $sudoers" > $ETC_SUDOERS_FILE if (( $? != 0 )) ; then print -u2 "Failed to create <$ETC_SUDOERS_FILE>" \ "file, aborting." # return to the previous umask umask $OLDUMASK enter_maintenance_mode fi # return to the previous umask umask $OLDUMASK fi else print -u1 "Unable to assign sudo privileges to user" \ "<$login_name>." print -u1 "Check for the existence of $PKG_SUDO on the system." fi # # Customize /etc/auto_home entry. # if [[ -n "$autohome" ]] ; then print -u1 " Adding entry '$autohome' for user" \ "<$login_name> into $ETC_AUTO_HOME file." set_autohome "$login_name" "$autohome" fi # # Create initial user's profile by copying .profile and .bashrc # from /etc/skel/ directory. # create_initial_user_profile "$login_name" "$gid" "$home_mntpoint" \ "$shell" # configure SSH keys svcprop -c -p $PROP_USER_SSHKEYS $SMF_FMRI | read -A keys if [[ -n "${keys[@]}" ]]; then mkdir $home_mntpoint/.ssh chmod 0700 $home_mntpoint/.ssh touch $home_mntpoint/.ssh/authorized_keys chmod 0644 $home_mntpoint/.ssh/authorized_keys chown -R $login_name:$gid $home_mntpoint/.ssh for key in "${keys[@]}"; do echo "$key" >> $home_mntpoint/.ssh/authorized_keys done fi # # Unmount home ZFS dataset and its parents as well as remove # related persistent mountpoints. # svc:/system/filesystem/local:default SMF service will later in the # boot process take care of mounting all ZFS datasets and creating # mountpoints in required order. # unmount_zfs_dataset_chain "$home_zfs_fs" # # Now when user account has been successfully configured, save # user account login for purposes of unconfiguration. # set_smf_prop ${SMF_FMRI%:default} $PROP_CONFIGURED_USER_LOGIN \ "$login_name" } # # remove_user_account() # # Description: # removes initial user account # # Parameters: # $1 - if set to true, user's home directory along with underlying dataset # is removed. # # Returns: # aborts with $SMF_EXIT_ERR_FATAL in case of failure # remove_user_account() { typeset destructive=$1 typeset login_name # # Obtain login of user account to be removed. If account does not # exist, just log warning and return. # login_name=$(svcprop -p $PROP_CONFIGURED_USER_LOGIN $SMF_FMRI) if [[ -z "$login_name" || "$login_name" == '""' ]]; then print -u1 " Login name not available, user account" \ "will not be removed." return fi grep "^${login_name}:" $ETC_SHADOW > /dev/null 2>&1 if (( $? != 0 )) ; then print -u1 " Login <$login_name> does not exist, " \ "user account will not be removed." return fi # Ensure the user we're removing is off the system pkill -KILL -U $login_name # Wait for user to disappear from utmp before continuing, # otherwise userdel will fail with user "in use". If this never # terminates then the timeout on the unconfigure milestone will # eventually throw us into maintenance. while who -q -n 1 | sed -e 's/ //g' | grep "^${login_name}\$"; do sleep 1 done # # Call userdel(1M) to # - remove user from local databases passwd(4), shadow(4), group(4), # user_attr(4), # - remove user entry from /etc/auto_home file, # - remove home directory along with underlying ZFS dataset # if required. # if $destructive ; then print -u1 " Calling 'userdel -S files -r $login_name' to" \ "remove user account <$login_name> and home directory." userdel -S files -r "$login_name" if (( $? != 0 )) ; then print -u2 "userdel(1M) failed to remove" \ "<$login_name> user account, aborting." exit $SMF_EXIT_ERR_FATAL fi else print -u1 " Calling 'userdel -S files $login_name' to remove" \ "user account <$login_name>, home directory will" \ "be preserved." userdel -S files "$login_name" if (( $? != 0 )) ; then print -u2 "userdel(1M) failed to remove" \ "<$login_name> user account, aborting." exit $SMF_EXIT_ERR_FATAL fi fi # # Remove user's sudoers(4) file. # As sudoers configuration is optional, the file may not have been # created. Thus check for its existence. # if [[ -f $ETC_SUDOERS_FILE ]] ; then print -u1 " Removing <$ETC_SUDOERS_FILE> file." rm $ETC_SUDOERS_FILE if (( $? != 0 )) ; then print -u2 "Failed to remove <$ETC_SUDOERS_FILE>," \ "aborting." exit $SMF_EXIT_ERR_FATAL fi fi # # Now when user account has been successfully unconfigured, clean up # smf property carrying user account login. # set_smf_prop ${SMF_FMRI%:default} $PROP_CONFIGURED_USER_LOGIN "\"\"" print -u1 " User account successfully removed." } # # configure_root_account() # # Description: # configures root account # # Returns: # aborts with $SMF_EXIT_ERR_FATAL in case of failure # configure_root_account() { typeset password typeset account_type typeset expire # password password=$(get_smf_prop $PROP_ROOT_PASSWORD) if (( $? == 0 )); then print -u1 " Setting root password." passwd -p "$password" root fi # root password has been successfully configured root_passwd_is_configured=true # configure account type (e.g. role) # set type of user account account_type=$(get_smf_prop $PROP_ROOT_TYPE) if [[ -n "$account_type" ]] ; then print -u1 " Configuring root account as type <$account_type>." configure_account_type root "$account_type" fi # set expiration date expire=$(get_smf_prop $PROP_ROOT_EXPIRE) if [[ -n "$expire" ]] ; then print -u1 " Setting expire date to <$expire> for root." set_expiration_date root "$expire" fi # configure SSH keys svcprop -c -p $PROP_ROOT_SSHKEYS $SMF_FMRI | read -A keys if [[ -n "${keys[@]}" ]]; then # create /root/.ssh if needed if [[ ! -x /root/.ssh ]]; then mkdir /root/.ssh chmod 0700 /root/.ssh fi # remove any pre-existing authorized_keys file if [[ -e /root/.ssh/authorized_keys ]]; then rm -f /root/.ssh/authorized_keys fi touch /root/.ssh/authorized_keys chmod 0644 /root/.ssh/authorized_keys for key in "${keys[@]}"; do echo "$key" >> /root/.ssh/authorized_keys done fi } # # unconfigure_root_account() # # Description: # reverts changes done for root account during configuration: # - password is deleted # - if root was configured as a role, change it to normal account # # Returns: # aborts with $SMF_EXIT_ERR_FATAL in case of failure # unconfigure_root_account() { # delete root password passwd -r files -d root if (( $? != 0 )) ; then print -u2 "Failed to delete root password, aborting." exit $SMF_EXIT_ERR_FATAL fi # if root was configured as a role, change it to normal account root_type=$(userattr type root) if [[ $? == 0 && "$root_type" == "role" ]] ; then print -u1 " Root is currently a role, reverting it to" \ "normal account." rolemod -K type=normal root if (( $? != 0 )) ; then print -u2 "Failed to configure root as a normal" \ "account, aborting." exit $SMF_EXIT_ERR_FATAL fi else print -u1 " Root configured as a normal account." fi print -u1 " Root account successfully unconfigured." } # # remove_pg() # # Description: # removes property group from service specified by $SMF_FMRI # # Parameters: # $1 - property group # # Returns: # aborts with $SMF_EXIT_ERR_FATAL in case of failure # remove_pg() { typeset pg=$1 print -u1 " Removing property group <$pg>." svccfg -s $SMF_FMRI delpg $pg if (( $? != 0 )) ; then print -u2 "Failed to remove <$pg> property group, aborting." exit $SMF_EXIT_ERR_FATAL fi # refresh service, so that change is reflected in running smf snapshot svcadm refresh $SMF_FMRI if (( $? != 0 )) ; then print -u2 "svcadm(1M) failed to refresh" \ "$SMF_FMRI smf service, aborting." exit $SMF_EXIT_ERR_FATAL fi } # # configure_user_root() # # Description: # Configures user and root account. # # configure_user_root() { # check if root account is to be configured. rootcheck=$(get_smf_prop $PROP_ROOT_PASSWORD) [[ -n $rootcheck ]] && configure_root=true || configure_root=false # check if user account is to be configured usercheck=$(get_smf_prop $PROP_USER_PASSWORD) [[ -n $usercheck ]] && configure_user=true || configure_user=false # No need to proceed if there is nothing to configure. if ! $configure_user && ! $configure_root ; then return fi # # Running configuration process is not permitted in ROZR # non-global zone booted in read-only mode. # check_rozr_and_abort "$ROZR_ERR_MSG" # configure root account if $configure_root; then print -u1 "Configuring root account." configure_root_account remove_pg $PG_ROOT_ACCOUNT print -u1 "root account successfully configured." fi # configure user account if $configure_user; then print -u1 "Configuring user account." create_user_account remove_pg $PG_USER_ACCOUNT print -u1 "User account successfully configured." fi } # # unconfigure_user_root() # # Description: # Unconfigures user and root account. # # Parameters: # $1 - if set to true, user's home directory along with underlying dataset # is removed. # unconfigure_user_root() { typeset destructive=$1 # unconfigure user account print -u1 " Removing initial user account from the system." remove_user_account $destructive # unconfigure root account print -u1 " Reverting configuration of root account into pristine" \ "state." unconfigure_root_account } ## Main ## # # Usage: /lib/svc/method/svc-config-user { start | unconfigure } # if (( $# != 1 )) ; then print -u2 "$USAGE" exit $SMF_EXIT_ERR_FATAL fi # only 'start' and 'unconfigure' methods are supported case "$1" in 'start') # configure user and root account configure_user_root ;; 'unconfigure') # # Running unconfiguration process is not permitted in ROZR # non-global zone booted in read-only mode. # check_rozr_and_abort "$ROZR_ERR_MSG" # # Destroy user's home directory (along with underlying ZFS dataset) # only if UNCONFIG_DESTRUCTIVE was set to "true" by caller. # typeset destructive="$UNCONFIG_DESTRUCTIVE" [[ -z "$destructive" ]] && destructive=false # unconfigure user and root account unconfigure_user_root $destructive # Unroll any admin customization svccfg -s $SMF_FMRI delcust if [ $? -ne 0 ]; then print -u2 "Failed to unroll administrative customizations for $SMF_FMRI" exit $SMF_EXIT_ERR_FATAL fi svcadm refresh $SMF_FMRI ;; *) print -u2 "$USAGE" exit $SMF_EXIT_ERR_FATAL ;; esac exit $SMF_EXIT_OK