# Copyright 2026 Nadeŭka # Distributed under the terms of the GNU General Public License v2 # @ECLASS: sysidentity.eclass # @MAINTAINER: # Nadeŭka # @SUPPORTED_EAPIS: 8 9 # @BLURB: Core engine and database operations for system identity management # @DESCRIPTION: # Foundation eclass providing shared primitives, root-aware database introspection, # and shadow execution wrappers for sysuser and sysgroup. if [[ ! -v _SYSIDENTITY_ECLASS ]]; then _SYSIDENTITY_ECLASS=1 case ${EAPI} in 8|9) ;; *) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;; esac inherit egetent : "${SLOT:=0}" : "${KEYWORDS:=~amd64}" : "${LICENSE:=metapackage}" S=${WORKDIR} IDEPEND="!prefix? ( >=sys-apps/shadow-4.6 )" # @FUNCTION: sysidentity_istrue # @USAGE: # @INTERNAL # @DESCRIPTION: # Returns 0 if the value is a boolean "1" or "true". sysidentity_istrue() { [[ $1 == 1 || $1 == true ]] } # @FUNCTION: sysidentity_isaltroot # @INTERNAL # @DESCRIPTION: # Returns 0 if ROOT points to an alternate installation root. sysidentity_isaltroot() { [[ ${ROOT:-/} != / ]] } # @FUNCTION: sysidentity_islock # @INTERNAL # @DESCRIPTION: # Returns 0 if the live system databases can be modified (EUID==0 and no EPREFIX). sysidentity_islock() { (( EUID == 0 )) && [[ -z ${EPREFIX} ]] && return 0 einfo "Insufficient privileges to execute ${FUNCNAME[1]}" return 1 } # @FUNCTION: sysidentity_field # @USAGE: # @DESCRIPTION: # Prints one colon-separated field of a database entry. sysidentity_field() { local entry f=() entry=$(egetent "$1" "$2") || return 1 IFS=: read -ra f <<< "${entry}" printf '%s' "${f[$3-1]}" } # @FUNCTION: sysidentity_usergroups # @USAGE: # @DESCRIPTION: # Prints comma-separated names of all groups the user belongs to. sysidentity_usergroups() { local gid gname local -a groups=() initg gid=$(sysidentity_field passwd "$1" 4) || return 1 gname=$(sysidentity_field group "${gid}" 1) && groups+=( "${gname}" ) initg=( $(egetent initgroups "$1") ) for gid in "${initg[@]:1}"; do gname=$(sysidentity_field group "${gid}" 1) || continue has "${gname}" "${groups[@]}" || groups+=( "${gname}" ) done local IFS=, printf '%s' "${groups[*]}" } # @FUNCTION: sysidentity_checkid # @USAGE: # @DESCRIPTION: # Validates the requested id and, if is set, checks it against the database. sysidentity_checkid() { local db=$1 name=$2 id=$3 id_force=$4 label=GID entity=group [[ ${db} == passwd ]] && label=UID entity=user [[ -n ${id} ]] || die "Ebuild error: ${label} must be set!" [[ ${id} == -1 || -z ${id//[0-9]} ]] || die "Ebuild error: ${label}=${id} invalid!" [[ ${id} != -1 && -n ${id_force} && ${id_force} != 0 ]] || return 0 local cur_name cur_entry cur_name=$(sysidentity_field "${db}" "${id}" 1) cur_entry=$(egetent "${db}" "${name}") if [[ -n ${cur_name} && ${cur_name} != "${name}" ]]; then eerror "The required ${label} is already taken by another ${entity}." eerror " ${label}: ${id}" eerror " needed for: ${name}" eerror " current ${entity}: ${cur_name}" die "${label} ${id} taken already" elif [[ -n ${cur_entry} && $(sysidentity_field "${db}" "${name}" 3) != "${id}" ]]; then eerror "The requested ${entity} exists already with wrong ${label}." eerror " ${entity}name: ${name}" eerror " requested ${label}: ${id}" eerror " current entry: ${cur_entry}" die "${entity^} ${name} exists with wrong ${label}" fi } # @FUNCTION: sysidentity_exec # @USAGE: [args...] # @INTERNAL # @DESCRIPTION: # Runs a shadow management command without dying; returns its exit status. sysidentity_exec() { debug-print-function ${FUNCNAME} "$@" printf ' %s\n' "$*" nonfatal "$@" } # @FUNCTION: sysidentity_exec_die # @USAGE: [args...] # @INTERNAL # @DESCRIPTION: # Like sysidentity_exec, but dies if the command fails. sysidentity_exec_die() { sysidentity_exec "$@" || die "$1 failed with status $?" } # @FUNCTION: sysidentity_add # @USAGE: [useradd/groupadd option...] # @INTERNAL # @DESCRIPTION: # Creates a system user or group unless it already exists. Returns 1 if it # already existed, 0 if it was added. sysidentity_add() { local db=$1 name=$2 id=$3 hash=$4 entity=group cmd=groupadd idopt=--gid shift 4 [[ ${db} == passwd ]] && entity=user cmd=useradd idopt=--uid if egetent "${db}" "${name}" >/dev/null; then elog "${entity^} ${name} already exists" return 1 fi local -a opts=( --system "$@" ) [[ ${id} != -1 ]] && ! egetent "${db}" "${id}" >/dev/null && opts+=( "${idopt}" "${id}" ) [[ -n ${hash} ]] && opts+=( --password "${hash}" ) sysidentity_isaltroot && opts+=( --prefix "${ROOT}" ) elog "Adding ${entity} ${name}" sysidentity_exec_die "${cmd}" "${opts[@]}" "${name}" } # @FUNCTION: sysidentity_dosysusers # @USAGE: ... # @DESCRIPTION: # Installs sysusers.d files into /usr/lib/sysusers.d. sysidentity_dosysusers() { debug-print-function ${FUNCNAME} "$@" insinto /usr/lib/sysusers.d doins "$@" } # @FUNCTION: sysidentity_newsysusers # @USAGE: # @DESCRIPTION: # Installs a sysusers.d file under a new name into /usr/lib/sysusers.d. sysidentity_newsysusers() { debug-print-function ${FUNCNAME} "$@" insinto /usr/lib/sysusers.d newins "$@" } # @FUNCTION: sysidentity_applyperms # @USAGE: # @DESCRIPTION: # Resolves ownership and applies fowners/fperms to the user's home directory. sysidentity_applyperms() { local home=$1 u=$2 g=$3 perms=$4 owner=${u}:${g} [[ ${home} == /dev/null ]] && return 0 # with an alternate ROOT names may differ from the live system: use numeric ids if sysidentity_isaltroot; then local euid egid [[ -n ${u} ]] && euid=$(sysidentity_field passwd "${u}" 3) if [[ -n ${g} ]]; then egid=$(sysidentity_field group "${g}" 3) elif [[ -n ${u} ]]; then egid=$(sysidentity_field passwd "${u}" 4) fi owner=${euid}:${egid} fi if [[ ! -e ${ED}/${home#/} ]]; then eerror "Home directory is missing from the installation image:" eerror " ${home}" eerror "Check INSTALL_MASK for entries that would cause this." die "${home} does not exist" fi fowners "${owner}" "${home}" fperms "${perms}" "${home}" } # @FUNCTION: sysidentity_lockuser # @USAGE: # @DESCRIPTION: # Expires and locks an account upon package removal. sysidentity_lockuser() { local user=$1 id=$2 lock=$3 comment today [[ -n ${REPLACED_BY_VERSION} ]] && return 0 sysidentity_islock || return 0 if [[ -n ${id} && -z ${id//0} ]]; then elog "Refusing to lock out the superuser (UID 0)" return 0 fi if ! sysidentity_istrue "${lock}"; then elog "Not locking user ${user} due to SYSUSER_LOCK=${lock}" return 0 fi if ! comment=$(sysidentity_field passwd "${user}" 5); then ewarn "User account not found: ${user}" ewarn "Locking process will be skipped." return 0 fi printf -v today '%(%Y-%m-%d)T' -1 local -a opts=( --expiredate 1 --lock --comment "${comment}; user account removed @ ${today}" --shell /sbin/nologin ) sysidentity_isaltroot && opts+=( --prefix "${ROOT}" ) elog "Locking user ${user}" sysidentity_exec_die usermod "${opts[@]}" "${user}" } fi