r26628: python: Add more documentation, simplify code in Samba3 module.
authorJelmer Vernooij <jelmer@samba.org>
Sun, 30 Dec 2007 00:14:15 +0000 (18:14 -0600)
committerStefan Metzmacher <metze@samba.org>
Sat, 29 Dec 2007 18:21:04 +0000 (12:21 -0600)
source/scripting/python/config.mk
source/scripting/python/samba/__init__.py
source/scripting/python/samba/getopt.py
source/scripting/python/samba/provision.py
source/scripting/python/samba/samba3.py
source/scripting/python/samba/samdb.py
source/scripting/python/samba/tests/__init__.py

index 4a531f5062d9e4f18ab23e1666f7c3899304584c..cfd179aff5c957df544e6634a29452f2b6bb54c3 100644 (file)
@@ -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))
index 359457d81500e67d3122df1913ed57dac76daf04..01fdea666539661ec5e1e15aa216da49a9e5ded2 100644 (file)
@@ -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)
index 87cf171ca2de69f24b81da2afed01df8cfa514ab..c0e70530626eec5e1003dc82503ff613fe160eed 100644 (file)
@@ -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
index fa45f7a79d2a00c4eb21f97fdb07478bf5a993bd..bdfe035c41aa430c73e6ff7810368039f2a0dd69 100644 (file)
@@ -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)
-
-
-
index 27656900adc41972f70eebb3c1c5052647a9ea1f..113348e6f13ee1f4008d78c4aa25e85c7cb08df4 100644 (file)
@@ -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):
index 46707f060f5090b561661935f2e44dcb847407a0..2af56d8d8eb0300cc4cc5ce07a5b0110c3c31041 100644 (file)
 # 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):
index 5e1ff87c2b51d94c1dc37987705b96feab5cbfda..ad8a2524b53a87d2d49d74087f30a672b9a13474 100644 (file)
@@ -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)