From 001ec398728afacd1e639c83fa7eea9dba091fc1 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 27 Jul 2021 14:35:05 +0200 Subject: useradd.inc: Rewrite helper once again --- core/includes/useradd.inc | 398 ++++++++++++++++++---------------------------- 1 file changed, 153 insertions(+), 245 deletions(-) (limited to 'core/includes') diff --git a/core/includes/useradd.inc b/core/includes/useradd.inc index e3bed754..359a859f 100644 --- a/core/includes/useradd.inc +++ b/core/includes/useradd.inc @@ -1,266 +1,174 @@ -# This helper is stupid because it reimplements what useradd etc. do. -# They could do the job just fine by using the -R option - maybe change this -# dome day. -# +#!/bin/bash -# Add a user to the system -# -# Usage: -# either do ID_OF_REQUESTED_USER=$(add_user herbert) -# or ID_OF_USER=$(USER=herbert GROUP=somegroup PASSWORD=secret123 add_user) -# Valid params are: -# USER, GROUP, USERID, GROUPID, PASSWORD, USERHOME, USERSHELL -# defaults are no password, home=/nonexistent, usershell=/bin/false -# IDs will be generated in the range of 5-999 if not explicitly given -# TODO: Make it possible to pass a range of IDs if you don't want one <1000 but don't care about the exact ID - -declare -rg NAME_REGEX='^[a-z][-a-z0-9]*$' - -# Generate a UID for a given USERNAME. Return existing UID if possible, generate new one otherwise -generate_uid() -{ - [ $# -ne 1 ] && perror "generate_uid fail. want 1 argument." - [ -z "${_PASSWD}" ] && perror "passwd file not set." - local _UID=$(grep -E "^$1:[^:]*:[0-9]+:" "${_PASSWD}" | head -1 | awk -F ':' '{print $3}') - if [ "x${_UID}" = "x" ] - then - local _TRIES=0 - while [ ${_TRIES} -lt 50 ] - do - _TRIES=$(( _TRIES + 1 )) - _UID=$(( 5 + RANDOM % 900 )) - local _TEST=$(grep -E "^[^:]+:[^:]*:${_UID}:" "${_PASSWD}") - [ "x${_TEST}" = "x" ] && break - done - [ ${_TRIES} -ge 50 ] && perror "Generating a UID failed." - fi - echo ${_UID} -} - -# Echo gid of given user if existent, nothing otherwise -get_gid_for_user() -{ - [ $# -ne 1 ] && perror "get_gid_for_user fail. want 1 argument." - [ -z "${_PASSWD}" ] && perror "passwd file not set." - local _GID=$(grep -E "^$1:[^:]*:[^:]*:[0-9]+:" "${_PASSWD}" | head -1 | awk -F ':' '{print $4}') - echo "${_GID}" -} - -get_gid_for_group() { - [ $# -ne 1 ] && perror "get_gid_for_group fail. want 1 argument." - [ -z "${_GROUP}" ] && perror "group file not set." - local _GID=$(grep -E "^$1:[^:]*:[0-9]+:" "${_GROUP}" | head -1 | awk -F ':' '{print $3}') - echo "${_GID}" -} - -# Echo group name of given gid, nothing if non-existent -get_group_for_gid() -{ - [ $# -ne 1 ] && perror "get_group_for_gid fail. want 1 argument." - [ -z "${_GROUP}" ] && perror "group file not set." - local _NAME=$(grep -E "^[^:]*:[^:]*:$1:" "${_GROUP}" | head -1 | awk -F ':' '{print $1}') - echo "${_NAME}" -} - -# Generate a GID for a given GROUPNAME. Return existing GID if possible, generate new one otherwise -generate_gid() -{ - [ $# -ne 2 ] && perror "generate_gid fail. want 2 arguments." - [ -z "${_GROUP}" ] && perror "group file not set." - local _GID=$(grep -E "^$1:[^:]*:[0-9]+:" "${_GROUP}" | head -1 | awk -F ':' '{print $3}') - if [ "x${_GID}" = "x" ] - then - # group does not exist, try to create - local _TRIES=0 - _GID=$2 # try to use uid as gid if not taken yet - while [ ${_TRIES} -lt 50 ] - do - _TRIES=$(( _TRIES + 1 )) - local _TEST=$(grep -E "^[^:]+:[^:]*:${_GID}:" "${_GROUP}") - [ "x${_TEST}" = "x" ] && break - _GID=$(( 5 + RANDOM % 900 )) # using uid as gid not possible, generate new one - done - (( _TRIES >= 50 )) && perror "Generating a GID failed." - fi - echo "${_GID}" -} - -add_system_user() { - SYSTEM_ENTITY="yes" add_user "$@" -} - -add_user() { - [ -z "${TARGET_BUILD_DIR}" ] && perror "add_user: TARGET_BUILD_DIR not set" - if [ -z "${USER}" ] && [ $# -eq 0 ]; then - pwarning " ** add_user usage **" - pwarning "add_user " - pwarning "OR" - pwarning "USER= [GROUP=] [USERID=] [GROUPID=] [USERHOME=] [USERSHELL=] [PASSWORD=] add_user" - perror "Aborting, please fix your script." - fi - - # In install mode, only work on the system's files directly and do *not* copy to TARGET_BUILD_DIR - declare -a _USERADD_OPTS +ua_set_vars () { + ua_set_vars () { + : + } if [ -z "$MLTK_INSTALL" ]; then # regular mltk mode, copy the current user-related files to TARGET_BUILD_DIR - local _PASSWD="${TARGET_BUILD_DIR}/etc/passwd" - local _GROUP="${TARGET_BUILD_DIR}/etc/group" - local _SHADOW="${TARGET_BUILD_DIR}/etc/shadow" - init_users_and_groups - [ ! -f "${_PASSWD}" ] && perror "add_user: password file does not exist in target system. (build base first)" - [ ! -f "${_GROUP}" ] && perror "add_user: group file does not exist in target system. (build base first)" - [ ! -f "${_SHADOW}" ] && perror "add_user: shadow file does not exist in target system. (build base first)" - - # also add the --root options - _USERADD_OPTS+=("--root" "$TARGET_BUILD_DIR") + if [ -z "$TARGET_BUILD_DIR" ] || [ "$TARGET_BUILD_DIR" == "/" ]; then + perror "Almost wrecked your local passwd, group and shadow file. phew." + fi + local prefix=0 + command useradd --help 2>&1 | grep -q -- "--prefix" && prefix=1 + declare -rg _PASSWD="${TARGET_BUILD_DIR}/etc/passwd" + declare -rg _GROUP="${TARGET_BUILD_DIR}/etc/group" + declare -rg _SHADOW="${TARGET_BUILD_DIR}/etc/shadow" + if (( prefix == 0 )); then + declare -rga _USER_EXTRA_OPTS=( "--root" "$TARGET_BUILD_DIR" ) + else + declare -rga _USER_EXTRA_OPTS=( "--prefix" "$TARGET_BUILD_DIR" ) + fi + # prepare files + [ -s "${_PASSWD}" ] && [ -s "${_GROUP}" ] && [ -s "${_SHADOW}" ] && return + pinfo "Creating users and groups based on local system...." + mkdir -p "${_PASSWD%/*}" || perror "Could not mkdir '${_PASSWD%/*}'." + cp -a "/etc/passwd" "$_PASSWD" || perror "Could not copy /etc/passwd" + cp -a "/etc/group" "$_GROUP" || perror "Could not copy /etc/group" + # remove local users from group file (TODO: currently assumes users have ids 1000-1999) + local LOCALUSERS=$( awk -F ':' '$3 >= 1000 && $3 < 2000 {print $1}' "${_PASSWD}" ) + local USER + for USER in $LOCALUSERS; do + sed -r -i "s/([:,])${USER}(,|$)/\1/g;s/,$//" "${_GROUP}" + done + # remove all non-system groups (also assumes users have 1000-1999, so nogroup will be kept) + awk -F ':' '$3 < 1000 || $3 >= 2000' "${_GROUP}" > "${_GROUP}.tmp" + mv -f "${_GROUP}.tmp" "${_GROUP}" || perror "Move error ($_GROUP)" + # same for users... + awk -F ':' '$3 < 1000 || $3 >= 2000' "${_PASSWD}" > "${_PASSWD}.tmp" + mv -f "${_PASSWD}.tmp" "${_PASSWD}" || perror "Move error ($_PASSWD)" + # generate fresh shadow file + awk -F ':' '{print $1":*:15555:0:99999:7:::"}' "${_PASSWD}" > "${_SHADOW}" + # make sure shadow has group shadow + chgrp shadow "$_SHADOW" + chmod 0640 "$_SHADOW" + if (( prefix == 0 )); then + # all user-related tools that support "--root" option require nss libs in the + # chroot target, thus we need to copy the libs over there. + tarcopy "$(find /lib /lib64 /usr/lib /usr/lib64 -maxdepth 4 -name "libnss_files*")" "$TARGET_BUILD_DIR" + fi else - local _PASSWD="/etc/passwd" - local _GROUP="/etc/group" - local _SHADOW="/etc/shadow" + declare -rg _PASSWD="/etc/passwd" + declare -rg _GROUP="/etc/group" + declare -rg _SHADOW="/etc/shadow" + declare -rga _USER_EXTRA_OPTS=() fi +} - if [ "x$1" != "x" ]; then - local USER="$1" - local GROUP="" - local USERID="" - local GROUPID="" - local USERHOME="" - local USERSHELL="" - local PASSWORD="" +useradd () { + ua_set_vars + declare -a opts=( "$@" ) + local _uid _gid name _nuid + local sys=0 + local uid= + local gid= + local mkgrp=0 + local pw="#" + while [ $# -gt 0 ]; do + name="$1" + case "$1" in + --root|-R) perror "Must not use --root" ;; + --prefix|-P) perror "Must not use --prefix" ;; + --system|-r) sys=1 ;; + --uid|-u) uid="$2"; shift ;; + --gid|-g) gid="$2"; shift ;; + --user-group|-U) mkgrp=1 ;; + --password|-p) pw="$2"; shift ;; + esac + shift + done + # Existing user id, if any + _nuid="$( getuid "$name" )" + # Group checks + if (( mkgrp == 1 )); then + [ -n "$gid" ] && perror "$name: Cannot use -U and -g at the same time" + _gid="$( getgid "$name" )" + elif [ -n "$gid" ]; then + _gid="$( getgid "$gid" )" + [ -z "$_gid" ] && perror "$name: Cannot create user and add to $gid: Group not found" fi - - USER=$(trim "$USER") - if ! [[ $USER =~ $NAME_REGEX ]]; then - perror "Invalid username: $USER" + # User checks + if [ -n "$uid" ]; then + _uid="$( getuid "$uid" )" + [ -n "$_uid" ] && [ "$_uid" != "$uid" ] && perror "$name: Requested uid already taken" + [ -n "$_nuid" ] && [ "$_nuid" != "$uid" ] && perror "$name: User already exists with $_nuid" + elif (( sys == 1 )) && [ -n "$_nuid" ] && (( _nuid >= 1000 )); then + perror "$name: Requested as system user, but already has $_nuid" + elif (( sys == 0 )) && [ -n "$_nuid" ] && (( _nuid < 1000 )); then + perror "$name: Requested as normal user, but already has $_nuid" fi - - [ -z "$USERID" ] && local USERID="$(generate_uid "${USER}")" - [ -z "$USERID" ] && perror "add_user: could not generate a user id for $USER" - _USERADD_OPTS+=("--uid" "$USERID") - if [ -z "$GROUP" ]; then - [ -z "$GROUPID" ] && local GROUPID=$(get_gid_for_user "${USER}") - [ -n "$GROUPID" ] && local GROUP=$(get_group_for_gid "${GROUPID}") - [ -z "$GROUP" ] && local GROUP="$USER" - fi - - if [ -n "$GROUP" ]; then - GROUP=$(trim "$GROUP") - if ! [[ $GROUP =~ $NAME_REGEX ]]; then - perror "Invalid group: $GROUP" - fi - # swallow stdout output since only user id is expected to be echo'ed - add_group "$GROUP" "$GROUPID" >/dev/null 2>&1 - _USERADD_OPTS+=("--no-user-group" "--gid" "$GROUP") + if [ -n "$_nuid" ]; then + [ -n "$_gid" ] && [ "$( getusergroup "$name" )" != "$_gid" ] && perror "$name: Exists with group != $_gid" + [ "$pw" != "#" ] && usr_setpw "$name" "$pw" + return 0 # Nothing to do fi + command useradd "${_USER_EXTRA_OPTS[@]}" "${opts[@]}" || perror "useradd failed" + [ "$pw" != "#" ] && usr_setpw "$name" "$pw" +} - if [ -z "${USERHOME}" ]; then - local USERHOME=/nonexistent - else - _USERADD_OPTS+=("--create-home") - # make sure the parent directory exists - if [ -z "$MLTK_INSTALL" ]; then - _udir="${TARGET_BUILD_DIR}/${USERHOME}" - else - _udir="$USERHOME" +groupadd () { + ua_set_vars + declare -a opts=( "$@" ) + local name _ngid + local sys=0 + local gid= + while [ $# -gt 0 ]; do + name="$1" + case "$1" in + --root|-R) perror "Must not use --root" ;; + --prefix|-P) perror "Must not use --prefix" ;; + --system|-r) sys=1 ;; + --gid|-g) gid="$2"; shift ;; + esac + shift + done + # Existing group id, if any + _ngid="$( getgid "$name" )" + if [ -n "$gid" ]; then # Want specific gid + _gid="$( getgid "$gid" )" + if [ -n "$_gid" ]; then # Requested gid exists + [ -n "$_ngid" ] && (( _gid == _ngid )) && return 0 # Nothing to do + perror "$name: gid $_gid already taken" fi - mkdir -p "${_udir%/*}" - + elif [ -n "$_ngid" ]; then + (( sys == 1 && _ngid >= 1000 )) && perror "$name: Already exists with non-system id $_ngid" + (( sys == 0 && _ngid < 1000 )) && perror "$name: Already exists with system id $_ngid" + return 0 fi - _USERADD_OPTS+=("--home-dir" "$USERHOME") - - [ -z "${USERSHELL}" ] && local USERSHELL=/bin/false - _USERADD_OPTS+=("--shell" "$USERSHELL") - - # create password - if [ -n "${PASSWORD}" ]; then - pdebug "Hashing password '$PASSWORD' for '$USER'" - local PW=$(mkpasswd -m sha-512 "${PASSWORD}") - [ -z "${PW}" ] && PW=$(openssl passwd -1 "${PASSWORD}") - [ -z "${PW}" ] && perror "Error generating hashed password for $USER" - PASSWORD=$PW - fi - _USERADD_OPTS+=("--password" "${PASSWORD:-*}") - - [ -n "$SYSTEM_ENTITY" ] && _USERADD_OPTS+=("--system") - # everything is ready, run useradd - useradd "${_USERADD_OPTS[@]}" "$USER" >/dev/null 2>&1 - local ret=$? - # TODO: Is this correct? If the user exists, we should probably make sure it's the USERID - # that was requested, or if none was requested make sure system vs. user range matches!? - [ "$ret" -ne 0 ] && [ "$ret" -ne 9 ] && perror "add_user: useradd failed for: ${_USERADD_OPTS[*]} $USER" - echo "${USERID}" + command groupadd "${_USER_EXTRA_OPTS[@]}" "${opts[@]}" || perror "groupadd failed" } -add_system_group() { - SYSTEM_ENTITY="yes" add_group "$@" +# Get numeric ID of given user (name or ID) +getuid () { + awk -F ':' -v p="$1" -v n= '{if ($1 == p) n = $3; if (n == "" && $3 == p) n = $3;}END{if (length(n)) print n}' "$_PASSWD" } -add_group () { - [ $# -lt 1 ] && perror "add_group called without argument." - [ -z "${TARGET_BUILD_DIR}" ] && perror "add_group: TARGET_BUILD_DIR not set" - declare -a _GROUPADD_OPTS - if [ -z "$MLTK_INSTALL" ]; then - # regular mltk mode, copy the current user-related files to TARGET_BUILD_DIR - local _PASSWD=${TARGET_BUILD_DIR}/etc/passwd - local _GROUP=${TARGET_BUILD_DIR}/etc/group - init_users_and_groups - [ ! -f "${_PASSWD}" ] && perror "add_user: passwd file does not exist in target system. (build base first)" - [ ! -f "${_GROUP}" ] && perror "add_user: group file does not exist in target system. (build base first)" - - # also add the --root options - _GROUPADD_OPTS+=("--root" "$TARGET_BUILD_DIR") - else - local _PASSWD=/etc/passwd - local _GROUP=/etc/group - fi - local GROUP=$1 - local GROUPID="" - [[ $GROUP =~ $NAME_REGEX ]] || perror "Invalid group: $GROUP" - [ $# -ge 2 ] && [ -n "$2" ] && _GROUPADD_OPTS+=("--gid" "$2") - [ -n "$SYSTEM_ENTITY" ] && _GROUPADD_OPTS+=("--system") - groupadd "${_GROUPADD_OPTS[@]}" "$GROUP" >/dev/null 2>&1 - local ret=$? - [ "$ret" -ne 0 ] && [ "$ret" -ne 9 ] && perror "add_group: groupadd failed: ${_GROUPADD_OPTS[*]}" - [ -z "$GROUPID" ] && local GROUPID="$(get_gid_for_group "$GROUP")" - echo "${GROUPID}" +# Get numeric ID of given group (name or ID) +getgid () { + awk -F ':' -v p="$1" -v n= '{if ($1 == p) n = $3; if (n == "" && $3 == p) n = $3;}END{if (length(n)) print n}' "$_GROUP" } +# Get numeric ID of primary group of given user (name or ID) +getusergroup () { + awk -F ':' -v p="$1" -v n= '{if ($1 == p) n = $4; if (n == "" && $4 == p) n = $3;}END{if (length(n)) print n}' "$_PASSWD" +} -init_users_and_groups() { - if [ -z "$TARGET_BUILD_DIR" ] || [ "$TARGET_BUILD_DIR" == "/" ]; then - perror "Almost wrecked your local passwd, group and shadow file. phew." +# usr_setpw username password +usr_setpw () { + local PW= + local pw="$2" + if [ -z "$pw" ]; then + PW="*" + elif [ "${pw:0:1}" != '$' ] || [ "${pw:2:1}" != '$' ]; then + PW="$( mkpasswd -m sha-512 "${pw}" )" + [ -z "${PW}" ] && PW="$( openssl passwd -6 "${pw}" )" + [ -z "${PW}" ] && PW="$( openssl passwd -1 "${pw}" )" + [ -z "${PW}" ] && perror "Error generating hashed password for $1" + else + PW="$pw" fi - local USER - local PASSWD="$TARGET_BUILD_DIR/etc/passwd" - local GROUP="$TARGET_BUILD_DIR/etc/group" - local SHADOW="$TARGET_BUILD_DIR/etc/shadow" - [ -s "${PASSWD}" ] && [ -s "${GROUP}" ] && [ -s "${SHADOW}" ] && return - pinfo "Creating users and groups based on local system...." - mkdir -p "${PASSWD%/*}" || perror "Could not mkdir '${PASSWD%/*}'." - cp -a "/etc/passwd" "$PASSWD" || perror "Could not copy /etc/passwd" - cp -a "/etc/group" "$GROUP" || perror "Could not copy /etc/group" - cp -a "/etc/shadow" "$SHADOW" || perror "Could not copy /etc/shadow" - # make sure shadow has group shadow (should be handled by cp -a but hey) - chgrp shadow "$SHADOW" - # remove local users from group file (TODO: currently assumes users have ids 1000-1999) - local LOCALUSERS=$(grep -E '^[^:]+:x?:1[0-9]{3}:' "${PASSWD}" | awk -F ':' '{print $1}') - for USER in $LOCALUSERS; do - sed -r -i "s/([:,])${USER}/\1/g" "${GROUP}" - done - # fix syntax: remove trailing ',' in group file - sed -r -i 's/,+$//g' "${GROUP}" - sed -r -i 's/,+/,/g' "${GROUP}" - sed -i 's/:,/:/g' "${GROUP}" - # remove all non-system groups (also assumes users have 1000-1999, so nogroup will be kept) - grep -v -E '^[^:]+:x?:1[0-9]{3}:' "${GROUP}" > "${GROUP}.tmp" - mv "${GROUP}.tmp" "${GROUP}" - # same for users... - grep -v -E '^[^:]+:x?:1[0-9]{3}:' "${PASSWD}" > "${PASSWD}.tmp" - mv "${PASSWD}.tmp" "${PASSWD}" - # generate fresh shadow file - awk -F ':' '{print $1":*:15555:0:99999:7:::"}' "${PASSWD}" > "${SHADOW}" - # all user-related tools that support "--root" option require nss libs in the - # chroot target, thus we need to copy the libs over there. - tarcopy "$(find /lib /lib64 /usr/lib /usr/lib64 -maxdepth 4 -name "libnss_files*")" "$TARGET_BUILD_DIR" + local s r + s="$( sed -r 's~[\[{(*+/^$?\\]~\\&~g' <<<"$1" )" + r="$( sed -r 's~[&/\\]~\\&~g' <<<"$1:$PW" )" + sed -i -r "s/$s:[^:]*:/$r:/" "${_SHADOW}" } - -- cgit v1.2.3-55-g7522