1""" 2Manage the password database on BSD systems 3 4.. important:: 5 If you feel that Salt should be using this module to manage passwords on a 6 minion, and it is using a different module (or gives an error similar to 7 *'shadow.info' is not available*), see :ref:`here 8 <module-provider-override>`. 9""" 10 11 12import salt.utils.files 13import salt.utils.stringutils 14from salt.exceptions import CommandExecutionError, SaltInvocationError 15 16try: 17 import pwd 18except ImportError: 19 pass 20 21try: 22 import salt.utils.pycrypto 23 24 HAS_CRYPT = True 25except ImportError: 26 HAS_CRYPT = False 27 28# Define the module's virtual name 29__virtualname__ = "shadow" 30 31 32def __virtual__(): 33 if "BSD" in __grains__.get("os", ""): 34 return __virtualname__ 35 return ( 36 False, 37 "The bsd_shadow execution module cannot be loaded: " 38 "only available on BSD family systems.", 39 ) 40 41 42def default_hash(): 43 """ 44 Returns the default hash used for unset passwords 45 46 CLI Example: 47 48 .. code-block:: bash 49 50 salt '*' shadow.default_hash 51 """ 52 return "*" if __grains__["os"].lower() == "freebsd" else "*************" 53 54 55def gen_password(password, crypt_salt=None, algorithm="sha512"): 56 """ 57 Generate hashed password 58 59 .. note:: 60 61 When called this function is called directly via remote-execution, 62 the password argument may be displayed in the system's process list. 63 This may be a security risk on certain systems. 64 65 password 66 Plaintext password to be hashed. 67 68 crypt_salt 69 Crpytographic salt. If not given, a random 8-character salt will be 70 generated. 71 72 algorithm 73 The following hash algorithms are supported: 74 75 * md5 76 * blowfish (not in mainline glibc, only available in distros that add it) 77 * sha256 78 * sha512 (default) 79 80 CLI Example: 81 82 .. code-block:: bash 83 84 salt '*' shadow.gen_password 'I_am_password' 85 salt '*' shadow.gen_password 'I_am_password' crypt_salt='I_am_salt' algorithm=sha256 86 """ 87 if not HAS_CRYPT: 88 raise CommandExecutionError( 89 "gen_password is not available on this operating system " 90 'because the "crypt" python module is not available.' 91 ) 92 return salt.utils.pycrypto.gen_hash(crypt_salt, password, algorithm) 93 94 95def info(name): 96 """ 97 Return information for the specified user 98 99 CLI Example: 100 101 .. code-block:: bash 102 103 salt '*' shadow.info someuser 104 """ 105 try: 106 data = pwd.getpwnam(name) 107 ret = {"name": data.pw_name, "passwd": data.pw_passwd} 108 except KeyError: 109 return {"name": "", "passwd": ""} 110 111 if not isinstance(name, str): 112 name = str(name) 113 if ":" in name: 114 raise SaltInvocationError("Invalid username '{}'".format(name)) 115 116 if __salt__["cmd.has_exec"]("pw"): 117 change, expire = __salt__["cmd.run_stdout"]( 118 ["pw", "user", "show", name], python_shell=False 119 ).split(":")[5:7] 120 elif __grains__["kernel"] in ("NetBSD", "OpenBSD"): 121 try: 122 with salt.utils.files.fopen("/etc/master.passwd", "r") as fp_: 123 for line in fp_: 124 line = salt.utils.stringutils.to_unicode(line) 125 if line.startswith("{}:".format(name)): 126 key = line.split(":") 127 change, expire = key[5:7] 128 ret["passwd"] = str(key[1]) 129 break 130 except OSError: 131 change = expire = None 132 else: 133 change = expire = None 134 135 try: 136 ret["change"] = int(change) 137 except ValueError: 138 pass 139 140 try: 141 ret["expire"] = int(expire) 142 except ValueError: 143 pass 144 145 return ret 146 147 148def set_change(name, change): 149 """ 150 Sets the time at which the password expires (in seconds since the UNIX 151 epoch). See ``man 8 usermod`` on NetBSD and OpenBSD or ``man 8 pw`` on 152 FreeBSD. 153 154 A value of ``0`` sets the password to never expire. 155 156 CLI Example: 157 158 .. code-block:: bash 159 160 salt '*' shadow.set_change username 1419980400 161 """ 162 pre_info = info(name) 163 if change == pre_info["change"]: 164 return True 165 if __grains__["kernel"] == "FreeBSD": 166 cmd = ["pw", "user", "mod", name, "-f", change] 167 else: 168 cmd = ["usermod", "-f", change, name] 169 __salt__["cmd.run"](cmd, python_shell=False) 170 post_info = info(name) 171 if post_info["change"] != pre_info["change"]: 172 return post_info["change"] == change 173 174 175def set_expire(name, expire): 176 """ 177 Sets the time at which the account expires (in seconds since the UNIX 178 epoch). See ``man 8 usermod`` on NetBSD and OpenBSD or ``man 8 pw`` on 179 FreeBSD. 180 181 A value of ``0`` sets the account to never expire. 182 183 CLI Example: 184 185 .. code-block:: bash 186 187 salt '*' shadow.set_expire username 1419980400 188 """ 189 pre_info = info(name) 190 if expire == pre_info["expire"]: 191 return True 192 if __grains__["kernel"] == "FreeBSD": 193 cmd = ["pw", "user", "mod", name, "-e", expire] 194 else: 195 cmd = ["usermod", "-e", expire, name] 196 __salt__["cmd.run"](cmd, python_shell=False) 197 post_info = info(name) 198 if post_info["expire"] != pre_info["expire"]: 199 return post_info["expire"] == expire 200 201 202def del_password(name): 203 """ 204 .. versionadded:: 2015.8.2 205 206 Delete the password from name user 207 208 CLI Example: 209 210 .. code-block:: bash 211 212 salt '*' shadow.del_password username 213 """ 214 cmd = "pw user mod {} -w none".format(name) 215 __salt__["cmd.run"](cmd, python_shell=False, output_loglevel="quiet") 216 uinfo = info(name) 217 return not uinfo["passwd"] 218 219 220def set_password(name, password): 221 """ 222 Set the password for a named user. The password must be a properly defined 223 hash. The password hash can be generated with this command: 224 225 ``python -c "import crypt; print crypt.crypt('password', ciphersalt)"`` 226 227 .. note:: 228 When constructing the ``ciphersalt`` string, you must escape any dollar 229 signs, to avoid them being interpolated by the shell. 230 231 ``'password'`` is, of course, the password for which you want to generate 232 a hash. 233 234 ``ciphersalt`` is a combination of a cipher identifier, an optional number 235 of rounds, and the cryptographic salt. The arrangement and format of these 236 fields depends on the cipher and which flavor of BSD you are using. For 237 more information on this, see the manpage for ``crpyt(3)``. On NetBSD, 238 additional information is available in ``passwd.conf(5)``. 239 240 It is important to make sure that a supported cipher is used. 241 242 CLI Example: 243 244 .. code-block:: bash 245 246 salt '*' shadow.set_password someuser '$1$UYCIxa628.9qXjpQCjM4a..' 247 """ 248 if __grains__.get("os", "") == "FreeBSD": 249 cmd = ["pw", "user", "mod", name, "-H", "0"] 250 stdin = password 251 else: 252 cmd = ["usermod", "-p", password, name] 253 stdin = None 254 __salt__["cmd.run"](cmd, stdin=stdin, output_loglevel="quiet", python_shell=False) 255 return info(name)["passwd"] == password 256