]> rtime.felk.cvut.cz Git - coffee/buildroot.git/blob - support/scripts/mkusers
mkusers: fix spelling in intro comment
[coffee/buildroot.git] / support / scripts / mkusers
1 #!/usr/bin/env bash
2 set -e
3 myname="${0##*/}"
4
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 #----------------------------------------------------------------------------
13
14 #----------------------------------------------------------------------------
15 error() {
16     local fmt="${1}"
17     shift
18
19     printf "%s: " "${myname}" >&2
20     printf "${fmt}" "${@}" >&2
21 }
22 fail() {
23     error "$@"
24     exit 1
25 }
26
27 #----------------------------------------------------------------------------
28 if [ ${#} -ne 2 ]; then
29     fail "usage: %s USERS_TABLE TARGET_DIR\n"
30 fi
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 have 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"
41
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 named '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}"                                        \
52                 )"
53
54 #----------------------------------------------------------------------------
55 get_uid() {
56     local username="${1}"
57
58     awk -F: -v username="${username}"                           \
59         '$1 == username { printf( "%d\n", $3 ); }' "${PASSWD}"
60 }
61
62 #----------------------------------------------------------------------------
63 get_ugid() {
64     local username="${1}"
65
66     awk -F: -v username="${username}"                          \
67         '$1 == username { printf( "%d\n", $4 ); }' "${PASSWD}"
68 }
69
70 #----------------------------------------------------------------------------
71 get_gid() {
72     local group="${1}"
73
74     awk -F: -v group="${group}"                             \
75         '$1 == group { printf( "%d\n", $3 ); }' "${GROUP}"
76 }
77
78 #----------------------------------------------------------------------------
79 get_username() {
80     local uid="${1}"
81
82     awk -F: -v uid="${uid}"                                 \
83         '$3 == uid { printf( "%s\n", $1 ); }' "${PASSWD}"
84 }
85
86 #----------------------------------------------------------------------------
87 get_group() {
88     local gid="${1}"
89
90     awk -F: -v gid="${gid}"                             \
91         '$3 == gid { printf( "%s\n", $1 ); }' "${GROUP}"
92 }
93
94 #----------------------------------------------------------------------------
95 get_ugroup() {
96     local username="${1}"
97     local ugid
98
99     ugid="$( get_ugid "${username}" )"
100     if [ -n "${ugid}" ]; then
101         get_group "${ugid}"
102     fi
103 }
104
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
119
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}" )"
126
127     if [ "${username}" = "root" ]; then
128         fail "invalid username '%s\n'" "${username}"
129     fi
130
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}"
138         fi
139
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}
146         fi
147
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}
154         fi
155     fi
156
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}"
164         fi
165
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}
172         fi
173     fi
174
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}"
179     fi
180
181     return 0
182 }
183
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
194
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
200             fi
201         done
202         if [ ${gid} -gt ${MAX_GID} ]; then
203             fail "can not allocate a GID for group '%s'\n" "${group}"
204         fi
205     fi
206     printf "%d\n" "${gid}"
207 }
208
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
215
216     # Generate a new GID if needed
217     if [ ${gid} -eq -1 ]; then
218         gid="$( generate_gid "${group}" )"
219     fi
220
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}"
224
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}"
229     fi
230 }
231
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
242
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
248             fi
249         done
250         if [ ${uid} -gt ${MAX_UID} ]; then
251             fail "can not allocate a UID for user '%s'\n" "${username}"
252         fi
253     fi
254     printf "%d\n" "${uid}"
255 }
256
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
263
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
272 }
273
274 #----------------------------------------------------------------------------
275 # Encode a password
276 encode_password() {
277     local passwd="${1}"
278
279     mkpasswd -m "${PASSWD_METHOD}" "${passwd}"
280 }
281
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
295
296     # First, sanity-check the user
297     check_user_validity "${username}" "${uid}" "${group}" "${gid}"
298
299     # Generate a new UID if needed
300     if [ ${uid} -eq -1 ]; then
301         uid="$( generate_uid "${username}" )"
302     fi
303
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
308
309     _gid="$( get_gid "${group}" )"
310     _shell="${shell}"
311     if [ "${shell}" = "-" ]; then
312         _shell="/bin/false"
313     fi
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
321         -)
322             _passwd=""
323             ;;
324         !=*)
325             _passwd='!'"$( encode_password "${passwd#!=}" )"
326             ;;
327         =*)
328             _passwd="$( encode_password "${passwd#=}" )"
329             ;;
330         *)
331             _passwd="${passwd}"
332             ;;
333     esac
334
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}"
342
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
348     fi
349
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}"
355     fi
356 }
357
358 #----------------------------------------------------------------------------
359 main() {
360     local username uid group gid passwd home shell groups comment
361     local line
362     local -a LINES
363
364     # Some sanity checks
365     if [ ${MIN_UID} -le 0 ]; then
366         fail "MIN_UID must be >0 (currently %d)\n" ${MIN_UID}
367     fi
368     if [ ${MIN_GID} -le 0 ]; then
369         fail "MIN_GID must be >0 (currently %d)\n" ${MIN_GID}
370     fi
371
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}" )
376
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.
381
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
388
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
395
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
405         fi
406     done
407
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.
412
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
421
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
430 }
431
432 #----------------------------------------------------------------------------
433 main "${@}"