mkusers 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. #!/usr/bin/env bash
  2. set -e
  3. myname="${0##*/}"
  4. #----------------------------------------------------------------------------
  5. # Configurable items
  6. FIRST_USER_UID=1000
  7. LAST_USER_UID=1999
  8. FIRST_USER_GID=1000
  9. LAST_USER_GID=1999
  10. # use names from /etc/adduser.conf
  11. FIRST_SYSTEM_UID=100
  12. LAST_SYSTEM_UID=999
  13. FIRST_SYSTEM_GID=100
  14. LAST_SYSTEM_GID=999
  15. # argument to automatically crease system/user id
  16. AUTO_SYSTEM_ID=-1
  17. AUTO_USER_ID=-2
  18. # No more is configurable below this point
  19. #----------------------------------------------------------------------------
  20. #----------------------------------------------------------------------------
  21. error() {
  22. local fmt="${1}"
  23. shift
  24. printf "%s: " "${myname}" >&2
  25. printf "${fmt}" "${@}" >&2
  26. }
  27. fail() {
  28. error "$@"
  29. exit 1
  30. }
  31. #----------------------------------------------------------------------------
  32. if [ ${#} -ne 2 ]; then
  33. fail "usage: %s USERS_TABLE TARGET_DIR\n"
  34. fi
  35. USERS_TABLE="${1}"
  36. TARGET_DIR="${2}"
  37. shift 2
  38. PASSWD="${TARGET_DIR}/etc/passwd"
  39. SHADOW="${TARGET_DIR}/etc/shadow"
  40. GROUP="${TARGET_DIR}/etc/group"
  41. # /etc/gshadow is not part of the standard skeleton, so not everybody
  42. # will have it, but some may have it, and its content must be in sync
  43. # with /etc/group, so any use of gshadow must be conditional.
  44. GSHADOW="${TARGET_DIR}/etc/gshadow"
  45. # We can't simply source ${BR2_CONFIG} as it may contains constructs
  46. # such as:
  47. # BR2_DEFCONFIG="$(CONFIG_DIR)/defconfig"
  48. # which when sourced from a shell script will eventually try to execute
  49. # a command named 'CONFIG_DIR', which is plain wrong for virtually every
  50. # systems out there.
  51. # So, we have to scan that file instead. Sigh... :-(
  52. PASSWD_METHOD="$( sed -r -e '/^BR2_TARGET_GENERIC_PASSWD_METHOD="(.*)"$/!d;' \
  53. -e 's//\1/;' \
  54. "${BR2_CONFIG}" \
  55. )"
  56. #----------------------------------------------------------------------------
  57. get_uid() {
  58. local username="${1}"
  59. awk -F: -v username="${username}" \
  60. '$1 == username { printf( "%d\n", $3 ); }' "${PASSWD}"
  61. }
  62. #----------------------------------------------------------------------------
  63. get_ugid() {
  64. local username="${1}"
  65. awk -F: -v username="${username}" \
  66. '$1 == username { printf( "%d\n", $4 ); }' "${PASSWD}"
  67. }
  68. #----------------------------------------------------------------------------
  69. get_gid() {
  70. local group="${1}"
  71. awk -F: -v group="${group}" \
  72. '$1 == group { printf( "%d\n", $3 ); }' "${GROUP}"
  73. }
  74. #----------------------------------------------------------------------------
  75. get_members() {
  76. local group="${1}"
  77. awk -F: -v group="${group}" \
  78. '$1 == group { printf( "%s\n", $4 ); }' "${GROUP}"
  79. }
  80. #----------------------------------------------------------------------------
  81. get_username() {
  82. local uid="${1}"
  83. awk -F: -v uid="${uid}" \
  84. '$3 == uid { printf( "%s\n", $1 ); }' "${PASSWD}"
  85. }
  86. #----------------------------------------------------------------------------
  87. get_group() {
  88. local gid="${1}"
  89. awk -F: -v gid="${gid}" \
  90. '$3 == gid { printf( "%s\n", $1 ); }' "${GROUP}"
  91. }
  92. #----------------------------------------------------------------------------
  93. get_ugroup() {
  94. local username="${1}"
  95. local ugid
  96. ugid="$( get_ugid "${username}" )"
  97. if [ -n "${ugid}" ]; then
  98. get_group "${ugid}"
  99. fi
  100. }
  101. #----------------------------------------------------------------------------
  102. # Sanity-check the new user/group:
  103. # - check the gid is not already used for another group
  104. # - check the group does not already exist with another gid
  105. # - check the user does not already exist with another gid
  106. # - check the uid is not already used for another user
  107. # - check the user does not already exist with another uid
  108. # - check the user does not already exist in another group
  109. check_user_validity() {
  110. local username="${1}"
  111. local uid="${2}"
  112. local group="${3}"
  113. local gid="${4}"
  114. local _uid _ugid _gid _username _group _ugroup
  115. _group="$( get_group "${gid}" )"
  116. _gid="$( get_gid "${group}" )"
  117. _ugid="$( get_ugid "${username}" )"
  118. _username="$( get_username "${uid}" )"
  119. _uid="$( get_uid "${username}" )"
  120. _ugroup="$( get_ugroup "${username}" )"
  121. if [ "${username}" = "root" ]; then
  122. fail "invalid username '%s\n'" "${username}"
  123. fi
  124. if [ ${gid} -lt -2 -o ${gid} -eq 0 ]; then
  125. fail "invalid gid '%d' for '%s'\n" ${gid} "${username}"
  126. elif [ ${gid} -ge 0 ]; then
  127. # check the gid is not already used for another group
  128. if [ -n "${_group}" -a "${_group}" != "${group}" ]; then
  129. fail "gid '%d' for '%s' is already used by group '%s'\n" \
  130. ${gid} "${username}" "${_group}"
  131. fi
  132. # check the group does not already exists with another gid
  133. # Need to split the check in two, otherwise '[' complains it
  134. # is missing arguments when _gid is empty
  135. if [ -n "${_gid}" ] && [ ${_gid} -ne ${gid} ]; then
  136. fail "group '%s' for '%s' already exists with gid '%d' (wants '%d')\n" \
  137. "${group}" "${username}" ${_gid} ${gid}
  138. fi
  139. # check the user does not already exists with another gid
  140. # Need to split the check in two, otherwise '[' complains it
  141. # is missing arguments when _ugid is empty
  142. if [ -n "${_ugid}" ] && [ ${_ugid} -ne ${gid} ]; then
  143. fail "user '%s' already exists with gid '%d' (wants '%d')\n" \
  144. "${username}" ${_ugid} ${gid}
  145. fi
  146. fi
  147. if [ ${uid} -lt -2 -o ${uid} -eq 0 ]; then
  148. fail "invalid uid '%d' for '%s'\n" ${uid} "${username}"
  149. elif [ ${uid} -ge 0 ]; then
  150. # check the uid is not already used for another user
  151. if [ -n "${_username}" -a "${_username}" != "${username}" ]; then
  152. fail "uid '%d' for '%s' already used by user '%s'\n" \
  153. ${uid} "${username}" "${_username}"
  154. fi
  155. # check the user does not already exists with another uid
  156. # Need to split the check in two, otherwise '[' complains it
  157. # is missing arguments when _uid is empty
  158. if [ -n "${_uid}" ] && [ ${_uid} -ne ${uid} ]; then
  159. fail "user '%s' already exists with uid '%d' (wants '%d')\n" \
  160. "${username}" ${_uid} ${uid}
  161. fi
  162. fi
  163. # check the user does not already exist in another group
  164. if [ -n "${_ugroup}" -a "${_ugroup}" != "${group}" ]; then
  165. fail "user '%s' already exists with group '%s' (wants '%s')\n" \
  166. "${username}" "${_ugroup}" "${group}"
  167. fi
  168. return 0
  169. }
  170. #----------------------------------------------------------------------------
  171. # Generate a unique GID for given group. If the group already exists,
  172. # then simply report its current GID. Otherwise, generate the lowest GID
  173. # that is:
  174. # - not 0
  175. # - comprised in [$2..$3]
  176. # - not already used by a group
  177. generate_gid() {
  178. local group="${1}"
  179. local mingid="${2}"
  180. local maxgid="${3}"
  181. local gid
  182. gid="$( get_gid "${group}" )"
  183. if [ -z "${gid}" ]; then
  184. for(( gid=mingid; gid<=maxgid; gid++ )); do
  185. if [ -z "$( get_group "${gid}" )" ]; then
  186. break
  187. fi
  188. done
  189. if [ ${gid} -gt ${maxgid} ]; then
  190. fail "can not allocate a GID for group '%s'\n" "${group}"
  191. fi
  192. fi
  193. printf "%d\n" "${gid}"
  194. }
  195. #----------------------------------------------------------------------------
  196. # Add a group; if it does already exist, remove it first
  197. add_one_group() {
  198. local group="${1}"
  199. local gid="${2}"
  200. local members
  201. # Generate a new GID if needed
  202. if [ ${gid} -eq ${AUTO_USER_ID} ]; then
  203. gid="$( generate_gid "${group}" $FIRST_USER_GID $LAST_USER_GID )"
  204. elif [ ${gid} -eq ${AUTO_SYSTEM_ID} ]; then
  205. gid="$( generate_gid "${group}" $FIRST_SYSTEM_GID $LAST_SYSTEM_GID )"
  206. fi
  207. members=$(get_members "$group")
  208. # Remove any previous instance of this group, and re-add the new one
  209. sed -i --follow-symlinks -e '/^'"${group}"':.*/d;' "${GROUP}"
  210. printf "%s:x:%d:%s\n" "${group}" "${gid}" "${members}" >>"${GROUP}"
  211. # Ditto for /etc/gshadow if it exists
  212. if [ -f "${GSHADOW}" ]; then
  213. sed -i --follow-symlinks -e '/^'"${group}"':.*/d;' "${GSHADOW}"
  214. printf "%s:*::\n" "${group}" >>"${GSHADOW}"
  215. fi
  216. }
  217. #----------------------------------------------------------------------------
  218. # Generate a unique UID for given username. If the username already exists,
  219. # then simply report its current UID. Otherwise, generate the lowest UID
  220. # that is:
  221. # - not 0
  222. # - comprised in [$2..$3]
  223. # - not already used by a user
  224. generate_uid() {
  225. local username="${1}"
  226. local minuid="${2}"
  227. local maxuid="${3}"
  228. local uid
  229. uid="$( get_uid "${username}" )"
  230. if [ -z "${uid}" ]; then
  231. for(( uid=minuid; uid<=maxuid; uid++ )); do
  232. if [ -z "$( get_username "${uid}" )" ]; then
  233. break
  234. fi
  235. done
  236. if [ ${uid} -gt ${maxuid} ]; then
  237. fail "can not allocate a UID for user '%s'\n" "${username}"
  238. fi
  239. fi
  240. printf "%d\n" "${uid}"
  241. }
  242. #----------------------------------------------------------------------------
  243. # Add given user to given group, if not already the case
  244. add_user_to_group() {
  245. local username="${1}"
  246. local group="${2}"
  247. local _f
  248. for _f in "${GROUP}" "${GSHADOW}"; do
  249. [ -f "${_f}" ] || continue
  250. sed -r -i --follow-symlinks \
  251. -e 's/^('"${group}"':.*:)(([^:]+,)?)'"${username}"'(,[^:]+*)?$/\1\2\4/;' \
  252. -e 's/^('"${group}"':.*)$/\1,'"${username}"'/;' \
  253. -e 's/,+/,/' \
  254. -e 's/:,/:/' \
  255. "${_f}"
  256. done
  257. }
  258. #----------------------------------------------------------------------------
  259. # Encode a password
  260. encode_password() {
  261. local passwd="${1}"
  262. mkpasswd -m "${PASSWD_METHOD}" "${passwd}"
  263. }
  264. #----------------------------------------------------------------------------
  265. # Add a user; if it does already exist, remove it first
  266. add_one_user() {
  267. local username="${1}"
  268. local uid="${2}"
  269. local group="${3}"
  270. local gid="${4}"
  271. local passwd="${5}"
  272. local home="${6}"
  273. local shell="${7}"
  274. local groups="${8}"
  275. local comment="${9}"
  276. local _f _group _home _shell _gid _passwd
  277. # First, sanity-check the user
  278. check_user_validity "${username}" "${uid}" "${group}" "${gid}"
  279. # Generate a new UID if needed
  280. if [ ${uid} -eq ${AUTO_USER_ID} ]; then
  281. uid="$( generate_uid "${username}" $FIRST_USER_GID $LAST_USER_GID )"
  282. elif [ ${uid} -eq ${AUTO_SYSTEM_ID} ]; then
  283. uid="$( generate_uid "${username}" $FIRST_SYSTEM_GID $LAST_SYSTEM_GID )"
  284. fi
  285. # Remove any previous instance of this user
  286. for _f in "${PASSWD}" "${SHADOW}"; do
  287. sed -r -i --follow-symlinks -e '/^'"${username}"':.*/d;' "${_f}"
  288. done
  289. _gid="$( get_gid "${group}" )"
  290. _shell="${shell}"
  291. if [ "${shell}" = "-" ]; then
  292. _shell="/bin/false"
  293. fi
  294. case "${home}" in
  295. -) _home="/";;
  296. /) fail "home can not explicitly be '/'\n";;
  297. /*) _home="${home}";;
  298. *) fail "home must be an absolute path\n";;
  299. esac
  300. case "${passwd}" in
  301. -)
  302. _passwd=""
  303. ;;
  304. !=*)
  305. _passwd='!'"$( encode_password "${passwd#!=}" )"
  306. ;;
  307. =*)
  308. _passwd="$( encode_password "${passwd#=}" )"
  309. ;;
  310. *)
  311. _passwd="${passwd}"
  312. ;;
  313. esac
  314. printf "%s:x:%d:%d:%s:%s:%s\n" \
  315. "${username}" "${uid}" "${_gid}" \
  316. "${comment}" "${_home}" "${_shell}" \
  317. >>"${PASSWD}"
  318. printf "%s:%s:::::::\n" \
  319. "${username}" "${_passwd}" \
  320. >>"${SHADOW}"
  321. # Add the user to its additional groups
  322. if [ "${groups}" != "-" ]; then
  323. for _group in ${groups//,/ }; do
  324. add_user_to_group "${username}" "${_group}"
  325. done
  326. fi
  327. # If the user has a home, chown it
  328. # (Note: stdout goes to the fakeroot-script)
  329. if [ "${home}" != "-" ]; then
  330. mkdir -p "${TARGET_DIR}/${home}"
  331. printf "chown -h -R %d:%d '%s'\n" "${uid}" "${_gid}" "${TARGET_DIR}/${home}"
  332. fi
  333. }
  334. #----------------------------------------------------------------------------
  335. main() {
  336. local username uid group gid passwd home shell groups comment
  337. local line
  338. local auto_id
  339. local -a ENTRIES
  340. # Some sanity checks
  341. if [ ${FIRST_USER_UID} -le 0 ]; then
  342. fail "FIRST_USER_UID must be >0 (currently %d)\n" ${FIRST_USER_UID}
  343. fi
  344. if [ ${FIRST_USER_GID} -le 0 ]; then
  345. fail "FIRST_USER_GID must be >0 (currently %d)\n" ${FIRST_USER_GID}
  346. fi
  347. # Read in all the file in memory, exclude empty lines and comments
  348. while read line; do
  349. ENTRIES+=( "${line}" )
  350. done < <( sed -r -e 's/#.*//; /^[[:space:]]*$/d;' "${USERS_TABLE}" )
  351. # We first create groups whose gid is positive, and then we create groups
  352. # whose gid is automatic, so that, if a group is defined both with
  353. # a specified gid and an automatic gid, we ensure the specified gid is
  354. # used, rather than a different automatic gid is computed.
  355. # First, create all the main groups which gid is *not* automatic
  356. for line in "${ENTRIES[@]}"; do
  357. read username uid group gid passwd home shell groups comment <<<"${line}"
  358. [ ${gid} -ge 0 ] || continue # Automatic gid
  359. add_one_group "${group}" "${gid}"
  360. done
  361. # Then, create all the main groups which gid *is* automatic
  362. for line in "${ENTRIES[@]}"; do
  363. read username uid group gid passwd home shell groups comment <<<"${line}"
  364. [ ${gid} -lt 0 ] || continue # Non-automatic gid
  365. add_one_group "${group}" "${gid}"
  366. done
  367. # Then, create all the additional groups
  368. # If any additional group is already a main group, we should use
  369. # the gid of that main group; otherwise, we can use any gid - a
  370. # system gid if the uid is a system user (<= LAST_SYSTEM_UID),
  371. # otherwise a user gid.
  372. for line in "${ENTRIES[@]}"; do
  373. read username uid group gid passwd home shell groups comment <<<"${line}"
  374. if [ "${groups}" != "-" ]; then
  375. if [ ${uid} -le 0 ]; then
  376. auto_id=${uid}
  377. elif [ ${uid} -le ${LAST_SYSTEM_UID} ]; then
  378. auto_id=${AUTO_SYSTEM_ID}
  379. else
  380. auto_id=${AUTO_USER_ID}
  381. fi
  382. for g in ${groups//,/ }; do
  383. add_one_group "${g}" ${auto_id}
  384. done
  385. fi
  386. done
  387. # When adding users, we do as for groups, in case two packages create
  388. # the same user, one with an automatic uid, the other with a specified
  389. # uid, to ensure the specified uid is used, rather than an incompatible
  390. # uid be generated.
  391. # Now, add users whose uid is *not* automatic
  392. for line in "${ENTRIES[@]}"; do
  393. read username uid group gid passwd home shell groups comment <<<"${line}"
  394. [ "${username}" != "-" ] || continue # Magic string to skip user creation
  395. [ ${uid} -ge 0 ] || continue # Automatic uid
  396. add_one_user "${username}" "${uid}" "${group}" "${gid}" "${passwd}" \
  397. "${home}" "${shell}" "${groups}" "${comment}"
  398. done
  399. # Finally, add users whose uid *is* automatic
  400. for line in "${ENTRIES[@]}"; do
  401. read username uid group gid passwd home shell groups comment <<<"${line}"
  402. [ "${username}" != "-" ] || continue # Magic string to skip user creation
  403. [ ${uid} -lt 0 ] || continue # Non-automatic uid
  404. add_one_user "${username}" "${uid}" "${group}" "${gid}" "${passwd}" \
  405. "${home}" "${shell}" "${groups}" "${comment}"
  406. done
  407. }
  408. #----------------------------------------------------------------------------
  409. main "${@}"