Search code examples
linuxshadowmultiple-userspasswd

Force password expiration multiple-users one-time


How can I query all users on a box and force password expiration?

Currently, I am querying all users:

getent shadow | awk -F: '$2 ~ /^\$/ || $2 ~ /^!\$/ {print $1} {print $3}'

And this gets me the user name as well as the last password change, but I only need to force the passwd -e on users who haven't changed their password since before March 1, 2022 - anyone who has changed their password after March 1, 2022 I can leave those alone (I believe this would be a value of 19052 - so any value greater than or equal to that I can skip).


Solution

  • I like the approach @KamilCuk took. To add to that I would include a minimum UID and maximum UID on parsing /etc/passwd to exclude system accounts. (note: some distributions start the first non-system UID at differing values, usually either 500 or 1000 -- check your distro). The maximum UID can exclude generic user accounts placed at the top of the range like the nobody account on openSUSE with UID == 65534

    To determine whether to expire an account with a password change older that March 1, 2022, it is fairly easy to convert that date and the date returned by chage to seconds-since-Epoch. That way you can use a simple comparison of if the last password change is less than the number of seconds since Epoch for March 1, 2022 -- expire the account.

    Below is one approach you can take to put it all together. xargs is another option to build the list instead of expiring accounts one-by-one. The actual expiration is commented out below and instead the command that would be run is printed to stdout to allow testing before actual expiration of accounts.

    #!/bin/bash
    
    ## validate script is run with root privilege
    [ $UID != 0 ] && [ $EUID != 0 ] && {
      printf "error: script must be run as root, UID '%s' can't,\n" "$UID" >&2
      exit 1
    } 
    
    minUID=1000     ## first non-system UID
    maxUID=65534    ## nobody
    
    march1epoch=$(date -d "2022-03-01" +%s)   ## seconds since epoch
    
    ## pipe non-system users to while loop to check aging with chage
    awk -v minU="$minUID" -v maxU="$maxUID" -F: '
      $3 >= minU && $3 < maxU { print $1 }
    ' /etc/passwd | 
    {
    ## read each user name, get age since last pw change
    while read -r usrnm; do 
      age=$(date -d "$(chage -l "$usrnm" | 
            awk -F: 'FNR==1 {print $NF; exit}')" +%s)
      ## compare with march1epoch, expire all that are older
      [ "$age" -lt "$march1epoch" ] && echo "passwd -e \"$usrnm\""
      ### uncomment line below to actually expire account
      # [ "$age" -lt "$march1epoch" ] && passwd -e "$usrnm"
    done
    }
    

    (note: you can use Process Substitution to feed the while loop in bash rather than piping the results to it -- up to you. If you are stuck with POSIX shell, then piping will work in both instances)

    When satisfied, uncomment the final line in the loop, and optionally remove the line that simply outputs the command that would be run.