blob: c554246c2721c464305b1c6a218255a77bba7ee1 [file] [log] [blame]
#!/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