1 # Copyright 2019 Gentoo Authors
2 # Distributed under the terms of the GNU General Public License v2
4 # @ECLASS: acct-user.eclass
6 # Michał Górny <mgorny@gentoo.org>
8 # Michael Orlitzky <mjo@gentoo.org>
9 # Michał Górny <mgorny@gentoo.org>
11 # @BLURB: Eclass used to create and maintain a single user entry
13 # This eclass represents and creates a single user entry. The name
14 # of the user is derived from ${PN}, while (preferred) UID needs to
15 # be specified via ACCT_USER_ID. Additional variables are provided
16 # to override the default home directory, shell and add group
17 # membership. Packages needing the user in question should depend
18 # on the package providing it.
20 # The ebuild needs to call acct-user_add_deps after specifying
24 # If your package needs user 'foo' belonging to same-named group, you
25 # create 'acct-user/foo' package and add an ebuild with the following
32 # ACCT_USER_GROUPS=( foo )
36 # Then you add appropriate dependency to your package. The dependency
38 # - DEPEND (+ RDEPEND) if the user is already needed at build time,
39 # - RDEPEND if it is needed at install time (e.g. you 'fowners' files
40 # in pkg_preinst) or run time.
42 if [[ -z ${_ACCT_USER_ECLASS} ]]; then
47 *) die "EAPI=${EAPI:-0} not supported";;
52 [[ ${CATEGORY} == acct-user ]] ||
53 die "Ebuild error: this eclass can be used only in acct-user category!"
56 # << Eclass variables >>
58 # @ECLASS-VARIABLE: ACCT_USER_NAME
61 # The name of the user. This is forced to ${PN} and the policy prohibits
62 # it from being changed.
64 readonly ACCT_USER_NAME
66 # @ECLASS-VARIABLE: ACCT_USER_ID
69 # Preferred UID for the new user. This variable is obligatory, and its
70 # value must be unique across all user packages.
72 # Overlays should set this to -1 to dynamically allocate UID. Using -1
73 # in ::gentoo is prohibited by policy.
75 # @ECLASS-VARIABLE: ACCT_USER_ENFORCE_ID
77 # If set to a non-null value, the eclass will require the user to have
78 # specified UID. If the user already exists with another UID, or
79 # the UID is taken by another user, the install will fail.
80 : ${ACCT_USER_ENFORCE_ID:=}
82 # @ECLASS-VARIABLE: ACCT_USER_SHELL
84 # The shell to use for the user. If not specified, a 'nologin' variant
85 # for the system is used.
86 : ${ACCT_USER_SHELL:=-1}
88 # @ECLASS-VARIABLE: ACCT_USER_HOME
90 # The home directory for the user. If not specified, /dev/null is used.
91 # The directory will be created with appropriate permissions if it does
92 # not exist. When updating, existing home directory will not be moved.
93 : ${ACCT_USER_HOME:=/dev/null}
95 # @ECLASS-VARIABLE: ACCT_USER_HOME_OWNER
98 # The ownership to use for the home directory, in chown ([user][:group])
99 # syntax. Defaults to the newly created user, and its primary group.
101 # @ECLASS-VARIABLE: ACCT_USER_HOME_PERMS
103 # The permissions to use for the home directory, in chmod (octal
105 : ${ACCT_USER_HOME_PERMS:=0755}
107 # @ECLASS-VARIABLE: ACCT_USER_GROUPS
110 # List of groups the user should belong to. This must be a bash
111 # array. The first group specified is the user's primary group, while
112 # the remaining groups (if any) become supplementary groups.
115 # << Boilerplate ebuild variables >>
116 : ${DESCRIPTION:="System user: ${ACCT_USER_NAME}"}
118 : ${KEYWORDS:=alpha amd64 arm arm64 hppa ia64 m68k ~mips ppc ppc64 ~riscv s390 sh sparc x86 ~ppc-aix ~x64-cygwin ~amd64-linux ~x86-linux ~ppc-macos ~x64-macos ~x86-macos ~m68k-mint ~sparc-solaris ~sparc64-solaris ~x64-solaris ~x86-solaris}
122 # << API functions >>
124 # @FUNCTION: acct-user_add_deps
126 # Generate appropriate RDEPEND from ACCT_USER_GROUPS. This must be
127 # called if ACCT_USER_GROUPS are set.
128 acct-user_add_deps() {
129 debug-print-function ${FUNCNAME} "${@}"
131 # ACCT_USER_GROUPS sanity check
132 if [[ $(declare -p ACCT_USER_GROUPS) != "declare -a"* ]]; then
133 die 'ACCT_USER_GROUPS must be an array.'
134 elif [[ ${#ACCT_USER_GROUPS[@]} -eq 0 ]]; then
135 die 'ACCT_USER_GROUPS must not be empty.'
138 RDEPEND+=${ACCT_USER_GROUPS[*]/#/ acct-group/}
139 _ACCT_USER_ADD_DEPS_CALLED=1
143 # << Helper functions >>
145 # @FUNCTION: eislocked
149 # Check whether the specified user account is currently locked.
150 # Returns 0 if it is locked, 1 if it is not, 2 if the platform
151 # does not support determining it.
153 [[ $# -eq 1 ]] || die "usage: ${FUNCNAME} <user>"
155 if [[ ${EUID} != 0 ]] ; then
156 einfo "Insufficient privileges to execute ${FUNCNAME[0]}"
161 *-freebsd*|*-dragonfly*|*-netbsd*)
162 [[ $(egetent "$1" | cut -d: -f2) == '*LOCKED*'* ]]
170 # NB: 'no password' and 'locked' are indistinguishable
171 # but we also expire the account which is more clear
172 [[ $(getent shadow "$1" | cut -d: -f2) == '!'* ]] &&
173 [[ $(getent shadow "$1" | cut -d: -f8) == 1 ]]
178 # @FUNCTION: elockuser
182 # Lock the specified user account, using the available platform-specific
183 # functions. This should prevent any login to the account.
185 # Established lock can be reverted using eunlockuser.
187 # This function returns 0 if locking succeeded, 2 if it is not supported
188 # by the platform code or dies if it fails.
190 [[ $# -eq 1 ]] || die "usage: ${FUNCNAME} <user>"
192 if [[ ${EUID} != 0 ]] ; then
193 einfo "Insufficient privileges to execute ${FUNCNAME[0]}"
198 [[ $? -eq 0 ]] && return 0
201 *-freebsd*|*-dragonfly*)
202 pw lock "$1" || die "Locking account $1 failed"
203 pw user mod "$1" -e 1 || die "Expiring account $1 failed"
207 usermod -e 1 -C yes "$1" || die "Locking account $1 failed"
215 usermod -e 1 -L "$1" || die "Locking account $1 failed"
219 elog "User account $1 locked"
223 # @FUNCTION: eunlockuser
227 # Unlock the specified user account, using the available platform-
228 # specific functions.
230 # This function returns 0 if unlocking succeeded, 1 if it is not
231 # supported by the platform code or dies if it fails.
233 [[ $# -eq 1 ]] || die "usage: ${FUNCNAME} <user>"
235 if [[ ${EUID} != 0 ]] ; then
236 einfo "Insufficient privileges to execute ${FUNCNAME[0]}"
241 [[ $? -eq 1 ]] && return 0
244 *-freebsd*|*-dragonfly*)
245 pw user mod "$1" -e 0 || die "Unexpiring account $1 failed"
246 pw unlock "$1" || die "Unlocking account $1 failed"
250 usermod -e 0 -C no "$1" || die "Unlocking account $1 failed"
258 # silence warning if account does not have a password
259 usermod -e "" -U "$1" 2>/dev/null || die "Unlocking account $1 failed"
263 ewarn "User account $1 unlocked after reinstating."
268 # << Phase functions >>
269 EXPORT_FUNCTIONS pkg_pretend src_install pkg_preinst pkg_postinst \
272 # @FUNCTION: acct-user_pkg_pretend
274 # Performs sanity checks for correct eclass usage, and early-checks
275 # whether requested UID can be enforced.
276 acct-user_pkg_pretend() {
277 debug-print-function ${FUNCNAME} "${@}"
279 # verify that acct-user_add_deps() has been called
280 # (it verifies ACCT_USER_GROUPS itself)
281 if [[ -z ${_ACCT_USER_ADD_DEPS_CALLED} ]]; then
282 die "Ebuild error: acct-user_add_deps must have been called in global scope!"
285 # verify ACCT_USER_ID
286 [[ -n ${ACCT_USER_ID} ]] || die "Ebuild error: ACCT_USER_ID must be set!"
287 [[ ${ACCT_USER_ID} -eq -1 ]] && return
288 [[ ${ACCT_USER_ID} -ge 0 ]] || die "Ebuild errors: ACCT_USER_ID=${ACCT_USER_ID} invalid!"
290 # check for ACCT_USER_ID collisions early
291 if [[ -n ${ACCT_USER_ENFORCE_ID} ]]; then
292 local user_by_id=$(egetusername "${ACCT_USER_ID}")
293 local user_by_name=$(egetent passwd "${ACCT_USER_NAME}")
294 if [[ -n ${user_by_id} ]]; then
295 if [[ ${user_by_id} != ${ACCT_USER_NAME} ]]; then
296 eerror "The required UID is already taken by another user."
297 eerror " UID: ${ACCT_USER_ID}"
298 eerror " needed for: ${ACCT_USER_NAME}"
299 eerror " current user: ${user_by_id}"
300 die "UID ${ACCT_USER_ID} taken already"
302 elif [[ -n ${user_by_name} ]]; then
303 eerror "The requested user exists already with wrong UID."
304 eerror " username: ${ACCT_USER_NAME}"
305 eerror " requested UID: ${ACCT_USER_ID}"
306 eerror " current entry: ${user_by_name}"
307 die "Username ${ACCT_USER_NAME} exists with wrong UID"
312 # @FUNCTION: acct-user_src_install
314 # Installs a keep-file into the user's home directory to ensure it is
315 # owned by the package.
316 acct-user_src_install() {
317 debug-print-function ${FUNCNAME} "${@}"
319 if [[ ${ACCT_USER_HOME} != /dev/null ]]; then
320 # note: we can't set permissions here since the user isn't
322 keepdir "${ACCT_USER_HOME}"
326 # @FUNCTION: acct-user_pkg_preinst
328 # Creates the user if it does not exist yet. Sets permissions
329 # of the home directory in install image.
330 acct-user_pkg_preinst() {
331 debug-print-function ${FUNCNAME} "${@}"
333 local groups=${ACCT_USER_GROUPS[*]}
334 enewuser ${ACCT_USER_ENFORCE_ID:+-F} -M "${ACCT_USER_NAME}" \
335 "${ACCT_USER_ID}" "${ACCT_USER_SHELL}" "${ACCT_USER_HOME}" \
338 if [[ ${ACCT_USER_HOME} != /dev/null ]]; then
339 # default ownership to user:group
340 if [[ -z ${ACCT_USER_HOME_OWNER} ]]; then
341 ACCT_USER_HOME_OWNER=${ACCT_USER_NAME}:${ACCT_USER_GROUPS[0]}
343 # Path might be missing due to INSTALL_MASK, etc.
344 # https://bugs.gentoo.org/691478
345 if [[ ! -e "${ED}/${ACCT_USER_HOME#/}" ]]; then
346 eerror "Home directory is missing from the installation image:"
347 eerror " ${ACCT_USER_HOME}"
348 eerror "Check INSTALL_MASK for entries that would cause this."
349 die "${ACCT_USER_HOME} does not exist"
351 fowners "${ACCT_USER_HOME_OWNER}" "${ACCT_USER_HOME}"
352 fperms "${ACCT_USER_HOME_PERMS}" "${ACCT_USER_HOME}"
356 # @FUNCTION: acct-user_pkg_postinst
358 # Updates user properties if necessary. This needs to be done after
359 # new home directory is installed.
360 acct-user_pkg_postinst() {
361 debug-print-function ${FUNCNAME} "${@}"
363 # NB: eset* functions check current value
364 esethome "${ACCT_USER_NAME}" "${ACCT_USER_HOME}"
365 esetshell "${ACCT_USER_NAME}" "${ACCT_USER_SHELL}"
366 local groups=${ACCT_USER_GROUPS[*]}
367 esetgroups "${ACCT_USER_NAME}" "${groups// /,}"
368 # comment field can not contain colons
369 esetcomment "${ACCT_USER_NAME}" "${DESCRIPTION//[:,=]/;}"
370 eunlockuser "${ACCT_USER_NAME}"
373 # @FUNCTION: acct-user_pkg_prerm
375 # Ensures that the user account is locked out when it is removed.
376 acct-user_pkg_prerm() {
377 debug-print-function ${FUNCNAME} "${@}"
379 if [[ -z ${REPLACED_BY_VERSION} ]]; then
380 if [[ -z $(egetent passwd "${ACCT_USER_NAME}") ]]; then
381 ewarn "User account not found: ${ACCT_USER_NAME}"
382 ewarn "Locking process will be skipped."
386 esetshell "${ACCT_USER_NAME}" -1
387 esetcomment "${ACCT_USER_NAME}" \
388 "$(egetcomment "${ACCT_USER_NAME}"); user account removed @ $(date +%Y-%m-%d)"
389 elockuser "${ACCT_USER_NAME}"