dev-python/josepy: 1.1.0 cleanup
[gentoo.git] / eclass / acct-user.eclass
1 # Copyright 2019 Gentoo Authors
2 # Distributed under the terms of the GNU General Public License v2
3
4 # @ECLASS: acct-user.eclass
5 # @MAINTAINER:
6 # Michał Górny <mgorny@gentoo.org>
7 # @AUTHOR:
8 # Michael Orlitzky <mjo@gentoo.org>
9 # Michał Górny <mgorny@gentoo.org>
10 # @SUPPORTED_EAPIS: 7
11 # @BLURB: Eclass used to create and maintain a single user entry
12 # @DESCRIPTION:
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.
19 #
20 # The ebuild needs to call acct-user_add_deps after specifying
21 # ACCT_USER_GROUPS.
22 #
23 # Example:
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
26 # contents:
27 #
28 # @CODE
29 # EAPI=7
30 # inherit acct-user
31 # ACCT_USER_ID=200
32 # ACCT_USER_GROUPS=( foo )
33 # acct-user_add_deps
34 # @CODE
35 #
36 # Then you add appropriate dependency to your package.  The dependency
37 # type(s) should be:
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.
41
42 if [[ -z ${_ACCT_USER_ECLASS} ]]; then
43 _ACCT_USER_ECLASS=1
44
45 case ${EAPI:-0} in
46         7) ;;
47         *) die "EAPI=${EAPI:-0} not supported";;
48 esac
49
50 inherit user
51
52 [[ ${CATEGORY} == acct-user ]] ||
53         die "Ebuild error: this eclass can be used only in acct-user category!"
54
55
56 # << Eclass variables >>
57
58 # @ECLASS-VARIABLE: ACCT_USER_NAME
59 # @INTERNAL
60 # @DESCRIPTION:
61 # The name of the user.  This is forced to ${PN} and the policy prohibits
62 # it from being changed.
63 ACCT_USER_NAME=${PN}
64 readonly ACCT_USER_NAME
65
66 # @ECLASS-VARIABLE: ACCT_USER_ID
67 # @REQUIRED
68 # @DESCRIPTION:
69 # Preferred UID for the new user.  This variable is obligatory, and its
70 # value must be unique across all user packages.
71 #
72 # Overlays should set this to -1 to dynamically allocate UID.  Using -1
73 # in ::gentoo is prohibited by policy.
74
75 # @ECLASS-VARIABLE: ACCT_USER_ENFORCE_ID
76 # @DESCRIPTION:
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:=}
81
82 # @ECLASS-VARIABLE: ACCT_USER_SHELL
83 # @DESCRIPTION:
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}
87
88 # @ECLASS-VARIABLE: ACCT_USER_HOME
89 # @DESCRIPTION:
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}
94
95 # @ECLASS-VARIABLE: ACCT_USER_HOME_OWNER
96 # @DEFAULT_UNSET
97 # @DESCRIPTION:
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.
100
101 # @ECLASS-VARIABLE: ACCT_USER_HOME_PERMS
102 # @DESCRIPTION:
103 # The permissions to use for the home directory, in chmod (octal
104 # or verbose) form.
105 : ${ACCT_USER_HOME_PERMS:=0755}
106
107 # @ECLASS-VARIABLE: ACCT_USER_GROUPS
108 # @REQUIRED
109 # @DESCRIPTION:
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.
113
114
115 # << Boilerplate ebuild variables >>
116 : ${DESCRIPTION:="System user: ${ACCT_USER_NAME}"}
117 : ${SLOT:=0}
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}
119 S=${WORKDIR}
120
121
122 # << API functions >>
123
124 # @FUNCTION: acct-user_add_deps
125 # @DESCRIPTION:
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} "${@}"
130
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.'
136         fi
137
138         RDEPEND+=${ACCT_USER_GROUPS[*]/#/ acct-group/}
139         _ACCT_USER_ADD_DEPS_CALLED=1
140 }
141
142
143 # << Helper functions >>
144
145 # @FUNCTION: eislocked
146 # @INTERNAL
147 # @USAGE: <user>
148 # @DESCRIPTION:
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.
152 eislocked() {
153         [[ $# -eq 1 ]] || die "usage: ${FUNCNAME} <user>"
154
155         if [[ ${EUID} != 0 ]] ; then
156                 einfo "Insufficient privileges to execute ${FUNCNAME[0]}"
157                 return 0
158         fi
159
160         case ${CHOST} in
161         *-freebsd*|*-dragonfly*|*-netbsd*)
162                 [[ $(egetent "$1" | cut -d: -f2) == '*LOCKED*'* ]]
163                 ;;
164
165         *-openbsd*)
166                 return 2
167                 ;;
168
169         *)
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 ]]
174                 ;;
175         esac
176 }
177
178 # @FUNCTION: elockuser
179 # @INTERNAL
180 # @USAGE: <user>
181 # @DESCRIPTION:
182 # Lock the specified user account, using the available platform-specific
183 # functions.  This should prevent any login to the account.
184 #
185 # Established lock can be reverted using eunlockuser.
186 #
187 # This function returns 0 if locking succeeded, 2 if it is not supported
188 # by the platform code or dies if it fails.
189 elockuser() {
190         [[ $# -eq 1 ]] || die "usage: ${FUNCNAME} <user>"
191
192         if [[ ${EUID} != 0 ]] ; then
193                 einfo "Insufficient privileges to execute ${FUNCNAME[0]}"
194                 return 0
195         fi
196
197         eislocked "$1"
198         [[ $? -eq 0 ]] && return 0
199
200         case ${CHOST} in
201         *-freebsd*|*-dragonfly*)
202                 pw lock "$1" || die "Locking account $1 failed"
203                 pw user mod "$1" -e 1 || die "Expiring account $1 failed"
204                 ;;
205
206         *-netbsd*)
207                 usermod -e 1 -C yes "$1" || die "Locking account $1 failed"
208                 ;;
209
210         *-openbsd*)
211                 return 2
212                 ;;
213
214         *)
215                 usermod -e 1 -L "$1" || die "Locking account $1 failed"
216                 ;;
217         esac
218
219         elog "User account $1 locked"
220         return 0
221 }
222
223 # @FUNCTION: eunlockuser
224 # @INTERNAL
225 # @USAGE: <user>
226 # @DESCRIPTION:
227 # Unlock the specified user account, using the available platform-
228 # specific functions.
229 #
230 # This function returns 0 if unlocking succeeded, 1 if it is not
231 # supported by the platform code or dies if it fails.
232 eunlockuser() {
233         [[ $# -eq 1 ]] || die "usage: ${FUNCNAME} <user>"
234
235         if [[ ${EUID} != 0 ]] ; then
236                 einfo "Insufficient privileges to execute ${FUNCNAME[0]}"
237                 return 0
238         fi
239
240         eislocked "$1"
241         [[ $? -eq 1 ]] && return 0
242
243         case ${CHOST} in
244         *-freebsd*|*-dragonfly*)
245                 pw user mod "$1" -e 0 || die "Unexpiring account $1 failed"
246                 pw unlock "$1" || die "Unlocking account $1 failed"
247                 ;;
248
249         *-netbsd*)
250                 usermod -e 0 -C no "$1" || die "Unlocking account $1 failed"
251                 ;;
252
253         *-openbsd*)
254                 return 1
255                 ;;
256
257         *)
258                 # silence warning if account does not have a password
259                 usermod -e "" -U "$1" 2>/dev/null || die "Unlocking account $1 failed"
260                 ;;
261         esac
262
263         ewarn "User account $1 unlocked after reinstating."
264         return 0
265 }
266
267
268 # << Phase functions >>
269 EXPORT_FUNCTIONS pkg_pretend src_install pkg_preinst pkg_postinst \
270         pkg_prerm
271
272 # @FUNCTION: acct-user_pkg_pretend
273 # @DESCRIPTION:
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} "${@}"
278
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!"
283         fi
284
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!"
289
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"
301                         fi
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"
308                 fi
309         fi
310 }
311
312 # @FUNCTION: acct-user_src_install
313 # @DESCRIPTION:
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} "${@}"
318
319         if [[ ${ACCT_USER_HOME} != /dev/null ]]; then
320                 # note: we can't set permissions here since the user isn't
321                 # created yet
322                 keepdir "${ACCT_USER_HOME}"
323         fi
324 }
325
326 # @FUNCTION: acct-user_pkg_preinst
327 # @DESCRIPTION:
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} "${@}"
332
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}" \
336                 "${groups// /,}"
337
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]}
342                 fi
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"
350                 fi
351                 fowners "${ACCT_USER_HOME_OWNER}" "${ACCT_USER_HOME}"
352                 fperms "${ACCT_USER_HOME_PERMS}" "${ACCT_USER_HOME}"
353         fi
354 }
355
356 # @FUNCTION: acct-user_pkg_postinst
357 # @DESCRIPTION:
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} "${@}"
362
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}"
371 }
372
373 # @FUNCTION: acct-user_pkg_prerm
374 # @DESCRIPTION:
375 # Ensures that the user account is locked out when it is removed.
376 acct-user_pkg_prerm() {
377         debug-print-function ${FUNCNAME} "${@}"
378
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."
383                         return
384                 fi
385
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}"
390         fi
391 }
392
393 fi