diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
commit | 8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch) | |
tree | 4099e8021376c7d8c05bdf8503093d80e9c7bad0 /python/samba/samba3 | |
parent | Initial commit. (diff) | |
download | samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip |
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'python/samba/samba3')
-rw-r--r-- | python/samba/samba3/__init__.py | 409 | ||||
-rw-r--r-- | python/samba/samba3/libsmb_samba_internal.py | 130 |
2 files changed, 539 insertions, 0 deletions
diff --git a/python/samba/samba3/__init__.py b/python/samba/samba3/__init__.py new file mode 100644 index 0000000..af00f69 --- /dev/null +++ b/python/samba/samba3/__init__.py @@ -0,0 +1,409 @@ +# Unix SMB/CIFS implementation. +# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +"""Support for reading Samba 3 data files.""" + +__docformat__ = "restructuredText" + +REGISTRY_VALUE_PREFIX = b"SAMBA_REGVAL" +REGISTRY_DB_VERSION = 1 + +import os +import struct +import tdb + +from samba.samba3 import passdb +from samba.samba3 import param as s3param +from samba.common import get_bytes + +def fetch_uint32(db, key): + try: + data = db[key] + except KeyError: + return None + assert len(data) == 4 + return struct.unpack("<L", data)[0] + + +def fetch_int32(db, key): + try: + data = db[key] + except KeyError: + return None + assert len(data) == 4 + return struct.unpack("<l", data)[0] + + +class DbDatabase(object): + """Simple Samba 3 TDB database reader.""" + def __init__(self, file): + """Open a file. + + :param file: Path of the file to open, appending .tdb or .ntdb. + """ + self.db = tdb.Tdb(file + ".tdb", flags=os.O_RDONLY) + self._check_version() + + def _check_version(self): + pass + + def close(self): + """Close resources associated with this object.""" + self.db.close() + + +class Registry(DbDatabase): + """Simple read-only support for reading the Samba3 registry. + + :note: This object uses the same syntax for registry key paths as + Samba 3. This particular format uses forward slashes for key path + separators and abbreviations for the predefined key names. + e.g.: HKLM/Software/Bar. + """ + def __len__(self): + """Return the number of keys.""" + return len(self.keys()) + + def keys(self): + """Return list with all the keys.""" + return [k.rstrip(b"\x00") for k in self.db if not k.startswith(REGISTRY_VALUE_PREFIX)] + + def subkeys(self, key): + """Retrieve the subkeys for the specified key. + + :param key: Key path. + :return: list with key names + """ + data = self.db.get(key + b"\x00") + if data is None: + return [] + (num, ) = struct.unpack("<L", data[0:4]) + keys = data[4:].split(b"\0") + assert keys[-1] == b"" + keys.pop() + assert len(keys) == num + return keys + + def values(self, key): + """Return a dictionary with the values set for a specific key. + + :param key: Key to retrieve values for. + :return: Dictionary with value names as key, tuple with type and + data as value.""" + data = self.db.get(REGISTRY_VALUE_PREFIX + b'/' + key + b'\x00') + if data is None: + return {} + ret = {} + (num, ) = struct.unpack("<L", data[0:4]) + data = data[4:] + for i in range(num): + # Value name + (name, data) = data.split(b"\0", 1) + + (type, ) = struct.unpack("<L", data[0:4]) + data = data[4:] + (value_len, ) = struct.unpack("<L", data[0:4]) + data = data[4:] + + ret[name] = (type, data[:value_len]) + data = data[value_len:] + + return ret + + +# High water mark keys +IDMAP_HWM_GROUP = b"GROUP HWM\0" +IDMAP_HWM_USER = b"USER HWM\0" + +IDMAP_GROUP_PREFIX = b"GID " +IDMAP_USER_PREFIX = b"UID " + +# idmap version determines auto-conversion +IDMAP_VERSION_V2 = 2 + + +class IdmapDatabase(DbDatabase): + """Samba 3 ID map database reader.""" + + def _check_version(self): + assert fetch_int32(self.db, b"IDMAP_VERSION\0") == IDMAP_VERSION_V2 + + def ids(self): + """Retrieve a list of all ids in this database.""" + for k in self.db: + if k.startswith(IDMAP_USER_PREFIX): + yield k.rstrip(b"\0").split(b" ") + if k.startswith(IDMAP_GROUP_PREFIX): + yield k.rstrip(b"\0").split(b" ") + + def uids(self): + """Retrieve a list of all uids in this database.""" + for k in self.db: + if k.startswith(IDMAP_USER_PREFIX): + yield int(k[len(IDMAP_USER_PREFIX):].rstrip(b"\0")) + + def gids(self): + """Retrieve a list of all gids in this database.""" + for k in self.db: + if k.startswith(IDMAP_GROUP_PREFIX): + yield int(k[len(IDMAP_GROUP_PREFIX):].rstrip(b"\0")) + + def get_sid(self, xid, id_type): + """Retrieve SID associated with a particular id and type. + + :param xid: UID or GID to retrieve SID for. + :param id_type: Type of id specified - 'UID' or 'GID' + """ + data = self.db.get(get_bytes("%s %s\0" % (id_type, str(xid)))) + if data is None: + return data + return data.rstrip("\0") + + def get_user_sid(self, uid): + """Retrieve the SID associated with a particular uid. + + :param uid: UID to retrieve SID for. + :return: A SID or None if no mapping was found. + """ + data = self.db.get(IDMAP_USER_PREFIX + str(uid).encode() + b'\0') + if data is None: + return data + return data.rstrip(b"\0") + + def get_group_sid(self, gid): + data = self.db.get(IDMAP_GROUP_PREFIX + str(gid).encode() + b'\0') + if data is None: + return data + return data.rstrip(b"\0") + + def get_user_hwm(self): + """Obtain the user high-water mark.""" + return fetch_uint32(self.db, IDMAP_HWM_USER) + + def get_group_hwm(self): + """Obtain the group high-water mark.""" + return fetch_uint32(self.db, IDMAP_HWM_GROUP) + + +class SecretsDatabase(DbDatabase): + """Samba 3 Secrets database reader.""" + + def get_auth_password(self): + return self.db.get(b"SECRETS/AUTH_PASSWORD") + + def get_auth_domain(self): + return self.db.get(b"SECRETS/AUTH_DOMAIN") + + def get_auth_user(self): + return self.db.get(b"SECRETS/AUTH_USER") + + def get_domain_guid(self, host): + return self.db.get(b"SECRETS/DOMGUID/%s" % host) + + def ldap_dns(self): + for k in self.db: + if k.startswith("SECRETS/LDAP_BIND_PW/"): + yield k[len("SECRETS/LDAP_BIND_PW/"):].rstrip("\0") + + def domains(self): + """Iterate over domains in this database. + + :return: Iterator over the names of domains in this database. + """ + for k in self.db: + if k.startswith("SECRETS/SID/"): + yield k[len("SECRETS/SID/"):].rstrip("\0") + + def get_ldap_bind_pw(self, host): + return self.db.get(get_bytes("SECRETS/LDAP_BIND_PW/%s" % host)) + + def get_afs_keyfile(self, host): + return self.db.get(get_bytes("SECRETS/AFS_KEYFILE/%s" % host)) + + def get_machine_sec_channel_type(self, host): + return fetch_uint32(self.db, get_bytes("SECRETS/MACHINE_SEC_CHANNEL_TYPE/%s" % host)) + + def get_machine_last_change_time(self, host): + return fetch_uint32(self.db, "SECRETS/MACHINE_LAST_CHANGE_TIME/%s" % host) + + def get_machine_password(self, host): + return self.db.get(get_bytes("SECRETS/MACHINE_PASSWORD/%s" % host)) + + def get_machine_acc(self, host): + return self.db.get(get_bytes("SECRETS/$MACHINE.ACC/%s" % host)) + + def get_domtrust_acc(self, host): + return self.db.get(get_bytes("SECRETS/$DOMTRUST.ACC/%s" % host)) + + def trusted_domains(self): + for k in self.db: + if k.startswith("SECRETS/$DOMTRUST.ACC/"): + yield k[len("SECRETS/$DOMTRUST.ACC/"):].rstrip("\0") + + def get_random_seed(self): + return self.db.get(b"INFO/random_seed") + + def get_sid(self, host): + return self.db.get(get_bytes("SECRETS/SID/%s" % host.upper())) + + +SHARE_DATABASE_VERSION_V1 = 1 +SHARE_DATABASE_VERSION_V2 = 2 + + +class ShareInfoDatabase(DbDatabase): + """Samba 3 Share Info database reader.""" + + def _check_version(self): + assert fetch_int32(self.db, "INFO/version\0") in (SHARE_DATABASE_VERSION_V1, SHARE_DATABASE_VERSION_V2) + + def get_secdesc(self, name): + """Obtain the security descriptor on a particular share. + + :param name: Name of the share + """ + secdesc = self.db.get(get_bytes("SECDESC/%s" % name)) + # FIXME: Run ndr_pull_security_descriptor + return secdesc + + +class Shares(object): + """Container for share objects.""" + def __init__(self, lp, shareinfo): + self.lp = lp + self.shareinfo = shareinfo + + def __len__(self): + """Number of shares.""" + return len(self.lp) - 1 + + def __iter__(self): + """Iterate over the share names.""" + return self.lp.__iter__() + + +def shellsplit(text): + """Very simple shell-like line splitting. + + :param text: Text to split. + :return: List with parts of the line as strings. + """ + ret = list() + inquotes = False + current = "" + for c in text: + if c == "\"": + inquotes = not inquotes + elif c in ("\t", "\n", " ") and not inquotes: + if current != "": + ret.append(current) + current = "" + else: + current += c + if current != "": + ret.append(current) + return ret + + +class WinsDatabase(object): + """Samba 3 WINS database reader.""" + def __init__(self, file): + self.entries = {} + f = open(file, 'r') + assert f.readline().rstrip("\n") == "VERSION 1 0" + for l in f.readlines(): + if l[0] == "#": # skip comments + continue + entries = shellsplit(l.rstrip("\n")) + name = entries[0] + ttl = int(entries[1]) + i = 2 + ips = [] + while "." in entries[i]: + ips.append(entries[i]) + i += 1 + nb_flags = int(entries[i][:-1], 16) + assert name not in self.entries, "Name %s exists twice" % name + self.entries[name] = (ttl, ips, nb_flags) + f.close() + + def __getitem__(self, name): + return self.entries[name] + + def __len__(self): + return len(self.entries) + + def __iter__(self): + return iter(self.entries) + + def items(self): + """Return the entries in this WINS database.""" + return self.entries.items() + + def close(self): # for consistency + pass + + +class Samba3(object): + """Samba 3 configuration and state data reader.""" + + def __init__(self, smbconfpath, s3_lp_ctx=None): + """Open the configuration and data for a Samba 3 installation. + + :param smbconfpath: Path to the smb.conf file. + :param s3_lp_ctx: Samba3 Loadparm context + """ + self.smbconfpath = smbconfpath + if s3_lp_ctx: + self.lp = s3_lp_ctx + else: + self.lp = s3param.get_context() + self.lp.load(smbconfpath) + + def statedir_path(self, path): + if path[0] == "/" or path[0] == ".": + return path + return os.path.join(self.lp.get("state directory"), path) + + def privatedir_path(self, path): + if path[0] == "/" or path[0] == ".": + return path + return os.path.join(self.lp.get("private dir"), path) + + def get_conf(self): + return self.lp + + def get_sam_db(self): + return passdb.PDB(self.lp.get('passdb backend')) + + def get_registry(self): + return Registry(self.statedir_path("registry")) + + def get_secrets_db(self): + return SecretsDatabase(self.privatedir_path("secrets")) + + def get_shareinfo_db(self): + return ShareInfoDatabase(self.statedir_path("share_info")) + + def get_idmap_db(self): + return IdmapDatabase(self.statedir_path("winbindd_idmap")) + + def get_wins_db(self): + return WinsDatabase(self.statedir_path("wins.dat")) + + def get_shares(self): + return Shares(self.get_conf(), self.get_shareinfo_db()) diff --git a/python/samba/samba3/libsmb_samba_internal.py b/python/samba/samba3/libsmb_samba_internal.py new file mode 100644 index 0000000..ef0b30d --- /dev/null +++ b/python/samba/samba3/libsmb_samba_internal.py @@ -0,0 +1,130 @@ +# Copyright (C) Volker Lendecke <vl@samba.org> 2020 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from samba.samba3.libsmb_samba_cwrapper import * +from samba.dcerpc import security + +class Conn(LibsmbCConn): + def deltree(self, path): + if self.chkpath(path): + for entry in self.list(path): + self.deltree(path + "\\" + entry['name']) + self.rmdir(path) + else: + self.unlink(path) + + SECINFO_DEFAULT_FLAGS = \ + security.SECINFO_OWNER | \ + security.SECINFO_GROUP | \ + security.SECINFO_DACL | \ + security.SECINFO_SACL + + def required_access_for_get_secinfo(self, secinfo): + access = 0 + + # + # This is based on MS-FSA + # 2.1.5.13 Server Requests a Query of Security Information + # + # Note that MS-SMB2 3.3.5.20.3 Handling SMB2_0_INFO_SECURITY + # doesn't specify any extra checks + # + + if secinfo & security.SECINFO_OWNER: + access |= security.SEC_STD_READ_CONTROL + if secinfo & security.SECINFO_GROUP: + access |= security.SEC_STD_READ_CONTROL + if secinfo & security.SECINFO_DACL: + access |= security.SEC_STD_READ_CONTROL + if secinfo & security.SECINFO_SACL: + access |= security.SEC_FLAG_SYSTEM_SECURITY + + if secinfo & security.SECINFO_LABEL: + access |= security.SEC_STD_READ_CONTROL + + return access + + def required_access_for_set_secinfo(self, secinfo): + access = 0 + + # + # This is based on MS-FSA + # 2.1.5.16 Server Requests Setting of Security Information + # and additional constraints from + # MS-SMB2 3.3.5.21.3 Handling SMB2_0_INFO_SECURITY + # + + if secinfo & security.SECINFO_OWNER: + access |= security.SEC_STD_WRITE_OWNER + if secinfo & security.SECINFO_GROUP: + access |= security.SEC_STD_WRITE_OWNER + if secinfo & security.SECINFO_DACL: + access |= security.SEC_STD_WRITE_DAC + if secinfo & security.SECINFO_SACL: + access |= security.SEC_FLAG_SYSTEM_SECURITY + + if secinfo & security.SECINFO_LABEL: + access |= security.SEC_STD_WRITE_OWNER + + if secinfo & security.SECINFO_ATTRIBUTE: + access |= security.SEC_STD_WRITE_DAC + + if secinfo & security.SECINFO_SCOPE: + access |= security.SEC_FLAG_SYSTEM_SECURITY + + if secinfo & security.SECINFO_BACKUP: + access |= security.SEC_STD_WRITE_OWNER + access |= security.SEC_STD_WRITE_DAC + access |= security.SEC_FLAG_SYSTEM_SECURITY + + return access + + def get_acl(self, + filename, + sinfo=None, + access_mask=None): + """Get security descriptor for file.""" + if sinfo is None: + sinfo = self.SECINFO_DEFAULT_FLAGS + if access_mask is None: + access_mask = self.required_access_for_get_secinfo(sinfo) + fnum = self.create( + Name=filename, + DesiredAccess=access_mask, + ShareAccess=(FILE_SHARE_READ|FILE_SHARE_WRITE)) + try: + sd = self.get_sd(fnum, sinfo) + finally: + self.close(fnum) + return sd + + def set_acl(self, + filename, + sd, + sinfo=None, + access_mask=None): + """Set security descriptor for file.""" + if sinfo is None: + sinfo = self.SECINFO_DEFAULT_FLAGS + if access_mask is None: + access_mask = self.required_access_for_set_secinfo(sinfo) + fnum = self.create( + Name=filename, + DesiredAccess=access_mask, + ShareAccess=(FILE_SHARE_READ|FILE_SHARE_WRITE)) + try: + self.set_sd(fnum, sd, sinfo) + finally: + self.close(fnum) |