user.eclass: Add sysroot/etc/shadow support

Upgrading sudo created auth issues during build_image traced to a lack of entries in etc/shadow.
This is a patch to populate entries in etc/shadow for proper auth.

BUG=chromium:905444
TEST=Built image with fresh board setup, rebuilt image, built from master then from changes
Cq-Depend: chromium:1828146
Change-Id: I3debc26a0e4110dc6015a8b4f910b24592b98970
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/overlays/eclass-overlay/+/1825515
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Benjamin Gordon <bmgordon@chromium.org>
Tested-by: Pranay Shoroff <pshoroff@google.com>
Commit-Queue: Pranay Shoroff <pshoroff@google.com>
diff --git a/eclass/user.eclass b/eclass/user.eclass
index 3874fb6..c4352d3 100644
--- a/eclass/user.eclass
+++ b/eclass/user.eclass
@@ -213,13 +213,13 @@
 # @USAGE: <entry> <database> <root>
 # @DESCRIPTION:
 # Writes an entry to the specified database under the specified root.
+# Supported databases: passwd group shadow
 _write_entry_to_db() {
 	local entry=$1 db=$2 root=$3
-
 	[[ $# -ne 3 ]] && die "usage: _write_entry_to_db <entry> <database> <root>"
 
 	case ${db} in
-	passwd|group) ;;
+	passwd|group|shadow) ;;
 	*) die "sorry, database '${db}' not supported." ;;
 	esac
 
@@ -253,16 +253,22 @@
 # @DESCRIPTION:
 # Provides getent-like functionality for databases under [root]. Defaults to ${ROOT}.
 #
-# Supported databases: group passwd
+# Supported databases: group passwd shadow
 egetent() {
 	local use_lock=true
-	[[ $1 == "--nolock" ]] && use_lock=false && shift
-	[[ $# -ne 2 && $# -ne 3 ]] && die "usage: egetent <database> <key> [root]"
+	if [[ $1 == "--nolock" ]]; then
+		use_lock=false
+		shift
+	fi
+
+	if [[ $# -ne 2 && $# -ne 3 ]]; then
+		die "usage: egetent <database> <key> [root]"
+	fi
 
 	local db=$1 key=$2 root=${3:-"${ROOT}"}
 
 	case ${db} in
-	passwd|group) ;;
+	passwd|group|shadow) ;;
 	*) die "sorry, database '${db}' not yet supported; file a bug" ;;
 	esac
 
@@ -301,14 +307,18 @@
 
 	# Lets see if the username already exists in ${ROOT} or in the system.
 	local is_in_root=false
-	[[ -n "$(egetent passwd ${euser})" ]] && is_in_root=true
-	local is_in_system=false
-	[[ -n "$(egetent passwd ${euser} /)" ]] && is_in_system=true
-	local should_be_in_system=false
-	[[ "${EBUILD_PHASE}" == "setup" ]] && should_be_in_system=true
+	if [[ -n "$(egetent passwd "${euser}")" ]]; then
+		is_in_root=true
+	fi
 
-	if "${is_in_root}" && (! "${should_be_in_system}" || "${is_in_system}") ; then
-		return 0
+	local is_in_system=false
+	if [[ -n "$(egetent passwd "${euser}" /)" ]]; then
+		is_in_system=true
+	fi
+
+	local should_be_in_system=false
+	if [[ "${EBUILD_PHASE}" == "setup" ]]; then
+		should_be_in_system=true
 	fi
 
 	# We can't support creating accounts on the system yet.
@@ -326,6 +336,26 @@
 		return 0
 	fi
 
+	# Check if user entry requires password (has a password of "x").
+	# If so, check if shadow file already contains an entry for the user.
+	# About passwords in shadow files: src/third_party/eclass-overlay/profiles/base/accounts/README.md
+	local epassword=$(_get_value_for_user "${euser}" password)
+	: "${epassword:="!"}"
+	local should_have_shadow_entry=false
+	local is_in_shadow=false
+	if [[ ${epassword} == "x" ]]; then
+		should_have_shadow_entry=true
+		if [[ -n "$(egetent shadow "${euser}")" ]]; then
+			is_in_shadow=true
+		fi
+	fi
+
+	if "${is_in_root}" &&
+			(! "${should_have_shadow_entry}" || "${is_in_shadow}") &&
+			(! "${should_be_in_system}" || "${is_in_system}") ; then
+		return 0
+	fi
+
 	# Ensure username exists in profile.
 	if [[ -z $(_get_value_for_user "${euser}" user) ]] ; then
 		die "'${euser}' does not exist in profile!"
@@ -358,7 +388,7 @@
 		# If profile has UID and caller specified same, OK.
 		if [[ ${euid} == -1 ]] ; then
 			euid=${provided_uid}
-		elif [[ ${euid} != ${provided_uid} ]] ; then
+		elif [[ ${euid} != "${provided_uid}" ]] ; then
 			eerror "Userid differs from the profile!"
 			die "${euid} != ${provided_uid} from profile."
 			# else...they're already equal, so do nothing.
@@ -433,12 +463,22 @@
 		einfo " - GECOS: ${comment}"
 	fi
 
-	local epassword=$(_get_value_for_user "${euser}" password)
-	: ${epassword:="!"}
 	local entry="${euser}:${epassword}:${euid}:${egid}:${comment}:${ehome}:${eshell}"
+	local sentry="${euser}:x:::::::"
+
 	if ! "${is_in_system}" && "${should_be_in_system}" ; then
 		_write_entry_to_db "${entry}" passwd / || die "Must be able to add users during setup."
 	fi
+
+	local is_in_system_shadow=false
+	if [[ -n "$(egetent shadow "${euser}" /)" ]]; then
+		is_in_system_shadow=true
+	fi
+
+	if ! "${is_in_system_shadow}" && "${should_be_in_system}" && "${should_have_shadow_entry}" ; then
+		_write_entry_to_db "${sentry}" shadow / || die "Must be able to add users during setup."
+	fi
+
 	if ! "${is_in_root}" ; then
 		if _write_entry_to_db "${entry}" passwd "${ROOT}" ; then
 			if [[ ! -e ${ROOT}/${ehome} ]] ; then
@@ -448,6 +488,9 @@
 				chmod 755 "${ROOT}/${ehome}"
 			fi
 		fi
+		if "${should_have_shadow_entry}" ; then
+			_write_entry_to_db "${sentry}" shadow "${ROOT}"
+		fi
 	fi
 }
 
@@ -470,9 +513,9 @@
 
 	# Lets see if the group already exists in ${ROOT} or in the system.
 	local is_in_root=false
-	[[ -n "$(egetent group ${egroup})" ]] && is_in_root=true
+	[[ -n "$(egetent group "${egroup}")" ]] && is_in_root=true
 	local is_in_system=false
-	[[ -n "$(egetent group ${egroup} /)" ]] && is_in_system=true
+	[[ -n "$(egetent group "${egroup}" /)" ]] && is_in_system=true
 	local should_be_in_system=false
 	[[ "${EBUILD_PHASE}" == "setup" ]] && should_be_in_system=true
 
@@ -516,7 +559,7 @@
 	if [[ -z ${egid} ]] ; then
 		# If caller specified nothing and profile has GID, use profile.
 		# If caller specified nothing and profile has no GID, barf.
-		if [[ ! -z ${provided_gid} ]] ; then
+		if [[ -n  ${provided_gid} ]] ; then
 			egid=${provided_gid}
 		else
 			die "No gid provided in PROFILE or in args!"
@@ -548,7 +591,7 @@
 
 	# Allow group passwords, if profile asks for it.
 	local epassword=$(_get_value_for_group "${egroup}" password)
-	: ${epassword:="!"}
+	: "${epassword:="!"}"
 	einfo " - Password entry: ${epassword}"
 
 	# Pre-populate group with users.