# Copyright (c) 2017 The NetBSD Foundation, Inc. # All rights reserved. # # This code is derived from software contributed to The NetBSD Foundation # by Johnny C. Lam. # # 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. # # NAME # usergroup_MirBSD -- usergroup implementation for MirBSD # # SYNOPSIS # platform_groupadd [-g ] # # platform_useradd [-c ] [-d ] [-g ] # [-s ] [-u ] # # DESCRIPTION # The functions implement a NetBSD/Solaris-compatible groupadd and # useradd. # # RETURN VALUES # All functions return 0 on success, and return non-zero otherwise. # # ENVIRONMENT # The following variables are used if they are set. # # CAT The name or path to the cat(1) utility. # # CHMOD The name or path to the chmod(1) utility. # # CHOWN The name or path to the chown(8) utility. # # ETC_GROUP # The path to the system group database text file. The # default value is "/etc/group". # # ETC_PASSWD # The path to the system user/password database text file. # The default value is "/etc/master.passwd". # # MKTEMP The name or path to the mktemp(1) utility. # # MV The name or path to the mv(1) utility. # # PWD_MKDB # The name or path to the pwd_mkdb(8) utility that is used # to regenerate the pwd.db and spwd.db binary databases # from ${ETC_PASSWD}. # # RM The name or path to the rm(1) utility. # # RMDIR The name or path to the rmdir(1) utility. # # SORT The name or path to the sort(1) utility. # __task_usergroup_MirBSD__="yes" task_load echo task_load lock task_load quote _platform_group_exists() { : ${ETC_GROUP:=/etc/group} # parameters local group="$1" local gid="$2" # return values _exists_group=; _exists_gid=; _exists_gid_max= local groupfile="${ETC_GROUP}" [ -f "$groupfile" ] || return 1 local group_to_gid= local gid_found= local gid_max=0 local line save_IFS word1 word3 while IFS=: read line; do save_IFS=$IFS; IFS=: set -o noglob; set -- $line; set +o noglob word1=$1; word3=$3 IFS=$save_IFS if [ "$group" = "$word1" ]; then group_to_gid=$word3 fi if [ "$gid" = "$word3" ]; then gid_found="yes" fi if [ $gid_max -lt $word3 ]; then gid_max=$word3 fi done < $groupfile _exists_gid_max=$gid_max if [ -z "$group_to_gid" ]; then # group not found if [ -z "$gid_found" ]; then # group not found, and group ID not given return 1 else # group not found, but group ID found _exists_gid=$gid return 2 fi else _exists_group=$group _exists_gid=$group_to_gid if [ -z "$gid" ]; then # group ID not given return 0 elif [ -z "$gid_found" ]; then # group found, but group ID not found return 2 elif [ "$gid" != "$group_to_gid" ]; then # group found, but group ID doesn't match return 2 else # group found, and group ID matches return 0 fi fi return 3 } _platform_addgroup() { : ${CAT:=cat} : ${CHMOD:=chmod} : ${CHOWN:=chown} : ${MKTEMP:=mktemp} : ${MV:=mv} : ${RM:=rm} : ${RMDIR:=rmdir} : ${SORT:=sort} : ${ETC_GROUP:=/etc/group} # parameters local group="$1" local gid="$2" local groupfile temp tmpdir tmpdir_quoted quoted groupfile="${ETC_GROUP}" tmpdir=$( ${MKTEMP} -d "$groupfile.tmp.XXXXXXXXXX" ) case $groupfile in /*) temp="$tmpdir/${groupfile##*/}" ;; *) temp="$tmpdir/${groupfile}" ;; esac task_quote "$tmpdir" tmpdir_quoted=$quoted __platform_addgroup_dirs__="$__platform_addgroup_dirs__ $quoted" local result=1 if [ -n "$tmpdir" -a -d "$tmpdir" ]; then local perms="root:wheel" local mode="0644" if { echo "$group:*:$gid:" && [ ! -f "$groupfile" ] || ${CAT} "$groupfile" } > $temp && ${CHOWN} $perms "$temp" && ${CHMOD} $mode "$temp" && ${SORT} -t: -nk3 -o "$temp" -u "$temp" && ${MV} -f "$temp" "$groupfile"; then result=0 else ${RM} -f "$temp" fi ${RMDIR} "$tmpdir" __platform_addgroup_dirs__=${__platform_addgroup_dirs__% $tmpdir_quoted} fi _platform_addgroup_cleanup return $result } platform_groupadd() { : ${ETC_GROUP:=/etc/group} # Lower bound of GID range for new groups. local lowgid=700 local gid= local arg local OPTIND=1 while getopts ":g:" arg "$@"; do case $arg in g) gid=${OPTARG} ;; *) return 127 ;; esac done shift $(( ${OPTIND} - 1 )) [ $# -gt 0 ] || return 127 local group="$1"; shift [ -n "$group" ] || return 1 # Lock ${ETC_GROUP} against other tasks. local groupfile="${ETC_GROUP}" local lock lock_quoted quoted lock="$groupfile.lock" task_quote "$lock" lock_quoted=$quoted __platform_groupadd_locks__="$quoted $__platform_groupadd_locks__" task_lock "$lock" || return 1 local result=0 # Check if the group and group ID are already in the database. local _exists_group _exists_gid _exists_gid_max _platform_group_exists "$group" "$gid" case $? in 0) # group already exists with matching group ID. result=1 ;; 1) : "fall through and add missing group" ;; *) result=1 ;; esac if [ $result -eq 0 ]; then # Set gid to the existing or proposed GID of the group. if [ -z "$gid" ]; then if [ -n "$_exists_gid" ]; then gid=$_exists_gid elif [ $_exists_gid_max -lt $lowgid ]; then gid=$lowgid else gid=$(( $_exists_gid_max + 1 )) fi fi # Add the group and group ID to the database. _platform_addgroup "$group" "$gid" || result=1 fi # Release lock on ${ETC_GROUP}. task_lock -r "$lock" __platform_groupadd_locks__=${__platform_groupadd_locks__#$lock_quoted } _platform_groupadd_cleanup return $result } _platform_user_exists() { : ${ETC_PASSWD:=/etc/master.passwd} # parameters local user="$1" local uid="$2" # return values _exists_user=; _exists_uid=; _exists_uid_max= local userfile="${ETC_PASSWD}" [ -f "$userfile" ] || return 1 local user_to_uid= local uid_found= local uid_max=0 local line save_IFS word1 word3 while read line; do save_IFS=$IFS; IFS=: set -o noglob; set -- $line; set +o noglob word1=$1; word3=$3 IFS=$save_IFS if [ "$user" = "$word1" ]; then user_to_uid=$word3 fi if [ "$uid" = "$word3" ]; then uid_found="yes" fi if [ $uid_max -lt $word3 ]; then uid_max=$word3 fi done < $userfile _exists_uid_max=$uid_max if [ -z "$user_to_uid" ]; then # user not found if [ -z "$uid_found" ]; then # user not found, and user ID not given return 1 else # user not found, but user ID found _exists_uid=$uid return 2 fi else _exists_user=$user _exists_uid=$user_to_uid if [ -z "$uid" ]; then # user ID not given return 0 elif [ -z "$uid_found" ]; then # user found, but user ID not found return 2 elif [ "$uid" != "$user_to_uid" ]; then # user found, but user ID doesn't match return 2 else # user found, and user ID matches return 0 fi fi return 3 } _platform_adduser() { : ${CAT:=cat} : ${CHMOD:=chmod} : ${CHOWN:=chown} : ${MKTEMP:=mktemp} : ${PWD_MKDB:=pwd_mkdb} : ${RM:=rm} : ${RMDIR:=rmdir} : ${SORT:=sort} : ${ETC_PASSWD:=/etc/master.passwd} # parameters local user="$1"; local group="$2"; local uid="$3" local descr="$4"; local home="$5"; local shell="$6" local userfile temp tmpdir tmpdir_quoted quoted userfile="${ETC_PASSWD}" tmpdir=$( ${MKTEMP} -d "$userfile.tmp.XXXXXXXXXX" ) || return 1 case $tmpdir in /*) temp="$tmpdir/${userfile##*/}" ;; *) temp="$tmpdir/$userfile" ;; esac task_quote "$tmpdir" tmpdir_quoted=$quoted __platform_adduser_dirs__="$__platform_adduser_dirs__ $quoted" local result=1 if [ -n "$tmpdir" -a -d "$tmpdir" ]; then local perms="root:wheel" local mode="0600" if { echo "$user:*:$uid:$group::0:0:$descr:$home:$shell" && [ ! -f "$userfile" ] || ${CAT} "$userfile" } > $temp && ${CHOWN} $perms "$temp" && ${CHMOD} $mode "$temp" && ${SORT} -t: -nk3 -o "$temp" -u "$temp" && ${PWD_MKDB} -p "$temp"; then result=0 else ${RM} -f "$temp" fi ${RMDIR} "$tmpdir" __platform_adduser_dirs__=${__platform_adduser_dirs__% $tmpdir_quoted} fi _platform_adduser_cleanup return $result } platform_useradd() { # Lower bound of UID range for new users. local lowuid=700 local basedir= local descr= local home= local group= local shell= local uid= local arg local OPTIND=1 while getopts ":c:d:g:s:u:" arg "$@"; do case $arg in c) descr=${OPTARG} ;; d) home=${OPTARG} ;; g) group=${OPTARG} ;; s) shell=${OPTARG} ;; u) uid=${OPTARG} ;; *) return 127 ;; esac done shift $(( ${OPTIND} - 1 )) [ $# -gt 0 ] || return 127 local user="$1"; shift [ -n "$user" ] || return 1 : ${basedir:=/home} : ${home:=$basedir/$user} : ${group:=users} : ${shell:=/bin/sh} # Lock ${ETC_PASSWD} against other tasks. local userfile="${ETC_PASSWD}" local lock lock_quoted quoted local quoted lock="$userfile.lock" task_quote "$lock" lock_quoted=$quoted __platform_useradd_locks__="$quoted $__platform_useradd_locks__" task_lock "$lock" || return 1 local result=0 # Check if the user and user ID are already in the database. local _exists_user _exists_uid _exists_uid_max _platform_user_exists "$user" "$uid" case $? in 0) # user already exists with matching user ID. result=1 ;; 1) : "fall through and add missing user" ;; *) result=1 ;; esac if [ $result -eq 0 ]; then # Set uid to the existing or proposed UID of the user. if [ -z "$uid" ]; then if [ -n "$_exists_uid" ]; then uid=$_exists_uid elif [ $_exists_uid_max -lt $lowuid ]; then uid=$lowuid else uid=$(( $_exists_uid_max + 1 )) fi fi # Get the group ID of the user to be added. case $group in ""|*[!0-9]*) # $group is not a GID platform_groupadd "$group" _platform_group_exists "$group" || result=1 group=$_exists_gid ;; esac fi if [ $result -eq 0 ]; then # Add the user and user ID to the database. _platform_adduser "$user" "$group" "$uid" \ "$descr" "$home" "$shell" || result=1 fi # Release lock on ${ETC_PASSWD}. task_lock -r "$lock" __platform_useradd_locks__=${__platform_useradd_locks__#$lock_quoted } _platform_useradd_cleanup return $result } _platform_addgroup_cleanup() { : ${RM:=rm} eval set -- $__platform_addgroup_dirs__ local dir for dir; do # Sanity check before blowing away $dir. case ${dir##*/} in *.tmp.*) ${RM} -fr "$dir" ;; *) task_echo "!!! WARNING: usergroup: Remove directory $dir" ;; esac done __platform_addgroup_dirs__= } _platform_groupadd_cleanup() { eval set -- $__platform_groupadd_locks__ local lockfile for lockfile; do task_lock -r "$lockfile" done __platform_groupadd_locks__= } _platform_adduser_cleanup() { : ${RM:=rm} eval set -- $__platform_adduser_dirs__ local dir for dir; do # Sanity check before blowing away $dir. case ${dir##*/} in *.tmp.*) ${RM} -fr "$dir" ;; *) task_echo "!!! WARNING: usergroup: Remove directory $dir" ;; esac done __platform_adduser_dirs__= } _platform_useradd_cleanup() { eval set -- $__platform_useradd_locks__ local lockfile for lockfile; do task_lock -r "$lockfile" done __platform_useradd_locks__= } __install_tasks_platform_usergroup_MirBSD_init__="_platform_usergroup_init" _platform_usergroup_init() { task_cleanup_add_hook \ _platform_addgroup_cleanup _platform_groupadd_cleanup \ _platform_adduser_cleanup _platform_useradd_cleanup } # Static variables for temporary directories that should be removed if an # error occurs. # __platform_addgroup_dirs__= __platform_adduser_dirs__= # Static variables for locks that should be released if an error occurs. __platform_groupadd_locks__= __platform_useradd_locks__=