1 # Copyright 1999-2020 Gentoo Authors
2 # Distributed under the terms of the GNU General Public License v2
6 # base-system@gentoo.org (Linux)
7 # Michał Górny <mgorny@gentoo.org> (NetBSD)
8 # @BLURB: user management in ebuilds
10 # The user eclass contains a suite of functions that allow ebuilds
11 # to quickly make sure users in the installed system are sane.
13 if [[ -z ${_USER_ECLASS} ]]; then
18 # @FUNCTION: _assert_pkg_ebuild_phase
20 # @USAGE: <calling func name>
21 _assert_pkg_ebuild_phase() {
22 case ${EBUILD_PHASE} in
23 setup|preinst|postinst|prerm|postrm) ;;
25 eerror "'$1()' called from '${EBUILD_PHASE}' phase which is not OK:"
26 eerror "You may only call from pkg_{setup,{pre,post}{inst,rm}} functions."
27 eerror "Package fails at QA and at life. Please file a bug."
28 die "Bad package! $1 is only for use in some pkg_* functions!"
32 # @FUNCTION: user_get_nologin
35 # Find an appropriate 'nologin' shell for the platform, and output
40 for eshell in /sbin/nologin /usr/sbin/nologin /bin/false /usr/bin/false /dev/null ; do
41 [[ -x ${ROOT}${eshell} ]] && break
44 if [[ ${eshell} == "/dev/null" ]] ; then
45 ewarn "Unable to identify the shell to use, proceeding with userland default."
47 GNU) eshell="/bin/false" ;;
48 BSD) eshell="/sbin/nologin" ;;
49 Darwin) eshell="/usr/sbin/nologin" ;;
50 *) die "Unable to identify the default shell for userland ${USERLAND}"
58 # @USAGE: <user> [-F] [-M] [uid] [shell] [homedir] [groups]
60 # Same as enewgroup, you are not required to understand how to properly add
61 # a user to the system. The only required parameter is the username.
62 # Default uid is (pass -1 for this) next available, default shell is
63 # /bin/false, default homedir is /dev/null, and there are no default groups.
65 # If -F is passed, enewuser will always enforce specified UID and fail if it
66 # can not be assigned.
67 # If -M is passed, enewuser does not create the home directory if it does not
70 if [[ ${EUID} != 0 ]] ; then
71 ewarn "Insufficient privileges to execute ${FUNCNAME[0]}"
74 _assert_pkg_ebuild_phase ${FUNCNAME}
76 local create_home=1 force_uid=
77 while [[ $1 == -* ]]; do
81 *) die "${FUNCNAME}: invalid option ${1}";;
88 if [[ -z ${euser} ]] ; then
89 eerror "No username specified !"
90 die "Cannot call enewuser without a username"
93 # lets see if the username already exists
94 if [[ -n $(egetent passwd "${euser}") ]] ; then
97 elog "Adding user '${euser}' to your system ..."
99 # options to pass to useradd
104 if [[ -n ${euid} && ${euid} != -1 ]] ; then
105 if [[ ${euid} -gt 0 ]] ; then
106 if [[ -n $(egetent passwd ${euid}) ]] ; then
107 [[ -n ${force_uid} ]] && die "${FUNCNAME}: UID ${euid} already taken"
111 eerror "Userid given but is not greater than 0 !"
112 die "${euid} is not a valid UID"
115 [[ -n ${force_uid} ]] && die "${FUNCNAME}: -F with uid==-1 makes no sense"
118 if [[ ${euid} == "next" ]] ; then
119 for ((euid = 999; euid >= 101; euid--)); do
120 [[ -z $(egetent passwd ${euid}) ]] && break
122 [[ ${euid} -ge 101 ]] || die "${FUNCNAME}: no free UID found"
125 elog " - Userid: ${euid}"
128 local eshell=$1; shift
129 if [[ ! -z ${eshell} ]] && [[ ${eshell} != "-1" ]] ; then
130 if [[ ! -e ${ROOT}${eshell} ]] ; then
131 eerror "A shell was specified but it does not exist !"
132 die "${eshell} does not exist in ${ROOT}"
134 if [[ ${eshell} == */false || ${eshell} == */nologin ]] ; then
135 eerror "Do not specify ${eshell} yourself, use -1"
136 die "Pass '-1' as the shell parameter"
139 eshell=$(user_get_nologin)
141 elog " - Shell: ${eshell}"
142 opts+=( -s "${eshell}" )
145 local ehome=$1; shift
146 if [[ -z ${ehome} ]] || [[ ${ehome} == "-1" ]] ; then
149 elog " - Home: ${ehome}"
150 opts+=( -d "${ehome}" )
153 local egroups=$1; shift
155 IFS="," read -r -a egroups_arr <<<"${egroups}"
156 if [[ ${#egroups_arr[@]} -gt 0 ]] ; then
157 local defgroup exgroups
158 for g in "${egroups_arr[@]}" ; do
159 if [[ -z $(egetent group "${g}") ]] ; then
160 eerror "You must add group ${g} to the system first"
161 die "${g} is not a valid GID"
163 if [[ -z ${defgroup} ]] ; then
169 opts+=( -g "${defgroup}" )
170 if [[ ! -z ${exgroups} ]] ; then
171 opts+=( -G "${exgroups:1}" )
174 elog " - Groups: ${egroups:-(none)}"
177 if [[ $# -gt 0 ]] ; then
178 die "extra arguments no longer supported; please file a bug"
180 local comment="added by portage for ${PN}"
181 opts+=( -c "${comment}" )
182 elog " - GECOS: ${comment}"
187 *-freebsd*|*-dragonfly*)
188 pw useradd "${euser}" "${opts[@]}" || die
192 useradd "${opts[@]}" "${euser}" || die
196 # all ops the same, except the -g vs -g/-G ...
197 useradd -u ${euid} -s "${eshell}" \
198 -d "${ehome}" -g "${egroups}" "${euser}" || die
202 useradd -M -N -r "${opts[@]}" "${euser}" || die
206 if [[ -n ${create_home} && ! -e ${ROOT}/${ehome} ]] ; then
207 elog " - Creating ${ehome} in ${ROOT}"
208 mkdir -p "${ROOT}/${ehome}"
209 chown "${euser}" "${ROOT}/${ehome}"
210 chmod 755 "${ROOT}/${ehome}"
214 # @FUNCTION: enewgroup
215 # @USAGE: <group> [gid]
217 # This function does not require you to understand how to properly add a
218 # group to the system. Just give it a group name to add and enewgroup will
219 # do the rest. You may specify the gid for the group or allow the group to
220 # allocate the next available one.
222 # If -F is passed, enewgroup will always enforce specified GID and fail if it
223 # can not be assigned.
225 if [[ ${EUID} != 0 ]] ; then
226 ewarn "Insufficient privileges to execute ${FUNCNAME[0]}"
229 _assert_pkg_ebuild_phase ${FUNCNAME}
232 while [[ $1 == -* ]]; do
235 *) die "${FUNCNAME}: invalid option ${1}";;
241 local egroup=$1; shift
242 if [[ -z ${egroup} ]] ; then
243 eerror "No group specified !"
244 die "Cannot call enewgroup without a group"
247 # see if group already exists
248 if [[ -n $(egetent group "${egroup}") ]] ; then
251 elog "Adding group '${egroup}' to your system ..."
255 if [[ -n ${egid} && ${egid} != -1 ]] ; then
256 if [[ ${egid} -gt 0 ]] ; then
257 if [[ -n $(egetent group ${egid}) ]] ; then
258 [[ -n ${force_gid} ]] && die "${FUNCNAME}: GID ${egid} already taken"
259 egid="next available; requested gid taken"
262 eerror "Groupid given but is not greater than 0 !"
263 die "${egid} is not a valid GID"
266 [[ -n ${force_gid} ]] && die "${FUNCNAME}: -F with gid==-1 makes no sense"
267 egid="next available"
269 elog " - Groupid: ${egid}"
272 if [[ $# -gt 0 ]] ; then
273 die "extra arguments no longer supported; please file a bug"
276 # Some targets need to find the next available GID manually
277 _enewgroup_next_gid() {
278 if [[ ${egid} == *[!0-9]* ]] ; then
280 for ((egid = 999; egid >= 101; egid--)) ; do
281 [[ -z $(egetent group ${egid}) ]] && break
283 [[ ${egid} -ge 101 ]] || die "${FUNCNAME}: no free GID found"
289 *-freebsd*|*-dragonfly*)
291 pw groupadd "${egroup}" -g ${egid} || die
296 groupadd -g ${egid} "${egroup}" || die
301 if [[ ${egid} == *[!0-9]* ]] ; then
302 # Non numeric; let groupadd figure out a GID for us
307 # We specify -r so that we get a GID in the system range from login.defs
308 groupadd -r ${opts} "${egroup}" || die
313 # @FUNCTION: esethome
314 # @USAGE: <user> <homedir>
316 # Update the home directory in a platform-agnostic way.
317 # Required parameters is the username and the new home directory.
318 # Specify -1 if you want to set home to the enewuser default
320 # If the new home directory does not exist, it is created.
321 # Any previously existing home directory is NOT moved.
323 _assert_pkg_ebuild_phase ${FUNCNAME}
326 local euser=$1; shift
327 if [[ -z ${euser} ]] ; then
328 eerror "No username specified !"
329 die "Cannot call esethome without a username"
332 # lets see if the username already exists
333 if [[ -z $(egetent passwd "${euser}") ]] ; then
334 ewarn "User does not exist, cannot set home dir -- skipping."
339 local ehome=$1; shift
340 if [[ -z ${ehome} ]] ; then
341 eerror "No home directory specified !"
342 die "Cannot call esethome without a home directory or '-1'"
345 if [[ ${ehome} == "-1" ]] ; then
349 # exit with no message if home dir is up to date
350 if [[ $(egethome "${euser}") == ${ehome} ]]; then
354 elog "Updating home for user '${euser}' ..."
355 elog " - Home: ${ehome}"
357 # ensure home directory exists, otherwise update will fail
358 if [[ ! -e ${ROOT}/${ehome} ]] ; then
359 elog " - Creating ${ehome} in ${ROOT}"
360 mkdir -p "${ROOT}/${ehome}"
361 chown "${euser}" "${ROOT}/${ehome}"
362 chmod 755 "${ROOT}/${ehome}"
365 # update the home directory
367 *-freebsd*|*-dragonfly*)
368 pw usermod "${euser}" -d "${ehome}" && return 0
369 [[ $? == 8 ]] && eerror "${euser} is in use, cannot update home"
370 eerror "There was an error when attempting to update the home directory for ${euser}"
371 eerror "Please update it manually on your system:"
372 eerror "\t pw usermod \"${euser}\" -d \"${ehome}\""
376 usermod -d "${ehome}" "${euser}" && return 0
377 [[ $? == 8 ]] && eerror "${euser} is in use, cannot update home"
378 eerror "There was an error when attempting to update the home directory for ${euser}"
379 eerror "Please update it manually on your system (as root):"
380 eerror "\t usermod -d \"${ehome}\" \"${euser}\""
385 # @FUNCTION: esetshell
386 # @USAGE: <user> <shell>
388 # Update the shell in a platform-agnostic way.
389 # Required parameters is the username and the new shell.
390 # Specify -1 if you want to set shell to platform-specific nologin.
392 _assert_pkg_ebuild_phase ${FUNCNAME}
395 local euser=$1; shift
396 if [[ -z ${euser} ]] ; then
397 eerror "No username specified !"
398 die "Cannot call esetshell without a username"
401 # lets see if the username already exists
402 if [[ -z $(egetent passwd "${euser}") ]] ; then
403 ewarn "User does not exist, cannot set shell -- skipping."
408 local eshell=$1; shift
409 if [[ -z ${eshell} ]] ; then
410 eerror "No shell specified !"
411 die "Cannot call esetshell without a shell or '-1'"
414 if [[ ${eshell} == "-1" ]] ; then
415 eshell=$(user_get_nologin)
418 # exit with no message if shell is up to date
419 if [[ $(egetshell "${euser}") == ${eshell} ]]; then
423 elog "Updating shell for user '${euser}' ..."
424 elog " - Shell: ${eshell}"
428 *-freebsd*|*-dragonfly*)
429 pw usermod "${euser}" -s "${eshell}" && return 0
430 [[ $? == 8 ]] && eerror "${euser} is in use, cannot update shell"
431 eerror "There was an error when attempting to update the shell for ${euser}"
432 eerror "Please update it manually on your system:"
433 eerror "\t pw usermod \"${euser}\" -s \"${eshell}\""
437 usermod -s "${eshell}" "${euser}" && return 0
438 [[ $? == 8 ]] && eerror "${euser} is in use, cannot update shell"
439 eerror "There was an error when attempting to update the shell for ${euser}"
440 eerror "Please update it manually on your system (as root):"
441 eerror "\t usermod -s \"${eshell}\" \"${euser}\""
446 # @FUNCTION: esetcomment
447 # @USAGE: <user> <comment>
449 # Update the comment field in a platform-agnostic way.
450 # Required parameters is the username and the new comment.
452 _assert_pkg_ebuild_phase ${FUNCNAME}
455 local euser=$1; shift
456 if [[ -z ${euser} ]] ; then
457 eerror "No username specified !"
458 die "Cannot call esetcomment without a username"
461 # lets see if the username already exists
462 if [[ -z $(egetent passwd "${euser}") ]] ; then
463 ewarn "User does not exist, cannot set comment -- skipping."
468 local ecomment=$1; shift
469 if [[ -z ${ecomment} ]] ; then
470 eerror "No comment specified !"
471 die "Cannot call esetcomment without a comment"
474 # exit with no message if comment is up to date
475 if [[ $(egetcomment "${euser}") == ${ecomment} ]]; then
479 elog "Updating comment for user '${euser}' ..."
480 elog " - Comment: ${ecomment}"
484 *-freebsd*|*-dragonfly*)
485 pw usermod "${euser}" -c "${ecomment}" && return 0
486 [[ $? == 8 ]] && eerror "${euser} is in use, cannot update comment"
487 eerror "There was an error when attempting to update the comment for ${euser}"
488 eerror "Please update it manually on your system:"
489 eerror "\t pw usermod \"${euser}\" -c \"${ecomment}\""
493 usermod -c "${ecomment}" "${euser}" && return 0
494 [[ $? == 8 ]] && eerror "${euser} is in use, cannot update comment"
495 eerror "There was an error when attempting to update the comment for ${euser}"
496 eerror "Please update it manually on your system (as root):"
497 eerror "\t usermod -c \"${ecomment}\" \"${euser}\""
502 # @FUNCTION: esetgroups
503 # @USAGE: <user> <groups>
505 # Update the group field in a platform-agnostic way.
506 # Required parameters is the username and the new list of groups,
507 # primary group first.
509 _assert_pkg_ebuild_phase ${FUNCNAME}
511 [[ ${#} -eq 2 ]] || die "Usage: ${FUNCNAME} <user> <groups>"
514 local euser=$1; shift
516 # lets see if the username already exists
517 if [[ -z $(egetent passwd "${euser}") ]] ; then
518 ewarn "User does not exist, cannot set group -- skipping."
523 local egroups=$1; shift
525 local g egroups_arr=()
526 IFS="," read -r -a egroups_arr <<<"${egroups}"
527 [[ ${#egroups_arr[@]} -gt 0 ]] || die "${FUNCNAME}: no groups specified"
529 for g in "${egroups_arr[@]}" ; do
530 if [[ -z $(egetent group "${g}") ]] ; then
531 eerror "You must add group ${g} to the system first"
532 die "${g} is not a valid GID"
536 local defgroup=${egroups_arr[0]} exgroups_arr=()
537 # sort supplementary groups to make comparison possible
538 readarray -t exgroups_arr < <(printf '%s\n' "${egroups_arr[@]:1}" | sort)
539 local exgroups=${exgroups_arr[*]}
540 exgroups=${exgroups// /,}
541 egroups=${defgroup}${exgroups:+,${exgroups}}
543 # exit with no message if group membership is up to date
544 if [[ $(egetgroups "${euser}") == ${egroups} ]]; then
548 local opts=( -g "${defgroup}" -G "${exgroups}" )
549 elog "Updating groups for user '${euser}' ..."
550 elog " - Groups: ${egroups}"
554 *-freebsd*|*-dragonfly*)
555 pw usermod "${euser}" "${opts[@]}" && return 0
556 [[ $? == 8 ]] && eerror "${euser} is in use, cannot update groups"
557 eerror "There was an error when attempting to update the groups for ${euser}"
558 eerror "Please update it manually on your system:"
559 eerror "\t pw usermod \"${euser}\" ${opts[*]}"
563 usermod "${opts[@]}" "${euser}" && return 0
564 [[ $? == 8 ]] && eerror "${euser} is in use, cannot update groups"
565 eerror "There was an error when attempting to update the groups for ${euser}"
566 eerror "Please update it manually on your system (as root):"
567 eerror "\t usermod ${opts[*]} \"${euser}\""