Mini Shell
"""
Manage the password database on Solaris systems
.. important::
If you feel that Salt should be using this module to manage passwords on a
minion, and it is using a different module (or gives an error similar to
*'shadow.info' is not available*), see :ref:`here
<module-provider-override>`.
"""
import os
import salt.utils.files
from salt.exceptions import CommandExecutionError
try:
import spwd
HAS_SPWD = True
except ImportError:
# SmartOS joyent_20130322T181205Z does not have spwd
HAS_SPWD = False
try:
import pwd
except ImportError:
pass # We're most likely on a Windows machine.
try:
import salt.utils.pycrypto
HAS_CRYPT = True
except ImportError:
HAS_CRYPT = False
# Define the module's virtual name
__virtualname__ = "shadow"
def __virtual__():
"""
Only work on POSIX-like systems
"""
if __grains__.get("kernel", "") == "SunOS":
return __virtualname__
return (
False,
"The solaris_shadow execution module failed to load: only available on Solaris"
" systems.",
)
def default_hash():
"""
Returns the default hash used for unset passwords
CLI Example:
.. code-block:: bash
salt '*' shadow.default_hash
"""
return "!"
def info(name):
"""
Return information for the specified user
CLI Example:
.. code-block:: bash
salt '*' shadow.info root
"""
if HAS_SPWD:
try:
data = spwd.getspnam(name)
ret = {
"name": data.sp_nam,
"passwd": data.sp_pwd,
"lstchg": data.sp_lstchg,
"min": data.sp_min,
"max": data.sp_max,
"warn": data.sp_warn,
"inact": data.sp_inact,
"expire": data.sp_expire,
}
except KeyError:
ret = {
"name": "",
"passwd": "",
"lstchg": "",
"min": "",
"max": "",
"warn": "",
"inact": "",
"expire": "",
}
return ret
# SmartOS joyent_20130322T181205Z does not have spwd, but not all is lost
# Return what we can know
ret = {
"name": "",
"passwd": "",
"lstchg": "",
"min": "",
"max": "",
"warn": "",
"inact": "",
"expire": "",
}
try:
data = pwd.getpwnam(name) # pylint: disable=used-before-assignment
ret.update({"name": name})
except KeyError:
return ret
# To compensate for lack of spwd module, read in password hash from /etc/shadow
s_file = "/etc/shadow"
if not os.path.isfile(s_file):
return ret
with salt.utils.files.fopen(s_file, "r") as ifile:
for line in ifile:
comps = line.strip().split(":")
if comps[0] == name:
ret.update({"passwd": comps[1]})
# For SmartOS `passwd -s <username>` and the output format is:
# name status mm/dd/yy min max warn
#
# Fields:
# 1. Name: username
# 2. Status:
# - LK: locked
# - NL: no login
# - NP: No password
# - PS: Password
# 3. Last password change
# 4. Minimum age
# 5. Maximum age
# 6. Warning period
output = __salt__["cmd.run_all"](f"passwd -s {name}", python_shell=False)
if output["retcode"] != 0:
return ret
fields = output["stdout"].split()
if len(fields) == 2:
# For example:
# root NL
return ret
# We have all fields:
# buildbot L 05/09/2013 0 99999 7
ret.update(
{
"name": data.pw_name,
"lstchg": fields[2],
"min": int(fields[3]),
"max": int(fields[4]),
"warn": int(fields[5]),
"inact": "",
"expire": "",
}
)
return ret
def set_maxdays(name, maxdays):
"""
Set the maximum number of days during which a password is valid. See man
passwd.
CLI Example:
.. code-block:: bash
salt '*' shadow.set_maxdays username 90
"""
pre_info = info(name)
if maxdays == pre_info["max"]:
return True
cmd = f"passwd -x {maxdays} {name}"
__salt__["cmd.run"](cmd, python_shell=False)
post_info = info(name)
if post_info["max"] != pre_info["max"]:
return post_info["max"] == maxdays
def set_mindays(name, mindays):
"""
Set the minimum number of days between password changes. See man passwd.
CLI Example:
.. code-block:: bash
salt '*' shadow.set_mindays username 7
"""
pre_info = info(name)
if mindays == pre_info["min"]:
return True
cmd = f"passwd -n {mindays} {name}"
__salt__["cmd.run"](cmd, python_shell=False)
post_info = info(name)
if post_info["min"] != pre_info["min"]:
return post_info["min"] == mindays
return False
def gen_password(password, crypt_salt=None, algorithm="sha512"):
"""
.. versionadded:: 2015.8.8
Generate hashed password
.. note::
When called this function is called directly via remote-execution,
the password argument may be displayed in the system's process list.
This may be a security risk on certain systems.
password
Plaintext password to be hashed.
crypt_salt
Crpytographic salt. If not given, a random 8-character salt will be
generated.
algorithm
The following hash algorithms are supported:
* md5
* blowfish (not in mainline glibc, only available in distros that add it)
* sha256
* sha512 (default)
CLI Example:
.. code-block:: bash
salt '*' shadow.gen_password 'I_am_password'
salt '*' shadow.gen_password 'I_am_password' crypt_salt='I_am_salt' algorithm=sha256
"""
if not HAS_CRYPT:
raise CommandExecutionError(
"gen_password is not available on this operating system "
'because the "crypt" python module is not available.'
)
return salt.utils.pycrypto.gen_hash(crypt_salt, password, algorithm)
def del_password(name):
"""
.. versionadded:: 2015.8.8
Delete the password from name user
CLI Example:
.. code-block:: bash
salt '*' shadow.del_password username
"""
cmd = f"passwd -d {name}"
__salt__["cmd.run"](cmd, python_shell=False, output_loglevel="quiet")
uinfo = info(name)
return not uinfo["passwd"]
def set_password(name, password):
"""
Set the password for a named user. The password must be a properly defined
hash, the password hash can be generated with this command:
``openssl passwd -1 <plaintext password>``
CLI Example:
.. code-block:: bash
salt '*' shadow.set_password root $1$UYCIxa628.9qXjpQCjM4a..
"""
s_file = "/etc/shadow"
ret = {}
if not os.path.isfile(s_file):
return ret
lines = []
with salt.utils.files.fopen(s_file, "r") as ifile:
for line in ifile:
comps = line.strip().split(":")
if comps[0] != name:
lines.append(line)
continue
comps[1] = password
line = ":".join(comps)
lines.append(f"{line}\n")
with salt.utils.files.fopen(s_file, "w+") as ofile:
lines = [salt.utils.stringutils.to_str(_l) for _l in lines]
ofile.writelines(lines)
uinfo = info(name)
return uinfo["passwd"] == password
def set_warndays(name, warndays):
"""
Set the number of days of warning before a password change is required.
See man passwd.
CLI Example:
.. code-block:: bash
salt '*' shadow.set_warndays username 7
"""
pre_info = info(name)
if warndays == pre_info["warn"]:
return True
cmd = f"passwd -w {warndays} {name}"
__salt__["cmd.run"](cmd, python_shell=False)
post_info = info(name)
if post_info["warn"] != pre_info["warn"]:
return post_info["warn"] == warndays
return False
Zerion Mini Shell 1.0