mirror of
https://dev.iopsys.eu/feed/iopsys.git
synced 2025-12-10 07:44:50 +01:00
308 lines
8.2 KiB
Bash
Executable file
308 lines
8.2 KiB
Bash
Executable file
#!/bin/sh /etc/rc.common
|
|
|
|
START=11
|
|
STOP=90
|
|
USE_PROCD=1
|
|
|
|
PROG=/usr/sbin/usermngr
|
|
|
|
# List of required .so files
|
|
REQUIRED_MODULES="
|
|
/usr/lib/security/pam_faildelay.so
|
|
/usr/lib/security/pam_faillock.so
|
|
/usr/lib/security/pam_unix.so
|
|
/usr/lib/security/pam_deny.so
|
|
/usr/lib/security/pam_permit.so
|
|
/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
|
|
log_err "ERROR: Cannot setup security policy, missing PAM module: $mod"
|
|
return 1
|
|
fi
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
write_line() {
|
|
local filepath="$1"
|
|
local line="$2"
|
|
|
|
echo "$line" >> "$filepath"
|
|
}
|
|
|
|
compare_and_replace() {
|
|
local src dst
|
|
src="$1"
|
|
dst="$2"
|
|
|
|
if [ ! -f "$dst" ] || ! cmp -s "$src" "$dst"; then
|
|
cp "$src" "$dst"
|
|
log "Updated $dst"
|
|
fi
|
|
}
|
|
|
|
update_auth() {
|
|
# Write /etc/pam.d/common-auth
|
|
local tmp_file pam_file
|
|
tmp_file="/tmp/common-auth"
|
|
pam_file="/etc/pam.d/common-auth"
|
|
|
|
local auth_enabled="${1}"
|
|
local enabled="${2}"
|
|
|
|
local faildelay="$(uci -q get users.authentication_policy.fail_delay)"
|
|
local faillock_lockout_time="$(uci -q get users.authentication_policy.faillock_lockout_time)"
|
|
local faillock_attempts="$(uci -q get users.authentication_policy.faillock_attempts)"
|
|
|
|
[ -n "$faildelay" ] || faildelay=3
|
|
[ -n "$faillock_attempts" ] || faillock_attempts=6
|
|
[ -n "$faillock_lockout_time" ] || faillock_lockout_time=300
|
|
|
|
# Convert seconds to microseconds for pam_faildelay
|
|
local faildelay_usec=$((faildelay * 1000000))
|
|
|
|
rm -f "$tmp_file"
|
|
touch "$tmp_file"
|
|
|
|
if [ "${auth_enabled}" -eq 1 ] && [ "${enabled}" -eq 1 ]; then
|
|
write_line "$tmp_file" "auth optional pam_faildelay.so delay=$faildelay_usec"
|
|
write_line "$tmp_file" "auth required pam_faillock.so preauth deny=$faillock_attempts even_deny_root unlock_time=$faillock_lockout_time"
|
|
fi
|
|
|
|
write_line "$tmp_file" "auth sufficient pam_unix.so nullok_secure"
|
|
|
|
if [ "${auth_enabled}" -eq 1 ] && [ "${enabled}" -eq 1 ]; then
|
|
write_line "$tmp_file" "auth [default=die] pam_faillock.so authfail audit deny=$faillock_attempts even_deny_root unlock_time=$faillock_lockout_time"
|
|
write_line "$tmp_file" ""
|
|
fi
|
|
|
|
write_line "$tmp_file" "auth requisite pam_deny.so"
|
|
write_line "$tmp_file" "auth required pam_permit.so"
|
|
|
|
compare_and_replace "$tmp_file" "$pam_file"
|
|
}
|
|
|
|
build_pam_passwdqc_line() {
|
|
local base="password requisite pam_passwdqc.so"
|
|
local k v line
|
|
|
|
for line in $(uci show users.passwdqc 2>/dev/null); do
|
|
case "$line" in
|
|
users.passwdqc=*) continue ;;
|
|
users.passwdqc.enabled=*) continue ;;
|
|
esac
|
|
|
|
k="${line%%=*}"
|
|
k="${k#users.passwdqc.}"
|
|
v="${line#*=}"
|
|
v="${v%\'}"
|
|
v="${v#\'}"
|
|
base="$base $k=$v"
|
|
done
|
|
|
|
base="$base match=0"
|
|
|
|
echo "$base"
|
|
}
|
|
|
|
# NOTE:
|
|
# for some reason setting min 8 makes passwdqc accept minimum 12 letter password with this configuration
|
|
# if we set it to 12 then we need atleast 16 characters and so on
|
|
# passphrase = 0 means no space separated words
|
|
# passphrase = N means the number of words required for a passphrase or 0 to disable the support for user-chosen passphrases.
|
|
# rest can be figured out from passwdqc man page
|
|
update_password() {
|
|
local tmp_file pam_file enabled line
|
|
tmp_file="/tmp/common-password"
|
|
pam_file="/etc/pam.d/common-password"
|
|
|
|
local auth_enabled="${1}"
|
|
|
|
rm -f "$tmp_file"
|
|
touch "$tmp_file"
|
|
|
|
# Check if section exists
|
|
if uci -q get users.passwdqc >/dev/null 2>&1; then
|
|
# if enabled is not present it is assumed to be 0
|
|
enabled=$(uci -q get users.passwdqc.enabled || echo "0")
|
|
if [ "${auth_enabled}" -eq 1 ] && [ "${enabled}" -eq 1 ]; then
|
|
line="$(build_pam_passwdqc_line)"
|
|
write_line "$tmp_file" "$line"
|
|
fi
|
|
fi
|
|
|
|
write_line "$tmp_file" "password [success=1 default=ignore] pam_unix.so sha512"
|
|
write_line "$tmp_file" ""
|
|
write_line "$tmp_file" "password requisite pam_deny.so"
|
|
write_line "$tmp_file" "password required pam_permit.so"
|
|
|
|
compare_and_replace "$tmp_file" "$pam_file"
|
|
}
|
|
|
|
update_account() {
|
|
# Write /etc/pam.d/common-account
|
|
local tmp_file pam_file
|
|
tmp_file="/tmp/common-account"
|
|
pam_file="/etc/pam.d/common-account"
|
|
|
|
local auth_enabled="${1}"
|
|
local enabled="${2}"
|
|
|
|
rm -f "$tmp_file"
|
|
touch "$tmp_file"
|
|
|
|
if [ "${auth_enabled}" -eq 1 ] && [ "${enabled}" -eq 1 ]; then
|
|
write_line "$tmp_file" "account required pam_faillock.so"
|
|
fi
|
|
|
|
write_line "$tmp_file" "account [success=1 new_authtok_reqd=done default=ignore] pam_unix.so"
|
|
write_line "$tmp_file" ""
|
|
write_line "$tmp_file" "account requisite pam_deny.so"
|
|
write_line "$tmp_file" "account required pam_permit.so"
|
|
|
|
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)"
|
|
|
|
# if any .so files are missing, then we cannot setup security
|
|
if ! check_required_modules; then
|
|
return
|
|
fi
|
|
|
|
update_auth "${auth_enabled}" "${enabled}"
|
|
update_account "${auth_enabled}" "${enabled}"
|
|
update_password "${auth_enabled}"
|
|
}
|
|
|
|
start_service() {
|
|
local loglevel
|
|
|
|
loglevel="$(uci -q get users.users.loglevel)"
|
|
|
|
handle_security_policy
|
|
|
|
procd_open_instance usermngr
|
|
procd_set_param command $PROG
|
|
|
|
if [ -n "${loglevel}" ]; then
|
|
procd_append_param command -l "${loglevel}"
|
|
fi
|
|
|
|
procd_set_param respawn
|
|
procd_close_instance
|
|
}
|
|
|
|
reload_service() {
|
|
ret=$(ubus call service list '{"name":"users"}' | jsonfilter -qe '@.users.instances.usermngr.running')
|
|
if [ "$ret" != "true" ]; then
|
|
stop
|
|
start
|
|
else
|
|
handle_security_policy
|
|
ubus send usermngr.reload
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
service_triggers()
|
|
{
|
|
procd_add_reload_trigger "users"
|
|
}
|
|
|