bmon: bump to version 3.9
[buildroot-gz.git] / support / scripts / mkusers
blobe2c24c72374b39315742a8567a63b21aa071392e
1 #!/usr/bin/env bash
2 set -e
3 myname="${0##*/}"
5 #----------------------------------------------------------------------------
6 # Configurable items
7 MIN_UID=1000
8 MAX_UID=1999
9 MIN_GID=1000
10 MAX_GID=1999
11 # No more is configurable below this point
12 #----------------------------------------------------------------------------
14 #----------------------------------------------------------------------------
15 error() {
16 local fmt="${1}"
17 shift
19 printf "%s: " "${myname}" >&2
20 printf "${fmt}" "${@}" >&2
22 fail() {
23 error "$@"
24 exit 1
27 #----------------------------------------------------------------------------
28 if [ ${#} -ne 2 ]; then
29 fail "usage: %s USERS_TABLE TARGET_DIR\n"
31 USERS_TABLE="${1}"
32 TARGET_DIR="${2}"
33 shift 2
34 PASSWD="${TARGET_DIR}/etc/passwd"
35 SHADOW="${TARGET_DIR}/etc/shadow"
36 GROUP="${TARGET_DIR}/etc/group"
37 # /etc/gshadow is not part of the standard skeleton, so not everybody
38 # will have it, but some may hav it, and its content must be in sync
39 # with /etc/group, so any use of gshadow must be conditional.
40 GSHADOW="${TARGET_DIR}/etc/gshadow"
42 # We can't simply source ${BR2_CONFIG} as it may contains constructs
43 # such as:
44 # BR2_DEFCONFIG="$(CONFIG_DIR)/defconfig"
45 # which when sourced from a shell script will eventually try to execute
46 # a command name 'CONFIG_DIR', which is plain wrong for virtually every
47 # systems out there.
48 # So, we have to scan that file instead. Sigh... :-(
49 PASSWD_METHOD="$( sed -r -e '/^BR2_TARGET_GENERIC_PASSWD_METHOD="(.*)"$/!d;' \
50 -e 's//\1/;' \
51 "${BR2_CONFIG}" \
54 #----------------------------------------------------------------------------
55 get_uid() {
56 local username="${1}"
58 awk -F: -v username="${username}" \
59 '$1 == username { printf( "%d\n", $3 ); }' "${PASSWD}"
62 #----------------------------------------------------------------------------
63 get_ugid() {
64 local username="${1}"
66 awk -F: -v username="${username}" \
67 '$1 == username { printf( "%d\n", $4 ); }' "${PASSWD}"
70 #----------------------------------------------------------------------------
71 get_gid() {
72 local group="${1}"
74 awk -F: -v group="${group}" \
75 '$1 == group { printf( "%d\n", $3 ); }' "${GROUP}"
78 #----------------------------------------------------------------------------
79 get_username() {
80 local uid="${1}"
82 awk -F: -v uid="${uid}" \
83 '$3 == uid { printf( "%s\n", $1 ); }' "${PASSWD}"
86 #----------------------------------------------------------------------------
87 get_group() {
88 local gid="${1}"
90 awk -F: -v gid="${gid}" \
91 '$3 == gid { printf( "%s\n", $1 ); }' "${GROUP}"
94 #----------------------------------------------------------------------------
95 get_ugroup() {
96 local username="${1}"
97 local ugid
99 ugid="$( get_ugid "${username}" )"
100 if [ -n "${ugid}" ]; then
101 get_group "${ugid}"
105 #----------------------------------------------------------------------------
106 # Sanity-check the new user/group:
107 # - check the gid is not already used for another group
108 # - check the group does not already exist with another gid
109 # - check the user does not already exist with another gid
110 # - check the uid is not already used for another user
111 # - check the user does not already exist with another uid
112 # - check the user does not already exist in another group
113 check_user_validity() {
114 local username="${1}"
115 local uid="${2}"
116 local group="${3}"
117 local gid="${4}"
118 local _uid _ugid _gid _username _group _ugroup
120 _group="$( get_group "${gid}" )"
121 _gid="$( get_gid "${group}" )"
122 _ugid="$( get_ugid "${username}" )"
123 _username="$( get_username "${uid}" )"
124 _uid="$( get_uid "${username}" )"
125 _ugroup="$( get_ugroup "${username}" )"
127 if [ "${username}" = "root" ]; then
128 fail "invalid username '%s\n'" "${username}"
131 if [ ${gid} -lt -1 -o ${gid} -eq 0 ]; then
132 fail "invalid gid '%d' for '%s'\n" ${gid} "${username}"
133 elif [ ${gid} -ne -1 ]; then
134 # check the gid is not already used for another group
135 if [ -n "${_group}" -a "${_group}" != "${group}" ]; then
136 fail "gid '%d' for '%s' is already used by group '%s'\n" \
137 ${gid} "${username}" "${_group}"
140 # check the group does not already exists with another gid
141 # Need to split the check in two, otherwise '[' complains it
142 # is missing arguments when _gid is empty
143 if [ -n "${_gid}" ] && [ ${_gid} -ne ${gid} ]; then
144 fail "group '%s' for '%s' already exists with gid '%d' (wants '%d')\n" \
145 "${group}" "${username}" ${_gid} ${gid}
148 # check the user does not already exists with another gid
149 # Need to split the check in two, otherwise '[' complains it
150 # is missing arguments when _ugid is empty
151 if [ -n "${_ugid}" ] && [ ${_ugid} -ne ${gid} ]; then
152 fail "user '%s' already exists with gid '%d' (wants '%d')\n" \
153 "${username}" ${_ugid} ${gid}
157 if [ ${uid} -lt -1 -o ${uid} -eq 0 ]; then
158 fail "invalid uid '%d' for '%s'\n" ${uid} "${username}"
159 elif [ ${uid} -ne -1 ]; then
160 # check the uid is not already used for another user
161 if [ -n "${_username}" -a "${_username}" != "${username}" ]; then
162 fail "uid '%d' for '%s' already used by user '%s'\n" \
163 ${uid} "${username}" "${_username}"
166 # check the user does not already exists with another uid
167 # Need to split the check in two, otherwise '[' complains it
168 # is missing arguments when _uid is empty
169 if [ -n "${_uid}" ] && [ ${_uid} -ne ${uid} ]; then
170 fail "user '%s' already exists with uid '%d' (wants '%d')\n" \
171 "${username}" ${_uid} ${uid}
175 # check the user does not already exist in another group
176 if [ -n "${_ugroup}" -a "${_ugroup}" != "${group}" ]; then
177 fail "user '%s' already exists with group '%s' (wants '%s')\n" \
178 "${username}" "${_ugroup}" "${group}"
181 return 0
184 #----------------------------------------------------------------------------
185 # Generate a unique GID for given group. If the group already exists,
186 # then simply report its current GID. Otherwise, generate the lowest GID
187 # that is:
188 # - not 0
189 # - comprised in [MIN_GID..MAX_GID]
190 # - not already used by a group
191 generate_gid() {
192 local group="${1}"
193 local gid
195 gid="$( get_gid "${group}" )"
196 if [ -z "${gid}" ]; then
197 for(( gid=MIN_GID; gid<=MAX_GID; gid++ )); do
198 if [ -z "$( get_group "${gid}" )" ]; then
199 break
201 done
202 if [ ${gid} -gt ${MAX_GID} ]; then
203 fail "can not allocate a GID for group '%s'\n" "${group}"
206 printf "%d\n" "${gid}"
209 #----------------------------------------------------------------------------
210 # Add a group; if it does already exist, remove it first
211 add_one_group() {
212 local group="${1}"
213 local gid="${2}"
214 local _f
216 # Generate a new GID if needed
217 if [ ${gid} -eq -1 ]; then
218 gid="$( generate_gid "${group}" )"
221 # Remove any previous instance of this group, and re-add the new one
222 sed -i -e '/^'"${group}"':.*/d;' "${GROUP}"
223 printf "%s:x:%d:\n" "${group}" "${gid}" >>"${GROUP}"
225 # Ditto for /etc/gshadow if it exists
226 if [ -f "${GSHADOW}" ]; then
227 sed -i -e '/^'"${group}"':.*/d;' "${GSHADOW}"
228 printf "%s:*::\n" "${group}" >>"${GSHADOW}"
232 #----------------------------------------------------------------------------
233 # Generate a unique UID for given username. If the username already exists,
234 # then simply report its current UID. Otherwise, generate the lowest UID
235 # that is:
236 # - not 0
237 # - comprised in [MIN_UID..MAX_UID]
238 # - not already used by a user
239 generate_uid() {
240 local username="${1}"
241 local uid
243 uid="$( get_uid "${username}" )"
244 if [ -z "${uid}" ]; then
245 for(( uid=MIN_UID; uid<=MAX_UID; uid++ )); do
246 if [ -z "$( get_username "${uid}" )" ]; then
247 break
249 done
250 if [ ${uid} -gt ${MAX_UID} ]; then
251 fail "can not allocate a UID for user '%s'\n" "${username}"
254 printf "%d\n" "${uid}"
257 #----------------------------------------------------------------------------
258 # Add given user to given group, if not already the case
259 add_user_to_group() {
260 local username="${1}"
261 local group="${2}"
262 local _f
264 for _f in "${GROUP}" "${GSHADOW}"; do
265 [ -f "${_f}" ] || continue
266 sed -r -i -e 's/^('"${group}"':.*:)(([^:]+,)?)'"${username}"'(,[^:]+*)?$/\1\2\4/;' \
267 -e 's/^('"${group}"':.*)$/\1,'"${username}"'/;' \
268 -e 's/,+/,/' \
269 -e 's/:,/:/' \
270 "${_f}"
271 done
274 #----------------------------------------------------------------------------
275 # Encode a password
276 encode_password() {
277 local passwd="${1}"
279 mkpasswd -m "${PASSWD_METHOD}" "${passwd}"
282 #----------------------------------------------------------------------------
283 # Add a user; if it does already exist, remove it first
284 add_one_user() {
285 local username="${1}"
286 local uid="${2}"
287 local group="${3}"
288 local gid="${4}"
289 local passwd="${5}"
290 local home="${6}"
291 local shell="${7}"
292 local groups="${8}"
293 local comment="${9}"
294 local _f _group _home _shell _gid _passwd
296 # First, sanity-check the user
297 check_user_validity "${username}" "${uid}" "${group}" "${gid}"
299 # Generate a new UID if needed
300 if [ ${uid} -eq -1 ]; then
301 uid="$( generate_uid "${username}" )"
304 # Remove any previous instance of this user
305 for _f in "${PASSWD}" "${SHADOW}"; do
306 sed -r -i -e '/^'"${username}"':.*/d;' "${_f}"
307 done
309 _gid="$( get_gid "${group}" )"
310 _shell="${shell}"
311 if [ "${shell}" = "-" ]; then
312 _shell="/bin/false"
314 case "${home}" in
315 -) _home="/";;
316 /) fail "home can not explicitly be '/'\n";;
317 /*) _home="${home}";;
318 *) fail "home must be an absolute path\n";;
319 esac
320 case "${passwd}" in
322 _passwd=""
324 !=*)
325 _passwd='!'"$( encode_password "${passwd#!=}" )"
328 _passwd="$( encode_password "${passwd#=}" )"
331 _passwd="${passwd}"
333 esac
335 printf "%s:x:%d:%d:%s:%s:%s\n" \
336 "${username}" "${uid}" "${_gid}" \
337 "${comment}" "${_home}" "${_shell}" \
338 >>"${PASSWD}"
339 printf "%s:%s:::::::\n" \
340 "${username}" "${_passwd}" \
341 >>"${SHADOW}"
343 # Add the user to its additional groups
344 if [ "${groups}" != "-" ]; then
345 for _group in ${groups//,/ }; do
346 add_user_to_group "${username}" "${_group}"
347 done
350 # If the user has a home, chown it
351 # (Note: stdout goes to the fakeroot-script)
352 if [ "${home}" != "-" ]; then
353 mkdir -p "${TARGET_DIR}/${home}"
354 printf "chown -h -R %d:%d '%s'\n" "${uid}" "${_gid}" "${TARGET_DIR}/${home}"
358 #----------------------------------------------------------------------------
359 main() {
360 local username uid group gid passwd home shell groups comment
361 local line
362 local -a LINES
364 # Some sanity checks
365 if [ ${MIN_UID} -le 0 ]; then
366 fail "MIN_UID must be >0 (currently %d)\n" ${MIN_UID}
368 if [ ${MIN_GID} -le 0 ]; then
369 fail "MIN_GID must be >0 (currently %d)\n" ${MIN_GID}
372 # Read in all the file in memory, exclude empty lines and comments
373 while read line; do
374 LINES+=( "${line}" )
375 done < <( sed -r -e 's/#.*//; /^[[:space:]]*$/d;' "${USERS_TABLE}" )
377 # We first create groups whose gid is not -1, and then we create groups
378 # whose gid is -1 (automatic), so that, if a group is defined both with
379 # a specified gid and an automatic gid, we ensure the specified gid is
380 # used, rather than a different automatic gid is computed.
382 # First, create all the main groups which gid is *not* automatic
383 for line in "${LINES[@]}"; do
384 read username uid group gid passwd home shell groups comment <<<"${line}"
385 [ ${gid} -ge 0 ] || continue # Automatic gid
386 add_one_group "${group}" "${gid}"
387 done
389 # Then, create all the main groups which gid *is* automatic
390 for line in "${LINES[@]}"; do
391 read username uid group gid passwd home shell groups comment <<<"${line}"
392 [ ${gid} -eq -1 ] || continue # Non-automatic gid
393 add_one_group "${group}" "${gid}"
394 done
396 # Then, create all the additional groups
397 # If any additional group is already a main group, we should use
398 # the gid of that main group; otherwise, we can use any gid
399 for line in "${LINES[@]}"; do
400 read username uid group gid passwd home shell groups comment <<<"${line}"
401 if [ "${groups}" != "-" ]; then
402 for g in ${groups//,/ }; do
403 add_one_group "${g}" -1
404 done
406 done
408 # When adding users, we do as for groups, in case two packages create
409 # the same user, one with an automatic uid, the other with a specified
410 # uid, to ensure the specified uid is used, rather than an incompatible
411 # uid be generated.
413 # Now, add users whose uid is *not* automatic
414 for line in "${LINES[@]}"; do
415 read username uid group gid passwd home shell groups comment <<<"${line}"
416 [ "${username}" != "-" ] || continue # Magic string to skip user creation
417 [ ${uid} -ge 0 ] || continue # Automatic uid
418 add_one_user "${username}" "${uid}" "${group}" "${gid}" "${passwd}" \
419 "${home}" "${shell}" "${groups}" "${comment}"
420 done
422 # Finally, add users whose uid *is* automatic
423 for line in "${LINES[@]}"; do
424 read username uid group gid passwd home shell groups comment <<<"${line}"
425 [ "${username}" != "-" ] || continue # Magic string to skip user creation
426 [ ${uid} -eq -1 ] || continue # Non-automatic uid
427 add_one_user "${username}" "${uid}" "${group}" "${gid}" "${passwd}" \
428 "${home}" "${shell}" "${groups}" "${comment}"
429 done
432 #----------------------------------------------------------------------------
433 main "${@}"