summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source4/scripting/python/config.mk2
-rw-r--r--source4/scripting/python/samba/__init__.py8
-rw-r--r--source4/scripting/python/samba/getopt.py2
-rw-r--r--source4/scripting/python/samba/provision.py96
-rw-r--r--source4/scripting/python/samba/samba3.py100
-rw-r--r--source4/scripting/python/samba/samdb.py50
-rw-r--r--source4/scripting/python/samba/tests/__init__.py4
7 files changed, 175 insertions, 87 deletions
diff --git a/source4/scripting/python/config.mk b/source4/scripting/python/config.mk
index 4a531f5062..cfd179aff5 100644
--- a/source4/scripting/python/config.mk
+++ b/source4/scripting/python/config.mk
@@ -30,7 +30,7 @@ realdistclean::
pythonmods: $(PYTHON_DSOS)
-PYDOCTOR_MODULES=bin/python/ldb.py bin/python/auth.py bin/python/credentials.py bin/python/registry.py bin/python/tdb.py bin/python/security.py
+PYDOCTOR_MODULES=bin/python/ldb.py bin/python/auth.py bin/python/credentials.py bin/python/registry.py bin/python/tdb.py bin/python/security.py bin/python/events.py bin/python/net.py
pydoctor:: pythonmods
LD_LIBRARY_PATH=bin/shared PYTHONPATH=bin/python pydoctor --make-html --docformat=restructuredtext --add-package scripting/python/samba/ $(addprefix --add-module , $(PYDOCTOR_MODULES))
diff --git a/source4/scripting/python/samba/__init__.py b/source4/scripting/python/samba/__init__.py
index 359457d815..01fdea6665 100644
--- a/source4/scripting/python/samba/__init__.py
+++ b/source4/scripting/python/samba/__init__.py
@@ -159,11 +159,19 @@ class Ldb(ldb.Ldb):
self.add_ldif(open(ldif_path, 'r').read())
def add_ldif(self, ldif):
+ """Add data based on a LDIF string.
+
+ :param ldif: LDIF text.
+ """
for changetype, msg in self.parse_ldif(ldif):
assert changetype == ldb.CHANGETYPE_NONE
self.add(msg)
def modify_ldif(self, ldif):
+ """Modify database based on a LDIF string.
+
+ :param ldif: LDIF text.
+ """
for (changetype, msg) in self.parse_ldif(ldif):
assert changetype == ldb.CHANGETYPE_MODIFY
self.modify(msg)
diff --git a/source4/scripting/python/samba/getopt.py b/source4/scripting/python/samba/getopt.py
index 87cf171ca2..c0e7053062 100644
--- a/source4/scripting/python/samba/getopt.py
+++ b/source4/scripting/python/samba/getopt.py
@@ -58,7 +58,7 @@ class CredentialsOptions(optparse.OptionGroup):
self.creds.set_password(arg)
def set_simple_bind_dn(self, option, opt_str, arg, parser):
- self.creds.set_simple_bind_dn(arg)
+ self.creds.set_bind_dn(arg)
def get_credentials(self):
return self.creds
diff --git a/source4/scripting/python/samba/provision.py b/source4/scripting/python/samba/provision.py
index fa45f7a79d..bdfe035c41 100644
--- a/source4/scripting/python/samba/provision.py
+++ b/source4/scripting/python/samba/provision.py
@@ -74,6 +74,14 @@ def findnss(nssfn, *names):
def open_ldb(session_info, credentials, lp, dbname):
+ """Open a LDB, thrashing it if it is corrupt.
+
+ :param session_info: auth session information
+ :param credentials: credentials
+ :param lp: Loadparm context
+ :param dbname: Path of the database to open.
+ :return: a Ldb object
+ """
assert session_info is not None
try:
return Ldb(dbname, session_info=session_info, credentials=credentials,
@@ -86,7 +94,12 @@ def open_ldb(session_info, credentials, lp, dbname):
def setup_add_ldif(ldb, ldif_path, subst_vars=None):
- """Setup a ldb in the private dir."""
+ """Setup a ldb in the private dir.
+
+ :param ldb: LDB file to import data into
+ :param ldif_path: Path of the LDIF file to load
+ :param subst_vars: Optional variables to subsitute in LDIF.
+ """
assert isinstance(ldif_path, str)
data = open(ldif_path, 'r').read()
@@ -126,7 +139,12 @@ def setup_ldb(ldb, ldif_path, subst_vars):
def setup_file(template, fname, substvars):
- """Setup a file in the private dir."""
+ """Setup a file in the private dir.
+
+ :param template: Path of the template file.
+ :param fname: Path of the file to create.
+ :param substvars: Substitution variables.
+ """
f = fname
if os.path.exists(f):
@@ -179,7 +197,17 @@ def provision_paths_from_lp(lp, dnsdomain):
def setup_name_mappings(ldb, sid, domaindn, root, nobody, nogroup, users,
wheel, backup):
- """setup reasonable name mappings for sam names to unix names."""
+ """setup reasonable name mappings for sam names to unix names.
+
+ :param ldb: SamDB object.
+ :param sid: The domain sid.
+ :param domaindn: The domain DN.
+ :param root: Name of the UNIX root user.
+ :param nobody: Name of the UNIX nobody user.
+ :param nogroup: Name of the unix nobody group.
+ :param users: Name of the unix users group.
+ :param wheel: Name of the wheel group (users that can become root).
+ :param backup: Name of the backup group."""
# add some foreign sids if they are not present already
ldb.add_foreign(domaindn, "S-1-5-7", "Anonymous")
ldb.add_foreign(domaindn, "S-1-1-0", "World")
@@ -591,7 +619,8 @@ def provision(lp, setup_dir, message, blank, paths, session_info,
if nogroup is None:
nogroup = findnss(grp.getgrnam, "nogroup", "nobody")[2]
if users is None:
- users = findnss(grp.getgrnam, "users", "guest", "other", "unknown", "usr")[2]
+ users = findnss(grp.getgrnam, "users", "guest", "other", "unknown",
+ "usr")[2]
if wheel is None:
wheel = findnss(grp.getgrnam, "wheel", "root", "staff", "adm")[2]
if backup is None:
@@ -748,13 +777,32 @@ def provision(lp, setup_dir, message, blank, paths, session_info,
return domaindn
def create_phplpapdadmin_config(path, setup_path, s4_ldapi_path):
+ """Create a PHP LDAP admin configuration file.
+
+ :param path: Path to write the configuration to.
+ :param setup_path: Function to generate setup paths.
+ :param s4_ldapi_path: Path to Samba 4 LDAPI socket.
+ """
setup_file(setup_path("phpldapadmin-config.php"),
path, {"S4_LDAPI_URI": "ldapi://%s" % s4_ldapi_path.replace("/", "%2F")})
def create_zone_file(path, setup_path, samdb, dnsdomain, domaindn,
hostip, hostname, dnspass, realm, domainguid, hostguid):
- """Write out a DNS zone file, from the info in the current database."""
+ """Write out a DNS zone file, from the info in the current database.
+
+ :param path: Path of the new file.
+ :param setup_path": Setup path function.
+ :param samdb: SamDB object
+ :param dnsdomain: DNS Domain name
+ :param domaindn: DN of the Domain
+ :param hostip: Local IP
+ :param hostname: Local hostname
+ :param dnspass: Password for DNS
+ :param realm: Realm name
+ :param domainguid: GUID of the domain.
+ :param hostguid: GUID of the host.
+ """
setup_file(setup_path("provision.zone"), path, {
"DNSPASS_B64": b64encode(dnspass),
@@ -795,7 +843,14 @@ def provision_ldapbase(setup_dir, message, paths):
def load_schema(setup_path, samdb, schemadn, netbiosname, configdn):
- """Load schema."""
+ """Load schema.
+
+ :param samdb: Load a schema into a SamDB.
+ :param setup_path: Setup path function.
+ :param schemadn: DN of the schema
+ :param netbiosname: NetBIOS name of the host.
+ :param configdn: DN of the configuration
+ """
schema_data = open(setup_path("schema.ldif"), 'r').read()
schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
@@ -807,32 +862,3 @@ def load_schema(setup_path, samdb, schemadn, netbiosname, configdn):
"DEFAULTSITE": DEFAULTSITE})
samdb.attach_schema_from_ldif(head_data, schema_data)
-
-def join_domain(domain, netbios_name, join_type, creds):
- ctx = NetContext(creds)
- joindom = object()
- joindom.domain = domain
- joindom.join_type = join_type
- joindom.netbios_name = netbios_name
- if not ctx.JoinDomain(joindom):
- raise Exception("Domain Join failed: " + joindom.error_string)
-
-
-def vampire(domain, session_info, credentials, message):
- """Vampire a remote domain.
-
- Session info and credentials are required for for
- access to our local database (might be remote ldap)
- """
- ctx = NetContext(credentials)
- machine_creds = Credentials()
- machine_creds.set_domain(form.domain)
- if not machine_creds.set_machine_account():
- raise Exception("Failed to access domain join information!")
- vampire_ctx.machine_creds = machine_creds
- vampire_ctx.session_info = session_info
- if not ctx.SamSyncLdb(vampire_ctx):
- raise Exception("Migration of remote domain to Samba failed: %s " % vampire_ctx.error_string)
-
-
-
diff --git a/source4/scripting/python/samba/samba3.py b/source4/scripting/python/samba/samba3.py
index 27656900ad..113348e6f1 100644
--- a/source4/scripting/python/samba/samba3.py
+++ b/source4/scripting/python/samba/samba3.py
@@ -25,14 +25,33 @@ REGISTRY_DB_VERSION = 1
import os
import tdb
-class Registry:
- """Simple read-only support for reading the Samba3 registry."""
+
+class TdbDatabase:
+ """Simple Samba 3 TDB database reader."""
def __init__(self, file):
+ """Open a file.
+
+ :param file: Path of the file to open.
+ """
self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
+ self._check_version()
+
+ def _check_version(self):
+ pass
def close(self):
+ """Close resources associated with this object."""
self.tdb.close()
+
+class Registry(TdbDatabase):
+ """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())
@@ -42,6 +61,11 @@ class Registry:
return [k.rstrip("\x00") for k in self.tdb.keys() 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.tdb.get("%s\x00" % key)
if data is None:
return []
@@ -54,7 +78,11 @@ class Registry:
return keys
def values(self, key):
- """Return a dictionary with the values set for a specific 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.tdb.get("%s/%s\x00" % (REGISTRY_VALUE_PREFIX, key))
if data is None:
return {}
@@ -77,9 +105,14 @@ class Registry:
return ret
-class PolicyDatabase:
+class PolicyDatabase(TdbDatabase):
+ """Samba 3 Account Policy database reader."""
def __init__(self, file):
- self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
+ """Open a policy database
+
+ :param file: Path to the file to open.
+ """
+ super(PolicyDatabase, self).__init__(file)
self.min_password_length = self.tdb.fetch_uint32("min password length\x00")
self.password_history = self.tdb.fetch_uint32("password history\x00")
self.user_must_logon_to_change_password = self.tdb.fetch_uint32("user must logon to change pasword\x00")
@@ -93,9 +126,6 @@ class PolicyDatabase:
# FIXME: Read privileges as well
- def close(self):
- self.tdb.close()
-
GROUPDB_DATABASE_VERSION_V1 = 1 # native byte format.
GROUPDB_DATABASE_VERSION_V2 = 2 # le format.
@@ -108,17 +138,27 @@ GROUP_PREFIX = "UNIXGROUP/"
# hanging of the member as key.
MEMBEROF_PREFIX = "MEMBEROF/"
-class GroupMappingDatabase:
- def __init__(self, file):
- self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
+class GroupMappingDatabase(TdbDatabase):
+ """Samba 3 group mapping database reader."""
+ def _check_version(self):
assert self.tdb.fetch_int32("INFO/version\x00") in (GROUPDB_DATABASE_VERSION_V1, GROUPDB_DATABASE_VERSION_V2)
def groupsids(self):
+ """Retrieve the SIDs for the groups in this database.
+
+ :return: List with sids as strings.
+ """
for k in self.tdb.keys():
if k.startswith(GROUP_PREFIX):
yield k[len(GROUP_PREFIX):].rstrip("\0")
def get_group(self, sid):
+ """Retrieve the group mapping information for a particular group.
+
+ :param sid: SID of the group
+ :return: None if the group can not be found, otherwise
+ a tuple with gid, sid_name_use, the NT name and comment.
+ """
data = self.tdb.get("%s%s\0" % (GROUP_PREFIX, sid))
if data is None:
return data
@@ -128,13 +168,11 @@ class GroupMappingDatabase:
return (gid, sid_name_use, nt_name, comment)
def aliases(self):
+ """Retrieve the aliases in this database."""
for k in self.tdb.keys():
if k.startswith(MEMBEROF_PREFIX):
yield k[len(MEMBEROF_PREFIX):].rstrip("\0")
- def close(self):
- self.tdb.close()
-
# High water mark keys
IDMAP_HWM_GROUP = "GROUP HWM\0"
@@ -146,22 +184,29 @@ IDMAP_USER_PREFIX = "UID "
# idmap version determines auto-conversion
IDMAP_VERSION_V2 = 2
-class IdmapDatabase:
- def __init__(self, file):
- self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
+class IdmapDatabase(TdbDatabase):
+ """Samba 3 ID map database reader."""
+ def _check_version(self):
assert self.tdb.fetch_int32("IDMAP_VERSION\0") == IDMAP_VERSION_V2
def uids(self):
+ """Retrieve a list of all uids in this database."""
for k in self.tdb.keys():
if k.startswith(IDMAP_USER_PREFIX):
yield int(k[len(IDMAP_USER_PREFIX):].rstrip("\0"))
def gids(self):
+ """Retrieve a list of all gids in this database."""
for k in self.tdb.keys():
if k.startswith(IDMAP_GROUP_PREFIX):
yield int(k[len(IDMAP_GROUP_PREFIX):].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.tdb.get("%s%d\0" % (IDMAP_USER_PREFIX, uid))
if data is None:
return data
@@ -174,19 +219,15 @@ class IdmapDatabase:
return data.rstrip("\0")
def get_user_hwm(self):
+ """Obtain the user high-water mark."""
return self.tdb.fetch_uint32(IDMAP_HWM_USER)
def get_group_hwm(self):
+ """Obtain the group high-water mark."""
return self.tdb.fetch_uint32(IDMAP_HWM_GROUP)
- def close(self):
- self.tdb.close()
-
-
-class SecretsDatabase:
- def __init__(self, file):
- self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
+class SecretsDatabase(TdbDatabase):
def get_auth_password(self):
return self.tdb.get("SECRETS/AUTH_PASSWORD")
@@ -241,16 +282,12 @@ class SecretsDatabase:
def get_sid(self, host):
return self.tdb.get("SECRETS/SID/%s" % host.upper())
- def close(self):
- self.tdb.close()
-
SHARE_DATABASE_VERSION_V1 = 1
SHARE_DATABASE_VERSION_V2 = 2
-class ShareInfoDatabase:
- def __init__(self, file):
- self.tdb = tdb.Tdb(file, flags=os.O_RDONLY)
+class ShareInfoDatabase(TdbDatabase):
+ def _check_version(self):
assert self.tdb.fetch_int32("INFO/version\0") in (SHARE_DATABASE_VERSION_V1, SHARE_DATABASE_VERSION_V2)
def get_secdesc(self, name):
@@ -258,9 +295,6 @@ class ShareInfoDatabase:
# FIXME: Run ndr_pull_security_descriptor
return secdesc
- def close(self):
- self.tdb.close()
-
class Shares:
def __init__(self, lp, shareinfo):
diff --git a/source4/scripting/python/samba/samdb.py b/source4/scripting/python/samba/samdb.py
index 46707f060f..2af56d8d8e 100644
--- a/source4/scripting/python/samba/samdb.py
+++ b/source4/scripting/python/samba/samdb.py
@@ -20,13 +20,20 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
+"""Convenience functions for using the SAM."""
+
import samba
import misc
import ldb
class SamDB(samba.Ldb):
+ """The SAM database."""
def __init__(self, url=None, session_info=None, credentials=None,
modules_dir=None, lp=None):
+ """Open the Sam Database.
+
+ :param url: URL of the database.
+ """
super(SamDB, self).__init__(session_info=session_info, credentials=credentials,
modules_dir=modules_dir, lp=lp)
assert misc.dsdb_set_global_schema(self) == 0
@@ -47,7 +54,12 @@ description: %s
self.add(msg[1])
def setup_name_mapping(self, domaindn, sid, unixname):
- """Setup a mapping between a sam name and a unix name."""
+ """Setup a mapping between a sam name and a unix name.
+
+ :param domaindn: DN of the domain.
+ :param sid: SID of the NT-side of the mapping.
+ :param unixname: Unix name to map to.
+ """
res = self.search(ldb.Dn(self, domaindn), ldb.SCOPE_SUBTREE,
"objectSid=%s" % sid, ["dn"])
assert len(res) == 1, "Failed to find record for objectSid %s" % sid
@@ -61,7 +73,7 @@ unixName: %s
self.modify(self.parse_ldif(mod).next()[1])
def enable_account(self, user_dn):
- """enable the account.
+ """Enable an account.
:param user_dn: Dn of the account to enable.
"""
@@ -75,10 +87,15 @@ changetype: modify
replace: userAccountControl
userAccountControl: %u
""" % (user_dn, userAccountControl)
- self.modify(mod)
+ self.modify_ldif(mod)
- def newuser(self, username, unixname, password, message):
- """add a new user record"""
+ def newuser(self, username, unixname, password):
+ """add a new user record.
+
+ :param username: Name of the new user.
+ :param unixname: Name of the unix user to map to.
+ :param password: Password for the new user
+ """
# connect to the sam
self.transaction_start()
@@ -97,13 +114,13 @@ userAccountControl: %u
# the new user record. note the reliance on the samdb module to fill
# in a sid, guid etc
#
- ldif = """
-dn: %s
-sAMAccountName: %s
-unixName: %s
-sambaPassword: %s
-objectClass: user
- """ % (user_dn, username, unixname, password)
+ # now the real work
+ self.add({"dn": user_dn,
+ "sAMAccountName": username,
+ "unixName": unixname,
+ "sambaPassword": password,
+ "objectClass": "user"})
+
# add the user to the users group as well
modgroup = """
dn: %s
@@ -113,11 +130,6 @@ member: %s
""" % (dom_users, user_dn)
- # now the real work
- message("Adding user %s" % user_dn)
- self.add(ldif)
-
- message("Modifying group %s" % dom_users)
self.modify(modgroup)
# modify the userAccountControl to remove the disabled bit
@@ -125,6 +137,10 @@ member: %s
self.transaction_commit()
def set_domain_sid(self, sid):
+ """Change the domain SID used by this SamDB.
+
+ :param sid: The new domain sid to use.
+ """
misc.samdb_set_domain_sid(self, sid)
def attach_schema_from_ldif(self, pf, df):
diff --git a/source4/scripting/python/samba/tests/__init__.py b/source4/scripting/python/samba/tests/__init__.py
index 5e1ff87c2b..ad8a2524b5 100644
--- a/source4/scripting/python/samba/tests/__init__.py
+++ b/source4/scripting/python/samba/tests/__init__.py
@@ -17,6 +17,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
+"""Samba Python tests."""
+
import os
import ldb
import samba
@@ -24,11 +26,13 @@ import tempfile
import unittest
class LdbTestCase(unittest.TestCase):
+ """Trivial test case for running tests against a LDB."""
def setUp(self):
self.filename = os.tempnam()
self.ldb = samba.Ldb(self.filename)
def set_modules(self, modules=[]):
+ """Change the modules for this Ldb."""
m = ldb.Message()
m.dn = ldb.Dn(self.ldb, "@MODULES")
m["@LIST"] = ",".join(modules)