meson.eclass: Don't mix host *FLAGS with build *FLAGS
[gentoo.git] / eclass / user.eclass
1 # Copyright 1999-2017 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 # @ECLASS: user.eclass
5 # @MAINTAINER:
6 # base-system@gentoo.org (Linux)
7 # @BLURB: user management in ebuilds
8 # @DESCRIPTION:
9 # The user eclass contains a suite of functions that allow ebuilds
10 # to quickly make sure users in the installed system are sane.
11
12 if [[ -z ${_USER_ECLASS} ]]; then
13 _USER_ECLASS=1
14
15 # @FUNCTION: _assert_pkg_ebuild_phase
16 # @INTERNAL
17 # @USAGE: <calling func name>
18 _assert_pkg_ebuild_phase() {
19         case ${EBUILD_PHASE} in
20         setup|preinst|postinst) ;;
21         *)
22                 eerror "'$1()' called from '${EBUILD_PHASE}' phase which is not OK:"
23                 eerror "You may only call from pkg_{setup,preinst,postinst} functions."
24                 eerror "Package fails at QA and at life.  Please file a bug."
25                 die "Bad package!  $1 is only for use in some pkg_* functions!"
26         esac
27 }
28
29 # @FUNCTION: egetent
30 # @USAGE: <database> <key>
31 # @DESCRIPTION:
32 # Small wrapper for getent (Linux), nidump (< Mac OS X 10.5),
33 # dscl (Mac OS X 10.5), and pw (FreeBSD) used in enewuser()/enewgroup().
34 #
35 # Supported databases: group passwd
36 egetent() {
37         local db=$1 key=$2
38
39         [[ $# -ge 3 ]] && die "usage: egetent <database> <key>"
40
41         case ${db} in
42         passwd|group) ;;
43         *) die "sorry, database '${db}' not yet supported; file a bug" ;;
44         esac
45
46         case ${CHOST} in
47         *-darwin[678])
48                 case ${key} in
49                 *[!0-9]*) # Non numeric
50                         nidump ${db} . | awk -F: "(\$1 ~ /^${key}\$/) {print;exit;}"
51                         ;;
52                 *)      # Numeric
53                         nidump ${db} . | awk -F: "(\$3 == ${key}) {print;exit;}"
54                         ;;
55                 esac
56                 ;;
57         *-darwin*)
58                 local mykey
59                 case ${db} in
60                 passwd) db="Users"  mykey="UniqueID" ;;
61                 group)  db="Groups" mykey="PrimaryGroupID" ;;
62                 esac
63
64                 case ${key} in
65                 *[!0-9]*) # Non numeric
66                         dscl . -read /${db}/${key} 2>/dev/null |grep RecordName
67                         ;;
68                 *)      # Numeric
69                         dscl . -search /${db} ${mykey} ${key} 2>/dev/null
70                         ;;
71                 esac
72                 ;;
73         *-freebsd*|*-dragonfly*)
74                 case ${db} in
75                 passwd) db="user" ;;
76                 *) ;;
77                 esac
78
79                 # lookup by uid/gid
80                 local opts
81                 if [[ ${key} == [[:digit:]]* ]] ; then
82                         [[ ${db} == "user" ]] && opts="-u" || opts="-g"
83                 fi
84
85                 pw show ${db} ${opts} "${key}" -q
86                 ;;
87         *-netbsd*|*-openbsd*)
88                 grep "${key}:\*:" /etc/${db}
89                 ;;
90         *)
91                 # ignore output if nscd doesn't exist, or we're not running as root
92                 nscd -i "${db}" 2>/dev/null
93                 getent "${db}" "${key}"
94                 ;;
95         esac
96 }
97
98 # @FUNCTION: enewuser
99 # @USAGE: <user> [uid] [shell] [homedir] [groups]
100 # @DESCRIPTION:
101 # Same as enewgroup, you are not required to understand how to properly add
102 # a user to the system.  The only required parameter is the username.
103 # Default uid is (pass -1 for this) next available, default shell is
104 # /bin/false, default homedir is /dev/null, and there are no default groups.
105 enewuser() {
106         if [[ ${EUID} != 0 ]] ; then
107                 einfo "Insufficient privileges to execute ${FUNCNAME[0]}"
108                 return 0
109         fi
110         _assert_pkg_ebuild_phase ${FUNCNAME}
111
112         # get the username
113         local euser=$1; shift
114         if [[ -z ${euser} ]] ; then
115                 eerror "No username specified !"
116                 die "Cannot call enewuser without a username"
117         fi
118
119         # lets see if the username already exists
120         if [[ -n $(egetent passwd "${euser}") ]] ; then
121                 return 0
122         fi
123         einfo "Adding user '${euser}' to your system ..."
124
125         # options to pass to useradd
126         local opts=()
127
128         # handle uid
129         local euid=$1; shift
130         if [[ -n ${euid} && ${euid} != -1 ]] ; then
131                 if [[ ${euid} -gt 0 ]] ; then
132                         if [[ -n $(egetent passwd ${euid}) ]] ; then
133                                 euid="next"
134                         fi
135                 else
136                         eerror "Userid given but is not greater than 0 !"
137                         die "${euid} is not a valid UID"
138                 fi
139         else
140                 euid="next"
141         fi
142         if [[ ${euid} == "next" ]] ; then
143                 for ((euid = 101; euid <= 999; euid++)); do
144                         [[ -z $(egetent passwd ${euid}) ]] && break
145                 done
146         fi
147         opts+=( -u ${euid} )
148         einfo " - Userid: ${euid}"
149
150         # handle shell
151         local eshell=$1; shift
152         if [[ ! -z ${eshell} ]] && [[ ${eshell} != "-1" ]] ; then
153                 if [[ ! -e ${ROOT}${eshell} ]] ; then
154                         eerror "A shell was specified but it does not exist !"
155                         die "${eshell} does not exist in ${ROOT}"
156                 fi
157                 if [[ ${eshell} == */false || ${eshell} == */nologin ]] ; then
158                         eerror "Do not specify ${eshell} yourself, use -1"
159                         die "Pass '-1' as the shell parameter"
160                 fi
161         else
162                 for eshell in /sbin/nologin /usr/sbin/nologin /bin/false /usr/bin/false /dev/null ; do
163                         [[ -x ${ROOT}${eshell} ]] && break
164                 done
165
166                 if [[ ${eshell} == "/dev/null" ]] ; then
167                         eerror "Unable to identify the shell to use, proceeding with userland default."
168                         case ${USERLAND} in
169                                 GNU)    eshell="/bin/false" ;;
170                                 BSD)    eshell="/sbin/nologin" ;;
171                                 Darwin) eshell="/usr/sbin/nologin" ;;
172                                 *) die "Unable to identify the default shell for userland ${USERLAND}"
173                         esac
174                 fi
175         fi
176         einfo " - Shell: ${eshell}"
177         opts+=( -s "${eshell}" )
178
179         # handle homedir
180         local ehome=$1; shift
181         if [[ -z ${ehome} ]] || [[ ${ehome} == "-1" ]] ; then
182                 ehome="/dev/null"
183         fi
184         einfo " - Home: ${ehome}"
185         opts+=( -d "${ehome}" )
186
187         # handle groups
188         local egroups=$1; shift
189         local g egroups_arr
190         IFS="," read -r -a egroups_arr <<<"${egroups}"
191         if [[ ${#egroups_arr[@]} -gt 0 ]] ; then
192                 local defgroup exgroups
193                 for g in "${egroups_arr[@]}" ; do
194                         if [[ -z $(egetent group "${g}") ]] ; then
195                                 eerror "You must add group ${g} to the system first"
196                                 die "${g} is not a valid GID"
197                         fi
198                         if [[ -z ${defgroup} ]] ; then
199                                 defgroup=${g}
200                         else
201                                 exgroups+=",${g}"
202                         fi
203                 done
204                 opts+=( -g "${defgroup}" )
205                 if [[ ! -z ${exgroups} ]] ; then
206                         opts+=( -G "${exgroups:1}" )
207                 fi
208         fi
209         einfo " - Groups: ${egroups:-(none)}"
210
211         # handle extra args
212         if [[ $# -gt 0 ]] ; then
213                 die "extra arguments no longer supported; please file a bug"
214         else
215                 local comment="added by portage for ${PN}"
216                 opts+=( -c "${comment}" )
217                 einfo " - GECOS: ${comment}"
218         fi
219
220         # add the user
221         case ${CHOST} in
222         *-darwin*)
223                 ### Make the user
224                 dscl . create "/users/${euser}" uid ${euid}
225                 dscl . create "/users/${euser}" shell "${eshell}"
226                 dscl . create "/users/${euser}" home "${ehome}"
227                 dscl . create "/users/${euser}" realname "added by portage for ${PN}"
228                 ### Add the user to the groups specified
229                 for g in "${egroups_arr[@]}" ; do
230                         dscl . merge "/groups/${g}" users "${euser}"
231                 done
232                 ;;
233
234         *-freebsd*|*-dragonfly*)
235                 pw useradd "${euser}" "${opts[@]}" || die
236                 ;;
237
238         *-netbsd*)
239                 useradd "${opts[@]}" "${euser}" || die
240                 ;;
241
242         *-openbsd*)
243                 # all ops the same, except the -g vs -g/-G ...
244                 useradd -u ${euid} -s "${eshell}" \
245                         -d "${ehome}" -g "${egroups}" "${euser}" || die
246                 ;;
247
248         *)
249                 useradd -r "${opts[@]}" "${euser}" || die
250                 ;;
251         esac
252
253         if [[ ! -e ${ROOT}/${ehome} ]] ; then
254                 einfo " - Creating ${ehome} in ${ROOT}"
255                 mkdir -p "${ROOT}/${ehome}"
256                 chown "${euser}" "${ROOT}/${ehome}"
257                 chmod 755 "${ROOT}/${ehome}"
258         fi
259 }
260
261 # @FUNCTION: enewgroup
262 # @USAGE: <group> [gid]
263 # @DESCRIPTION:
264 # This function does not require you to understand how to properly add a
265 # group to the system.  Just give it a group name to add and enewgroup will
266 # do the rest.  You may specify the gid for the group or allow the group to
267 # allocate the next available one.
268 enewgroup() {
269         if [[ ${EUID} != 0 ]] ; then
270                 einfo "Insufficient privileges to execute ${FUNCNAME[0]}"
271                 return 0
272         fi
273         _assert_pkg_ebuild_phase ${FUNCNAME}
274
275         # get the group
276         local egroup=$1; shift
277         if [[ -z ${egroup} ]] ; then
278                 eerror "No group specified !"
279                 die "Cannot call enewgroup without a group"
280         fi
281
282         # see if group already exists
283         if [[ -n $(egetent group "${egroup}") ]] ; then
284                 return 0
285         fi
286         einfo "Adding group '${egroup}' to your system ..."
287
288         # handle gid
289         local egid=$1; shift
290         if [[ ! -z ${egid} ]] ; then
291                 if [[ ${egid} -gt 0 ]] ; then
292                         if [[ -n $(egetent group ${egid}) ]] ; then
293                                 egid="next available; requested gid taken"
294                         fi
295                 else
296                         eerror "Groupid given but is not greater than 0 !"
297                         die "${egid} is not a valid GID"
298                 fi
299         else
300                 egid="next available"
301         fi
302         einfo " - Groupid: ${egid}"
303
304         # handle extra
305         if [[ $# -gt 0 ]] ; then
306                 die "extra arguments no longer supported; please file a bug"
307         fi
308
309         # Some targets need to find the next available GID manually
310         _enewgroup_next_gid() {
311                 if [[ ${egid} == *[!0-9]* ]] ; then
312                         # Non numeric
313                         for ((egid = 101; egid <= 999; egid++)) ; do
314                                 [[ -z $(egetent group ${egid}) ]] && break
315                         done
316                 fi
317         }
318
319         # add the group
320         case ${CHOST} in
321         *-darwin*)
322                 _enewgroup_next_gid
323                 dscl . create "/groups/${egroup}" gid ${egid}
324                 dscl . create "/groups/${egroup}" passwd '*'
325                 ;;
326
327         *-freebsd*|*-dragonfly*)
328                 _enewgroup_next_gid
329                 pw groupadd "${egroup}" -g ${egid} || die
330                 ;;
331
332         *-netbsd*)
333                 _enewgroup_next_gid
334                 groupadd -g ${egid} "${egroup}" || die
335                 ;;
336
337         *)
338                 local opts
339                 if [[ ${egid} == *[!0-9]* ]] ; then
340                         # Non numeric; let groupadd figure out a GID for us
341                         opts=""
342                 else
343                         opts="-g ${egid}"
344                 fi
345                 # We specify -r so that we get a GID in the system range from login.defs
346                 groupadd -r ${opts} "${egroup}" || die
347                 ;;
348         esac
349 }
350
351 # @FUNCTION: egethome
352 # @USAGE: <user>
353 # @DESCRIPTION:
354 # Gets the home directory for the specified user.
355 egethome() {
356         local pos
357
358         [[ $# -eq 1 ]] || die "usage: egethome <user>"
359
360         case ${CHOST} in
361         *-darwin*|*-freebsd*|*-dragonfly*)
362                 pos=9
363                 ;;
364         *)      # Linux, NetBSD, OpenBSD, etc...
365                 pos=6
366                 ;;
367         esac
368
369         egetent passwd "$1" | cut -d: -f${pos}
370 }
371
372 # @FUNCTION: egetshell
373 # @USAGE: <user>
374 # @DESCRIPTION:
375 # Gets the shell for the specified user.
376 egetshell() {
377         local pos
378
379         [[ $# -eq 1 ]] || die "usage: egetshell <user>"
380
381         case ${CHOST} in
382         *-darwin*|*-freebsd*|*-dragonfly*)
383                 pos=10
384                 ;;
385         *)      # Linux, NetBSD, OpenBSD, etc...
386                 pos=7
387                 ;;
388         esac
389
390         egetent passwd "$1" | cut -d: -f${pos}
391 }
392
393 # @FUNCTION: esethome
394 # @USAGE: <user> <homedir>
395 # @DESCRIPTION:
396 # Update the home directory in a platform-agnostic way.
397 # Required parameters is the username and the new home directory.
398 # Specify -1 if you want to set home to the enewuser default
399 # of /dev/null.
400 # If the new home directory does not exist, it is created.
401 # Any previously existing home directory is NOT moved.
402 esethome() {
403         _assert_pkg_ebuild_phase ${FUNCNAME}
404
405         # get the username
406         local euser=$1; shift
407         if [[ -z ${euser} ]] ; then
408                 eerror "No username specified !"
409                 die "Cannot call esethome without a username"
410         fi
411
412         # lets see if the username already exists
413         if [[ -z $(egetent passwd "${euser}") ]] ; then
414                 ewarn "User does not exist, cannot set home dir -- skipping."
415                 return 1
416         fi
417
418         # handle homedir
419         local ehome=$1; shift
420         if [[ -z ${ehome} ]] ; then
421                 eerror "No home directory specified !"
422                 die "Cannot call esethome without a home directory or '-1'"
423         fi
424
425         if [[ ${ehome} == "-1" ]] ; then
426                 ehome="/dev/null"
427         fi
428
429         # exit with no message if home dir is up to date
430         if [[ $(egethome "${euser}") == ${ehome} ]]; then
431                 return 0
432         fi
433
434         einfo "Updating home for user '${euser}' ..."
435         einfo " - Home: ${ehome}"
436
437         # ensure home directory exists, otherwise update will fail
438         if [[ ! -e ${ROOT}/${ehome} ]] ; then
439                 einfo " - Creating ${ehome} in ${ROOT}"
440                 mkdir -p "${ROOT}/${ehome}"
441                 chown "${euser}" "${ROOT}/${ehome}"
442                 chmod 755 "${ROOT}/${ehome}"
443         fi
444
445         # update the home directory
446         case ${CHOST} in
447         *-darwin*)
448                 dscl . change "/users/${euser}" home "${ehome}"
449                 ;;
450
451         *-freebsd*|*-dragonfly*)
452                 pw usermod "${euser}" -d "${ehome}" && return 0
453                 [[ $? == 8 ]] && eerror "${euser} is in use, cannot update home"
454                 eerror "There was an error when attempting to update the home directory for ${euser}"
455                 eerror "Please update it manually on your system:"
456                 eerror "\t pw usermod \"${euser}\" -d \"${ehome}\""
457                 ;;
458
459         *)
460                 usermod -d "${ehome}" "${euser}" && return 0
461                 [[ $? == 8 ]] && eerror "${euser} is in use, cannot update home"
462                 eerror "There was an error when attempting to update the home directory for ${euser}"
463                 eerror "Please update it manually on your system (as root):"
464                 eerror "\t usermod -d \"${ehome}\" \"${euser}\""
465                 ;;
466         esac
467 }
468
469 fi