| #!/usr/bin/python |
| |
| from collections import namedtuple |
| import datetime |
| |
| |
| PasswdEntry = namedtuple('PasswdEntry', [ |
| 'pw_name', # username |
| 'pw_passwd', # user password |
| 'pw_uid', # user ID |
| 'pw_gid', # group ID |
| 'pw_gecos', # user information |
| 'pw_dir', # home directory |
| 'pw_shell', # shell program |
| ]) |
| |
| ShadowEntry = namedtuple('ShadowEntry', [ |
| 'sp_namp', # user login name |
| 'sp_pwdp', # encrypted password |
| 'sp_lstchg', # last password change |
| 'sp_min', # days until change allowed |
| 'sp_max', # days before change required |
| 'sp_warn', # days warning for expiration |
| 'sp_inact', # days before account inactive |
| 'sp_expire', # date when account expires |
| 'sp_flag', # reserved for future use |
| ]) |
| |
| |
| def fix_last_password_change(*args, **kwargs): |
| """ |
| This helper function updates last password change timestamp of a user(s). |
| It doesn't update password itself. |
| |
| In case username (a list of usernames) passed as input to this function, |
| only these users might be affected. |
| |
| If there is no input, then all the users in the system will be tried. |
| |
| Regardless of how username(s) was/were get, all the following rules SHOULD |
| match in order to succeed: |
| * user ID MUST be >= 1000 |
| * user's password from /etc/shadow MUST be '*'. This is an indicator that |
| password is disabled for this user, and last password change timestamp |
| can safely be updated. |
| |
| If an account ID is < 1000 (system account) or a user have locked |
| password (that starts with exclamation mark) or have a valid password |
| string (even if it's an empty string) then last password change timestamp |
| won't be updated. |
| |
| Returns dictionary where key is account username and value is status of |
| update operation. |
| |
| Possible status values are: |
| * None - user doesn't exist on the system or an error occured |
| * True - updated successfully |
| * False - not updated (UID < 1000 or password is locked / set) |
| |
| Usage: |
| |
| * to fix last password change timestamp for all accounts |
| |
| .. code-block:: shell |
| |
| # salt '*' sharedlib.call cis.fix_last_password_change |
| |
| * to fix last password change timestamp for specified account(s) |
| |
| .. code-block:: shell |
| |
| # salt '*' sharedlib.call cis.fix_last_password_change <account1> [<account2>] |
| |
| |
| """ |
| def _update_last_password_change(username): |
| if username not in passwd: |
| return None |
| |
| if int(passwd[username].pw_uid) < 1000: |
| # Ignore 'system' accounts |
| return False |
| |
| if shadow[username].sp_pwdp != '*': |
| # Do not touch password for accounts that are locked |
| # or use valid password |
| return False |
| |
| try: |
| __salt__['cmd.run']('chage -d {} {}'.format(today, username)) |
| return True |
| except: |
| return None |
| |
| |
| today = datetime.date.today().strftime('%Y-%m-%d') |
| |
| passwd = {} |
| with open('/etc/passwd') as f: |
| for line in f: |
| entry = PasswdEntry(*line.strip().split(':')) |
| passwd[entry.pw_name] = entry |
| |
| shadow = {} |
| with open('/etc/shadow') as f: |
| for line in f: |
| entry = ShadowEntry(*line.strip().split(':')) |
| shadow[entry.sp_namp] = entry |
| |
| result = {} |
| if args: |
| for username in args: |
| result[username] = _update_last_password_change(username) |
| else: |
| for username in passwd: |
| result[username] = _update_last_password_change(username) |
| |
| return result |