Mini Shell
"""
Manage accounts in Samba's passdb using pdbedit
:maintainer: Jorge Schrauwen <sjorge@blackdot.be>
:maturity: new
:platform: posix
.. versionadded:: 2017.7.0
"""
import binascii
import hashlib
import logging
import re
import shlex
import salt.modules.cmdmod
import salt.utils.path
log = logging.getLogger(__name__)
# Define the module's virtual name
__virtualname__ = "pdbedit"
# Function aliases
__func_alias__ = {
"list_users": "list",
"get_user": "get",
}
def __virtual__():
"""
Provides pdbedit if available
"""
# NOTE: check for pdbedit command
if not salt.utils.path.which("pdbedit"):
return (False, "pdbedit command is not available")
# NOTE: check version is >= 4.5.x
ver = salt.modules.cmdmod.run("pdbedit -V")
ver_regex = re.compile(r"^Version\s(\d+)\.(\d+)\.(\d+).*$")
ver_match = ver_regex.match(ver)
if not ver_match:
return (False, "pdbedit -V returned an unknown version format")
if not (int(ver_match.group(1)) >= 4 and int(ver_match.group(2)) >= 5):
return (False, "pdbedit is to old, 4.5.0 or newer is required")
try:
hashlib.new("md4", "".encode("utf-16le"))
except ValueError:
return (False, "Hash type md4 unsupported")
return __virtualname__
def generate_nt_hash(password):
"""
Generate a NT HASH
CLI Example:
.. code-block:: bash
salt '*' pdbedit.generate_nt_hash my_passwd
"""
return binascii.hexlify(
hashlib.new("md4", password.encode("utf-16le")).digest()
).upper()
def list_users(verbose=True, hashes=False):
"""
List user accounts
verbose : boolean
return all information
hashes : boolean
include NT HASH and LM HASH in verbose output
CLI Example:
.. code-block:: bash
salt '*' pdbedit.list
"""
users = {} if verbose else []
if verbose:
# parse detailed user data
res = __salt__["cmd.run_all"](
"pdbedit --list --verbose {hashes}".format(
hashes="--smbpasswd-style" if hashes else ""
),
)
if res["retcode"] > 0:
log.error(res["stderr"] if "stderr" in res else res["stdout"])
return users
for batch in re.split("\n-+|-+\n", res["stdout"]):
user_data = {}
last_label = None
for line in batch.splitlines():
if not line.strip():
continue
label, sep, data = line.partition(":")
label = label.strip().lower()
data = data.strip()
if not sep:
user_data[last_label] += line.strip()
else:
last_label = label
user_data[label] = data
if user_data:
users[user_data["unix username"]] = user_data
else:
# list users
res = __salt__["cmd.run_all"]("pdbedit --list")
if res["retcode"] > 0:
return {"Error": res["stderr"] if "stderr" in res else res["stdout"]}
for user in res["stdout"].splitlines():
if ":" not in user:
continue
user_data = user.split(":")
if len(user_data) >= 3:
users.append(user_data[0])
return users
def get_user(login, hashes=False):
"""
Get user account details
login : string
login name
hashes : boolean
include NTHASH and LMHASH in verbose output
CLI Example:
.. code-block:: bash
salt '*' pdbedit.get kaylee
"""
users = list_users(verbose=True, hashes=hashes)
return users[login] if login in users else {}
def delete(login):
"""
Delete user account
login : string
login name
CLI Example:
.. code-block:: bash
salt '*' pdbedit.delete wash
"""
if login in list_users(False):
res = __salt__["cmd.run_all"](
f"pdbedit --delete {shlex.quote(login)}",
)
if res["retcode"] > 0:
return {login: res["stderr"] if "stderr" in res else res["stdout"]}
return {login: "deleted"}
return {login: "absent"}
def create(login, password, password_hashed=False, machine_account=False):
"""
Create user account
login : string
login name
password : string
password
password_hashed : boolean
set if password is a nt hash instead of plain text
machine_account : boolean
set to create a machine trust account instead
CLI Example:
.. code-block:: bash
salt '*' pdbedit.create zoe 9764951149F84E770889011E1DC4A927 nthash
salt '*' pdbedit.create river 1sw4ll0w3d4bug
"""
ret = "unchanged"
# generate nt hash if needed
if password_hashed:
password_hash = password.upper()
password = "" # wipe password
else:
password_hash = generate_nt_hash(password).decode("ascii")
# create user
if login not in list_users(False):
# NOTE: --create requires a password, even if blank
res = __salt__["cmd.run_all"](
cmd="pdbedit --create --user {login} -t {machine}".format(
login=shlex.quote(login),
machine="--machine" if machine_account else "",
),
stdin="{password}\n{password}\n".format(password=password),
)
if res["retcode"] > 0:
return {login: res["stderr"] if "stderr" in res else res["stdout"]}
ret = "created"
# update password if needed
user = get_user(login, True)
if user["nt hash"] != password_hash:
res = __salt__["cmd.run_all"](
"pdbedit --modify --user {login} --set-nt-hash={nthash}".format(
login=shlex.quote(login), nthash=shlex.quote(password_hash)
),
)
if res["retcode"] > 0:
return {login: res["stderr"] if "stderr" in res else res["stdout"]}
if ret != "created":
ret = "updated"
return {login: ret}
def modify(
login,
password=None,
password_hashed=False,
domain=None,
profile=None,
script=None,
drive=None,
homedir=None,
fullname=None,
account_desc=None,
account_control=None,
machine_sid=None,
user_sid=None,
reset_login_hours=False,
reset_bad_password_count=False,
):
"""
Modify user account
login : string
login name
password : string
password
password_hashed : boolean
set if password is a nt hash instead of plain text
domain : string
users domain
profile : string
profile path
script : string
logon script
drive : string
home drive
homedir : string
home directory
fullname : string
full name
account_desc : string
account description
machine_sid : string
specify the machines new primary group SID or rid
user_sid : string
specify the users new primary group SID or rid
account_control : string
specify user account control properties
.. note::
Only the following can be set:
- N: No password required
- D: Account disabled
- H: Home directory required
- L: Automatic Locking
- X: Password does not expire
reset_login_hours : boolean
reset the users allowed logon hours
reset_bad_password_count : boolean
reset the stored bad login counter
.. note::
if user is absent and password is provided, the user will be created
CLI Example:
.. code-block:: bash
salt '*' pdbedit.modify inara fullname='Inara Serra'
salt '*' pdbedit.modify simon password=r1v3r
salt '*' pdbedit.modify jane drive='V:' homedir='\\\\serenity\\jane\\profile'
salt '*' pdbedit.modify mal account_control=NX
"""
ret = "unchanged"
# flag mapping
flags = {
"domain": "--domain=",
"full name": "--fullname=",
"account desc": "--account-desc=",
"home directory": "--homedir=",
"homedir drive": "--drive=",
"profile path": "--profile=",
"logon script": "--script=",
"account flags": "--account-control=",
"user sid": "-U ",
"machine sid": "-M ",
}
# field mapping
provided = {
"domain": domain,
"full name": fullname,
"account desc": account_desc,
"home directory": homedir,
"homedir drive": drive,
"profile path": profile,
"logon script": script,
"account flags": account_control,
"user sid": user_sid,
"machine sid": machine_sid,
}
# update password
if password:
ret = create(login, password, password_hashed)[login]
if ret not in ["updated", "created", "unchanged"]:
return {login: ret}
elif login not in list_users(False):
return {login: "absent"}
# check for changes
current = get_user(login, hashes=True)
changes = {}
for key, val in provided.items():
if key in ["user sid", "machine sid"]:
if (
val is not None
and key in current
and not current[key].endswith(str(val))
):
changes[key] = str(val)
elif key in ["account flags"]:
if val is not None:
if val.startswith("["):
val = val[1:-1]
new = []
for f in val.upper():
if f not in ["N", "D", "H", "L", "X"]:
log.warning(
"pdbedit.modify - unknown %s flag for account_control, ignored",
f,
)
else:
new.append(f)
changes[key] = "[{flags}]".format(flags="".join(new))
else:
if val is not None and key in current and current[key] != val:
changes[key] = val
# apply changes
if len(changes) > 0 or reset_login_hours or reset_bad_password_count:
cmds = []
for change in changes:
cmds.append(
"{flag}{value}".format(
flag=flags[change],
value=shlex.quote(changes[change]),
)
)
if reset_login_hours:
cmds.append("--logon-hours-reset")
if reset_bad_password_count:
cmds.append("--bad-password-count-reset")
res = __salt__["cmd.run_all"](
"pdbedit --modify --user {login} {changes}".format(
login=shlex.quote(login),
changes=" ".join(cmds),
),
)
if res["retcode"] > 0:
return {login: res["stderr"] if "stderr" in res else res["stdout"]}
if ret != "created":
ret = "updated"
return {login: ret}
Zerion Mini Shell 1.0