s4-net: allow a username to be displayed in setpassword errors
[samba.git] / source4 / scripting / python / samba / samdb.py
index a58d6c5b12473ba32126a38fafda460d1ac22110..d41b3ec637860eef52f93b6e33d44f084fa87444 100644 (file)
@@ -2,6 +2,7 @@
 
 # Unix SMB/CIFS implementation.
 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
+# Copyright (C) Matthias Dieter Wallnoefer 2009
 #
 # Based on the original in EJS:
 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
@@ -22,8 +23,8 @@
 
 """Convenience functions for using the SAM."""
 
+import dsdb
 import samba
-import glue
 import ldb
 from samba.idmap import IDmapDB
 import pwd
@@ -36,32 +37,41 @@ class SamDB(samba.Ldb):
     """The SAM database."""
 
     def __init__(self, url=None, lp=None, modules_dir=None, session_info=None,
-                 credentials=None, flags=0, options=None):
-        """Opens the Sam Database.
-        For parameter meanings see the super class (samba.Ldb)
-        """
-
+                 credentials=None, flags=0, options=None, global_schema=True):
         self.lp = lp
         if url is None:
-                url = lp.get("sam database")
+            url = lp.get("sam database")
 
         super(SamDB, self).__init__(url=url, lp=lp, modules_dir=modules_dir,
                 session_info=session_info, credentials=credentials, flags=flags,
                 options=options)
 
-        glue.dsdb_set_global_schema(self)
+        if global_schema:
+            dsdb.dsdb_set_global_schema(self)
 
     def connect(self, url=None, flags=0, options=None):
         super(SamDB, self).connect(url=self.lp.private_path(url), flags=flags,
                 options=options)
 
-    def enable_account(self, user_dn):
-        """Enable an account.
+    def domain_dn(self):
+        # find the DNs for the domain
+        res = self.search(base="",
+                          scope=ldb.SCOPE_BASE,
+                          expression="(defaultNamingContext=*)",
+                          attrs=["defaultNamingContext"])
+        assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None)
+        return res[0]["defaultNamingContext"][0]
+
+    def enable_account(self, filter):
+        """Enables an account
         
-        :param user_dn: Dn of the account to enable.
+        :param filter: LDAP filter to find the user (eg samccountname=name)
         """
-        res = self.search(user_dn, ldb.SCOPE_BASE, None, ["userAccountControl"])
-        assert len(res) == 1
+        res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
+                          expression=filter, attrs=["userAccountControl"])
+        assert(len(res) == 1)
+        user_dn = res[0].dn
+
         userAccountControl = int(res[0]["userAccountControl"][0])
         if (userAccountControl & 0x2):
             userAccountControl = userAccountControl & ~0x2 # remove disabled bit
@@ -76,11 +86,16 @@ userAccountControl: %u
 """ % (user_dn, userAccountControl)
         self.modify_ldif(mod)
         
-    def force_password_change_at_next_login(self, user_dn):
-        """Force a password change at next login
+    def force_password_change_at_next_login(self, filter):
+        """Forces a password change at next login
         
-        :param user_dn: Dn of the account to force password change on
+        :param filter: LDAP filter to find the user (eg samccountname=name)
         """
+        res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
+                          expression=filter, attrs=[])
+        assert(len(res) == 1)
+        user_dn = res[0].dn
+
         mod = """
 dn: %s
 changetype: modify
@@ -89,37 +104,33 @@ pwdLastSet: 0
 """ % (user_dn)
         self.modify_ldif(mod)
 
-    def domain_dn(self):
-        # find the DNs for the domain
-        res = self.search(base="",
-                          scope=ldb.SCOPE_BASE,
-                          expression="(defaultNamingContext=*)", 
-                          attrs=["defaultNamingContext"])
-        assert(len(res) == 1 and res[0]["defaultNamingContext"] is not None)
-        return res[0]["defaultNamingContext"][0]
+    def newuser(self, username, unixname, password,
+                force_password_change_at_next_login_req=False):
+        """Adds a new user
 
-    def newuser(self, username, unixname, password, force_password_change_at_next_login=False):
-        """add a new user record.
+        Note: This call adds also the ID mapping for winbind; therefore it works
+        *only* on SAMBA 4.
         
-        :param username: Name of the new user.
-        :param unixname: Name of the unix user to map to.
+        :param username: Name of the new user
+        :param unixname: Name of the unix user to map to
         :param password: Password for the new user
+        :param force_password_change_at_next_login_req: Force password change
         """
-        # connect to the sam 
         self.transaction_start()
         try:
             user_dn = "CN=%s,CN=Users,%s" % (username, self.domain_dn())
 
-            #
-            #  the new user record. note the reliance on the samdb module to 
-            #  fill in a sid, guid etc
-            #
-            #  now the real work
+            # The new user record. Note the reliance on the SAMLDB module which
+            # fills in the default informations
             self.add({"dn": user_dn, 
                 "sAMAccountName": username,
-                "userPassword": password,
                 "objectClass": "user"})
 
+            # Sets the password for it
+            self.setpassword("(dn=" + user_dn + ")", password,
+              force_password_change_at_next_login_req)
+
+            # Gets the user SID (for the account mapping setup)
             res = self.search(user_dn, scope=ldb.SCOPE_BASE,
                               expression="objectclass=*",
                               attrs=["objectSid"])
@@ -130,34 +141,38 @@ pwdLastSet: 0
                 idmap = IDmapDB(lp=self.lp)
 
                 user = pwd.getpwnam(unixname)
+
                 # setup ID mapping for this UID
-                
                 idmap.setup_name_mapping(user_sid, idmap.TYPE_UID, user[2])
 
             except KeyError:
                 pass
-
-            if force_password_change_at_next_login:
-                self.force_password_change_at_next_login(user_dn)
-
-            #  modify the userAccountControl to remove the disabled bit
-            self.enable_account(user_dn)
         except:
             self.transaction_cancel()
             raise
-        self.transaction_commit()
+        else:
+            self.transaction_commit()
 
-    def setpassword(self, filter, password, force_password_change_at_next_login=False):
-        """Set a password on a user record
+    def setpassword(self, filter, password,
+                    force_change_at_next_login=False,
+                    username=None):
+        """Sets the password for a user
         
+        Note: This call uses the "userPassword" attribute to set the password.
+        This works correctly on SAMBA 4 and on Windows DCs with
+        "2003 Native" or higer domain function level.
+
         :param filter: LDAP filter to find the user (eg samccountname=name)
         :param password: Password for the user
+        :param force_change_at_next_login: Force password change
         """
-        # connect to the sam 
         self.transaction_start()
         try:
             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
                               expression=filter, attrs=[])
+            if len(res) == 0:
+                print('Unable to find user "%s"' % (username or filter))
+                raise
             assert(len(res) == 1)
             user_dn = res[0].dn
 
@@ -170,49 +185,93 @@ userPassword:: %s
 
             self.modify_ldif(setpw)
 
-            if force_password_change_at_next_login:
-                self.force_password_change_at_next_login(user_dn)
+            if force_change_at_next_login:
+                self.force_password_change_at_next_login(
+                  "(dn=" + str(user_dn) + ")")
 
             #  modify the userAccountControl to remove the disabled bit
-            self.enable_account(user_dn)
+            self.enable_account(filter)
         except:
             self.transaction_cancel()
             raise
-        self.transaction_commit()
+        else:
+            self.transaction_commit()
 
-    def setexpiry(self, user, expiry_seconds, noexpiry):
-        """Set the account expiry for a user
+    def setexpiry(self, filter, expiry_seconds, no_expiry_req=False):
+        """Sets the account expiry for a user
         
+        :param filter: LDAP filter to find the user (eg samccountname=name)
         :param expiry_seconds: expiry time from now in seconds
-        :param noexpiry: if set, then don't expire password
+        :param no_expiry_req: if set, then don't expire password
         """
         self.transaction_start()
         try:
             res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE,
-                              expression=("(samAccountName=%s)" % user),
-                              attrs=["userAccountControl", "accountExpires"])
-            assert len(res) == 1
+                          expression=filter,
+                          attrs=["userAccountControl", "accountExpires"])
+            assert(len(res) == 1)
+            user_dn = res[0].dn
+
             userAccountControl = int(res[0]["userAccountControl"][0])
             accountExpires     = int(res[0]["accountExpires"][0])
-            if noexpiry:
+            if no_expiry_req:
                 userAccountControl = userAccountControl | 0x10000
                 accountExpires = 0
             else:
                 userAccountControl = userAccountControl & ~0x10000
-                accountExpires = glue.unix2nttime(expiry_seconds + int(time.time()))
+                accountExpires = samba.unix2nttime(expiry_seconds + int(time.time()))
 
-            mod = """
+            setexp = """
 dn: %s
 changetype: modify
 replace: userAccountControl
 userAccountControl: %u
 replace: accountExpires
 accountExpires: %u
-""" % (res[0].dn, userAccountControl, accountExpires)
-            # now change the database
-            self.modify_ldif(mod)
+""" % (user_dn, userAccountControl, accountExpires)
+
+            self.modify_ldif(setexp)
         except:
             self.transaction_cancel()
             raise
-        self.transaction_commit();
+        else:
+            self.transaction_commit()
+
+    def set_domain_sid(self, sid):
+        """Change the domain SID used by this LDB.
+
+        :param sid: The new domain sid to use.
+        """
+        dsdb.samdb_set_domain_sid(self, sid)
+
+    def get_domain_sid(self):
+        """Read the domain SID used by this LDB.
+
+        """
+        dsdb.samdb_get_domain_sid(self)
+
+    def set_invocation_id(self, invocation_id):
+        """Set the invocation id for this SamDB handle.
+
+        :param invocation_id: GUID of the invocation id.
+        """
+        dsdb.dsdb_set_ntds_invocation_id(self, invocation_id)
+
+    def get_invocation_id(self):
+        "Get the invocation_id id"
+        return dsdb.samdb_ntds_invocation_id(self)
+
+    invocation_id = property(get_invocation_id, set_invocation_id)
+
+    domain_sid = property(get_domain_sid, set_domain_sid)
+
+    def get_ntds_GUID(self):
+        "Get the NTDS objectGUID"
+        return dsdb.samdb_ntds_objectGUID(self)
+
+    def server_site_name(self):
+        "Get the server site name"
+        return dsdb.samdb_server_site_name(self)
 
+    def load_partition_usn(self, base_dn):
+        return dsdb.dsdb_load_partition_usn(self, base_dn)