diff --git a/usermngr/Config.in b/usermngr/Config.in index 895615dff..fa1125d24 100644 --- a/usermngr/Config.in +++ b/usermngr/Config.in @@ -6,15 +6,15 @@ config USERMNGR_SECURITY_HARDENING help Enable this option to use PAM based faillock, passwdqc, faildelay for security hardening. -config USERMNGR_ENABLE_AUTH_VENDOR_EXT +config USERMNGR_ENABLE_VENDOR_EXT depends on USERMNGR_SECURITY_HARDENING - bool "Exposes vendor datamodel extensions for AuthenticationPolicy" + bool "Expose vendor datamodel extensions." default y help - Enable this option to expose TR181 vendor extensions for AuthenticationPolicy. + Enable this option to expose TR181 vendor extensions. config USERMNGR_VENDOR_PREFIX - depends on USERMNGR_ENABLE_AUTH_VENDOR_EXT + depends on USERMNGR_ENABLE_VENDOR_EXT string "Package specific datamodel Vendor Prefix for TR181 extensions" default "" diff --git a/usermngr/Makefile b/usermngr/Makefile index a7e6ebe53..03c3848d8 100644 --- a/usermngr/Makefile +++ b/usermngr/Makefile @@ -5,13 +5,13 @@ include $(TOPDIR)/rules.mk PKG_NAME:=usermngr -PKG_VERSION:=1.4.5 +PKG_VERSION:=1.4.6 LOCAL_DEV:=0 ifneq ($(LOCAL_DEV),1) PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=https://dev.iopsys.eu/bbf/usermngr.git -PKG_SOURCE_VERSION:=1082342fda754d7219dbca7dec22d34ad4ff7f8e +PKG_SOURCE_VERSION:=416c49b53ed2fbbc983f404c65b8a8ce722152bd PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(PKG_SOURCE_VERSION).tar.gz PKG_MIRROR_HASH:=skip endif @@ -36,6 +36,7 @@ define Package/usermngr DEPENDS+=+@USERMNGR_SECURITY_HARDENING:BUSYBOX_CONFIG_PAM DEPENDS+=+USERMNGR_SECURITY_HARDENING:linux-pam DEPENDS+=+USERMNGR_SECURITY_HARDENING:passwdqc + DEPENDS+=+USERMNGR_ENABLE_VENDOR_EXT:shadow-chage TITLE:=Package to add Device.Users. datamodel support endef @@ -57,8 +58,8 @@ ifeq ($(CONFIG_USERMNGR_SECURITY_HARDENING),y) MAKE_FLAGS += USERMNGR_SECURITY_HARDENING=y endif -ifeq ($(CONFIG_USERMNGR_ENABLE_AUTH_VENDOR_EXT),y) -MAKE_FLAGS += USERMNGR_ENABLE_AUTH_VENDOR_EXT=y +ifeq ($(CONFIG_USERMNGR_ENABLE_VENDOR_EXT),y) +MAKE_FLAGS += USERMNGR_ENABLE_VENDOR_EXT=y endif ifeq ($(CONFIG_USERMNGR_VENDOR_PREFIX),"") diff --git a/usermngr/files/etc/init.d/users b/usermngr/files/etc/init.d/users index 35e0da542..be953e5a4 100755 --- a/usermngr/files/etc/init.d/users +++ b/usermngr/files/etc/init.d/users @@ -16,10 +16,18 @@ REQUIRED_MODULES=" /usr/lib/security/pam_passwdqc.so " +log() { + echo "$*" | logger -t user.init -p info +} + +log_err() { + echo "$*" | logger -t user.init -p err +} + check_required_modules() { for mod in $REQUIRED_MODULES; do if [ ! -f "$mod" ]; then - logger -p err -t usermngr "ERROR: Cannot setup security policy, missing PAM module: $mod" + log_err "ERROR: Cannot setup security policy, missing PAM module: $mod" return 1 fi done @@ -41,7 +49,7 @@ compare_and_replace() { if [ ! -f "$dst" ] || ! cmp -s "$src" "$dst"; then cp "$src" "$dst" - logger -t pam_policy_setup "Updated $dst" + log "Updated $dst" fi } @@ -165,9 +173,87 @@ update_account() { compare_and_replace "$tmp_file" "$pam_file" } +update_password_expiry() { + local user password_expiry shadow_entry lastchg_days lastchg_epoch expiry_epoch expiry_days max_days current_max_days + user="$1" + + # Fetch expiry (in yyyy-mm-dd format) from UCI + config_get password_expiry "$user" password_expiry + + # Get /etc/shadow entry for the user + shadow_entry="$(grep "^${user}:" /etc/shadow 2>/dev/null)" + if [ -z "$shadow_entry" ]; then + log_err "No shadow entry found for $user, skipping" + return + fi + + current_max_days="$(echo "$shadow_entry" | cut -d: -f5)" + + # Handle infinite lifetime (no expiry set) + if [ -z "$password_expiry" ]; then + if [ "$current_max_days" = "-1" ] || [ -z "$current_max_days" ]; then + log "Password for $user already set to never expire, no action needed" + else + chage -M -1 "$user" + log "Set password expiry for $user to never expire (previous max_days=$current_max_days)" + fi + return + fi + + # option value is of the form 9999-12-31T23:59:59Z, so extract date + # because shadow/chage only allow us to specify expiry date + password_expiry="$(echo "$password_expiry" | cut -d 'T' -f 1)" + + # Validate date format (yyyy-mm-dd) + if ! echo "$password_expiry" | grep -Eq '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'; then + log_err "Invalid password_expiry format for $user: '$password_expiry' (expected yyyy-mm-dd)" + return + fi + + # Convert expiry date to epoch seconds + expiry_epoch="$(date -d "$password_expiry" +%s 2>/dev/null)" + if [ -z "$expiry_epoch" ]; then + log_err "Failed to parse expiry date '$password_expiry' for $user" + return + fi + + # Extract 'lastchg' field (3rd colon-separated field) + lastchg_days="$(echo "$shadow_entry" | cut -d: -f3)" + if [ -z "$lastchg_days" ] || [ "$lastchg_days" = "0" ]; then + log_err "No valid last password change date for $user, skipping" + return + fi + + # Convert lastchg_days to epoch seconds + lastchg_epoch=$(( lastchg_days * 86400 )) + + # Compute difference in days between expiry and last change + expiry_days=$(( (expiry_epoch - lastchg_epoch) / 86400 )) + # round up to full day, because of this expiry_days can never be 0, but I think that is a non-issue + expiry_days=$(( expiry_days + 1 )) + + if [ "$expiry_days" -lt 0 ]; then + log_err "Expiry date for $user ($password_expiry) is before last password change, skipping" + return + fi + + max_days="$expiry_days" + + # Apply update if changed + if [ "$current_max_days" != "$max_days" ]; then + chage -M "$max_days" "$user" + log "Updated max_days for $user to $max_days (expiry=$password_expiry, lastchg_days=$lastchg_days)" + else + log "max_days for $user already set to $max_days (expiry=$password_expiry), no change" + fi +} + handle_security_policy() { local auth_enabled enabled + config_load users + config_foreach update_password_expiry user + # Read UCI values auth_enabled="$(uci -q get users.users.auth_policy_enable || echo 0)" enabled="$(uci -q get users.authentication_policy.enabled || echo 0)"