117 lines
4.7 KiB
Bash
Executable File
117 lines
4.7 KiB
Bash
Executable File
#! /bin/bash
|
|
#
|
|
# This script attempts to fix the following issue:
|
|
# https://github.com/wayblueorg/wayblue/issues/89
|
|
# More specifically, it does the following:
|
|
# * Iterates /etc/shadow and /etc/gshadow; and
|
|
# * For every entry that cannot be getent'd, delete it
|
|
#
|
|
# This script should be invoked before systemd-sysusers on system boot
|
|
#
|
|
# The reason for this is as follows:
|
|
# At time of writing, using rpm-ostree to build OCI container images fails to
|
|
# update /usr/lib/passwd and /usr/lib/group, instead dropping items in
|
|
# /usr/lib/sysusers.d for systemd-sysusers to process at boot time. This would
|
|
# fine under normal circumstances.
|
|
#
|
|
# HOWEVER. If you are coming from a distro that had entries in those /usr/lib
|
|
# files for that users/group, you will have entries in /etc/{,g}shadow for said
|
|
# users/groups.
|
|
#
|
|
# If an entry is present in /etc/shadow or /etc/gshadow that matches an object
|
|
# that systemd-sysusers is trying to add, it will fail and then abort further
|
|
# object processing. Thus, we remove objects that cannot be looked up, assuming
|
|
# that the cause is this disparity and that it will be smoothed out when
|
|
# systemd-sysusers next runs
|
|
#
|
|
set -euo pipefail
|
|
|
|
# Iterate over each file we're interested in
|
|
for file in /etc/shadow /etc/gshadow; do
|
|
# Prelim check for zero-byte files (shouldn't proc)
|
|
if ! [ -s "$file" ]; then
|
|
echo "File is missing or empty: $file"
|
|
continue
|
|
fi
|
|
# Prelim check to ensure we can read the file
|
|
if ! [ -r "$file" ]; then
|
|
echo "Unable to read file: $file"
|
|
continue
|
|
fi
|
|
# Prelim checks succeeded, move forward
|
|
echo "Parsing $file for junk"
|
|
# Read each line in the file to iterate over it
|
|
while read -r line; do
|
|
# This should never happen, but if for some reason we get an empty line,
|
|
# continue
|
|
[ -z "$line" ] && continue
|
|
# Per shadow(5), we are guaranteed that all characters leading up to the
|
|
# first colon are the user's/group's name. To that end, we'll do a bash
|
|
# string substitution to extract that first column.
|
|
name="${line%%:*}"
|
|
# Swith case here for what ents we care about. I don't think the array is
|
|
# strictly required but it affords us flexibility for zero cost so whatever.
|
|
ents=()
|
|
case "$file" in
|
|
*/shadow)
|
|
ents=("passwd")
|
|
;;
|
|
*/gshadow)
|
|
ents=("group")
|
|
;;
|
|
*)
|
|
echo "Unknown file to parse for junk: $file"
|
|
exit 101
|
|
;;
|
|
esac
|
|
# Now, we use getent to find a match for the shadow entry. It's at this point
|
|
# that one might ask why we're doing this instead of using grpck or something
|
|
# The answer is that those aren't nss-aware, whereas getent is. /etc/shadow
|
|
# and /etc/gshadow can have entries for things like passwords and group
|
|
# membership for groups that live in /usr/lib/passwd and /usr/lib/group,
|
|
# but grpck isn't aware of those and will obliterate their data from the
|
|
# shadow files if we're not careful.
|
|
#
|
|
# Thus, we use getent to parse through nsswitch.conf intelligently and tell
|
|
# us whether this object we're operating on is found elsewhere in the OS
|
|
matched_ent=""
|
|
for ent in "${ents[@]}"; do
|
|
[ -n "$matched_ent" ] && break
|
|
# We have to do || true here because getent exits nonzero if it fails to
|
|
# find an entity. We need to store its output so we can analyze the status
|
|
# of this command outside the loop -- we can't just "if ! getent"
|
|
result="$(getent "$ent" "$name" || true)"
|
|
[ -n "$result" ] && matched_ent="$result"
|
|
done
|
|
if [ -n "$matched_ent" ]; then
|
|
# getent found a matching entity and we don't care about this entry
|
|
# Just continue down the line
|
|
continue
|
|
else
|
|
# If we're at this point in the code path, we now know that we for-sure are
|
|
# operating on an entry that will cause systemd-sysusers to bail out
|
|
# on invocation. We are thus going to remove it.
|
|
echo "Analyzing broken entity: $name"
|
|
# First, we're going to pattern match the username against the systemd
|
|
# common core username regex. If this fails to match, we bail. I was unable
|
|
# to find a Fedora username that didn't match this but it's best to have
|
|
# this type of safety -- you never know what might happen.
|
|
# https://systemd.io/USER_NAMES/
|
|
if ! [[ $name =~ ^[a-z][a-z0-9-]{0,30}$ ]]; then
|
|
echo "Not touching nonconformant name: $name"
|
|
continue
|
|
fi
|
|
# We've succeeded in all our checks and for sure have a username loaded
|
|
# that isn't going to cause our regex to explode in terrifying ways.
|
|
# We're now going to load sed up and fire it at the shadowfile, making
|
|
# a backup along the way
|
|
echo "Removing from $file: $name"
|
|
sed --in-place=- \
|
|
"/^$name:/d" \
|
|
"$file"
|
|
fi
|
|
# Not useless use of cat. We're buffering the file here to avoid conditions
|
|
# where we edit the file we're reading and face issues.
|
|
done < <(cat "$file")
|
|
done
|