Dmitry Teselkin | f8f2a59 | 2019-07-31 16:22:01 +0300 | [diff] [blame^] | 1 | #!/usr/bin/python |
| 2 | |
| 3 | from collections import namedtuple |
| 4 | import datetime |
| 5 | |
| 6 | |
| 7 | PasswdEntry = namedtuple('PasswdEntry', [ |
| 8 | 'pw_name', # username |
| 9 | 'pw_passwd', # user password |
| 10 | 'pw_uid', # user ID |
| 11 | 'pw_gid', # group ID |
| 12 | 'pw_gecos', # user information |
| 13 | 'pw_dir', # home directory |
| 14 | 'pw_shell', # shell program |
| 15 | ]) |
| 16 | |
| 17 | ShadowEntry = namedtuple('ShadowEntry', [ |
| 18 | 'sp_namp', # user login name |
| 19 | 'sp_pwdp', # encrypted password |
| 20 | 'sp_lstchg', # last password change |
| 21 | 'sp_min', # days until change allowed |
| 22 | 'sp_max', # days before change required |
| 23 | 'sp_warn', # days warning for expiration |
| 24 | 'sp_inact', # days before account inactive |
| 25 | 'sp_expire', # date when account expires |
| 26 | 'sp_flag', # reserved for future use |
| 27 | ]) |
| 28 | |
| 29 | |
| 30 | def fix_last_password_change(*args, **kwargs): |
| 31 | """ |
| 32 | This helper function updates last password change timestamp of a user(s). |
| 33 | It doesn't update password itself. |
| 34 | |
| 35 | In case username (a list of usernames) passed as input to this function, |
| 36 | only these users might be affected. |
| 37 | |
| 38 | If there is no input, then all the users in the system will be tried. |
| 39 | |
| 40 | Regardless of how username(s) was/were get, all the following rules SHOULD |
| 41 | match in order to succeed: |
| 42 | * user ID MUST be >= 1000 |
| 43 | * user's password from /etc/shadow MUST be '*'. This is an indicator that |
| 44 | password is disabled for this user, and last password change timestamp |
| 45 | can safely be updated. |
| 46 | |
| 47 | If an account ID is < 1000 (system account) or a user have locked |
| 48 | password (that starts with exclamation mark) or have a valid password |
| 49 | string (even if it's an empty string) then last password change timestamp |
| 50 | won't be updated. |
| 51 | |
| 52 | Returns dictionary where key is account username and value is status of |
| 53 | update operation. |
| 54 | |
| 55 | Possible status values are: |
| 56 | * None - user doesn't exist on the system or an error occured |
| 57 | * True - updated successfully |
| 58 | * False - not updated (UID < 1000 or password is locked / set) |
| 59 | |
| 60 | Usage: |
| 61 | |
| 62 | * to fix last password change timestamp for all accounts |
| 63 | |
| 64 | .. code-block:: shell |
| 65 | |
| 66 | # salt '*' sharedlib.call cis.fix_last_password_change |
| 67 | |
| 68 | * to fix last password change timestamp for specified account(s) |
| 69 | |
| 70 | .. code-block:: shell |
| 71 | |
| 72 | # salt '*' sharedlib.call cis.fix_last_password_change <account1> [<account2>] |
| 73 | |
| 74 | |
| 75 | """ |
| 76 | def _update_last_password_change(username): |
| 77 | if username not in passwd: |
| 78 | return None |
| 79 | |
| 80 | if int(passwd[username].pw_uid) < 1000: |
| 81 | # Ignore 'system' accounts |
| 82 | return False |
| 83 | |
| 84 | if shadow[username].sp_pwdp != '*': |
| 85 | # Do not touch password for accounts that are locked |
| 86 | # or use valid password |
| 87 | return False |
| 88 | |
| 89 | try: |
| 90 | __salt__['cmd.run']('chage -d {} {}'.format(today, username)) |
| 91 | return True |
| 92 | except: |
| 93 | return None |
| 94 | |
| 95 | |
| 96 | today = datetime.date.today().strftime('%Y-%m-%d') |
| 97 | |
| 98 | passwd = {} |
| 99 | with open('/etc/passwd') as f: |
| 100 | for line in f: |
| 101 | entry = PasswdEntry(*line.strip().split(':')) |
| 102 | passwd[entry.pw_name] = entry |
| 103 | |
| 104 | shadow = {} |
| 105 | with open('/etc/shadow') as f: |
| 106 | for line in f: |
| 107 | entry = ShadowEntry(*line.strip().split(':')) |
| 108 | shadow[entry.sp_namp] = entry |
| 109 | |
| 110 | result = {} |
| 111 | if args: |
| 112 | for username in args: |
| 113 | result[username] = _update_last_password_change(username) |
| 114 | else: |
| 115 | for username in passwd: |
| 116 | result[username] = _update_last_password_change(username) |
| 117 | |
| 118 | return result |