net-irc/limnoria: use HTTPS for GitHub and HOMEPAGE
[gentoo.git] / eclass / user.eclass
1 # Copyright 1999-2014 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         _assert_pkg_ebuild_phase ${FUNCNAME}
107
108         # get the username
109         local euser=$1; shift
110         if [[ -z ${euser} ]] ; then
111                 eerror "No username specified !"
112                 die "Cannot call enewuser without a username"
113         fi
114
115         # lets see if the username already exists
116         if [[ -n $(egetent passwd "${euser}") ]] ; then
117                 return 0
118         fi
119         einfo "Adding user '${euser}' to your system ..."
120
121         # options to pass to useradd
122         local opts=()
123
124         # handle uid
125         local euid=$1; shift
126         if [[ -n ${euid} && ${euid} != -1 ]] ; then
127                 if [[ ${euid} -gt 0 ]] ; then
128                         if [[ -n $(egetent passwd ${euid}) ]] ; then
129                                 euid="next"
130                         fi
131                 else
132                         eerror "Userid given but is not greater than 0 !"
133                         die "${euid} is not a valid UID"
134                 fi
135         else
136                 euid="next"
137         fi
138         if [[ ${euid} == "next" ]] ; then
139                 for ((euid = 101; euid <= 999; euid++)); do
140                         [[ -z $(egetent passwd ${euid}) ]] && break
141                 done
142         fi
143         opts+=( -u ${euid} )
144         einfo " - Userid: ${euid}"
145
146         # handle shell
147         local eshell=$1; shift
148         if [[ ! -z ${eshell} ]] && [[ ${eshell} != "-1" ]] ; then
149                 if [[ ! -e ${ROOT}${eshell} ]] ; then
150                         eerror "A shell was specified but it does not exist !"
151                         die "${eshell} does not exist in ${ROOT}"
152                 fi
153                 if [[ ${eshell} == */false || ${eshell} == */nologin ]] ; then
154                         eerror "Do not specify ${eshell} yourself, use -1"
155                         die "Pass '-1' as the shell parameter"
156                 fi
157         else
158                 for eshell in /sbin/nologin /usr/sbin/nologin /bin/false /usr/bin/false /dev/null ; do
159                         [[ -x ${ROOT}${eshell} ]] && break
160                 done
161
162                 if [[ ${eshell} == "/dev/null" ]] ; then
163                         eerror "Unable to identify the shell to use, proceeding with userland default."
164                         case ${USERLAND} in
165                                 GNU)    eshell="/bin/false" ;;
166                                 BSD)    eshell="/sbin/nologin" ;;
167                                 Darwin) eshell="/usr/sbin/nologin" ;;
168                                 *) die "Unable to identify the default shell for userland ${USERLAND}"
169                         esac
170                 fi
171         fi
172         einfo " - Shell: ${eshell}"
173         opts+=( -s "${eshell}" )
174
175         # handle homedir
176         local ehome=$1; shift
177         if [[ -z ${ehome} ]] || [[ ${ehome} == "-1" ]] ; then
178                 ehome="/dev/null"
179         fi
180         einfo " - Home: ${ehome}"
181         opts+=( -d "${ehome}" )
182
183         # handle groups
184         local egroups=$1; shift
185         local g egroups_arr
186         IFS="," read -r -a egroups_arr <<<"${egroups}"
187         if [[ ${#egroups_arr[@]} -gt 0 ]] ; then
188                 local defgroup exgroups
189                 for g in "${egroups_arr[@]}" ; do
190                         if [[ -z $(egetent group "${g}") ]] ; then
191                                 eerror "You must add group ${g} to the system first"
192                                 die "${g} is not a valid GID"
193                         fi
194                         if [[ -z ${defgroup} ]] ; then
195                                 defgroup=${g}
196                         else
197                                 exgroups+=",${g}"
198                         fi
199                 done
200                 opts+=( -g "${defgroup}" )
201                 if [[ ! -z ${exgroups} ]] ; then
202                         opts+=( -G "${exgroups:1}" )
203                 fi
204         fi
205         einfo " - Groups: ${egroups:-(none)}"
206
207         # handle extra args
208         if [[ $# -gt 0 ]] ; then
209                 die "extra arguments no longer supported; please file a bug"
210         else
211                 local comment="added by portage for ${PN}"
212                 opts+=( -c "${comment}" )
213                 einfo " - GECOS: ${comment}"
214         fi
215
216         # add the user
217         case ${CHOST} in
218         *-darwin*)
219                 ### Make the user
220                 dscl . create "/users/${euser}" uid ${euid}
221                 dscl . create "/users/${euser}" shell "${eshell}"
222                 dscl . create "/users/${euser}" home "${ehome}"
223                 dscl . create "/users/${euser}" realname "added by portage for ${PN}"
224                 ### Add the user to the groups specified
225                 for g in "${egroups_arr[@]}" ; do
226                         dscl . merge "/groups/${g}" users "${euser}"
227                 done
228                 ;;
229
230         *-freebsd*|*-dragonfly*)
231                 pw useradd "${euser}" "${opts[@]}" || die
232                 ;;
233
234         *-netbsd*)
235                 useradd "${opts[@]}" "${euser}" || die
236                 ;;
237
238         *-openbsd*)
239                 # all ops the same, except the -g vs -g/-G ...
240                 useradd -u ${euid} -s "${eshell}" \
241                         -d "${ehome}" -g "${egroups}" "${euser}" || die
242                 ;;
243
244         *)
245                 useradd -r "${opts[@]}" "${euser}" || die
246                 ;;
247         esac
248
249         if [[ ! -e ${ROOT}/${ehome} ]] ; then
250                 einfo " - Creating ${ehome} in ${ROOT}"
251                 mkdir -p "${ROOT}/${ehome}"
252                 chown "${euser}" "${ROOT}/${ehome}"
253                 chmod 755 "${ROOT}/${ehome}"
254         fi
255 }
256
257 # @FUNCTION: enewgroup
258 # @USAGE: <group> [gid]
259 # @DESCRIPTION:
260 # This function does not require you to understand how to properly add a
261 # group to the system.  Just give it a group name to add and enewgroup will
262 # do the rest.  You may specify the gid for the group or allow the group to
263 # allocate the next available one.
264 enewgroup() {
265         _assert_pkg_ebuild_phase ${FUNCNAME}
266
267         # get the group
268         local egroup=$1; shift
269         if [[ -z ${egroup} ]] ; then
270                 eerror "No group specified !"
271                 die "Cannot call enewgroup without a group"
272         fi
273
274         # see if group already exists
275         if [[ -n $(egetent group "${egroup}") ]] ; then
276                 return 0
277         fi
278         einfo "Adding group '${egroup}' to your system ..."
279
280         # handle gid
281         local egid=$1; shift
282         if [[ ! -z ${egid} ]] ; then
283                 if [[ ${egid} -gt 0 ]] ; then
284                         if [[ -n $(egetent group ${egid}) ]] ; then
285                                 egid="next available; requested gid taken"
286                         fi
287                 else
288                         eerror "Groupid given but is not greater than 0 !"
289                         die "${egid} is not a valid GID"
290                 fi
291         else
292                 egid="next available"
293         fi
294         einfo " - Groupid: ${egid}"
295
296         # handle extra
297         if [[ $# -gt 0 ]] ; then
298                 die "extra arguments no longer supported; please file a bug"
299         fi
300
301         # Some targets need to find the next available GID manually
302         _enewgroup_next_gid() {
303                 if [[ ${egid} == *[!0-9]* ]] ; then
304                         # Non numeric
305                         for ((egid = 101; egid <= 999; egid++)) ; do
306                                 [[ -z $(egetent group ${egid}) ]] && break
307                         done
308                 fi
309         }
310
311         # add the group
312         case ${CHOST} in
313         *-darwin*)
314                 _enewgroup_next_gid
315                 dscl . create "/groups/${egroup}" gid ${egid}
316                 dscl . create "/groups/${egroup}" passwd '*'
317                 ;;
318
319         *-freebsd*|*-dragonfly*)
320                 _enewgroup_next_gid
321                 pw groupadd "${egroup}" -g ${egid} || die
322                 ;;
323
324         *-netbsd*)
325                 _enewgroup_next_gid
326                 groupadd -g ${egid} "${egroup}" || die
327                 ;;
328
329         *)
330                 local opts
331                 if [[ ${egid} == *[!0-9]* ]] ; then
332                         # Non numeric; let groupadd figure out a GID for us
333                         opts=""
334                 else
335                         opts="-g ${egid}"
336                 fi
337                 # We specify -r so that we get a GID in the system range from login.defs
338                 groupadd -r ${opts} "${egroup}" || die
339                 ;;
340         esac
341 }
342
343 # @FUNCTION: egethome
344 # @USAGE: <user>
345 # @DESCRIPTION:
346 # Gets the home directory for the specified user.
347 egethome() {
348         local pos
349
350         [[ $# -eq 1 ]] || die "usage: egethome <user>"
351
352         case ${CHOST} in
353         *-darwin*|*-freebsd*|*-dragonfly*)
354                 pos=9
355                 ;;
356         *)      # Linux, NetBSD, OpenBSD, etc...
357                 pos=6
358                 ;;
359         esac
360
361         egetent passwd "$1" | cut -d: -f${pos}
362 }
363
364 # @FUNCTION: egetshell
365 # @USAGE: <user>
366 # @DESCRIPTION:
367 # Gets the shell for the specified user.
368 egetshell() {
369         local pos
370
371         [[ $# -eq 1 ]] || die "usage: egetshell <user>"
372
373         case ${CHOST} in
374         *-darwin*|*-freebsd*|*-dragonfly*)
375                 pos=10
376                 ;;
377         *)      # Linux, NetBSD, OpenBSD, etc...
378                 pos=7
379                 ;;
380         esac
381
382         egetent passwd "$1" | cut -d: -f${pos}
383 }
384
385 # @FUNCTION: esethome
386 # @USAGE: <user> <homedir>
387 # @DESCRIPTION:
388 # Update the home directory in a platform-agnostic way.
389 # Required parameters is the username and the new home directory.
390 # Specify -1 if you want to set home to the enewuser default
391 # of /dev/null.
392 # If the new home directory does not exist, it is created.
393 # Any previously existing home directory is NOT moved.
394 esethome() {
395         _assert_pkg_ebuild_phase ${FUNCNAME}
396
397         # get the username
398         local euser=$1; shift
399         if [[ -z ${euser} ]] ; then
400                 eerror "No username specified !"
401                 die "Cannot call esethome without a username"
402         fi
403
404         # lets see if the username already exists
405         if [[ -z $(egetent passwd "${euser}") ]] ; then
406                 ewarn "User does not exist, cannot set home dir -- skipping."
407                 return 1
408         fi
409
410         # handle homedir
411         local ehome=$1; shift
412         if [[ -z ${ehome} ]] ; then
413                 eerror "No home directory specified !"
414                 die "Cannot call esethome without a home directory or '-1'"
415         fi
416
417         if [[ ${ehome} == "-1" ]] ; then
418                 ehome="/dev/null"
419         fi
420
421         # exit with no message if home dir is up to date
422         if [[ $(egethome "${euser}") == ${ehome} ]]; then
423                 return 0
424         fi
425
426         einfo "Updating home for user '${euser}' ..."
427         einfo " - Home: ${ehome}"
428
429         # ensure home directory exists, otherwise update will fail
430         if [[ ! -e ${ROOT}/${ehome} ]] ; then
431                 einfo " - Creating ${ehome} in ${ROOT}"
432                 mkdir -p "${ROOT}/${ehome}"
433                 chown "${euser}" "${ROOT}/${ehome}"
434                 chmod 755 "${ROOT}/${ehome}"
435         fi
436
437         # update the home directory
438         case ${CHOST} in
439         *-darwin*)
440                 dscl . change "/users/${euser}" home "${ehome}"
441                 ;;
442
443         *-freebsd*|*-dragonfly*)
444                 pw usermod "${euser}" -d "${ehome}" && return 0
445                 [[ $? == 8 ]] && eerror "${euser} is in use, cannot update home"
446                 eerror "There was an error when attempting to update the home directory for ${euser}"
447                 eerror "Please update it manually on your system:"
448                 eerror "\t pw usermod \"${euser}\" -d \"${ehome}\""
449                 ;;
450
451         *)
452                 usermod -d "${ehome}" "${euser}" && 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 (as root):"
456                 eerror "\t usermod -d \"${ehome}\" \"${euser}\""
457                 ;;
458         esac
459 }
460
461 fi