class BaseDeleteTests(samba.tests.TestCase): def GUID_string(self, guid): return self.ldb.schema_format_value("objectGUID", guid) def setUp(self): super(BaseDeleteTests, self).setUp() self.ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp) self.base_dn = self.ldb.domain_dn() self.configuration_dn = self.ldb.get_config_basedn().get_linearized() def search_guid(self, guid): print("SEARCH by GUID %s" % self.GUID_string(guid)) res = self.ldb.search(base="<GUID=%s>" % self.GUID_string(guid), scope=SCOPE_BASE, controls=["show_deleted:1"], attrs=["*", "parentGUID"]) self.assertEquals(len(res), 1) return res[0] def search_dn(self, dn): print("SEARCH by DN %s" % dn) res = self.ldb.search(expression="(objectClass=*)", base=dn, scope=SCOPE_BASE, controls=["show_deleted:1"], attrs=["*", "parentGUID"]) self.assertEquals(len(res), 1) return res[0]
class DirsyncBaseTests(samba.tests.TestCase): def setUp(self): super(DirsyncBaseTests, self).setUp() self.ldb_admin = SamDB(ldapshost, credentials=creds, session_info=system_session(lp), lp=lp) self.base_dn = self.ldb_admin.domain_dn() self.domain_sid = security.dom_sid(self.ldb_admin.get_domain_sid()) self.user_pass = samba.generate_random_password(12, 16) self.configuration_dn = self.ldb_admin.get_config_basedn( ).get_linearized() self.sd_utils = sd_utils.SDUtils(self.ldb_admin) #used for anonymous login print("baseDN: %s" % self.base_dn) def get_user_dn(self, name): return "CN=%s,CN=Users,%s" % (name, self.base_dn) def get_ldb_connection(self, target_username, target_password): creds_tmp = Credentials() creds_tmp.set_username(target_username) creds_tmp.set_password(target_password) creds_tmp.set_domain(creds.get_domain()) creds_tmp.set_realm(creds.get_realm()) creds_tmp.set_workstation(creds.get_workstation()) creds_tmp.set_gensec_features(creds_tmp.get_gensec_features() | gensec.FEATURE_SEAL) creds_tmp.set_kerberos_state( DONT_USE_KERBEROS) # kinit is too expensive to use in a tight loop ldb_target = SamDB(url=ldaphost, credentials=creds_tmp, lp=lp) return ldb_target
class DirsyncBaseTests(samba.tests.TestCase): def setUp(self): super(DirsyncBaseTests, self).setUp() self.ldb_admin = SamDB(ldapshost, credentials=creds, session_info=system_session(lp), lp=lp) self.base_dn = self.ldb_admin.domain_dn() self.domain_sid = security.dom_sid(self.ldb_admin.get_domain_sid()) self.user_pass = samba.generate_random_password(12, 16) self.configuration_dn = self.ldb_admin.get_config_basedn().get_linearized() self.sd_utils = sd_utils.SDUtils(self.ldb_admin) #used for anonymous login print("baseDN: %s" % self.base_dn) def get_user_dn(self, name): return "CN=%s,CN=Users,%s" % (name, self.base_dn) def get_ldb_connection(self, target_username, target_password): creds_tmp = Credentials() creds_tmp.set_username(target_username) creds_tmp.set_password(target_password) creds_tmp.set_domain(creds.get_domain()) creds_tmp.set_realm(creds.get_realm()) creds_tmp.set_workstation(creds.get_workstation()) creds_tmp.set_gensec_features(creds_tmp.get_gensec_features() | gensec.FEATURE_SEAL) creds_tmp.set_kerberos_state(DONT_USE_KERBEROS) # kinit is too expensive to use in a tight loop ldb_target = SamDB(url=ldaphost, credentials=creds_tmp, lp=lp) return ldb_target
def run(self, H=None, credopts=None, sambaopts=None, versionopts=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp, fallback_machine=True) samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp) domain_dn = samdb.domain_dn() forest_dn = samba.dn_from_dns_name(samdb.forest_dns_name()) infrastructure_dn = "CN=Infrastructure," + domain_dn naming_dn = "CN=Partitions,%s" % samdb.get_config_basedn() schema_dn = samdb.get_schema_basedn() rid_dn = "CN=RID Manager$,CN=System," + domain_dn domaindns_dn = "CN=Infrastructure,DC=DomainDnsZones," + domain_dn forestdns_dn = "CN=Infrastructure,DC=ForestDnsZones," + forest_dn masters = [(schema_dn, "schema", "SchemaMasterRole"), (infrastructure_dn, "infrastructure", "InfrastructureMasterRole"), (rid_dn, "rid", "RidAllocationMasterRole"), (domain_dn, "pdc", "PdcEmulationMasterRole"), (naming_dn, "naming", "DomainNamingMasterRole"), (domaindns_dn, "domaindns", "DomainDnsZonesMasterRole"), (forestdns_dn, "forestdns", "ForestDnsZonesMasterRole"), ] for master in masters: (dn, short_name, long_name) = master try: master = get_fsmo_roleowner(samdb, dn, short_name) if master is not None: self.message("%s owner: %s" % (long_name, str(master))) else: self.message("%s has no current owner" % (long_name)) except CommandError, e: self.message("%s: * %s" % (long_name, e.message))
def run(self, subnetname, site_of_subnet, H=None, sambaopts=None, credopts=None, versionopts=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp) samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp) samdb.transaction_start() try: subnets.create_subnet(samdb, samdb.get_config_basedn(), subnetname, site_of_subnet) samdb.transaction_commit() except subnets.SubnetException as e: samdb.transaction_cancel() raise CommandError("Error while creating subnet %s: %s" % (subnetname, e)) self.outf.write("Subnet %s created !\n" % subnetname)
def run(self, subnetname, site_of_subnet, H=None, sambaopts=None, credopts=None, versionopts=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp) samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp) samdb.transaction_start() try: subnets.set_subnet_site(samdb, samdb.get_config_basedn(), subnetname, site_of_subnet) samdb.transaction_commit() except subnets.SubnetException as e: samdb.transaction_cancel() raise CommandError("Error assigning subnet %s to site %s: %s" % (subnetname, site_of_subnet, e)) print(("Subnet %s shifted to site %s" % (subnetname, site_of_subnet)), file=self.outf)
class BaseDeleteTests(samba.tests.TestCase): def GUID_string(self, guid): return self.ldb.schema_format_value("objectGUID", guid) def setUp(self): super(BaseDeleteTests, self).setUp() self.ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp) self.base_dn = self.ldb.domain_dn() self.configuration_dn = self.ldb.get_config_basedn().get_linearized() def search_guid(self, guid): print "SEARCH by GUID {0!s}".format(self.GUID_string(guid)) res = self.ldb.search(base="<GUID={0!s}>".format(self.GUID_string(guid)), scope=SCOPE_BASE, controls=["show_deleted:1"]) self.assertEquals(len(res), 1) return res[0] def search_dn(self,dn): print "SEARCH by DN {0!s}".format(dn) res = self.ldb.search(expression="(objectClass=*)", base=dn, scope=SCOPE_BASE, controls=["show_deleted:1"]) self.assertEquals(len(res), 1) return res[0]
def run(self, H=None, credopts=None, sambaopts=None, versionopts=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp, fallback_machine=True) samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp) domain_dn = samdb.domain_dn() forest_dn = samba.dn_from_dns_name(samdb.forest_dns_name()) infrastructure_dn = "CN=Infrastructure," + domain_dn naming_dn = "CN=Partitions,%s" % samdb.get_config_basedn() schema_dn = samdb.get_schema_basedn() rid_dn = "CN=RID Manager$,CN=System," + domain_dn domaindns_dn = "CN=Infrastructure,DC=DomainDnsZones," + domain_dn forestdns_dn = "CN=Infrastructure,DC=ForestDnsZones," + forest_dn infrastructureMaster = get_fsmo_roleowner(samdb, infrastructure_dn) pdcEmulator = get_fsmo_roleowner(samdb, domain_dn) namingMaster = get_fsmo_roleowner(samdb, naming_dn) schemaMaster = get_fsmo_roleowner(samdb, schema_dn) ridMaster = get_fsmo_roleowner(samdb, rid_dn) domaindnszonesMaster = get_fsmo_roleowner(samdb, domaindns_dn) forestdnszonesMaster = get_fsmo_roleowner(samdb, forestdns_dn) self.message("SchemaMasterRole owner: " + schemaMaster) self.message("InfrastructureMasterRole owner: " + infrastructureMaster) self.message("RidAllocationMasterRole owner: " + ridMaster) self.message("PdcEmulationMasterRole owner: " + pdcEmulator) self.message("DomainNamingMasterRole owner: " + namingMaster) self.message("DomainDnsZonesMasterRole owner: " + domaindnszonesMaster) self.message("ForestDnsZonesMasterRole owner: " + forestdnszonesMaster)
def check_restored_database(self, bkp_lp, expect_secrets=True): paths = provision.provision_paths_from_lp(bkp_lp, bkp_lp.get("realm")) bkp_pd = get_prim_dom(paths.secrets, bkp_lp) self.assertEqual(len(bkp_pd), 1) acn = bkp_pd[0].get('samAccountName') self.assertIsNotNone(acn) self.assertEqual(str(acn[0]), self.new_server + '$') self.assertIsNotNone(bkp_pd[0].get('secret')) samdb = SamDB(url=paths.samdb, session_info=system_session(), lp=bkp_lp, credentials=self.get_credentials()) # check that the backup markers have been removed from the restored DB res = samdb.search(base=ldb.Dn(samdb, "@SAMBA_DSDB"), scope=ldb.SCOPE_BASE, attrs=self.backup_markers) self.assertEqual(len(res), 1) for marker in self.backup_markers: self.assertIsNone(res[0].get(marker), "%s backup-marker left behind" % marker) # check that the repsFrom and repsTo values have been removed # from the restored DB res = samdb.search(base=samdb.get_default_basedn(), scope=ldb.SCOPE_BASE, attrs=['repsFrom', 'repsTo']) self.assertEqual(len(res), 1) self.assertIsNone(res[0].get('repsFrom')) self.assertIsNone(res[0].get('repsTo')) res = samdb.search(base=samdb.get_config_basedn(), scope=ldb.SCOPE_BASE, attrs=['repsFrom', 'repsTo']) self.assertEqual(len(res), 1) self.assertIsNone(res[0].get('repsFrom')) self.assertIsNone(res[0].get('repsTo')) # check the DB is using the backend we supplied if self.backend: res = samdb.search(base="@PARTITION", scope=ldb.SCOPE_BASE, attrs=["backendStore"]) backend = str(res[0].get("backendStore")) self.assertEqual(backend, self.backend) # check the restored DB has the expected partitions/DC/FSMO roles self.assert_partitions_present(samdb) self.assert_dcs_present(samdb, self.new_server, expected_count=1) self.assert_fsmo_roles(samdb, self.new_server, self.server) self.assert_secrets(samdb, expect_secrets=expect_secrets) # check we still have an uptodateness vector for the original DC self.assert_repl_uptodate_vector(samdb) return samdb
def deprovision(setup_path, lp, creds, firstorg=None, firstou=None, reporter=None): """Remove all configuration entries added by the OpenChange installation. :param setup_path: Path to the setup directory. :param names: provision names object. :param lp: Loadparm context :param creds: Credentials Context :param reporter: A progress reporter instance (subclass of AbstractProgressReporter) """ if reporter is None: reporter = TextProgressReporter() session_info = system_session() lp.set("dsdb:schema update allowed", "yes") names = guess_names_from_smbconf(lp, None, None) samdb = SamDB(url=get_ldb_url(lp, creds, names), session_info=session_info, credentials=creds, lp=lp) try: config_dn = samdb.get_config_basedn() ret = samdb.search(base=config_dn, scope=ldb.SCOPE_SUBTREE, expression="(objectClass=msExchExchangeServer)") if len(ret) > 1: # If we are the primary folder store server, raise exception # The user has to set another server as primary before unregister # this one our_siteFolderName = "CN=Public Folder Store (%s),CN=First Storage Group,CN=InformationStore,CN=%s,CN=Servers,CN=First Administrative Group,CN=Administrative Groups,%s" % (names.netbiosname, names.netbiosname, names.firstorgdn) dn = "CN=First Administrative Group,CN=Administrative Groups,%s" % names.firstorgdn ret = samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=['siteFolderServer']) assert len(ret) == 1 siteFolderName = ret[0]["siteFolderServer"][0] if our_siteFolderName.lower() == siteFolderName.lower(): raise Exception("This server is the primary folder store server") # If we are the primary receipt update service, raise exception our_addressListServiceLink = "CN=%s,CN=Servers,CN=First Administrative Group,CN=Administrative Groups,%s" % (names.netbiosname, names.firstorgdn) dn = "CN=Recipient Update Service (%s),CN=Recipient Update Services,CN=Address Lists Container,%s" % (names.domain, names.firstorgdn) ret = samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=['msExchAddressListServiceLink']) assert len(ret) == 1 addressListServiceLink = ret[0]['msExchAddressListServiceLink'][0] if our_addressListServiceLink.lower() == addressListServiceLink.lower(): raise Exception("This server is the primary receipt update service server") # Unregister the server deprovision_schema(setup_path, names, lp, creds, reporter, "AD/oc_provision_configuration_new_server.ldif", "Remove Exchange samba registration") else: # This is the unique server, remove full schema deprovision_schema(setup_path, names, lp, creds, reporter, "AD/oc_provision_configuration.ldif", "Remove Exchange configuration objects") except LdbError, ldb_error: print ("[!] error while deprovisioning the Exchange configuration" " objects (%d): %s" % ldb_error.args)
def run(self, subnetname, H=None, sambaopts=None, credopts=None, versionopts=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp) samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp) samdb.transaction_start() try: subnets.delete_subnet(samdb, samdb.get_config_basedn(), subnetname) samdb.transaction_commit() except subnets.SubnetException, e: samdb.transaction_cancel() raise CommandError("Error while removing subnet %s, error: %s" % (subnetname, e))
def run(self, subnetname, site_of_subnet, H=None, sambaopts=None, credopts=None, versionopts=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp) samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp) samdb.transaction_start() try: subnets.set_subnet_site(samdb, samdb.get_config_basedn(), subnetname, site_of_subnet) samdb.transaction_commit() except subnets.SubnetException, e: samdb.transaction_cancel() raise CommandError("Error assigning subnet %s to site %s: %s" % (subnetname, site_of_subnet, e))
def run(self, sitename, H=None, sambaopts=None, credopts=None, versionopts=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp, fallback_machine=True) samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp) samdb.transaction_start() try: sites.delete_site(samdb, samdb.get_config_basedn(), sitename) samdb.transaction_commit() except sites.SiteException, e: samdb.transaction_cancel() raise CommandError("Error while removing site %s, error: %s" % (sitename, str(e)))
class SitesBaseTests(samba.tests.TestCase): def setUp(self): super(SitesBaseTests, self).setUp() self.ldb = SamDB(ldaphost, credentials=creds, session_info=system_session(lp), lp=lp) self.base_dn = self.ldb.domain_dn() self.domain_sid = security.dom_sid(self.ldb.get_domain_sid()) self.configuration_dn = self.ldb.get_config_basedn().get_linearized() def get_user_dn(self, name): return "CN=%s,CN=Users,%s" % (name, self.base_dn)
class SitesBaseTests(samba.tests.TestCase): def setUp(self): super(SitesBaseTests, self).setUp() self.ldb = SamDB(ldaphost, credentials=creds, session_info=system_session(lp), lp=lp) self.base_dn = self.ldb.domain_dn() self.domain_sid = security.dom_sid(self.ldb.get_domain_sid()) self.configuration_dn = self.ldb.get_config_basedn().get_linearized() def get_user_dn(self, name): return "CN={0!s},CN=Users,{1!s}".format(name, self.base_dn)
def run(self, H=None, credopts=None, sambaopts=None, versionopts=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp, fallback_machine=True) samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp) domain_dn = samdb.domain_dn() self.infrastructure_dn = "CN=Infrastructure," + domain_dn self.naming_dn = "CN=Partitions,%s" % samdb.get_config_basedn() self.schema_dn = samdb.get_schema_basedn() self.rid_dn = "CN=RID Manager$,CN=System," + domain_dn res = samdb.search(self.infrastructure_dn, scope=ldb.SCOPE_BASE, attrs=["fSMORoleOwner"]) assert len(res) == 1 self.infrastructureMaster = res[0]["fSMORoleOwner"][0] res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE, attrs=["fSMORoleOwner"]) assert len(res) == 1 self.pdcEmulator = res[0]["fSMORoleOwner"][0] res = samdb.search(self.naming_dn, scope=ldb.SCOPE_BASE, attrs=["fSMORoleOwner"]) assert len(res) == 1 self.namingMaster = res[0]["fSMORoleOwner"][0] res = samdb.search(self.schema_dn, scope=ldb.SCOPE_BASE, attrs=["fSMORoleOwner"]) assert len(res) == 1 self.schemaMaster = res[0]["fSMORoleOwner"][0] res = samdb.search(self.rid_dn, scope=ldb.SCOPE_BASE, attrs=["fSMORoleOwner"]) assert len(res) == 1 self.ridMaster = res[0]["fSMORoleOwner"][0] self.message("InfrastructureMasterRole owner: " + self.infrastructureMaster) self.message("RidAllocationMasterRole owner: " + self.ridMaster) self.message("PdcEmulationMasterRole owner: " + self.pdcEmulator) self.message("DomainNamingMasterRole owner: " + self.namingMaster) self.message("SchemaMasterRole owner: " + self.schemaMaster)
def run(self, subnetname, site_of_subnet, H=None, sambaopts=None, credopts=None, versionopts=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp) samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp) samdb.transaction_start() try: subnets.create_subnet(samdb, samdb.get_config_basedn(), subnetname, site_of_subnet) samdb.transaction_commit() except subnets.SubnetException, e: samdb.transaction_cancel() raise CommandError("Error while creating subnet {0!s}: {1!s}".format(subnetname, e))
def check_restored_database(self, bkp_lp, expect_secrets=True): paths = provision.provision_paths_from_lp(bkp_lp, bkp_lp.get("realm")) bkp_pd = get_prim_dom(paths.secrets, bkp_lp) self.assertEqual(len(bkp_pd), 1) acn = bkp_pd[0].get('samAccountName') self.assertIsNotNone(acn) self.assertEqual(acn[0].replace('$', ''), self.new_server) self.assertIsNotNone(bkp_pd[0].get('secret')) samdb = SamDB(url=paths.samdb, session_info=system_session(), lp=bkp_lp, credentials=self.get_credentials()) # check that the backup markers have been removed from the restored DB res = samdb.search(base=ldb.Dn(samdb, "@SAMBA_DSDB"), scope=ldb.SCOPE_BASE, attrs=self.backup_markers) self.assertEqual(len(res), 1) for marker in self.backup_markers: self.assertIsNone(res[0].get(marker), "%s backup-marker left behind" % marker) # check that the repsFrom and repsTo values have been removed # from the restored DB res = samdb.search(base=samdb.get_default_basedn(), scope=ldb.SCOPE_BASE, attrs=['repsFrom', 'repsTo']) self.assertEqual(len(res), 1) self.assertIsNone(res[0].get('repsFrom')) self.assertIsNone(res[0].get('repsTo')) res = samdb.search(base=samdb.get_config_basedn(), scope=ldb.SCOPE_BASE, attrs=['repsFrom', 'repsTo']) self.assertEqual(len(res), 1) self.assertIsNone(res[0].get('repsFrom')) self.assertIsNone(res[0].get('repsTo')) # check the restored DB has the expected partitions/DC/FSMO roles self.assert_partitions_present(samdb) self.assert_dcs_present(samdb, self.new_server, expected_count=1) self.assert_fsmo_roles(samdb, self.new_server, self.server) self.assert_secrets(samdb, expect_secrets=expect_secrets) return samdb
def run(self, sitename, sambaopts=None, credopts=None, versionopts=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp, fallback_machine=True) url = lp.private_path("sam.ldb") if not os.path.exists(url): raise CommandError("secret database not found at %s " % url) samdb = SamDB(url=url, session_info=system_session(), credentials=creds, lp=lp) samdb.transaction_start() try: ok = sites.delete_site(samdb, samdb.get_config_basedn(), sitename) samdb.transaction_commit() except sites.SiteException, e: samdb.transaction_cancel() raise CommandError("Error while removing site %s, error: %s" % (sitename, str(e)))
def run(self, sitename, H=None, sambaopts=None, credopts=None, versionopts=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp, fallback_machine=True) samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp) samdb.transaction_start() try: sites.delete_site(samdb, samdb.get_config_basedn(), sitename) samdb.transaction_commit() except sites.SiteException as e: samdb.transaction_cancel() raise CommandError( "Error while removing site %s, error: %s" % (sitename, str(e))) self.outf.write("Site %s removed!\n" % sitename)
class PassWordHashTests(TestCase): def setUp(self): self.lp = samba.tests.env_loadparm() super(PassWordHashTests, self).setUp() def set_store_cleartext(self, cleartext): # get the current pwdProperties pwdProperties = self.ldb.get_pwdProperties() # update the clear-text properties flag props = int(pwdProperties) if cleartext: props |= DOMAIN_PASSWORD_STORE_CLEARTEXT else: props &= ~DOMAIN_PASSWORD_STORE_CLEARTEXT self.ldb.set_pwdProperties(str(props)) # Add a user to ldb, this will exercise the password_hash code # and calculate the appropriate supplemental credentials def add_user(self, options=None, clear_text=False, ldb=None): # set any needed options if options is not None: for (option, value) in options: self.lp.set(option, value) if ldb is None: self.creds = Credentials() self.session = system_session() self.creds.guess(self.lp) self.session = system_session() self.ldb = SamDB(session_info=self.session, credentials=self.creds, lp=self.lp) else: self.ldb = ldb res = self.ldb.search(base=self.ldb.get_config_basedn(), expression="ncName=%s" % self.ldb.get_default_basedn(), attrs=["nETBIOSName"]) self.netbios_domain = res[0]["nETBIOSName"][0] self.dns_domain = self.ldb.domain_dns_name() # Gets back the basedn base_dn = self.ldb.domain_dn() # Gets back the configuration basedn configuration_dn = self.ldb.get_config_basedn().get_linearized() # permit password changes during this test PasswordCommon.allow_password_changes(self, self.ldb) self.base_dn = self.ldb.domain_dn() account_control = 0 if clear_text: # Restore the current domain setting on exit. pwdProperties = self.ldb.get_pwdProperties() self.addCleanup(self.ldb.set_pwdProperties, pwdProperties) # Update the domain setting self.set_store_cleartext(clear_text) account_control |= UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED # (Re)adds the test user USER_NAME with password USER_PASS # and userPrincipalName UPN delete_force(self.ldb, "cn=" + USER_NAME + ",cn=users," + self.base_dn) self.ldb.add({ "dn": "cn=" + USER_NAME + ",cn=users," + self.base_dn, "objectclass": "user", "sAMAccountName": USER_NAME, "userPassword": USER_PASS, "userPrincipalName": UPN, "userAccountControl": str(account_control) }) # Get the supplemental credentials for the user under test def get_supplemental_creds(self): base = "cn=" + USER_NAME + ",cn=users," + self.base_dn res = self.ldb.search(scope=ldb.SCOPE_BASE, base=base, attrs=["supplementalCredentials"]) self.assertIs(True, len(res) > 0) obj = res[0] sc_blob = obj["supplementalCredentials"][0] sc = ndr_unpack(drsblobs.supplementalCredentialsBlob, sc_blob) return sc # Calculate and validate a Wdigest value def check_digest(self, user, realm, password, digest): expected = calc_digest(user, realm, password) actual = binascii.hexlify(bytearray(digest)) error = "Digest expected[%s], actual[%s], " \ "user[%s], realm[%s], pass[%s]" % \ (expected, actual, user, realm, password) self.assertEquals(expected, actual, error) # Check all of the 29 expected WDigest values # def check_wdigests(self, digests): self.assertEquals(29, digests.num_hashes) # Using the n-1 pattern in the array indexes to make it easier # to check the tests against the spec and the samba-tool user tests. self.check_digest(USER_NAME, self.netbios_domain, USER_PASS, digests.hashes[1-1].hash) self.check_digest(USER_NAME.lower(), self.netbios_domain.lower(), USER_PASS, digests.hashes[2-1].hash) self.check_digest(USER_NAME.upper(), self.netbios_domain.upper(), USER_PASS, digests.hashes[3-1].hash) self.check_digest(USER_NAME, self.netbios_domain.upper(), USER_PASS, digests.hashes[4-1].hash) self.check_digest(USER_NAME, self.netbios_domain.lower(), USER_PASS, digests.hashes[5-1].hash) self.check_digest(USER_NAME.upper(), self.netbios_domain.lower(), USER_PASS, digests.hashes[6-1].hash) self.check_digest(USER_NAME.lower(), self.netbios_domain.upper(), USER_PASS, digests.hashes[7-1].hash) self.check_digest(USER_NAME, self.dns_domain, USER_PASS, digests.hashes[8-1].hash) self.check_digest(USER_NAME.lower(), self.dns_domain.lower(), USER_PASS, digests.hashes[9-1].hash) self.check_digest(USER_NAME.upper(), self.dns_domain.upper(), USER_PASS, digests.hashes[10-1].hash) self.check_digest(USER_NAME, self.dns_domain.upper(), USER_PASS, digests.hashes[11-1].hash) self.check_digest(USER_NAME, self.dns_domain.lower(), USER_PASS, digests.hashes[12-1].hash) self.check_digest(USER_NAME.upper(), self.dns_domain.lower(), USER_PASS, digests.hashes[13-1].hash) self.check_digest(USER_NAME.lower(), self.dns_domain.upper(), USER_PASS, digests.hashes[14-1].hash) self.check_digest(UPN, "", USER_PASS, digests.hashes[15-1].hash) self.check_digest(UPN.lower(), "", USER_PASS, digests.hashes[16-1].hash) self.check_digest(UPN.upper(), "", USER_PASS, digests.hashes[17-1].hash) name = "%s\\%s" % (self.netbios_domain, USER_NAME) self.check_digest(name, "", USER_PASS, digests.hashes[18-1].hash) name = "%s\\%s" % (self.netbios_domain.lower(), USER_NAME.lower()) self.check_digest(name, "", USER_PASS, digests.hashes[19-1].hash) name = "%s\\%s" % (self.netbios_domain.upper(), USER_NAME.upper()) self.check_digest(name, "", USER_PASS, digests.hashes[20-1].hash) self.check_digest(USER_NAME, "Digest", USER_PASS, digests.hashes[21-1].hash) self.check_digest(USER_NAME.lower(), "Digest", USER_PASS, digests.hashes[22-1].hash) self.check_digest(USER_NAME.upper(), "Digest", USER_PASS, digests.hashes[23-1].hash) self.check_digest(UPN, "Digest", USER_PASS, digests.hashes[24-1].hash) self.check_digest(UPN.lower(), "Digest", USER_PASS, digests.hashes[25-1].hash) self.check_digest(UPN.upper(), "Digest", USER_PASS, digests.hashes[26-1].hash) name = "%s\\%s" % (self.netbios_domain, USER_NAME) self.check_digest(name, "Digest", USER_PASS, digests.hashes[27-1].hash) name = "%s\\%s" % (self.netbios_domain.lower(), USER_NAME.lower()) self.check_digest(name, "Digest", USER_PASS, digests.hashes[28-1].hash) name = "%s\\%s" % (self.netbios_domain.upper(), USER_NAME.upper()) self.check_digest(name, "Digest", USER_PASS, digests.hashes[29-1].hash) def checkUserPassword(self, up, expected): # Check we've received the correct number of hashes self.assertEquals(len(expected), up.num_hashes) i = 0 for (tag, alg, rounds) in expected: self.assertEquals(tag, up.hashes[i].scheme) data = up.hashes[i].value.split("$") # Check we got the expected crypt algorithm self.assertEquals(alg, data[1]) if rounds is None: cmd = "$%s$%s" % (alg, data[2]) else: cmd = "$%s$rounds=%d$%s" % (alg, rounds, data[3]) # Calculate the expected hash value expected = crypt.crypt(USER_PASS, cmd) self.assertEquals(expected, up.hashes[i].value) i += 1 # Check that the correct nt_hash was stored for userPassword def checkNtHash(self, password, nt_hash): creds = Credentials() creds.set_anonymous() creds.set_password(password) expected = creds.get_nt_hash() actual = bytearray(nt_hash) self.assertEquals(expected, actual)
def run(self, sambaopts=None, credopts=None, backup_file=None, targetdir=None, newservername=None, host_ip=None, host_ip6=None): if not (backup_file and os.path.exists(backup_file)): raise CommandError('Backup file not found.') if targetdir is None: raise CommandError('Please specify a target directory') # allow restoredc to install into a directory prepopulated by selftest if (os.path.exists(targetdir) and os.listdir(targetdir) and os.environ.get('SAMBA_SELFTEST') != '1'): raise CommandError('Target directory is not empty') if not newservername: raise CommandError('Server name required') logger = logging.getLogger() logger.setLevel(logging.DEBUG) logger.addHandler(logging.StreamHandler(sys.stdout)) # ldapcmp prefers the server's netBIOS name in upper-case newservername = newservername.upper() # extract the backup .tar to a temp directory targetdir = os.path.abspath(targetdir) tf = tarfile.open(backup_file) tf.extractall(targetdir) tf.close() # use the smb.conf that got backed up, by default (save what was # actually backed up, before we mess with it) smbconf = os.path.join(targetdir, 'etc', 'smb.conf') shutil.copyfile(smbconf, smbconf + ".orig") # if a smb.conf was specified on the cmd line, then use that instead cli_smbconf = sambaopts.get_loadparm_path() if cli_smbconf: logger.info("Using %s as restored domain's smb.conf" % cli_smbconf) shutil.copyfile(cli_smbconf, smbconf) lp = samba.param.LoadParm() lp.load(smbconf) # open a DB connection to the restored DB private_dir = os.path.join(targetdir, 'private') samdb_path = os.path.join(private_dir, 'sam.ldb') samdb = SamDB(url=samdb_path, session_info=system_session(), lp=lp) # Create account using the join_add_objects function in the join object # We need namingContexts, account control flags, and the sid saved by # the backup process. res = samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=['namingContexts']) ncs = [str(r) for r in res[0].get('namingContexts')] creds = credopts.get_credentials(lp) ctx = DCJoinContext(logger, creds=creds, lp=lp, forced_local_samdb=samdb, netbios_name=newservername) ctx.nc_list = ncs ctx.full_nc_list = ncs ctx.userAccountControl = (samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION) # rewrite the smb.conf to make sure it uses the new targetdir settings. # (This doesn't update all filepaths in a customized config, but it # corrects the same paths that get set by a new provision) logger.info('Updating basic smb.conf settings...') make_smbconf(smbconf, newservername, ctx.domain_name, ctx.realm, targetdir, lp=lp, serverrole="active directory domain controller") # Get the SID saved by the backup process and create account res = samdb.search(base=ldb.Dn(samdb, "@SAMBA_DSDB"), scope=ldb.SCOPE_BASE, attrs=['sidForRestore', 'backupRename']) is_rename = True if 'backupRename' in res[0] else False sid = res[0].get('sidForRestore')[0] logger.info('Creating account with SID: ' + str(sid)) ctx.join_add_objects(specified_sid=dom_sid(sid)) m = ldb.Message() m.dn = ldb.Dn(samdb, '@ROOTDSE') ntds_guid = str(ctx.ntds_guid) m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % ntds_guid, ldb.FLAG_MOD_REPLACE, "dsServiceName") samdb.modify(m) # if we renamed the backed-up domain, then we need to add the DNS # objects for the new realm (we do this in the restore, now that we # know the new DC's IP address) if is_rename: self.register_dns_zone(logger, samdb, lp, ctx.ntds_guid, host_ip, host_ip6) secrets_path = os.path.join(private_dir, 'secrets.ldb') secrets_ldb = Ldb(secrets_path, session_info=system_session(), lp=lp) secretsdb_self_join(secrets_ldb, domain=ctx.domain_name, realm=ctx.realm, dnsdomain=ctx.dnsdomain, netbiosname=ctx.myname, domainsid=ctx.domsid, machinepass=ctx.acct_pass, key_version_number=ctx.key_version_number, secure_channel_type=misc.SEC_CHAN_BDC) # Seize DNS roles domain_dn = samdb.domain_dn() forest_dn = samba.dn_from_dns_name(samdb.forest_dns_name()) domaindns_dn = ("CN=Infrastructure,DC=DomainDnsZones,", domain_dn) forestdns_dn = ("CN=Infrastructure,DC=ForestDnsZones,", forest_dn) for dn_prefix, dns_dn in [forestdns_dn, domaindns_dn]: if dns_dn not in ncs: continue full_dn = dn_prefix + dns_dn m = ldb.Message() m.dn = ldb.Dn(samdb, full_dn) m["fSMORoleOwner"] = ldb.MessageElement(samdb.get_dsServiceName(), ldb.FLAG_MOD_REPLACE, "fSMORoleOwner") samdb.modify(m) # Seize other roles for role in ['rid', 'pdc', 'naming', 'infrastructure', 'schema']: self.seize_role(role, samdb, force=True) # Get all DCs and remove them (this ensures these DCs cannot # replicate because they will not have a password) search_expr = "(&(objectClass=Server)(serverReference=*))" res = samdb.search(samdb.get_config_basedn(), scope=ldb.SCOPE_SUBTREE, expression=search_expr) for m in res: cn = m.get('cn')[0] if cn != newservername: remove_dc(samdb, logger, cn) # Remove the repsFrom and repsTo from each NC to ensure we do # not try (and fail) to talk to the old DCs for nc in ncs: msg = ldb.Message() msg.dn = ldb.Dn(samdb, nc) msg["repsFrom"] = ldb.MessageElement([], ldb.FLAG_MOD_REPLACE, "repsFrom") msg["repsTo"] = ldb.MessageElement([], ldb.FLAG_MOD_REPLACE, "repsTo") samdb.modify(msg) # Update the krbtgt passwords twice, ensuring no tickets from # the old domain are valid update_krbtgt_account_password(samdb) update_krbtgt_account_password(samdb) # restore the sysvol directory from the backup tar file, including the # original NTACLs. Note that the backup_restore() will fail if not root sysvol_tar = os.path.join(targetdir, 'sysvol.tar.gz') dest_sysvol_dir = lp.get('path', 'sysvol') if not os.path.exists(dest_sysvol_dir): os.makedirs(dest_sysvol_dir) backup_restore(sysvol_tar, dest_sysvol_dir, samdb, smbconf) os.remove(sysvol_tar) # fix up any stale links to the old DCs we just removed logger.info("Fixing up any remaining references to the old DCs...") self.fix_old_dc_references(samdb) # Remove DB markers added by the backup process m = ldb.Message() m.dn = ldb.Dn(samdb, "@SAMBA_DSDB") m["backupDate"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "backupDate") m["sidForRestore"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "sidForRestore") if is_rename: m["backupRename"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "backupRename") samdb.modify(m) logger.info("Backup file successfully restored to %s" % targetdir) logger.info("Please check the smb.conf settings are correct before " "starting samba.")
class PasswordTests(PasswordTestCase): def setUp(self): super(PasswordTests, self).setUp() self.ldb = SamDB(url=host, session_info=system_session(lp), credentials=creds, lp=lp) # Gets back the basedn base_dn = self.ldb.domain_dn() # Gets back the configuration basedn configuration_dn = self.ldb.get_config_basedn().get_linearized() # permit password changes during this test self.allow_password_changes() self.base_dn = self.ldb.domain_dn() # (Re)adds the test user "testuser" with no password atm delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn) self.ldb.add({ "dn": "cn=testuser,cn=users," + self.base_dn, "objectclass": "user", "sAMAccountName": "testuser"}) # Tests a password change when we don't have any password yet with a # wrong old password try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: noPassword add: userPassword userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e: (num, msg) = e.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) # Windows (2008 at least) seems to have some small bug here: it # returns "0000056A" on longer (always wrong) previous passwords. self.assertTrue('00000056' in msg) # Sets the initial user password with a "special" password change # I think that this internally is a password set operation and it can # only be performed by someone which has password set privileges on the # account (at least in s4 we do handle it like that). self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword add: userPassword userPassword: thatsAcomplPASS1 """) # But in the other way around this special syntax doesn't work try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword """) self.fail() except LdbError as e1: (num, _) = e1.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) # Enables the user account self.ldb.enable_account("(sAMAccountName=testuser)") # Open a second LDB connection with the user credentials. Use the # command line credentials for informations like the domain, the realm # and the workstation. creds2 = Credentials() creds2.set_username("testuser") creds2.set_password("thatsAcomplPASS1") creds2.set_domain(creds.get_domain()) creds2.set_realm(creds.get_realm()) creds2.set_workstation(creds.get_workstation()) creds2.set_gensec_features(creds2.get_gensec_features() | gensec.FEATURE_SEAL) self.ldb2 = SamDB(url=host, credentials=creds2, lp=lp) def test_unicodePwd_hash_set(self): """Performs a password hash set operation on 'unicodePwd' which should be prevented""" # Notice: Direct hash password sets should never work m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["unicodePwd"] = MessageElement("XXXXXXXXXXXXXXXX", FLAG_MOD_REPLACE, "unicodePwd") try: self.ldb.modify(m) self.fail() except LdbError as e2: (num, _) = e2.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) def test_unicodePwd_hash_change(self): """Performs a password hash change operation on 'unicodePwd' which should be prevented""" # Notice: Direct hash password changes should never work # Hash password changes should never work try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: unicodePwd unicodePwd: XXXXXXXXXXXXXXXX add: unicodePwd unicodePwd: YYYYYYYYYYYYYYYY """) self.fail() except LdbError as e3: (num, _) = e3.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) def test_unicodePwd_clear_set(self): """Performs a password cleartext set operation on 'unicodePwd'""" m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["unicodePwd"] = MessageElement("\"thatsAcomplPASS2\"".encode('utf-16-le'), FLAG_MOD_REPLACE, "unicodePwd") self.ldb.modify(m) def test_unicodePwd_clear_change(self): """Performs a password cleartext change operation on 'unicodePwd'""" self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')).decode('utf8') + """ add: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """ """) # Wrong old password try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """ add: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS4\"".encode('utf-16-le')).decode('utf8') + """ """) self.fail() except LdbError as e4: (num, msg) = e4.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) self.assertTrue('00000056' in msg) # A change to the same password again will not work (password history) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """ add: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')).decode('utf8') + """ """) self.fail() except LdbError as e5: (num, msg) = e5.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) self.assertTrue('0000052D' in msg) def test_dBCSPwd_hash_set(self): """Performs a password hash set operation on 'dBCSPwd' which should be prevented""" # Notice: Direct hash password sets should never work m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["dBCSPwd"] = MessageElement("XXXXXXXXXXXXXXXX", FLAG_MOD_REPLACE, "dBCSPwd") try: self.ldb.modify(m) self.fail() except LdbError as e6: (num, _) = e6.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) def test_dBCSPwd_hash_change(self): """Performs a password hash change operation on 'dBCSPwd' which should be prevented""" # Notice: Direct hash password changes should never work try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: dBCSPwd dBCSPwd: XXXXXXXXXXXXXXXX add: dBCSPwd dBCSPwd: YYYYYYYYYYYYYYYY """) self.fail() except LdbError as e7: (num, _) = e7.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) def test_userPassword_clear_set(self): """Performs a password cleartext set operation on 'userPassword'""" # Notice: This works only against Windows if "dSHeuristics" has been set # properly m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement("thatsAcomplPASS2", FLAG_MOD_REPLACE, "userPassword") self.ldb.modify(m) def test_userPassword_clear_change(self): """Performs a password cleartext change operation on 'userPassword'""" # Notice: This works only against Windows if "dSHeuristics" has been set # properly self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 """) # Wrong old password try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS3 add: userPassword userPassword: thatsAcomplPASS4 """) self.fail() except LdbError as e8: (num, msg) = e8.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) self.assertTrue('00000056' in msg) # A change to the same password again will not work (password history) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS2 add: userPassword userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e9: (num, msg) = e9.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) self.assertTrue('0000052D' in msg) def test_clearTextPassword_clear_set(self): """Performs a password cleartext set operation on 'clearTextPassword'""" # Notice: This never works against Windows - only supported by us try: m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["clearTextPassword"] = MessageElement("thatsAcomplPASS2".encode('utf-16-le'), FLAG_MOD_REPLACE, "clearTextPassword") self.ldb.modify(m) # this passes against s4 except LdbError as e10: (num, msg) = e10.args # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it if num != ERR_NO_SUCH_ATTRIBUTE: raise LdbError(num, msg) def test_clearTextPassword_clear_change(self): """Performs a password cleartext change operation on 'clearTextPassword'""" # Notice: This never works against Windows - only supported by us try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: clearTextPassword clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS1".encode('utf-16-le')).decode('utf8') + """ add: clearTextPassword clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')).decode('utf8') + """ """) # this passes against s4 except LdbError as e11: (num, msg) = e11.args # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it if num != ERR_NO_SUCH_ATTRIBUTE: raise LdbError(num, msg) # Wrong old password try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: clearTextPassword clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS3".encode('utf-16-le')).decode('utf8') + """ add: clearTextPassword clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS4".encode('utf-16-le')).decode('utf8') + """ """) self.fail() except LdbError as e12: (num, msg) = e12.args # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it if num != ERR_NO_SUCH_ATTRIBUTE: self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) self.assertTrue('00000056' in msg) # A change to the same password again will not work (password history) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: clearTextPassword clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')).decode('utf8') + """ add: clearTextPassword clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')).decode('utf8') + """ """) self.fail() except LdbError as e13: (num, msg) = e13.args # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it if num != ERR_NO_SUCH_ATTRIBUTE: self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) self.assertTrue('0000052D' in msg) def test_failures(self): """Performs some failure testing""" try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 """) self.fail() except LdbError as e14: (num, _) = e14.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 """) self.fail() except LdbError as e15: (num, _) = e15.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword """) self.fail() except LdbError as e16: (num, _) = e16.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword """) self.fail() except LdbError as e17: (num, _) = e17.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify add: userPassword userPassword: thatsAcomplPASS1 """) self.fail() except LdbError as e18: (num, _) = e18.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify add: userPassword userPassword: thatsAcomplPASS1 """) self.fail() except LdbError as e19: (num, _) = e19.args self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e20: (num, _) = e20.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e21: (num, _) = e21.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e22: (num, _) = e22.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e23: (num, _) = e23.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 add: userPassword userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e24: (num, _) = e24.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 add: userPassword userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e25: (num, _) = e25.args self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e26: (num, _) = e26.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e27: (num, _) = e27.args self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 replace: userPassword userPassword: thatsAcomplPASS3 """) self.fail() except LdbError as e28: (num, _) = e28.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 replace: userPassword userPassword: thatsAcomplPASS3 """) self.fail() except LdbError as e29: (num, _) = e29.args self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) # Reverse order does work self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify add: userPassword userPassword: thatsAcomplPASS2 delete: userPassword userPassword: thatsAcomplPASS1 """) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS2 add: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """ """) # this passes against s4 except LdbError as e30: (num, _) = e30.args self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')).decode('utf8') + """ add: userPassword userPassword: thatsAcomplPASS4 """) # this passes against s4 except LdbError as e31: (num, _) = e31.args self.assertEquals(num, ERR_NO_SUCH_ATTRIBUTE) # Several password changes at once are allowed self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify replace: userPassword userPassword: thatsAcomplPASS1 userPassword: thatsAcomplPASS2 """) # Several password changes at once are allowed self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify replace: userPassword userPassword: thatsAcomplPASS1 userPassword: thatsAcomplPASS2 replace: userPassword userPassword: thatsAcomplPASS3 replace: userPassword userPassword: thatsAcomplPASS4 """) # This surprisingly should work delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn) self.ldb.add({ "dn": "cn=testuser2,cn=users," + self.base_dn, "objectclass": "user", "userPassword": ["thatsAcomplPASS1", "thatsAcomplPASS2"] }) # This surprisingly should work delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn) self.ldb.add({ "dn": "cn=testuser2,cn=users," + self.base_dn, "objectclass": "user", "userPassword": ["thatsAcomplPASS1", "thatsAcomplPASS1"] }) def test_empty_passwords(self): print("Performs some empty passwords testing") try: self.ldb.add({ "dn": "cn=testuser2,cn=users," + self.base_dn, "objectclass": "user", "unicodePwd": [] }) self.fail() except LdbError as e32: (num, _) = e32.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb.add({ "dn": "cn=testuser2,cn=users," + self.base_dn, "objectclass": "user", "dBCSPwd": [] }) self.fail() except LdbError as e33: (num, _) = e33.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb.add({ "dn": "cn=testuser2,cn=users," + self.base_dn, "objectclass": "user", "userPassword": [] }) self.fail() except LdbError as e34: (num, _) = e34.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb.add({ "dn": "cn=testuser2,cn=users," + self.base_dn, "objectclass": "user", "clearTextPassword": [] }) self.fail() except LdbError as e35: (num, _) = e35.args self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or num == ERR_NO_SUCH_ATTRIBUTE) # for Windows delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["unicodePwd"] = MessageElement([], FLAG_MOD_ADD, "unicodePwd") try: self.ldb.modify(m) self.fail() except LdbError as e36: (num, _) = e36.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["dBCSPwd"] = MessageElement([], FLAG_MOD_ADD, "dBCSPwd") try: self.ldb.modify(m) self.fail() except LdbError as e37: (num, _) = e37.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement([], FLAG_MOD_ADD, "userPassword") try: self.ldb.modify(m) self.fail() except LdbError as e38: (num, _) = e38.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["clearTextPassword"] = MessageElement([], FLAG_MOD_ADD, "clearTextPassword") try: self.ldb.modify(m) self.fail() except LdbError as e39: (num, _) = e39.args self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or num == ERR_NO_SUCH_ATTRIBUTE) # for Windows m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["unicodePwd"] = MessageElement([], FLAG_MOD_REPLACE, "unicodePwd") try: self.ldb.modify(m) self.fail() except LdbError as e40: (num, _) = e40.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["dBCSPwd"] = MessageElement([], FLAG_MOD_REPLACE, "dBCSPwd") try: self.ldb.modify(m) self.fail() except LdbError as e41: (num, _) = e41.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement([], FLAG_MOD_REPLACE, "userPassword") try: self.ldb.modify(m) self.fail() except LdbError as e42: (num, _) = e42.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["clearTextPassword"] = MessageElement([], FLAG_MOD_REPLACE, "clearTextPassword") try: self.ldb.modify(m) self.fail() except LdbError as e43: (num, _) = e43.args self.assertTrue(num == ERR_UNWILLING_TO_PERFORM or num == ERR_NO_SUCH_ATTRIBUTE) # for Windows m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["unicodePwd"] = MessageElement([], FLAG_MOD_DELETE, "unicodePwd") try: self.ldb.modify(m) self.fail() except LdbError as e44: (num, _) = e44.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["dBCSPwd"] = MessageElement([], FLAG_MOD_DELETE, "dBCSPwd") try: self.ldb.modify(m) self.fail() except LdbError as e45: (num, _) = e45.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement([], FLAG_MOD_DELETE, "userPassword") try: self.ldb.modify(m) self.fail() except LdbError as e46: (num, _) = e46.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["clearTextPassword"] = MessageElement([], FLAG_MOD_DELETE, "clearTextPassword") try: self.ldb.modify(m) self.fail() except LdbError as e47: (num, _) = e47.args self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or num == ERR_NO_SUCH_ATTRIBUTE) # for Windows def test_plain_userPassword(self): print("Performs testing about the standard 'userPassword' behaviour") # Delete the "dSHeuristics" self.ldb.set_dsheuristics(None) time.sleep(1) # This switching time is strictly needed! m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement("myPassword", FLAG_MOD_ADD, "userPassword") self.ldb.modify(m) res = self.ldb.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) self.assertTrue(len(res) == 1) self.assertTrue("userPassword" in res[0]) self.assertEquals(res[0]["userPassword"][0], "myPassword") m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement("myPassword2", FLAG_MOD_REPLACE, "userPassword") self.ldb.modify(m) res = self.ldb.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) self.assertTrue(len(res) == 1) self.assertTrue("userPassword" in res[0]) self.assertEquals(res[0]["userPassword"][0], "myPassword2") m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement([], FLAG_MOD_DELETE, "userPassword") self.ldb.modify(m) res = self.ldb.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) self.assertTrue(len(res) == 1) self.assertFalse("userPassword" in res[0]) # Set the test "dSHeuristics" to deactivate "userPassword" pwd changes self.ldb.set_dsheuristics("000000000") m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement("myPassword3", FLAG_MOD_REPLACE, "userPassword") self.ldb.modify(m) res = self.ldb.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) self.assertTrue(len(res) == 1) self.assertTrue("userPassword" in res[0]) self.assertEquals(res[0]["userPassword"][0], "myPassword3") # Set the test "dSHeuristics" to deactivate "userPassword" pwd changes self.ldb.set_dsheuristics("000000002") m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement("myPassword4", FLAG_MOD_REPLACE, "userPassword") self.ldb.modify(m) res = self.ldb.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) self.assertTrue(len(res) == 1) self.assertTrue("userPassword" in res[0]) self.assertEquals(res[0]["userPassword"][0], "myPassword4") # Reset the test "dSHeuristics" (reactivate "userPassword" pwd changes) self.ldb.set_dsheuristics("000000001") def test_modify_dsheuristics_userPassword(self): print("Performs testing about reading userPassword between dsHeuristic modifies") # Make sure userPassword cannot be read self.ldb.set_dsheuristics("000000000") # Open a new connection (with dsHeuristic=000000000) ldb1 = SamDB(url=host, session_info=system_session(lp), credentials=creds, lp=lp) # Set userPassword to be read # This setting only affects newer connections (ldb2) ldb1.set_dsheuristics("000000001") time.sleep(1) m = Message() m.dn = Dn(ldb1, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement("thatsAcomplPASS1", FLAG_MOD_REPLACE, "userPassword") ldb1.modify(m) res = ldb1.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) # userPassword cannot be read, it wasn't set, instead the # password was self.assertTrue(len(res) == 1) self.assertFalse("userPassword" in res[0]) # Open another new connection (with dsHeuristic=000000001) ldb2 = SamDB(url=host, session_info=system_session(lp), credentials=creds, lp=lp) res = ldb2.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) # Check on the new connection that userPassword was not stored # from ldb1 or is not readable self.assertTrue(len(res) == 1) self.assertFalse("userPassword" in res[0]) # Set userPassword to be readable # This setting does not affect this connection ldb2.set_dsheuristics("000000000") time.sleep(1) res = ldb2.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) # Check that userPassword was not stored from ldb1 self.assertTrue(len(res) == 1) self.assertFalse("userPassword" in res[0]) m = Message() m.dn = Dn(ldb2, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement("thatsAcomplPASS2", FLAG_MOD_REPLACE, "userPassword") ldb2.modify(m) res = ldb2.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) # Check despite setting it with userPassword support disabled # on this connection it should still not be readable self.assertTrue(len(res) == 1) self.assertFalse("userPassword" in res[0]) # Only password from ldb1 is the user's password creds2 = Credentials() creds2.set_username("testuser") creds2.set_password("thatsAcomplPASS1") creds2.set_domain(creds.get_domain()) creds2.set_realm(creds.get_realm()) creds2.set_workstation(creds.get_workstation()) creds2.set_gensec_features(creds2.get_gensec_features() | gensec.FEATURE_SEAL) try: SamDB(url=host, credentials=creds2, lp=lp) except: self.fail("testuser used the wrong password") ldb3 = SamDB(url=host, session_info=system_session(lp), credentials=creds, lp=lp) # Check that userPassword was stored from ldb2 res = ldb3.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) # userPassword can be read self.assertTrue(len(res) == 1) self.assertTrue("userPassword" in res[0]) self.assertEquals(res[0]["userPassword"][0], "thatsAcomplPASS2") # Reset the test "dSHeuristics" (reactivate "userPassword" pwd changes) self.ldb.set_dsheuristics("000000001") ldb4 = SamDB(url=host, session_info=system_session(lp), credentials=creds, lp=lp) # Check that userPassword that was stored from ldb2 res = ldb4.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) # userPassword can be not be read self.assertTrue(len(res) == 1) self.assertFalse("userPassword" in res[0]) def test_zero_length(self): # Get the old "minPwdLength" minPwdLength = self.ldb.get_minPwdLength() # Set it temporarely to "0" self.ldb.set_minPwdLength("0") # Get the old "pwdProperties" pwdProperties = self.ldb.get_pwdProperties() # Set them temporarely to "0" (to deactivate eventually the complexity) self.ldb.set_pwdProperties("0") self.ldb.setpassword("(sAMAccountName=testuser)", "") # Reset the "pwdProperties" as they were before self.ldb.set_pwdProperties(pwdProperties) # Reset the "minPwdLength" as it was before self.ldb.set_minPwdLength(minPwdLength) def test_pw_change_delete_no_value_userPassword(self): """Test password change with userPassword where the delete attribute doesn't have a value""" try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword add: userPassword userPassword: thatsAcomplPASS1 """) except LdbError, (num, msg): self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) else:
class SiteCoverageTests(samba.tests.TestCase): def setUp(self): self.prefix = "kcc_" self.lp = samba.tests.env_loadparm() self.sites = {} self.site_links = {} self.creds = Credentials() self.creds.guess(self.lp) self.session = system_session() self.samdb = SamDB(session_info=self.session, credentials=self.creds, lp=self.lp) def tearDown(self): self.samdb.transaction_start() for site in self.sites: delete_force(self.samdb, site, controls=['tree_delete:1']) for site_link in self.site_links: delete_force(self.samdb, site_link) self.samdb.transaction_commit() def _add_server(self, name, site): dn = "CN={},CN=Servers,{}".format(name, site) self.samdb.add({ "dn": dn, "objectClass": "server", "serverReference": self.samdb.domain_dn() }) return dn def _add_site(self, name): dn = "CN={},CN=Sites,{}".format( name, self.samdb.get_config_basedn() ) self.samdb.add({ "dn": dn, "objectClass": "site" }) self.samdb.add({ "dn": "CN=Servers," + dn, "objectClass": ["serversContainer"] }) self.sites[dn] = name return dn, name.lower() def _add_site_link(self, name, links=[], cost=100): dn = "CN={},CN=IP,CN=Inter-Site Transports,CN=Sites,{}".format( name, self.samdb.get_config_basedn() ) self.samdb.add({ "dn": dn, "objectClass": "siteLink", "cost": str(cost), "siteList": links }) self.site_links[dn] = name return dn def test_single_site_link_same_dc_count(self): self.samdb.transaction_start() site1, name1 = self._add_site(self.prefix + "ABCD") site2, name2 = self._add_site(self.prefix + "BCDE") uncovered_dn, uncovered = self._add_site(self.prefix + "uncovered") self._add_server(self.prefix + "ABCD" + '1', site1) self._add_server(self.prefix + "BCDE" + '1', site2) self._add_site_link(self.prefix + "link", [site1, site2, uncovered_dn]) self.samdb.transaction_commit() to_cover = uncovered_sites_to_cover(self.samdb, name1) to_cover.sort() self.assertEqual([uncovered], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name2) to_cover.sort() self.assertEqual([], to_cover) def test_single_site_link_different_dc_count(self): self.samdb.transaction_start() site1, name1 = self._add_site(self.prefix + "ABCD") site2, name2 = self._add_site(self.prefix + "BCDE") uncovered_dn, uncovered = self._add_site(self.prefix + "uncovered") self._add_server(self.prefix + "ABCD" + '1', site1) self._add_server(self.prefix + "ABCD" + '2', site1) self._add_server(self.prefix + "BCDE" + '1', site2) self._add_server(self.prefix + "BCDE" + '2', site2) self._add_server(self.prefix + "BCDE" + '3', site2) self._add_site_link(self.prefix + "link", [site1, site2, uncovered_dn]) self.samdb.transaction_commit() to_cover = uncovered_sites_to_cover(self.samdb, name1) to_cover.sort() self.assertEqual([], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name2) to_cover.sort() self.assertEqual([uncovered], to_cover) def test_two_site_links_same_cost(self): self.samdb.transaction_start() site1, name1 = self._add_site(self.prefix + "ABCD") site2, name2 = self._add_site(self.prefix + "BCDE") uncovered_dn, uncovered = self._add_site(self.prefix + "uncovered") self._add_server(self.prefix + "ABCD" + '1', site1) self._add_server(self.prefix + "ABCD" + '2', site1) self._add_server(self.prefix + "BCDE" + '1', site2) self._add_server(self.prefix + "BCDE" + '2', site2) self._add_server(self.prefix + "BCDE" + '3', site2) self._add_site_link(self.prefix + "link1", [site1, uncovered_dn]) self._add_site_link(self.prefix + "link2", [site2, uncovered_dn]) self.samdb.transaction_commit() to_cover = uncovered_sites_to_cover(self.samdb, name1) to_cover.sort() self.assertEqual([uncovered], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name2) to_cover.sort() self.assertEqual([uncovered], to_cover) def test_two_site_links_different_costs(self): self.samdb.transaction_start() site1, name1 = self._add_site(self.prefix + "ABCD") site2, name2 = self._add_site(self.prefix + "BCDE") uncovered_dn, uncovered = self._add_site(self.prefix + "uncovered") self._add_server(self.prefix + "ABCD" + '1', site1) self._add_server(self.prefix + "BCDE" + '1', site2) self._add_server(self.prefix + "BCDE" + '2', site2) self._add_site_link(self.prefix + "link1", [site1, uncovered_dn], cost=50) self._add_site_link(self.prefix + "link2", [site2, uncovered_dn], cost=75) self.samdb.transaction_commit() to_cover = uncovered_sites_to_cover(self.samdb, name1) to_cover.sort() self.assertEqual([uncovered], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name2) to_cover.sort() self.assertEqual([], to_cover) def test_three_site_links_different_costs(self): self.samdb.transaction_start() site1, name1 = self._add_site(self.prefix + "ABCD") site2, name2 = self._add_site(self.prefix + "BCDE") site3, name3 = self._add_site(self.prefix + "CDEF") uncovered_dn, uncovered = self._add_site(self.prefix + "uncovered") self._add_server(self.prefix + "ABCD" + '1', site1) self._add_server(self.prefix + "BCDE" + '1', site2) self._add_server(self.prefix + "CDEF" + '1', site3) self._add_server(self.prefix + "CDEF" + '2', site3) self._add_site_link(self.prefix + "link1", [site1, uncovered_dn], cost=50) self._add_site_link(self.prefix + "link2", [site2, uncovered_dn], cost=75) self._add_site_link(self.prefix + "link3", [site3, uncovered_dn], cost=60) self.samdb.transaction_commit() to_cover = uncovered_sites_to_cover(self.samdb, name1) to_cover.sort() self.assertEqual([uncovered], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name2) to_cover.sort() self.assertEqual([], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name3) to_cover.sort() self.assertEqual([], to_cover) def test_three_site_links_different_costs(self): self.samdb.transaction_start() site1, name1 = self._add_site(self.prefix + "ABCD") site2, name2 = self._add_site(self.prefix + "BCDE") site3, name3 = self._add_site(self.prefix + "CDEF") uncovered_dn, uncovered = self._add_site(self.prefix + "uncovered") self._add_server(self.prefix + "ABCD" + '1', site1) self._add_server(self.prefix + "BCDE" + '1', site2) self._add_server(self.prefix + "CDEF" + '1', site3) self._add_server(self.prefix + "CDEF" + '2', site3) self._add_site_link(self.prefix + "link1", [site1, uncovered_dn], cost=50) self._add_site_link(self.prefix + "link2", [site2, uncovered_dn], cost=75) self._add_site_link(self.prefix + "link3", [site3, uncovered_dn], cost=50) self.samdb.transaction_commit() to_cover = uncovered_sites_to_cover(self.samdb, name1) to_cover.sort() self.assertEqual([uncovered], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name2) to_cover.sort() self.assertEqual([], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name3) to_cover.sort() self.assertEqual([uncovered], to_cover) def test_complex_setup_with_multiple_uncovered_sites(self): self.samdb.transaction_start() site1, name1 = self._add_site(self.prefix + "ABCD") site2, name2 = self._add_site(self.prefix + "BCDE") site3, name3 = self._add_site(self.prefix + "CDEF") site4, name4 = self._add_site(self.prefix + "1234") site5, name5 = self._add_site(self.prefix + "2345") site6, name6 = self._add_site(self.prefix + "3456") uncovered_dn1, uncovered1 = self._add_site(self.prefix + "uncovered1") uncovered_dn2, uncovered2 = self._add_site(self.prefix + "uncovered2") uncovered_dn3, uncovered3 = self._add_site(self.prefix + "uncovered3") # Site Link Cluster 1 - Server List self._add_server(self.prefix + "ABCD" + '1', site1) self._add_server(self.prefix + "BCDE" + '1', site2) self._add_server(self.prefix + "BCDE" + '2', site2) self._add_server(self.prefix + "CDEF" + '1', site3) self._add_server(self.prefix + "CDEF" + '2', site3) self._add_server(self.prefix + "CDEF" + '3', site3) # Site Link Cluster 2 - Server List self._add_server(self.prefix + "1234" + '1', site4) self._add_server(self.prefix + "1234" + '2', site4) self._add_server(self.prefix + "2345" + '1', site5) self._add_server(self.prefix + "2345" + '2', site5) self._add_server(self.prefix + "3456" + '1', site6) # Join to Uncovered1 (preference to site link cluster 1) self._add_site_link(self.prefix + "link1A", [site1, site2, site3, uncovered_dn1], cost=49) self._add_site_link(self.prefix + "link2A", [site4, site5, site6, uncovered_dn1], cost=50) # Join to Uncovered2 (no preferene on site links) self._add_site_link(self.prefix + "link1B", [site1, site2, site3, uncovered_dn2], cost=50) self._add_site_link(self.prefix + "link2B", [site4, site5, site6, uncovered_dn2], cost=50) # Join to Uncovered3 (preference to site link cluster 2) self._add_site_link(self.prefix + "link1C", [site1, site2, site3, uncovered_dn3], cost=50) self._add_site_link(self.prefix + "link2C", [site4, site5, site6, uncovered_dn3], cost=49) self.samdb.transaction_commit() to_cover = uncovered_sites_to_cover(self.samdb, name1) to_cover.sort() self.assertEqual([], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name2) to_cover.sort() self.assertEqual([], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name3) to_cover.sort() self.assertEqual([uncovered1, uncovered2], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name4) to_cover.sort() self.assertEqual([uncovered2, uncovered3], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name5) to_cover.sort() self.assertEqual([], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name6) to_cover.sort() self.assertEqual([], to_cover) for to_check in [uncovered1, uncovered2, uncovered3]: to_cover = uncovered_sites_to_cover(self.samdb, to_check) to_cover.sort() self.assertEqual([], to_cover)
class PassWordHashTests(TestCase): def setUp(self): self.lp = samba.tests.env_loadparm() super(PassWordHashTests, self).setUp() def set_store_cleartext(self, cleartext): # get the current pwdProperties pwdProperties = self.ldb.get_pwdProperties() # update the clear-text properties flag props = int(pwdProperties) if cleartext: props |= DOMAIN_PASSWORD_STORE_CLEARTEXT else: props &= ~DOMAIN_PASSWORD_STORE_CLEARTEXT self.ldb.set_pwdProperties(str(props)) # Add a user to ldb, this will exercise the password_hash code # and calculate the appropriate supplemental credentials def add_user(self, options=None, clear_text=False, ldb=None): # set any needed options if options is not None: for (option, value) in options: self.lp.set(option, value) if ldb is None: self.creds = Credentials() self.session = system_session() self.creds.guess(self.lp) self.session = system_session() self.ldb = SamDB(session_info=self.session, credentials=self.creds, lp=self.lp) else: self.ldb = ldb res = self.ldb.search(base=self.ldb.get_config_basedn(), expression="ncName=%s" % self.ldb.get_default_basedn(), attrs=["nETBIOSName"]) self.netbios_domain = str(res[0]["nETBIOSName"][0]) self.dns_domain = self.ldb.domain_dns_name() # Gets back the basedn base_dn = self.ldb.domain_dn() # Gets back the configuration basedn configuration_dn = self.ldb.get_config_basedn().get_linearized() # permit password changes during this test PasswordCommon.allow_password_changes(self, self.ldb) self.base_dn = self.ldb.domain_dn() account_control = 0 if clear_text: # Restore the current domain setting on exit. pwdProperties = self.ldb.get_pwdProperties() self.addCleanup(self.ldb.set_pwdProperties, pwdProperties) # Update the domain setting self.set_store_cleartext(clear_text) account_control |= UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED # (Re)adds the test user USER_NAME with password USER_PASS # and userPrincipalName UPN delete_force(self.ldb, "cn=" + USER_NAME + ",cn=users," + self.base_dn) self.ldb.add({ "dn": "cn=" + USER_NAME + ",cn=users," + self.base_dn, "objectclass": "user", "sAMAccountName": USER_NAME, "userPassword": USER_PASS, "userPrincipalName": UPN, "userAccountControl": str(account_control) }) # Get the supplemental credentials for the user under test def get_supplemental_creds(self): base = "cn=" + USER_NAME + ",cn=users," + self.base_dn res = self.ldb.search(scope=ldb.SCOPE_BASE, base=base, attrs=["supplementalCredentials"]) self.assertIs(True, len(res) > 0) obj = res[0] sc_blob = obj["supplementalCredentials"][0] sc = ndr_unpack(drsblobs.supplementalCredentialsBlob, sc_blob) return sc # Calculate and validate a Wdigest value def check_digest(self, user, realm, password, digest): expected = calc_digest(user, realm, password) actual = binascii.hexlify(bytearray(digest)).decode('utf8') error = "Digest expected[%s], actual[%s], " \ "user[%s], realm[%s], pass[%s]" % \ (expected, actual, user, realm, password) self.assertEquals(expected, actual, error) # Check all of the 29 expected WDigest values # def check_wdigests(self, digests): self.assertEquals(29, digests.num_hashes) # Using the n-1 pattern in the array indexes to make it easier # to check the tests against the spec and the samba-tool user tests. self.check_digest(USER_NAME, self.netbios_domain, USER_PASS, digests.hashes[1 - 1].hash) self.check_digest(USER_NAME.lower(), self.netbios_domain.lower(), USER_PASS, digests.hashes[2 - 1].hash) self.check_digest(USER_NAME.upper(), self.netbios_domain.upper(), USER_PASS, digests.hashes[3 - 1].hash) self.check_digest(USER_NAME, self.netbios_domain.upper(), USER_PASS, digests.hashes[4 - 1].hash) self.check_digest(USER_NAME, self.netbios_domain.lower(), USER_PASS, digests.hashes[5 - 1].hash) self.check_digest(USER_NAME.upper(), self.netbios_domain.lower(), USER_PASS, digests.hashes[6 - 1].hash) self.check_digest(USER_NAME.lower(), self.netbios_domain.upper(), USER_PASS, digests.hashes[7 - 1].hash) self.check_digest(USER_NAME, self.dns_domain, USER_PASS, digests.hashes[8 - 1].hash) self.check_digest(USER_NAME.lower(), self.dns_domain.lower(), USER_PASS, digests.hashes[9 - 1].hash) self.check_digest(USER_NAME.upper(), self.dns_domain.upper(), USER_PASS, digests.hashes[10 - 1].hash) self.check_digest(USER_NAME, self.dns_domain.upper(), USER_PASS, digests.hashes[11 - 1].hash) self.check_digest(USER_NAME, self.dns_domain.lower(), USER_PASS, digests.hashes[12 - 1].hash) self.check_digest(USER_NAME.upper(), self.dns_domain.lower(), USER_PASS, digests.hashes[13 - 1].hash) self.check_digest(USER_NAME.lower(), self.dns_domain.upper(), USER_PASS, digests.hashes[14 - 1].hash) self.check_digest(UPN, "", USER_PASS, digests.hashes[15 - 1].hash) self.check_digest(UPN.lower(), "", USER_PASS, digests.hashes[16 - 1].hash) self.check_digest(UPN.upper(), "", USER_PASS, digests.hashes[17 - 1].hash) name = "%s\\%s" % (self.netbios_domain, USER_NAME) self.check_digest(name, "", USER_PASS, digests.hashes[18 - 1].hash) name = "%s\\%s" % (self.netbios_domain.lower(), USER_NAME.lower()) self.check_digest(name, "", USER_PASS, digests.hashes[19 - 1].hash) name = "%s\\%s" % (self.netbios_domain.upper(), USER_NAME.upper()) self.check_digest(name, "", USER_PASS, digests.hashes[20 - 1].hash) self.check_digest(USER_NAME, "Digest", USER_PASS, digests.hashes[21 - 1].hash) self.check_digest(USER_NAME.lower(), "Digest", USER_PASS, digests.hashes[22 - 1].hash) self.check_digest(USER_NAME.upper(), "Digest", USER_PASS, digests.hashes[23 - 1].hash) self.check_digest(UPN, "Digest", USER_PASS, digests.hashes[24 - 1].hash) self.check_digest(UPN.lower(), "Digest", USER_PASS, digests.hashes[25 - 1].hash) self.check_digest(UPN.upper(), "Digest", USER_PASS, digests.hashes[26 - 1].hash) name = "%s\\%s" % (self.netbios_domain, USER_NAME) self.check_digest(name, "Digest", USER_PASS, digests.hashes[27 - 1].hash) name = "%s\\%s" % (self.netbios_domain.lower(), USER_NAME.lower()) self.check_digest(name, "Digest", USER_PASS, digests.hashes[28 - 1].hash) name = "%s\\%s" % (self.netbios_domain.upper(), USER_NAME.upper()) self.check_digest(name, "Digest", USER_PASS, digests.hashes[29 - 1].hash) def checkUserPassword(self, up, expected): # Check we've received the correct number of hashes self.assertEquals(len(expected), up.num_hashes) i = 0 for (tag, alg, rounds) in expected: self.assertEquals(tag, up.hashes[i].scheme) data = up.hashes[i].value.decode('utf8').split("$") # Check we got the expected crypt algorithm self.assertEquals(alg, data[1]) if rounds is None: cmd = "$%s$%s" % (alg, data[2]) else: cmd = "$%s$rounds=%d$%s" % (alg, rounds, data[3]) # Calculate the expected hash value expected = crypt.crypt(USER_PASS, cmd) self.assertEquals(expected, up.hashes[i].value.decode('utf8')) i += 1 # Check that the correct nt_hash was stored for userPassword def checkNtHash(self, password, nt_hash): creds = Credentials() creds.set_anonymous() creds.set_password(password) expected = creds.get_nt_hash() actual = bytearray(nt_hash) self.assertEquals(expected, actual)
def samdb_to_ldif_file(samdb, dburl, lp, creds, ldif_file): """Routine to extract all objects and attributes that are relevent to the KCC algorithms from a DC database. The point of this function is to allow a programmer/debugger to extract an LDIF file with non-security relevent information from a DC database. The LDIF file can then be used to "import" via the import_ldif() function this file into a temporary abbreviated database. The KCC algorithm can then run against this abbreviated database for debug or test verification that the topology generated is computationally the same between different OSes and algorithms. :param dburl: LDAP database URL to extract info from :param ldif_file: output LDIF file name to create """ try: samdb = SamDB(url=dburl, session_info=system_session(), credentials=creds, lp=lp) except ldb.LdbError as e: (enum, estr) = e.args raise LdifError("Unable to open sam database (%s) : %s" % (dburl, estr)) if os.path.exists(ldif_file): raise LdifError("Specify a file (%s) that doesn't already exist." % ldif_file) try: f = open(ldif_file, "w") except IOError as ioerr: raise LdifError("Unable to open (%s) : %s" % (ldif_file, str(ioerr))) try: # Query Partitions attrs = [ "objectClass", "objectGUID", "cn", "whenChanged", "objectSid", "Enabled", "systemFlags", "dnsRoot", "nCName", "msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations" ] sstr = "CN=Partitions,%s" % samdb.get_config_basedn() res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=crossRef)") # Write partitions output write_search_result(samdb, f, res) # Query cross reference container attrs = [ "objectClass", "objectGUID", "cn", "whenChanged", "fSMORoleOwner", "systemFlags", "msDS-Behavior-Version", "msDS-EnabledFeature" ] sstr = "CN=Partitions,%s" % samdb.get_config_basedn() res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=crossRefContainer)") # Write cross reference container output write_search_result(samdb, f, res) # Query Sites attrs = [ "objectClass", "objectGUID", "cn", "whenChanged", "systemFlags" ] sstr = "CN=Sites,%s" % samdb.get_config_basedn() sites = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=site)") # Write sites output write_search_result(samdb, f, sites) # Query NTDS Site Settings for msg in sites: sitestr = str(msg.dn) attrs = [ "objectClass", "objectGUID", "cn", "whenChanged", "interSiteTopologyGenerator", "interSiteTopologyFailover", "schedule", "options" ] sstr = "CN=NTDS Site Settings,%s" % sitestr res = samdb.search(base=sstr, scope=ldb.SCOPE_BASE, attrs=attrs) # Write Site Settings output write_search_result(samdb, f, res) # Naming context list nclist = [] # Query Directory Service Agents for msg in sites: sstr = str(msg.dn) ncattrs = [ "hasMasterNCs", "msDS-hasMasterNCs", "hasPartialReplicaNCs", "msDS-HasDomainNCs", "msDS-hasFullReplicaNCs", "msDS-HasInstantiatedNCs" ] attrs = [ "objectClass", "objectGUID", "cn", "whenChanged", "invocationID", "options", "msDS-isRODC", "msDS-Behavior-Version" ] res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs + ncattrs, expression="(objectClass=nTDSDSA)") # Spin thru all the DSAs looking for NC replicas # and build a list of all possible Naming Contexts # for subsequent retrieval below for msg in res: for k in msg.keys(): if k in ncattrs: for value in msg[k]: # Some of these have binary DNs so # use dsdb_Dn to split out relevent parts dsdn = dsdb_Dn(samdb, value) dnstr = str(dsdn.dn) if dnstr not in nclist: nclist.append(dnstr) # Write DSA output write_search_result(samdb, f, res) # Query NTDS Connections for msg in sites: sstr = str(msg.dn) attrs = [ "objectClass", "objectGUID", "cn", "whenChanged", "options", "whenCreated", "enabledConnection", "schedule", "transportType", "fromServer", "systemFlags" ] res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=nTDSConnection)") # Write NTDS Connection output write_search_result(samdb, f, res) # Query Intersite transports attrs = [ "objectClass", "objectGUID", "cn", "whenChanged", "options", "name", "bridgeheadServerListBL", "transportAddressAttribute" ] sstr = "CN=Inter-Site Transports,CN=Sites,%s" % \ samdb.get_config_basedn() res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=interSiteTransport)") # Write inter-site transport output write_search_result(samdb, f, res) # Query siteLink attrs = [ "objectClass", "objectGUID", "cn", "whenChanged", "systemFlags", "options", "schedule", "replInterval", "siteList", "cost" ] sstr = "CN=Sites,%s" % \ samdb.get_config_basedn() res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=siteLink)", controls=['extended_dn:0']) # Write siteLink output write_search_result(samdb, f, res) # Query siteLinkBridge attrs = [ "objectClass", "objectGUID", "cn", "whenChanged", "siteLinkList" ] sstr = "CN=Sites,%s" % samdb.get_config_basedn() res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=siteLinkBridge)") # Write siteLinkBridge output write_search_result(samdb, f, res) # Query servers containers # Needed for samdb.server_site_name() attrs = [ "objectClass", "objectGUID", "cn", "whenChanged", "systemFlags" ] sstr = "CN=Sites,%s" % samdb.get_config_basedn() res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=serversContainer)") # Write servers container output write_search_result(samdb, f, res) # Query servers # Needed because some transport interfaces refer back to # attributes found in the server object. Also needed # so extended-dn will be happy with dsServiceName in rootDSE attrs = [ "objectClass", "objectGUID", "cn", "whenChanged", "systemFlags", "dNSHostName", "mailAddress" ] sstr = "CN=Sites,%s" % samdb.get_config_basedn() res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=server)") # Write server output write_search_result(samdb, f, res) # Query Naming Context replicas attrs = [ "objectClass", "objectGUID", "cn", "whenChanged", "objectSid", "fSMORoleOwner", "msDS-Behavior-Version", "repsFrom", "repsTo" ] for sstr in nclist: res = samdb.search(sstr, scope=ldb.SCOPE_BASE, attrs=attrs) # Write naming context output write_search_result(samdb, f, res) # Query rootDSE replicas attrs = [ "objectClass", "objectGUID", "cn", "whenChanged", "rootDomainNamingContext", "configurationNamingContext", "schemaNamingContext", "defaultNamingContext", "dsServiceName" ] sstr = "" res = samdb.search(sstr, scope=ldb.SCOPE_BASE, attrs=attrs) # Record the rootDSE object as a dn as it # would appear in the base ldb file. We have # to save it this way because we are going to # be importing as an abbreviated database. res[0].dn = ldb.Dn(samdb, "@ROOTDSE") # Write rootdse output write_search_result(samdb, f, res) except ldb.LdbError as e1: (enum, estr) = e1.args raise LdifError("Error processing (%s) : %s" % (sstr, estr)) f.close()
class BasicDeleteTests(samba.tests.TestCase): def GUID_string(self, guid): return self.ldb.schema_format_value("objectGUID", guid) def setUp(self): super(BasicDeleteTests, self).setUp() self.ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp) self.base_dn = self.ldb.domain_dn() self.configuration_dn = self.ldb.get_config_basedn().get_linearized() def search_guid(self, guid): print "SEARCH by GUID %s" % self.GUID_string(guid) res = self.ldb.search(base="<GUID=%s>" % self.GUID_string(guid), scope=SCOPE_BASE, controls=["show_deleted:1"]) self.assertEquals(len(res), 1) return res[0] def search_dn(self,dn): print "SEARCH by DN %s" % dn res = self.ldb.search(expression="(objectClass=*)", base=dn, scope=SCOPE_BASE, controls=["show_deleted:1"]) self.assertEquals(len(res), 1) return res[0] def del_attr_values(self, delObj): print "Checking attributes for %s" % delObj["dn"] self.assertEquals(delObj["isDeleted"][0],"TRUE") self.assertTrue(not("objectCategory" in delObj)) self.assertTrue(not("sAMAccountType" in delObj)) def preserved_attributes_list(self, liveObj, delObj): print "Checking for preserved attributes list" preserved_list = ["nTSecurityDescriptor", "attributeID", "attributeSyntax", "dNReferenceUpdate", "dNSHostName", "flatName", "governsID", "groupType", "instanceType", "lDAPDisplayName", "legacyExchangeDN", "isDeleted", "isRecycled", "lastKnownParent", "msDS-LastKnownRDN", "mS-DS-CreatorSID", "mSMQOwnerID", "nCName", "objectClass", "distinguishedName", "objectGUID", "objectSid", "oMSyntax", "proxiedObjectName", "name", "replPropertyMetaData", "sAMAccountName", "securityIdentifier", "sIDHistory", "subClassOf", "systemFlags", "trustPartner", "trustDirection", "trustType", "trustAttributes", "userAccountControl", "uSNChanged", "uSNCreated", "whenCreated"] for a in liveObj: if a in preserved_list: self.assertTrue(a in delObj) def check_rdn(self, liveObj, delObj, rdnName): print "Checking for correct rDN" rdn=liveObj[rdnName][0] rdn2=delObj[rdnName][0] name2=delObj[rdnName][0] guid=liveObj["objectGUID"][0] self.assertEquals(rdn2, rdn + "\nDEL:" + self.GUID_string(guid)) self.assertEquals(name2, rdn + "\nDEL:" + self.GUID_string(guid)) def delete_deleted(self, ldb, dn): print "Testing the deletion of the already deleted dn %s" % dn try: ldb.delete(dn) self.fail() except LdbError, (num, _): self.assertEquals(num, ERR_NO_SUCH_OBJECT)
class PasswordTests(samba.tests.TestCase): def setUp(self): super(PasswordTests, self).setUp() self.ldb = SamDB(url=host, session_info=system_session(lp), credentials=creds, lp=lp) # Gets back the basedn base_dn = self.ldb.domain_dn() # Gets back the configuration basedn configuration_dn = self.ldb.get_config_basedn().get_linearized() # Get the old "dSHeuristics" if it was set dsheuristics = self.ldb.get_dsheuristics() # Set the "dSHeuristics" to activate the correct "userPassword" behaviour self.ldb.set_dsheuristics("000000001") # Reset the "dSHeuristics" as they were before self.addCleanup(self.ldb.set_dsheuristics, dsheuristics) # Get the old "minPwdAge" minPwdAge = self.ldb.get_minPwdAge() # Set it temporarily to "0" self.ldb.set_minPwdAge("0") self.base_dn = self.ldb.domain_dn() # Reset the "minPwdAge" as it was before self.addCleanup(self.ldb.set_minPwdAge, minPwdAge) # (Re)adds the test user "testuser" with no password atm delete_force(self.ldb, "cn=testuser,cn=users," + self.base_dn) self.ldb.add({ "dn": "cn=testuser,cn=users," + self.base_dn, "objectclass": "user", "sAMAccountName": "testuser"}) # Tests a password change when we don't have any password yet with a # wrong old password try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: noPassword add: userPassword userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e: (num, msg) = e.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) # Windows (2008 at least) seems to have some small bug here: it # returns "0000056A" on longer (always wrong) previous passwords. self.assertTrue('00000056' in msg) # Sets the initial user password with a "special" password change # I think that this internally is a password set operation and it can # only be performed by someone which has password set privileges on the # account (at least in s4 we do handle it like that). self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword add: userPassword userPassword: thatsAcomplPASS1 """) # But in the other way around this special syntax doesn't work try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword """) self.fail() except LdbError as e1: (num, _) = e1.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) # Enables the user account self.ldb.enable_account("(sAMAccountName=testuser)") # Open a second LDB connection with the user credentials. Use the # command line credentials for informations like the domain, the realm # and the workstation. creds2 = Credentials() creds2.set_username("testuser") creds2.set_password("thatsAcomplPASS1") creds2.set_domain(creds.get_domain()) creds2.set_realm(creds.get_realm()) creds2.set_workstation(creds.get_workstation()) creds2.set_gensec_features(creds2.get_gensec_features() | gensec.FEATURE_SEAL) self.ldb2 = SamDB(url=host, credentials=creds2, lp=lp) def test_unicodePwd_hash_set(self): """Performs a password hash set operation on 'unicodePwd' which should be prevented""" # Notice: Direct hash password sets should never work m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["unicodePwd"] = MessageElement("XXXXXXXXXXXXXXXX", FLAG_MOD_REPLACE, "unicodePwd") try: self.ldb.modify(m) self.fail() except LdbError as e2: (num, _) = e2.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) def test_unicodePwd_hash_change(self): """Performs a password hash change operation on 'unicodePwd' which should be prevented""" # Notice: Direct hash password changes should never work # Hash password changes should never work try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: unicodePwd unicodePwd: XXXXXXXXXXXXXXXX add: unicodePwd unicodePwd: YYYYYYYYYYYYYYYY """) self.fail() except LdbError as e3: (num, _) = e3.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) def test_unicodePwd_clear_set(self): """Performs a password cleartext set operation on 'unicodePwd'""" m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["unicodePwd"] = MessageElement("\"thatsAcomplPASS2\"".encode('utf-16-le'), FLAG_MOD_REPLACE, "unicodePwd") self.ldb.modify(m) def test_unicodePwd_clear_change(self): """Performs a password cleartext change operation on 'unicodePwd'""" self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1\"".encode('utf-16-le')) + """ add: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """ """) # Wrong old password try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')) + """ add: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS4\"".encode('utf-16-le')) + """ """) self.fail() except LdbError as e4: (num, msg) = e4.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) self.assertTrue('00000056' in msg) # A change to the same password again will not work (password history) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """ add: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """ """) self.fail() except LdbError as e5: (num, msg) = e5.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) self.assertTrue('0000052D' in msg) def test_dBCSPwd_hash_set(self): """Performs a password hash set operation on 'dBCSPwd' which should be prevented""" # Notice: Direct hash password sets should never work m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["dBCSPwd"] = MessageElement("XXXXXXXXXXXXXXXX", FLAG_MOD_REPLACE, "dBCSPwd") try: self.ldb.modify(m) self.fail() except LdbError as e6: (num, _) = e6.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) def test_dBCSPwd_hash_change(self): """Performs a password hash change operation on 'dBCSPwd' which should be prevented""" # Notice: Direct hash password changes should never work try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: dBCSPwd dBCSPwd: XXXXXXXXXXXXXXXX add: dBCSPwd dBCSPwd: YYYYYYYYYYYYYYYY """) self.fail() except LdbError as e7: (num, _) = e7.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) def test_userPassword_clear_set(self): """Performs a password cleartext set operation on 'userPassword'""" # Notice: This works only against Windows if "dSHeuristics" has been set # properly m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement("thatsAcomplPASS2", FLAG_MOD_REPLACE, "userPassword") self.ldb.modify(m) def test_userPassword_clear_change(self): """Performs a password cleartext change operation on 'userPassword'""" # Notice: This works only against Windows if "dSHeuristics" has been set # properly self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 """) # Wrong old password try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS3 add: userPassword userPassword: thatsAcomplPASS4 """) self.fail() except LdbError as e8: (num, msg) = e8.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) self.assertTrue('00000056' in msg) # A change to the same password again will not work (password history) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS2 add: userPassword userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e9: (num, msg) = e9.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) self.assertTrue('0000052D' in msg) def test_clearTextPassword_clear_set(self): """Performs a password cleartext set operation on 'clearTextPassword'""" # Notice: This never works against Windows - only supported by us try: m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["clearTextPassword"] = MessageElement("thatsAcomplPASS2".encode('utf-16-le'), FLAG_MOD_REPLACE, "clearTextPassword") self.ldb.modify(m) # this passes against s4 except LdbError as e10: (num, msg) = e10.args # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it if num != ERR_NO_SUCH_ATTRIBUTE: raise LdbError(num, msg) def test_clearTextPassword_clear_change(self): """Performs a password cleartext change operation on 'clearTextPassword'""" # Notice: This never works against Windows - only supported by us try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: clearTextPassword clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS1".encode('utf-16-le')) + """ add: clearTextPassword clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')) + """ """) # this passes against s4 except LdbError as e11: (num, msg) = e11.args # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it if num != ERR_NO_SUCH_ATTRIBUTE: raise LdbError(num, msg) # Wrong old password try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: clearTextPassword clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS3".encode('utf-16-le')) + """ add: clearTextPassword clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS4".encode('utf-16-le')) + """ """) self.fail() except LdbError as e12: (num, msg) = e12.args # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it if num != ERR_NO_SUCH_ATTRIBUTE: self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) self.assertTrue('00000056' in msg) # A change to the same password again will not work (password history) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: clearTextPassword clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')) + """ add: clearTextPassword clearTextPassword:: """ + base64.b64encode("thatsAcomplPASS2".encode('utf-16-le')) + """ """) self.fail() except LdbError as e13: (num, msg) = e13.args # "NO_SUCH_ATTRIBUTE" is returned by Windows -> ignore it if num != ERR_NO_SUCH_ATTRIBUTE: self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) self.assertTrue('0000052D' in msg) def test_failures(self): """Performs some failure testing""" try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 """) self.fail() except LdbError as e14: (num, _) = e14.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 """) self.fail() except LdbError as e15: (num, _) = e15.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword """) self.fail() except LdbError as e16: (num, _) = e16.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword """) self.fail() except LdbError as e17: (num, _) = e17.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify add: userPassword userPassword: thatsAcomplPASS1 """) self.fail() except LdbError as e18: (num, _) = e18.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify add: userPassword userPassword: thatsAcomplPASS1 """) self.fail() except LdbError as e19: (num, _) = e19.args self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e20: (num, _) = e20.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e21: (num, _) = e21.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e22: (num, _) = e22.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e23: (num, _) = e23.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 add: userPassword userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e24: (num, _) = e24.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 add: userPassword userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e25: (num, _) = e25.args self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e26: (num, _) = e26.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 """) self.fail() except LdbError as e27: (num, _) = e27.args self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) try: self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 replace: userPassword userPassword: thatsAcomplPASS3 """) self.fail() except LdbError as e28: (num, _) = e28.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS1 add: userPassword userPassword: thatsAcomplPASS2 replace: userPassword userPassword: thatsAcomplPASS3 """) self.fail() except LdbError as e29: (num, _) = e29.args self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) # Reverse order does work self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify add: userPassword userPassword: thatsAcomplPASS2 delete: userPassword userPassword: thatsAcomplPASS1 """) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword userPassword: thatsAcomplPASS2 add: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')) + """ """) # this passes against s4 except LdbError as e30: (num, _) = e30.args self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS) try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: unicodePwd unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS3\"".encode('utf-16-le')) + """ add: userPassword userPassword: thatsAcomplPASS4 """) # this passes against s4 except LdbError as e31: (num, _) = e31.args self.assertEquals(num, ERR_NO_SUCH_ATTRIBUTE) # Several password changes at once are allowed self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify replace: userPassword userPassword: thatsAcomplPASS1 userPassword: thatsAcomplPASS2 """) # Several password changes at once are allowed self.ldb.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify replace: userPassword userPassword: thatsAcomplPASS1 userPassword: thatsAcomplPASS2 replace: userPassword userPassword: thatsAcomplPASS3 replace: userPassword userPassword: thatsAcomplPASS4 """) # This surprisingly should work delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn) self.ldb.add({ "dn": "cn=testuser2,cn=users," + self.base_dn, "objectclass": "user", "userPassword": ["thatsAcomplPASS1", "thatsAcomplPASS2"] }) # This surprisingly should work delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn) self.ldb.add({ "dn": "cn=testuser2,cn=users," + self.base_dn, "objectclass": "user", "userPassword": ["thatsAcomplPASS1", "thatsAcomplPASS1"] }) def test_empty_passwords(self): print("Performs some empty passwords testing") try: self.ldb.add({ "dn": "cn=testuser2,cn=users," + self.base_dn, "objectclass": "user", "unicodePwd": [] }) self.fail() except LdbError as e32: (num, _) = e32.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb.add({ "dn": "cn=testuser2,cn=users," + self.base_dn, "objectclass": "user", "dBCSPwd": [] }) self.fail() except LdbError as e33: (num, _) = e33.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb.add({ "dn": "cn=testuser2,cn=users," + self.base_dn, "objectclass": "user", "userPassword": [] }) self.fail() except LdbError as e34: (num, _) = e34.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) try: self.ldb.add({ "dn": "cn=testuser2,cn=users," + self.base_dn, "objectclass": "user", "clearTextPassword": [] }) self.fail() except LdbError as e35: (num, _) = e35.args self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or num == ERR_NO_SUCH_ATTRIBUTE) # for Windows delete_force(self.ldb, "cn=testuser2,cn=users," + self.base_dn) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["unicodePwd"] = MessageElement([], FLAG_MOD_ADD, "unicodePwd") try: self.ldb.modify(m) self.fail() except LdbError as e36: (num, _) = e36.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["dBCSPwd"] = MessageElement([], FLAG_MOD_ADD, "dBCSPwd") try: self.ldb.modify(m) self.fail() except LdbError as e37: (num, _) = e37.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement([], FLAG_MOD_ADD, "userPassword") try: self.ldb.modify(m) self.fail() except LdbError as e38: (num, _) = e38.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["clearTextPassword"] = MessageElement([], FLAG_MOD_ADD, "clearTextPassword") try: self.ldb.modify(m) self.fail() except LdbError as e39: (num, _) = e39.args self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or num == ERR_NO_SUCH_ATTRIBUTE) # for Windows m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["unicodePwd"] = MessageElement([], FLAG_MOD_REPLACE, "unicodePwd") try: self.ldb.modify(m) self.fail() except LdbError as e40: (num, _) = e40.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["dBCSPwd"] = MessageElement([], FLAG_MOD_REPLACE, "dBCSPwd") try: self.ldb.modify(m) self.fail() except LdbError as e41: (num, _) = e41.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement([], FLAG_MOD_REPLACE, "userPassword") try: self.ldb.modify(m) self.fail() except LdbError as e42: (num, _) = e42.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["clearTextPassword"] = MessageElement([], FLAG_MOD_REPLACE, "clearTextPassword") try: self.ldb.modify(m) self.fail() except LdbError as e43: (num, _) = e43.args self.assertTrue(num == ERR_UNWILLING_TO_PERFORM or num == ERR_NO_SUCH_ATTRIBUTE) # for Windows m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["unicodePwd"] = MessageElement([], FLAG_MOD_DELETE, "unicodePwd") try: self.ldb.modify(m) self.fail() except LdbError as e44: (num, _) = e44.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["dBCSPwd"] = MessageElement([], FLAG_MOD_DELETE, "dBCSPwd") try: self.ldb.modify(m) self.fail() except LdbError as e45: (num, _) = e45.args self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement([], FLAG_MOD_DELETE, "userPassword") try: self.ldb.modify(m) self.fail() except LdbError as e46: (num, _) = e46.args self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["clearTextPassword"] = MessageElement([], FLAG_MOD_DELETE, "clearTextPassword") try: self.ldb.modify(m) self.fail() except LdbError as e47: (num, _) = e47.args self.assertTrue(num == ERR_CONSTRAINT_VIOLATION or num == ERR_NO_SUCH_ATTRIBUTE) # for Windows def test_plain_userPassword(self): print("Performs testing about the standard 'userPassword' behaviour") # Delete the "dSHeuristics" self.ldb.set_dsheuristics(None) time.sleep(1) # This switching time is strictly needed! m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement("myPassword", FLAG_MOD_ADD, "userPassword") self.ldb.modify(m) res = self.ldb.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) self.assertTrue(len(res) == 1) self.assertTrue("userPassword" in res[0]) self.assertEquals(res[0]["userPassword"][0], "myPassword") m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement("myPassword2", FLAG_MOD_REPLACE, "userPassword") self.ldb.modify(m) res = self.ldb.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) self.assertTrue(len(res) == 1) self.assertTrue("userPassword" in res[0]) self.assertEquals(res[0]["userPassword"][0], "myPassword2") m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement([], FLAG_MOD_DELETE, "userPassword") self.ldb.modify(m) res = self.ldb.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) self.assertTrue(len(res) == 1) self.assertFalse("userPassword" in res[0]) # Set the test "dSHeuristics" to deactivate "userPassword" pwd changes self.ldb.set_dsheuristics("000000000") m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement("myPassword3", FLAG_MOD_REPLACE, "userPassword") self.ldb.modify(m) res = self.ldb.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) self.assertTrue(len(res) == 1) self.assertTrue("userPassword" in res[0]) self.assertEquals(res[0]["userPassword"][0], "myPassword3") # Set the test "dSHeuristics" to deactivate "userPassword" pwd changes self.ldb.set_dsheuristics("000000002") m = Message() m.dn = Dn(self.ldb, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement("myPassword4", FLAG_MOD_REPLACE, "userPassword") self.ldb.modify(m) res = self.ldb.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) self.assertTrue(len(res) == 1) self.assertTrue("userPassword" in res[0]) self.assertEquals(res[0]["userPassword"][0], "myPassword4") # Reset the test "dSHeuristics" (reactivate "userPassword" pwd changes) self.ldb.set_dsheuristics("000000001") def test_modify_dsheuristics_userPassword(self): print("Performs testing about reading userPassword between dsHeuristic modifies") # Make sure userPassword cannot be read self.ldb.set_dsheuristics("000000000") # Open a new connection (with dsHeuristic=000000000) ldb1 = SamDB(url=host, session_info=system_session(lp), credentials=creds, lp=lp) # Set userPassword to be read # This setting only affects newer connections (ldb2) ldb1.set_dsheuristics("000000001") time.sleep(1) m = Message() m.dn = Dn(ldb1, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement("thatsAcomplPASS1", FLAG_MOD_REPLACE, "userPassword") ldb1.modify(m) res = ldb1.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) # userPassword cannot be read, despite the dsHeuristic setting self.assertTrue(len(res) == 1) self.assertFalse("userPassword" in res[0]) # Open another new connection (with dsHeuristic=000000001) ldb2 = SamDB(url=host, session_info=system_session(lp), credentials=creds, lp=lp) # Set userPassword to be unreadable # This setting does not affect this connection ldb2.set_dsheuristics("000000000") time.sleep(1) res = ldb2.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) # Check that userPassword was not stored from ldb1 self.assertTrue(len(res) == 1) self.assertFalse("userPassword" in res[0]) m = Message() m.dn = Dn(ldb2, "cn=testuser,cn=users," + self.base_dn) m["userPassword"] = MessageElement("thatsAcomplPASS2", FLAG_MOD_REPLACE, "userPassword") ldb2.modify(m) res = ldb2.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) # userPassword can be read in this connection # This is regardless of the current dsHeuristics setting self.assertTrue(len(res) == 1) self.assertTrue("userPassword" in res[0]) self.assertEquals(res[0]["userPassword"][0], "thatsAcomplPASS2") # Only password from ldb1 is the user's password creds2 = Credentials() creds2.set_username("testuser") creds2.set_password("thatsAcomplPASS1") creds2.set_domain(creds.get_domain()) creds2.set_realm(creds.get_realm()) creds2.set_workstation(creds.get_workstation()) creds2.set_gensec_features(creds2.get_gensec_features() | gensec.FEATURE_SEAL) try: SamDB(url=host, credentials=creds2, lp=lp) except: self.fail("testuser used the wrong password") ldb3 = SamDB(url=host, session_info=system_session(lp), credentials=creds, lp=lp) # Check that userPassword was stored from ldb2 res = ldb3.search("cn=testuser,cn=users," + self.base_dn, scope=SCOPE_BASE, attrs=["userPassword"]) # userPassword can be read self.assertTrue(len(res) == 1) self.assertTrue("userPassword" in res[0]) self.assertEquals(res[0]["userPassword"][0], "thatsAcomplPASS2") # Reset the test "dSHeuristics" (reactivate "userPassword" pwd changes) self.ldb.set_dsheuristics("000000001") def test_zero_length(self): # Get the old "minPwdLength" minPwdLength = self.ldb.get_minPwdLength() # Set it temporarely to "0" self.ldb.set_minPwdLength("0") # Get the old "pwdProperties" pwdProperties = self.ldb.get_pwdProperties() # Set them temporarely to "0" (to deactivate eventually the complexity) self.ldb.set_pwdProperties("0") self.ldb.setpassword("(sAMAccountName=testuser)", "") # Reset the "pwdProperties" as they were before self.ldb.set_pwdProperties(pwdProperties) # Reset the "minPwdLength" as it was before self.ldb.set_minPwdLength(minPwdLength) def test_pw_change_delete_no_value_userPassword(self): """Test password change with userPassword where the delete attribute doesn't have a value""" try: self.ldb2.modify_ldif(""" dn: cn=testuser,cn=users,""" + self.base_dn + """ changetype: modify delete: userPassword add: userPassword userPassword: thatsAcomplPASS1 """) except LdbError, (num, msg): self.assertEquals(num, ERR_CONSTRAINT_VIOLATION) else:
class cmd_drs_replicate(Command): """Replicate a naming context between two DCs.""" synopsis = "%prog <destinationDC> <sourceDC> <NC> [options]" takes_optiongroups = { "sambaopts": options.SambaOptions, "versionopts": options.VersionOptions, "credopts": options.CredentialsOptions, } takes_args = ["DEST_DC", "SOURCE_DC", "NC"] takes_options = [ Option("--add-ref", help="use ADD_REF to add to repsTo on source", action="store_true"), Option("--sync-forced", help="use SYNC_FORCED to force inbound replication", action="store_true"), Option("--sync-all", help="use SYNC_ALL to replicate from all DCs", action="store_true"), Option("--full-sync", help="resync all objects", action="store_true"), Option("--local", help="pull changes directly into the local database (destination DC is ignored)", action="store_true"), Option("--local-online", help="pull changes into the local database (destination DC is ignored) as a normal online replication", action="store_true"), Option("--async-op", help="use ASYNC_OP for the replication", action="store_true"), Option("--single-object", help="Replicate only the object specified, instead of the whole Naming Context (only with --local)", action="store_true"), ] def drs_local_replicate(self, SOURCE_DC, NC, full_sync=False, single_object=False, sync_forced=False): '''replicate from a source DC to the local SAM''' self.server = SOURCE_DC drsuapi_connect(self) self.local_samdb = SamDB(session_info=system_session(), url=None, credentials=self.creds, lp=self.lp) self.samdb = SamDB(url="ldap://%s" % self.server, session_info=system_session(), credentials=self.creds, lp=self.lp) # work out the source and destination GUIDs res = self.local_samdb.search(base="", scope=ldb.SCOPE_BASE, attrs=["dsServiceName"]) self.ntds_dn = res[0]["dsServiceName"][0] res = self.local_samdb.search(base=self.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"]) self.ntds_guid = misc.GUID( self.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0])) source_dsa_invocation_id = misc.GUID(self.samdb.get_invocation_id()) dest_dsa_invocation_id = misc.GUID(self.local_samdb.get_invocation_id()) destination_dsa_guid = self.ntds_guid exop = drsuapi.DRSUAPI_EXOP_NONE if single_object: exop = drsuapi.DRSUAPI_EXOP_REPL_OBJ full_sync = True self.samdb.transaction_start() repl = drs_utils.drs_Replicate("ncacn_ip_tcp:%s[seal]" % self.server, self.lp, self.creds, self.local_samdb, dest_dsa_invocation_id) # Work out if we are an RODC, so that a forced local replicate # with the admin pw does not sync passwords rodc = self.local_samdb.am_rodc() try: (num_objects, num_links) = repl.replicate(NC, source_dsa_invocation_id, destination_dsa_guid, rodc=rodc, full_sync=full_sync, exop=exop, sync_forced=sync_forced) except Exception as e: raise CommandError("Error replicating DN %s" % NC, e) self.samdb.transaction_commit() if full_sync: self.message("Full Replication of all %d objects and %d links " "from %s to %s was successful." % (num_objects, num_links, SOURCE_DC, self.local_samdb.url)) else: self.message("Incremental replication of %d objects and %d links " "from %s to %s was successful." % (num_objects, num_links, SOURCE_DC, self.local_samdb.url)) def run(self, DEST_DC, SOURCE_DC, NC, add_ref=False, sync_forced=False, sync_all=False, full_sync=False, local=False, local_online=False, async_op=False, single_object=False, sambaopts=None, credopts=None, versionopts=None): self.server = DEST_DC self.lp = sambaopts.get_loadparm() self.creds = credopts.get_credentials(self.lp, fallback_machine=True) if local: self.drs_local_replicate(SOURCE_DC, NC, full_sync=full_sync, single_object=single_object, sync_forced=sync_forced) return if local_online: server_bind = drsuapi.drsuapi("irpc:dreplsrv", lp_ctx=self.lp) server_bind_handle = misc.policy_handle() else: drsuapi_connect(self) server_bind = self.drsuapi server_bind_handle = self.drsuapi_handle if not async_op: # Give the sync replication 5 minutes time server_bind.request_timeout = 5 * 60 samdb_connect(self) # we need to find the NTDS GUID of the source DC msg = self.samdb.search(base=self.samdb.get_config_basedn(), expression="(&(objectCategory=server)(|(name=%s)(dNSHostName=%s)))" % ( ldb.binary_encode(SOURCE_DC), ldb.binary_encode(SOURCE_DC)), attrs=[]) if len(msg) == 0: raise CommandError("Failed to find source DC %s" % SOURCE_DC) server_dn = msg[0]['dn'] msg = self.samdb.search(base=server_dn, scope=ldb.SCOPE_ONELEVEL, expression="(|(objectCategory=nTDSDSA)(objectCategory=nTDSDSARO))", attrs=['objectGUID', 'options']) if len(msg) == 0: raise CommandError("Failed to find source NTDS DN %s" % SOURCE_DC) source_dsa_guid = msg[0]['objectGUID'][0] dsa_options = int(attr_default(msg, 'options', 0)) req_options = 0 if not (dsa_options & dsdb.DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL): req_options |= drsuapi.DRSUAPI_DRS_WRIT_REP if add_ref: req_options |= drsuapi.DRSUAPI_DRS_ADD_REF if sync_forced: req_options |= drsuapi.DRSUAPI_DRS_SYNC_FORCED if sync_all: req_options |= drsuapi.DRSUAPI_DRS_SYNC_ALL if full_sync: req_options |= drsuapi.DRSUAPI_DRS_FULL_SYNC_NOW if async_op: req_options |= drsuapi.DRSUAPI_DRS_ASYNC_OP try: drs_utils.sendDsReplicaSync(server_bind, server_bind_handle, source_dsa_guid, NC, req_options) except drs_utils.drsException as estr: raise CommandError("DsReplicaSync failed", estr) if async_op: self.message("Replicate from %s to %s was started." % (SOURCE_DC, DEST_DC)) else: self.message("Replicate from %s to %s was successful." % (SOURCE_DC, DEST_DC))
class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase): def setUp(self): super(AuthLogPassChangeTests, self).setUp() self.remoteAddress = os.environ["CLIENT_IP"] self.server_ip = os.environ["SERVER_IP"] host = "ldap://%s" % os.environ["SERVER"] self.ldb = SamDB(url=host, session_info=system_session(), credentials=self.get_credentials(), lp=self.get_loadparm()) print "ldb %s" % type(self.ldb) # Gets back the basedn base_dn = self.ldb.domain_dn() print "base_dn %s" % base_dn # Gets back the configuration basedn configuration_dn = self.ldb.get_config_basedn().get_linearized() # Get the old "dSHeuristics" if it was set dsheuristics = self.ldb.get_dsheuristics() # Set the "dSHeuristics" to activate the correct "userPassword" # behaviour self.ldb.set_dsheuristics("000000001") # Reset the "dSHeuristics" as they were before self.addCleanup(self.ldb.set_dsheuristics, dsheuristics) # Get the old "minPwdAge" minPwdAge = self.ldb.get_minPwdAge() # Set it temporarily to "0" self.ldb.set_minPwdAge("0") self.base_dn = self.ldb.domain_dn() # Reset the "minPwdAge" as it was before self.addCleanup(self.ldb.set_minPwdAge, minPwdAge) # (Re)adds the test user USER_NAME with password USER_PASS delete_force(self.ldb, "cn=" + USER_NAME + ",cn=users," + self.base_dn) self.ldb.add({ "dn": "cn=" + USER_NAME + ",cn=users," + self.base_dn, "objectclass": "user", "sAMAccountName": USER_NAME, "userPassword": USER_PASS }) # discard any auth log messages for the password setup self.discardMessages() def tearDown(self): super(AuthLogPassChangeTests, self).tearDown() def test_admin_change_password(self): def isLastExpectedMessage(msg): return (msg["type"] == "Authentication" and msg["Authentication"]["status"] == "NT_STATUS_OK" and msg["Authentication"]["serviceDescription"] == "SAMR Password Change" and msg["Authentication"]["authDescription"] == "samr_ChangePasswordUser3") creds = self.insta_creds(template = self.get_credentials()) lp = self.get_loadparm() net = Net(creds, lp, server=self.server_ip) password = "******" net.change_password(newpassword=password.encode('utf-8'), username=USER_NAME, oldpassword=USER_PASS) messages = self.waitForMessages(isLastExpectedMessage) print "Received %d messages" % len(messages) self.assertEquals(8, len(messages), "Did not receive the expected number of messages") def test_admin_change_password_new_password_fails_restriction(self): def isLastExpectedMessage(msg): return (msg["type"] == "Authentication" and msg["Authentication"]["status"] == "NT_STATUS_PASSWORD_RESTRICTION" and msg["Authentication"]["serviceDescription"] == "SAMR Password Change" and msg["Authentication"]["authDescription"] == "samr_ChangePasswordUser3") creds = self.insta_creds(template=self.get_credentials()) lp = self.get_loadparm() net = Net(creds, lp, server=self.server_ip) password = "******" exception_thrown = False try: net.change_password(newpassword=password.encode('utf-8'), oldpassword=USER_PASS, username=USER_NAME) except Exception as msg: exception_thrown = True self.assertEquals(True, exception_thrown, "Expected exception not thrown") messages = self.waitForMessages(isLastExpectedMessage) self.assertEquals(8, len(messages), "Did not receive the expected number of messages") def test_admin_change_password_unknown_user(self): def isLastExpectedMessage(msg): return (msg["type"] == "Authentication" and msg["Authentication"]["status"] == "NT_STATUS_NO_SUCH_USER" and msg["Authentication"]["serviceDescription"] == "SAMR Password Change" and msg["Authentication"]["authDescription"] == "samr_ChangePasswordUser3") creds = self.insta_creds(template=self.get_credentials()) lp = self.get_loadparm() net = Net(creds, lp, server=self.server_ip) password = "******" exception_thrown = False try: net.change_password(newpassword=password.encode('utf-8'), oldpassword=USER_PASS, username="******") except Exception as msg: exception_thrown = True self.assertEquals(True, exception_thrown, "Expected exception not thrown") messages = self.waitForMessages(isLastExpectedMessage) self.assertEquals(8, len(messages), "Did not receive the expected number of messages") def test_admin_change_password_bad_original_password(self): def isLastExpectedMessage(msg): return (msg["type"] == "Authentication" and msg["Authentication"]["status"] == "NT_STATUS_WRONG_PASSWORD" and msg["Authentication"]["serviceDescription"] == "SAMR Password Change" and msg["Authentication"]["authDescription"] == "samr_ChangePasswordUser3") creds = self.insta_creds(template=self.get_credentials()) lp = self.get_loadparm() net = Net(creds, lp, server=self.server_ip) password = "******" exception_thrown = False try: net.change_password(newpassword=password.encode('utf-8'), oldpassword="******", username=USER_NAME) except Exception as msg: exception_thrown = True self.assertEquals(True, exception_thrown, "Expected exception not thrown") messages = self.waitForMessages(isLastExpectedMessage) self.assertEquals(8, len(messages), "Did not receive the expected number of messages") # net rap password changes are broken, but they trigger enough of the # server side behaviour to exercise the code paths of interest. # if we used the real password it would be too long and does not hash # correctly, so we just check it triggers the wrong password path. def test_rap_change_password(self): def isLastExpectedMessage(msg): return (msg["type"] == "Authentication" and msg["Authentication"]["serviceDescription"] == "SAMR Password Change" and msg["Authentication"]["status"] == "NT_STATUS_WRONG_PASSWORD" and msg["Authentication"]["authDescription"] == "OemChangePasswordUser2") username = os.environ["USERNAME"] server = os.environ["SERVER"] password = os.environ["PASSWORD"] server_param = "--server=%s" % server creds = "-U%s%%%s" % (username,password) call(["bin/net", "rap", server_param, "password", USER_NAME, "notMyPassword", "notGoingToBeMyPassword", server, creds, "--option=client ipc max protocol=nt1"]) messages = self.waitForMessages(isLastExpectedMessage) self.assertEquals(7, len(messages), "Did not receive the expected number of messages") def test_ldap_change_password(self): def isLastExpectedMessage(msg): return (msg["type"] == "Authentication" and msg["Authentication"]["status"] == "NT_STATUS_OK" and msg["Authentication"]["serviceDescription"] == "LDAP Password Change" and msg["Authentication"]["authDescription"] == "LDAP Modify") new_password = samba.generate_random_password(32,32) self.ldb.modify_ldif( "dn: cn=" + USER_NAME + ",cn=users," + self.base_dn + "\n" + "changetype: modify\n" + "delete: userPassword\n" + "userPassword: "******"\n" + "add: userPassword\n" + "userPassword: "******"\n" ) messages = self.waitForMessages(isLastExpectedMessage) print "Received %d messages" % len(messages) self.assertEquals(4, len(messages), "Did not receive the expected number of messages") # # Currently this does not get logged, so we expect to only see the log # entries for the underlying ldap bind. # def test_ldap_change_password_bad_user(self): def isLastExpectedMessage(msg): return (msg["type"] == "Authorization" and msg["Authorization"]["serviceDescription"] == "LDAP" and msg["Authorization"]["authType"] == "krb5") new_password = samba.generate_random_password(32,32) try: self.ldb.modify_ldif( "dn: cn=" + "badUser" + ",cn=users," + self.base_dn + "\n" + "changetype: modify\n" + "delete: userPassword\n" + "userPassword: "******"\n" + "add: userPassword\n" + "userPassword: "******"\n" ) self.fail() except LdbError, (num, msg): pass messages = self.waitForMessages(isLastExpectedMessage) print "Received %d messages" % len(messages) self.assertEquals(3, len(messages), "Did not receive the expected number of messages")
def checkusage(names, lp, creds): """Checks whether this server is already provisioned and is being used. :param names: provision names object. :param lp: Loadparm context :param creds: Credentials Context """ session_info = system_session() samdb = SamDB(url=get_ldb_url(lp, creds, names), session_info=session_info, credentials=creds, lp=lp) try: config_dn = samdb.get_config_basedn() mapi_servers = samdb.search( base=config_dn, scope=ldb.SCOPE_SUBTREE, expression="(&(objectClass=msExchExchangeServer)(cn=%s))" % names.netbiosname) server_uses = [] if len(mapi_servers) == 0: # The server is not provisioned. raise NotProvisionedError if len(mapi_servers) > 1: # Check if we are the primary folder store server. our_siteFolderName = "CN=Public Folder Store (%s),CN=First Storage Group,CN=InformationStore,CN=%s,CN=Servers,CN=%s,CN=AdministrativeGroups,%s" % (names.netbiosname, names.netbiosname, names.firstou, names.firstorgdn) dn = "CN=%s,CN=Administrative Groups,%s" % (names.firstou, names.firstorgdn) ret = samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=['siteFolderServer']) assert len(ret) == 1 siteFolderName = ret[0]["siteFolderServer"][0] if our_siteFolderName.lower() == siteFolderName.lower(): server_uses.append("primary folder store server") # Check if we are the primary receipt update service our_addressListServiceLink = "CN=%s,CN=Servers,CN=%s,CN=Administrative Groups,%s" % (names.netbiosname, names.firstou, names.firstorgdn) dn = "CN=Recipient Update Service (%s),CN=Recipient Update Services,CN=Address Lists Container,%s" % (names.domain, names.firstorgdn) ret = samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=['msExchAddressListServiceLink']) assert len(ret) == 1 addressListServiceLink = ret[0]['msExchAddressListServiceLink'][0] if our_addressListServiceLink.lower() == addressListServiceLink.lower(): server_uses.append("primary receipt update service server") # Check if we handle any mailbox. db = Ldb( url=get_ldb_url(lp, creds, names), session_info=system_session(), credentials=creds, lp=lp) our_mailbox_store = "CN=Mailbox Store (%s),CN=First Storage Group,CN=InformationStore,CN=%s,CN=Servers,CN=%s,CN=Administrative Groups,%s" % (names.netbiosname, names.netbiosname, names.firstou, names.firstorgdn) mailboxes = db.search( base=names.domaindn, scope=ldb.SCOPE_SUBTREE, expression="(homeMDB=*)") mailboxes_handled = 0 for user_mailbox in mailboxes: if (user_mailbox['homeMDB'][0] == our_mailbox_store and user_mailbox['msExchUserAccountControl'][0] != '2'): mailboxes_handled += 1 if mailboxes_handled > 0: server_uses.append( "handling %d mailboxes" % mailboxes_handled) return server_uses except LdbError, ldb_error: print >> sys.stderr, "[!] error while checking whether this server is being used (%d): %s" % ldb_error.args raise ldb_error
class DsdbLockTestCase(SamDBTestCase): def test_db_lock1(self): basedn = self.samdb.get_default_basedn() (r1, w1) = os.pipe() pid = os.fork() if pid == 0: # In the child, close the main DB, re-open just one DB del (self.samdb) gc.collect() self.samdb = SamDB(session_info=self.session, lp=self.lp) self.samdb.transaction_start() dn = "cn=test_db_lock_user,cn=users," + str(basedn) self.samdb.add({ "dn": dn, "objectclass": "user", }) self.samdb.delete(dn) # Obtain a write lock self.samdb.transaction_prepare_commit() os.write(w1, b"prepared") time.sleep(2) # Drop the write lock self.samdb.transaction_cancel() os._exit(0) self.assertEqual(os.read(r1, 8), b"prepared") start = time.time() # We need to hold this iterator open to hold the all-record lock. res = self.samdb.search_iterator() # This should take at least 2 seconds because the transaction # has a write lock on one backend db open # Release the locks for l in res: pass end = time.time() self.assertGreater(end - start, 1.9) (got_pid, status) = os.waitpid(pid, 0) self.assertEqual(got_pid, pid) self.assertTrue(os.WIFEXITED(status)) self.assertEqual(os.WEXITSTATUS(status), 0) def test_db_lock2(self): basedn = self.samdb.get_default_basedn() (r1, w1) = os.pipe() (r2, w2) = os.pipe() pid = os.fork() if pid == 0: # In the child, close the main DB, re-open del (self.samdb) gc.collect() self.samdb = SamDB(session_info=self.session, lp=self.lp) # We need to hold this iterator open to hold the all-record lock. res = self.samdb.search_iterator() os.write(w2, b"start") if (os.read(r1, 7) != b"started"): os._exit(1) os.write(w2, b"add") if (os.read(r1, 5) != b"added"): os._exit(2) # Wait 2 seconds to block prepare_commit() in the child. os.write(w2, b"prepare") time.sleep(2) # Release the locks for l in res: pass if (os.read(r1, 8) != b"prepared"): os._exit(3) os._exit(0) # We can start the transaction during the search # because both just grab the all-record read lock. self.assertEqual(os.read(r2, 5), b"start") self.samdb.transaction_start() os.write(w1, b"started") self.assertEqual(os.read(r2, 3), b"add") dn = "cn=test_db_lock_user,cn=users," + str(basedn) self.samdb.add({ "dn": dn, "objectclass": "user", }) self.samdb.delete(dn) os.write(w1, b"added") # Obtain a write lock, this will block until # the parent releases the read lock. self.assertEqual(os.read(r2, 7), b"prepare") start = time.time() self.samdb.transaction_prepare_commit() end = time.time() try: self.assertGreater(end - start, 1.9) except: raise finally: os.write(w1, b"prepared") # Drop the write lock self.samdb.transaction_cancel() (got_pid, status) = os.waitpid(pid, 0) self.assertEqual(got_pid, pid) self.assertTrue(os.WIFEXITED(status)) self.assertEqual(os.WEXITSTATUS(status), 0) def test_db_lock3(self): basedn = self.samdb.get_default_basedn() (r1, w1) = os.pipe() (r2, w2) = os.pipe() pid = os.fork() if pid == 0: # In the child, close the main DB, re-open del (self.samdb) gc.collect() self.samdb = SamDB(session_info=self.session, lp=self.lp) # We need to hold this iterator open to hold the all-record lock. res = self.samdb.search_iterator() os.write(w2, b"start") if (os.read(r1, 7) != b"started"): os._exit(1) os.write(w2, b"add") if (os.read(r1, 5) != b"added"): os._exit(2) # Wait 2 seconds to block prepare_commit() in the child. os.write(w2, b"prepare") time.sleep(2) # Release the locks for l in res: pass if (os.read(r1, 8) != b"prepared"): os._exit(3) os._exit(0) # We can start the transaction during the search # because both just grab the all-record read lock. self.assertEqual(os.read(r2, 5), b"start") self.samdb.transaction_start() os.write(w1, b"started") self.assertEqual(os.read(r2, 3), b"add") # This will end up in the top level db dn = "@DSDB_LOCK_TEST" self.samdb.add({"dn": dn}) self.samdb.delete(dn) os.write(w1, b"added") # Obtain a write lock, this will block until # the child releases the read lock. self.assertEqual(os.read(r2, 7), b"prepare") start = time.time() self.samdb.transaction_prepare_commit() end = time.time() self.assertGreater(end - start, 1.9) os.write(w1, b"prepared") # Drop the write lock self.samdb.transaction_cancel() (got_pid, status) = os.waitpid(pid, 0) self.assertTrue(os.WIFEXITED(status)) self.assertEqual(os.WEXITSTATUS(status), 0) self.assertEqual(got_pid, pid) def _test_full_db_lock1(self, backend_path): (r1, w1) = os.pipe() pid = os.fork() if pid == 0: # In the child, close the main DB, re-open just one DB del (self.samdb) gc.collect() backenddb = ldb.Ldb(backend_path) backenddb.transaction_start() backenddb.add({"dn": "@DSDB_LOCK_TEST"}) backenddb.delete("@DSDB_LOCK_TEST") # Obtain a write lock backenddb.transaction_prepare_commit() os.write(w1, b"prepared") time.sleep(2) # Drop the write lock backenddb.transaction_cancel() os._exit(0) self.assertEqual(os.read(r1, 8), b"prepared") start = time.time() # We need to hold this iterator open to hold the all-record lock. res = self.samdb.search_iterator() # This should take at least 2 seconds because the transaction # has a write lock on one backend db open end = time.time() self.assertGreater(end - start, 1.9) # Release the locks for l in res: pass (got_pid, status) = os.waitpid(pid, 0) self.assertEqual(got_pid, pid) self.assertTrue(os.WIFEXITED(status)) self.assertEqual(os.WEXITSTATUS(status), 0) def test_full_db_lock1(self): basedn = self.samdb.get_default_basedn() backend_filename = "%s.ldb" % basedn.get_casefold() backend_subpath = os.path.join("sam.ldb.d", backend_filename) backend_path = self.lp.private_path(backend_subpath) self._test_full_db_lock1(backend_path) def test_full_db_lock1_config(self): basedn = self.samdb.get_config_basedn() backend_filename = "%s.ldb" % basedn.get_casefold() backend_subpath = os.path.join("sam.ldb.d", backend_filename) backend_path = self.lp.private_path(backend_subpath) self._test_full_db_lock1(backend_path) def _test_full_db_lock2(self, backend_path): (r1, w1) = os.pipe() (r2, w2) = os.pipe() pid = os.fork() if pid == 0: # In the child, close the main DB, re-open del (self.samdb) gc.collect() self.samdb = SamDB(session_info=self.session, lp=self.lp) # We need to hold this iterator open to hold the all-record lock. res = self.samdb.search_iterator() os.write(w2, b"start") if (os.read(r1, 7) != b"started"): os._exit(1) os.write(w2, b"add") if (os.read(r1, 5) != b"added"): os._exit(2) # Wait 2 seconds to block prepare_commit() in the child. os.write(w2, b"prepare") time.sleep(2) # Release the locks for l in res: pass if (os.read(r1, 8) != b"prepared"): os._exit(3) os._exit(0) # In the parent, close the main DB, re-open just one DB del (self.samdb) gc.collect() backenddb = ldb.Ldb(backend_path) # We can start the transaction during the search # because both just grab the all-record read lock. self.assertEqual(os.read(r2, 5), b"start") backenddb.transaction_start() os.write(w1, b"started") self.assertEqual(os.read(r2, 3), b"add") backenddb.add({"dn": "@DSDB_LOCK_TEST"}) backenddb.delete("@DSDB_LOCK_TEST") os.write(w1, b"added") # Obtain a write lock, this will block until # the child releases the read lock. self.assertEqual(os.read(r2, 7), b"prepare") start = time.time() backenddb.transaction_prepare_commit() end = time.time() try: self.assertGreater(end - start, 1.9) except: raise finally: os.write(w1, b"prepared") # Drop the write lock backenddb.transaction_cancel() (got_pid, status) = os.waitpid(pid, 0) self.assertEqual(got_pid, pid) self.assertTrue(os.WIFEXITED(status)) self.assertEqual(os.WEXITSTATUS(status), 0) def test_full_db_lock2(self): basedn = self.samdb.get_default_basedn() backend_filename = "%s.ldb" % basedn.get_casefold() backend_subpath = os.path.join("sam.ldb.d", backend_filename) backend_path = self.lp.private_path(backend_subpath) self._test_full_db_lock2(backend_path) def test_full_db_lock2_config(self): basedn = self.samdb.get_config_basedn() backend_filename = "%s.ldb" % basedn.get_casefold() backend_subpath = os.path.join("sam.ldb.d", backend_filename) backend_path = self.lp.private_path(backend_subpath) self._test_full_db_lock2(backend_path)
class PassWordHashTests(TestCase): def setUp(self): super(PassWordHashTests, self).setUp() # Add a user to ldb, this will exercise the password_hash code # and calculate the appropriate supplemental credentials def add_user(self, options=None, clear_text=False): self.lp = samba.tests.env_loadparm() # set any needed options if options is not None: for (option,value) in options: self.lp.set(option, value) self.creds = Credentials() self.session = system_session() self.ldb = SamDB( session_info=self.session, credentials=self.creds, lp=self.lp) # Gets back the basedn base_dn = self.ldb.domain_dn() # Gets back the configuration basedn configuration_dn = self.ldb.get_config_basedn().get_linearized() # Get the old "dSHeuristics" if it was set dsheuristics = self.ldb.get_dsheuristics() # Set the "dSHeuristics" to activate the correct "userPassword" # behaviour self.ldb.set_dsheuristics("000000001") # Reset the "dSHeuristics" as they were before self.addCleanup(self.ldb.set_dsheuristics, dsheuristics) # Get the old "minPwdAge" minPwdAge = self.ldb.get_minPwdAge() # Set it temporarily to "0" self.ldb.set_minPwdAge("0") self.base_dn = self.ldb.domain_dn() # Reset the "minPwdAge" as it was before self.addCleanup(self.ldb.set_minPwdAge, minPwdAge) account_control = 0 if clear_text: # get the current pwdProperties pwdProperties = self.ldb.get_pwdProperties() # enable clear text properties props = int(pwdProperties) props |= DOMAIN_PASSWORD_STORE_CLEARTEXT self.ldb.set_pwdProperties(str(props)) # Restore the value on exit. self.addCleanup(self.ldb.set_pwdProperties, pwdProperties) account_control |= UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED # (Re)adds the test user USER_NAME with password USER_PASS # and userPrincipalName UPN delete_force(self.ldb, "cn=" + USER_NAME + ",cn=users," + self.base_dn) self.ldb.add({ "dn": "cn=" + USER_NAME + ",cn=users," + self.base_dn, "objectclass": "user", "sAMAccountName": USER_NAME, "userPassword": USER_PASS, "userPrincipalName": UPN, "userAccountControl": str(account_control) }) # Get the supplemental credentials for the user under test def get_supplemental_creds(self): base = "cn=" + USER_NAME + ",cn=users," + self.base_dn res = self.ldb.search(scope=ldb.SCOPE_BASE, base=base, attrs=["supplementalCredentials"]) self.assertIs( True, len(res) > 0) obj = res[0] sc_blob = obj["supplementalCredentials"][0] sc = ndr_unpack(drsblobs.supplementalCredentialsBlob, sc_blob) return sc # Calculate and validate a Wdigest value def check_digest(self, user, realm, password, digest): expected = calc_digest( user, realm, password) actual = binascii.hexlify(bytearray(digest)) error = "Digest expected[%s], actual[%s], " \ "user[%s], realm[%s], pass[%s]" % \ (expected, actual, user, realm, password) self.assertEquals(expected, actual, error) # Check all of the 29 expected WDigest values # def check_wdigests(self, digests): self.assertEquals(29, digests.num_hashes) self.check_digest(USER_NAME, self.lp.get("workgroup"), USER_PASS, digests.hashes[0].hash) self.check_digest(USER_NAME.lower(), self.lp.get("workgroup").lower(), USER_PASS, digests.hashes[1].hash) self.check_digest(USER_NAME.upper(), self.lp.get("workgroup").upper(), USER_PASS, digests.hashes[2].hash) self.check_digest(USER_NAME, self.lp.get("workgroup").upper(), USER_PASS, digests.hashes[3].hash) self.check_digest(USER_NAME, self.lp.get("workgroup").lower(), USER_PASS, digests.hashes[4].hash) self.check_digest(USER_NAME.upper(), self.lp.get("workgroup").lower(), USER_PASS, digests.hashes[5].hash) self.check_digest(USER_NAME.lower(), self.lp.get("workgroup").upper(), USER_PASS, digests.hashes[6].hash) self.check_digest(USER_NAME, self.lp.get("realm").lower(), USER_PASS, digests.hashes[7].hash) self.check_digest(USER_NAME.lower(), self.lp.get("realm").lower(), USER_PASS, digests.hashes[8].hash) self.check_digest(USER_NAME.upper(), self.lp.get("realm"), USER_PASS, digests.hashes[9].hash) self.check_digest(USER_NAME, self.lp.get("realm"), USER_PASS, digests.hashes[10].hash) self.check_digest(USER_NAME, self.lp.get("realm").lower(), USER_PASS, digests.hashes[11].hash) self.check_digest(USER_NAME.upper(), self.lp.get("realm").lower(), USER_PASS, digests.hashes[12].hash) self.check_digest(USER_NAME.lower(), self.lp.get("realm"), USER_PASS, digests.hashes[13].hash) self.check_digest(UPN, "", USER_PASS, digests.hashes[14].hash) self.check_digest(UPN.lower(), "", USER_PASS, digests.hashes[15].hash) self.check_digest(UPN.upper(), "", USER_PASS, digests.hashes[16].hash) name = "%s\\%s" % (self.lp.get("workgroup"), USER_NAME) self.check_digest(name, "", USER_PASS, digests.hashes[17].hash) name = "%s\\%s" % (self.lp.get("workgroup").lower(), USER_NAME.lower()) self.check_digest(name, "", USER_PASS, digests.hashes[18].hash) name = "%s\\%s" % (self.lp.get("workgroup").upper(), USER_NAME.upper()) self.check_digest(name, "", USER_PASS, digests.hashes[19].hash) self.check_digest(USER_NAME, "Digest", USER_PASS, digests.hashes[20].hash) self.check_digest(USER_NAME.lower(), "Digest", USER_PASS, digests.hashes[21].hash) self.check_digest(USER_NAME.upper(), "Digest", USER_PASS, digests.hashes[22].hash) self.check_digest(UPN, "Digest", USER_PASS, digests.hashes[23].hash) self.check_digest(UPN.lower(), "Digest", USER_PASS, digests.hashes[24].hash) self.check_digest(UPN.upper(), "Digest", USER_PASS, digests.hashes[25].hash) name = "%s\\%s" % (self.lp.get("workgroup"), USER_NAME) self.check_digest(name, "Digest", USER_PASS, digests.hashes[26].hash) name = "%s\\%s" % (self.lp.get("workgroup").lower(), USER_NAME.lower()) self.check_digest(name, "Digest", USER_PASS, digests.hashes[27].hash) name = "%s\\%s" % (self.lp.get("workgroup").upper(), USER_NAME.upper()) self.check_digest(name, "Digest", USER_PASS, digests.hashes[28].hash)
try: # Query Partitions attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "objectSid", "Enabled", "systemFlags", "dnsRoot", "nCName", "msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"] sstr = "CN=Partitions,{0!s}".format(samdb.get_config_basedn()) res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=crossRef)") # Write partitions output write_search_result(samdb, f, res) # Query cross reference container attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "fSMORoleOwner", "systemFlags", "msDS-Behavior-Version",
class SiteCoverageTests(samba.tests.TestCase): def setUp(self): self.prefix = "kcc_" self.lp = samba.tests.env_loadparm() self.sites = {} self.site_links = {} self.creds = Credentials() self.creds.guess(self.lp) self.session = system_session() self.samdb = SamDB(session_info=self.session, credentials=self.creds, lp=self.lp) def tearDown(self): self.samdb.transaction_start() for site in self.sites: delete_force(self.samdb, site, controls=['tree_delete:1']) for site_link in self.site_links: delete_force(self.samdb, site_link) self.samdb.transaction_commit() def _add_server(self, name, site): dn = "CN={},CN=Servers,{}".format(name, site) self.samdb.add({ "dn": dn, "objectClass": "server", "serverReference": self.samdb.domain_dn() }) return dn def _add_site(self, name): dn = "CN={},CN=Sites,{}".format(name, self.samdb.get_config_basedn()) self.samdb.add({"dn": dn, "objectClass": "site"}) self.samdb.add({ "dn": "CN=Servers," + dn, "objectClass": ["serversContainer"] }) self.sites[dn] = name return dn, name.lower() def _add_site_link(self, name, links=[], cost=100): dn = "CN={},CN=IP,CN=Inter-Site Transports,CN=Sites,{}".format( name, self.samdb.get_config_basedn()) self.samdb.add({ "dn": dn, "objectClass": "siteLink", "cost": str(cost), "siteList": links }) self.site_links[dn] = name return dn def test_single_site_link_same_dc_count(self): self.samdb.transaction_start() site1, name1 = self._add_site(self.prefix + "ABCD") site2, name2 = self._add_site(self.prefix + "BCDE") uncovered_dn, uncovered = self._add_site(self.prefix + "uncovered") self._add_server(self.prefix + "ABCD" + '1', site1) self._add_server(self.prefix + "BCDE" + '1', site2) self._add_site_link(self.prefix + "link", [site1, site2, uncovered_dn]) self.samdb.transaction_commit() to_cover = uncovered_sites_to_cover(self.samdb, name1) to_cover.sort() self.assertEqual([uncovered], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name2) to_cover.sort() self.assertEqual([], to_cover) def test_single_site_link_different_dc_count(self): self.samdb.transaction_start() site1, name1 = self._add_site(self.prefix + "ABCD") site2, name2 = self._add_site(self.prefix + "BCDE") uncovered_dn, uncovered = self._add_site(self.prefix + "uncovered") self._add_server(self.prefix + "ABCD" + '1', site1) self._add_server(self.prefix + "ABCD" + '2', site1) self._add_server(self.prefix + "BCDE" + '1', site2) self._add_server(self.prefix + "BCDE" + '2', site2) self._add_server(self.prefix + "BCDE" + '3', site2) self._add_site_link(self.prefix + "link", [site1, site2, uncovered_dn]) self.samdb.transaction_commit() to_cover = uncovered_sites_to_cover(self.samdb, name1) to_cover.sort() self.assertEqual([], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name2) to_cover.sort() self.assertEqual([uncovered], to_cover) def test_two_site_links_same_cost(self): self.samdb.transaction_start() site1, name1 = self._add_site(self.prefix + "ABCD") site2, name2 = self._add_site(self.prefix + "BCDE") uncovered_dn, uncovered = self._add_site(self.prefix + "uncovered") self._add_server(self.prefix + "ABCD" + '1', site1) self._add_server(self.prefix + "ABCD" + '2', site1) self._add_server(self.prefix + "BCDE" + '1', site2) self._add_server(self.prefix + "BCDE" + '2', site2) self._add_server(self.prefix + "BCDE" + '3', site2) self._add_site_link(self.prefix + "link1", [site1, uncovered_dn]) self._add_site_link(self.prefix + "link2", [site2, uncovered_dn]) self.samdb.transaction_commit() to_cover = uncovered_sites_to_cover(self.samdb, name1) to_cover.sort() self.assertEqual([uncovered], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name2) to_cover.sort() self.assertEqual([uncovered], to_cover) def test_two_site_links_different_costs(self): self.samdb.transaction_start() site1, name1 = self._add_site(self.prefix + "ABCD") site2, name2 = self._add_site(self.prefix + "BCDE") uncovered_dn, uncovered = self._add_site(self.prefix + "uncovered") self._add_server(self.prefix + "ABCD" + '1', site1) self._add_server(self.prefix + "BCDE" + '1', site2) self._add_server(self.prefix + "BCDE" + '2', site2) self._add_site_link(self.prefix + "link1", [site1, uncovered_dn], cost=50) self._add_site_link(self.prefix + "link2", [site2, uncovered_dn], cost=75) self.samdb.transaction_commit() to_cover = uncovered_sites_to_cover(self.samdb, name1) to_cover.sort() self.assertEqual([uncovered], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name2) to_cover.sort() self.assertEqual([], to_cover) def test_three_site_links_different_costs(self): self.samdb.transaction_start() site1, name1 = self._add_site(self.prefix + "ABCD") site2, name2 = self._add_site(self.prefix + "BCDE") site3, name3 = self._add_site(self.prefix + "CDEF") uncovered_dn, uncovered = self._add_site(self.prefix + "uncovered") self._add_server(self.prefix + "ABCD" + '1', site1) self._add_server(self.prefix + "BCDE" + '1', site2) self._add_server(self.prefix + "CDEF" + '1', site3) self._add_server(self.prefix + "CDEF" + '2', site3) self._add_site_link(self.prefix + "link1", [site1, uncovered_dn], cost=50) self._add_site_link(self.prefix + "link2", [site2, uncovered_dn], cost=75) self._add_site_link(self.prefix + "link3", [site3, uncovered_dn], cost=60) self.samdb.transaction_commit() to_cover = uncovered_sites_to_cover(self.samdb, name1) to_cover.sort() self.assertEqual([uncovered], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name2) to_cover.sort() self.assertEqual([], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name3) to_cover.sort() self.assertEqual([], to_cover) def test_three_site_links_different_costs(self): self.samdb.transaction_start() site1, name1 = self._add_site(self.prefix + "ABCD") site2, name2 = self._add_site(self.prefix + "BCDE") site3, name3 = self._add_site(self.prefix + "CDEF") uncovered_dn, uncovered = self._add_site(self.prefix + "uncovered") self._add_server(self.prefix + "ABCD" + '1', site1) self._add_server(self.prefix + "BCDE" + '1', site2) self._add_server(self.prefix + "CDEF" + '1', site3) self._add_server(self.prefix + "CDEF" + '2', site3) self._add_site_link(self.prefix + "link1", [site1, uncovered_dn], cost=50) self._add_site_link(self.prefix + "link2", [site2, uncovered_dn], cost=75) self._add_site_link(self.prefix + "link3", [site3, uncovered_dn], cost=50) self.samdb.transaction_commit() to_cover = uncovered_sites_to_cover(self.samdb, name1) to_cover.sort() self.assertEqual([uncovered], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name2) to_cover.sort() self.assertEqual([], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name3) to_cover.sort() self.assertEqual([uncovered], to_cover) def test_complex_setup_with_multiple_uncovered_sites(self): self.samdb.transaction_start() site1, name1 = self._add_site(self.prefix + "ABCD") site2, name2 = self._add_site(self.prefix + "BCDE") site3, name3 = self._add_site(self.prefix + "CDEF") site4, name4 = self._add_site(self.prefix + "1234") site5, name5 = self._add_site(self.prefix + "2345") site6, name6 = self._add_site(self.prefix + "3456") uncovered_dn1, uncovered1 = self._add_site(self.prefix + "uncovered1") uncovered_dn2, uncovered2 = self._add_site(self.prefix + "uncovered2") uncovered_dn3, uncovered3 = self._add_site(self.prefix + "uncovered3") # Site Link Cluster 1 - Server List self._add_server(self.prefix + "ABCD" + '1', site1) self._add_server(self.prefix + "BCDE" + '1', site2) self._add_server(self.prefix + "BCDE" + '2', site2) self._add_server(self.prefix + "CDEF" + '1', site3) self._add_server(self.prefix + "CDEF" + '2', site3) self._add_server(self.prefix + "CDEF" + '3', site3) # Site Link Cluster 2 - Server List self._add_server(self.prefix + "1234" + '1', site4) self._add_server(self.prefix + "1234" + '2', site4) self._add_server(self.prefix + "2345" + '1', site5) self._add_server(self.prefix + "2345" + '2', site5) self._add_server(self.prefix + "3456" + '1', site6) # Join to Uncovered1 (preference to site link cluster 1) self._add_site_link(self.prefix + "link1A", [site1, site2, site3, uncovered_dn1], cost=49) self._add_site_link(self.prefix + "link2A", [site4, site5, site6, uncovered_dn1], cost=50) # Join to Uncovered2 (no preferene on site links) self._add_site_link(self.prefix + "link1B", [site1, site2, site3, uncovered_dn2], cost=50) self._add_site_link(self.prefix + "link2B", [site4, site5, site6, uncovered_dn2], cost=50) # Join to Uncovered3 (preference to site link cluster 2) self._add_site_link(self.prefix + "link1C", [site1, site2, site3, uncovered_dn3], cost=50) self._add_site_link(self.prefix + "link2C", [site4, site5, site6, uncovered_dn3], cost=49) self.samdb.transaction_commit() to_cover = uncovered_sites_to_cover(self.samdb, name1) to_cover.sort() self.assertEqual([], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name2) to_cover.sort() self.assertEqual([], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name3) to_cover.sort() self.assertEqual([uncovered1, uncovered2], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name4) to_cover.sort() self.assertEqual([uncovered2, uncovered3], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name5) to_cover.sort() self.assertEqual([], to_cover) to_cover = uncovered_sites_to_cover(self.samdb, name6) to_cover.sort() self.assertEqual([], to_cover) for to_check in [uncovered1, uncovered2, uncovered3]: to_cover = uncovered_sites_to_cover(self.samdb, to_check) to_cover.sort() self.assertEqual([], to_cover)
def run(self, sambaopts=None, credopts=None, versionopts=None, server=None, targetdir=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp) net = Net(creds, lp, server=credopts.ipaddress) netbios_name = lp.get("netbios name") samdb = SamDB(session_info=system_session(), credentials=creds, lp=lp) if not server: res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"]) if (len(res) == 0): raise CommandError("Unable to search for servers") if (len(res) == 1): raise CommandError("You are the latest server in the domain") server = None for e in res: if str(e["name"]).lower() != netbios_name.lower(): server = e["dnsHostName"] break ntds_guid = samdb.get_ntds_GUID() msg = samdb.search(base=str(samdb.get_config_basedn()), scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid, attrs=['options']) if len(msg) == 0 or "options" not in msg[0]: raise CommandError("Failed to find options on %s" % ntds_guid) ntds_dn = msg[0].dn dsa_options = int(str(msg[0]['options'])) res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn), controls=["search_options:1:2"]) if len(res) != 0: raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res)) print "Using %s as partner server for the demotion" % server (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds) print "Desactivating inbound replication" nmsg = ldb.Message() nmsg.dn = msg[0].dn dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options") samdb.modify(nmsg) if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc(): print "Asking partner server %s to synchronize from us" % server for part in (samdb.get_schema_basedn(), samdb.get_config_basedn(), samdb.get_root_basedn()): try: sendDsReplicaSync(drsuapiBind, drsuapi_handle, ntds_guid, str(part), drsuapi.DRSUAPI_DRS_WRIT_REP) except drsException, e: print "Error while demoting, re-enabling inbound replication" dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options") samdb.modify(nmsg) raise CommandError("Error while sending a DsReplicaSync for partion %s" % str(part), e)
class DsdbLockTestCase(SamDBTestCase): def test_db_lock1(self): basedn = self.samdb.get_default_basedn() (r1, w1) = os.pipe() pid = os.fork() if pid == 0: # In the child, close the main DB, re-open just one DB del(self.samdb) gc.collect() self.samdb = SamDB(session_info=self.session, lp=self.lp) self.samdb.transaction_start() dn = "cn=test_db_lock_user,cn=users," + str(basedn) self.samdb.add({ "dn": dn, "objectclass": "user", }) self.samdb.delete(dn) # Obtain a write lock self.samdb.transaction_prepare_commit() os.write(w1, b"prepared") time.sleep(2) # Drop the write lock self.samdb.transaction_cancel() os._exit(0) self.assertEqual(os.read(r1, 8), b"prepared") start = time.time() # We need to hold this iterator open to hold the all-record lock. res = self.samdb.search_iterator() # This should take at least 2 seconds because the transaction # has a write lock on one backend db open # Release the locks for l in res: pass end = time.time() self.assertGreater(end - start, 1.9) (got_pid, status) = os.waitpid(pid, 0) self.assertEqual(got_pid, pid) self.assertTrue(os.WIFEXITED(status)) self.assertEqual(os.WEXITSTATUS(status), 0) def test_db_lock2(self): basedn = self.samdb.get_default_basedn() (r1, w1) = os.pipe() (r2, w2) = os.pipe() pid = os.fork() if pid == 0: # In the child, close the main DB, re-open del(self.samdb) gc.collect() self.samdb = SamDB(session_info=self.session, lp=self.lp) # We need to hold this iterator open to hold the all-record lock. res = self.samdb.search_iterator() os.write(w2, b"start") if (os.read(r1, 7) != b"started"): os._exit(1) os.write(w2, b"add") if (os.read(r1, 5) != b"added"): os._exit(2) # Wait 2 seconds to block prepare_commit() in the child. os.write(w2, b"prepare") time.sleep(2) # Release the locks for l in res: pass if (os.read(r1, 8) != b"prepared"): os._exit(3) os._exit(0) # We can start the transaction during the search # because both just grab the all-record read lock. self.assertEqual(os.read(r2, 5), b"start") self.samdb.transaction_start() os.write(w1, b"started") self.assertEqual(os.read(r2, 3), b"add") dn = "cn=test_db_lock_user,cn=users," + str(basedn) self.samdb.add({ "dn": dn, "objectclass": "user", }) self.samdb.delete(dn) os.write(w1, b"added") # Obtain a write lock, this will block until # the parent releases the read lock. self.assertEqual(os.read(r2, 7), b"prepare") start = time.time() self.samdb.transaction_prepare_commit() end = time.time() try: self.assertGreater(end - start, 1.9) except: raise finally: os.write(w1, b"prepared") # Drop the write lock self.samdb.transaction_cancel() (got_pid, status) = os.waitpid(pid, 0) self.assertEqual(got_pid, pid) self.assertTrue(os.WIFEXITED(status)) self.assertEqual(os.WEXITSTATUS(status), 0) def test_db_lock3(self): basedn = self.samdb.get_default_basedn() (r1, w1) = os.pipe() (r2, w2) = os.pipe() pid = os.fork() if pid == 0: # In the child, close the main DB, re-open del(self.samdb) gc.collect() self.samdb = SamDB(session_info=self.session, lp=self.lp) # We need to hold this iterator open to hold the all-record lock. res = self.samdb.search_iterator() os.write(w2, b"start") if (os.read(r1, 7) != b"started"): os._exit(1) os.write(w2, b"add") if (os.read(r1, 5) != b"added"): os._exit(2) # Wait 2 seconds to block prepare_commit() in the child. os.write(w2, b"prepare") time.sleep(2) # Release the locks for l in res: pass if (os.read(r1, 8) != b"prepared"): os._exit(3) os._exit(0) # We can start the transaction during the search # because both just grab the all-record read lock. self.assertEqual(os.read(r2, 5), b"start") self.samdb.transaction_start() os.write(w1, b"started") self.assertEqual(os.read(r2, 3), b"add") # This will end up in the top level db dn = "@DSDB_LOCK_TEST" self.samdb.add({ "dn": dn}) self.samdb.delete(dn) os.write(w1, b"added") # Obtain a write lock, this will block until # the child releases the read lock. self.assertEqual(os.read(r2, 7), b"prepare") start = time.time() self.samdb.transaction_prepare_commit() end = time.time() self.assertGreater(end - start, 1.9) os.write(w1, b"prepared") # Drop the write lock self.samdb.transaction_cancel() (got_pid, status) = os.waitpid(pid, 0) self.assertTrue(os.WIFEXITED(status)) self.assertEqual(os.WEXITSTATUS(status), 0) self.assertEqual(got_pid, pid) def _test_full_db_lock1(self, backend_path): (r1, w1) = os.pipe() pid = os.fork() if pid == 0: # In the child, close the main DB, re-open just one DB del(self.samdb) gc.collect() backenddb = ldb.Ldb(backend_path) backenddb.transaction_start() backenddb.add({"dn":"@DSDB_LOCK_TEST"}) backenddb.delete("@DSDB_LOCK_TEST") # Obtain a write lock backenddb.transaction_prepare_commit() os.write(w1, b"prepared") time.sleep(2) # Drop the write lock backenddb.transaction_cancel() os._exit(0) self.assertEqual(os.read(r1, 8), b"prepared") start = time.time() # We need to hold this iterator open to hold the all-record lock. res = self.samdb.search_iterator() # This should take at least 2 seconds because the transaction # has a write lock on one backend db open end = time.time() self.assertGreater(end - start, 1.9) # Release the locks for l in res: pass (got_pid, status) = os.waitpid(pid, 0) self.assertEqual(got_pid, pid) self.assertTrue(os.WIFEXITED(status)) self.assertEqual(os.WEXITSTATUS(status), 0) def test_full_db_lock1(self): basedn = self.samdb.get_default_basedn() backend_filename = "%s.ldb" % basedn.get_casefold() backend_subpath = os.path.join("sam.ldb.d", backend_filename) backend_path = self.lp.private_path(backend_subpath) self._test_full_db_lock1(backend_path) def test_full_db_lock1_config(self): basedn = self.samdb.get_config_basedn() backend_filename = "%s.ldb" % basedn.get_casefold() backend_subpath = os.path.join("sam.ldb.d", backend_filename) backend_path = self.lp.private_path(backend_subpath) self._test_full_db_lock1(backend_path) def _test_full_db_lock2(self, backend_path): (r1, w1) = os.pipe() (r2, w2) = os.pipe() pid = os.fork() if pid == 0: # In the child, close the main DB, re-open del(self.samdb) gc.collect() self.samdb = SamDB(session_info=self.session, lp=self.lp) # We need to hold this iterator open to hold the all-record lock. res = self.samdb.search_iterator() os.write(w2, b"start") if (os.read(r1, 7) != b"started"): os._exit(1) os.write(w2, b"add") if (os.read(r1, 5) != b"added"): os._exit(2) # Wait 2 seconds to block prepare_commit() in the child. os.write(w2, b"prepare") time.sleep(2) # Release the locks for l in res: pass if (os.read(r1, 8) != b"prepared"): os._exit(3) os._exit(0) # In the parent, close the main DB, re-open just one DB del(self.samdb) gc.collect() backenddb = ldb.Ldb(backend_path) # We can start the transaction during the search # because both just grab the all-record read lock. self.assertEqual(os.read(r2, 5), b"start") backenddb.transaction_start() os.write(w1, b"started") self.assertEqual(os.read(r2, 3), b"add") backenddb.add({"dn":"@DSDB_LOCK_TEST"}) backenddb.delete("@DSDB_LOCK_TEST") os.write(w1, b"added") # Obtain a write lock, this will block until # the child releases the read lock. self.assertEqual(os.read(r2, 7), b"prepare") start = time.time() backenddb.transaction_prepare_commit() end = time.time() try: self.assertGreater(end - start, 1.9) except: raise finally: os.write(w1, b"prepared") # Drop the write lock backenddb.transaction_cancel() (got_pid, status) = os.waitpid(pid, 0) self.assertEqual(got_pid, pid) self.assertTrue(os.WIFEXITED(status)) self.assertEqual(os.WEXITSTATUS(status), 0) def test_full_db_lock2(self): basedn = self.samdb.get_default_basedn() backend_filename = "%s.ldb" % basedn.get_casefold() backend_subpath = os.path.join("sam.ldb.d", backend_filename) backend_path = self.lp.private_path(backend_subpath) self._test_full_db_lock2(backend_path) def test_full_db_lock2_config(self): basedn = self.samdb.get_config_basedn() backend_filename = "%s.ldb" % basedn.get_casefold() backend_subpath = os.path.join("sam.ldb.d", backend_filename) backend_path = self.lp.private_path(backend_subpath) self._test_full_db_lock2(backend_path)
def run(self, subcommand, H=None, forest_level=None, domain_level=None, quiet=False, credopts=None, sambaopts=None, versionopts=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp, fallback_machine=True) samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp) domain_dn = samdb.domain_dn() res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(), scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"]) assert len(res_forest) == 1 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version", "nTMixedDomain"]) assert len(res_domain) == 1 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(), scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)", attrs=["msDS-Behavior-Version"]) assert len(res_dc_s) >= 1 try: level_forest = int(res_forest[0]["msDS-Behavior-Version"][0]) level_domain = int(res_domain[0]["msDS-Behavior-Version"][0]) level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0]) min_level_dc = int(res_dc_s[0]["msDS-Behavior-Version"][0]) # Init value for msg in res_dc_s: if int(msg["msDS-Behavior-Version"][0]) < min_level_dc: min_level_dc = int(msg["msDS-Behavior-Version"][0]) if level_forest < 0 or level_domain < 0: raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!") if min_level_dc < 0: raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!") if level_forest > level_domain: raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!") if level_domain > min_level_dc: raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!") except KeyError: raise CommandError("Could not retrieve the actual domain, forest level and/or lowest DC function level!") if subcommand == "show": self.message("Domain and forest function level for domain '%s'" % domain_dn) if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0: self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!") if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0: self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!") if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0: self.message("\nATTENTION: You run SAMBA 4 on a lowest function level of a DC lower than Windows 2003. This isn't supported! Please step-up or upgrade the concerning DC(s)!") self.message("") if level_forest == DS_DOMAIN_FUNCTION_2000: outstr = "2000" elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED: outstr = "2003 with mixed domains/interim (NT4 DC support)" elif level_forest == DS_DOMAIN_FUNCTION_2003: outstr = "2003" elif level_forest == DS_DOMAIN_FUNCTION_2008: outstr = "2008" elif level_forest == DS_DOMAIN_FUNCTION_2008_R2: outstr = "2008 R2" else: outstr = "higher than 2008 R2" self.message("Forest function level: (Windows) " + outstr) if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0: outstr = "2000 mixed (NT4 DC support)" elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0: outstr = "2000" elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED: outstr = "2003 with mixed domains/interim (NT4 DC support)" elif level_domain == DS_DOMAIN_FUNCTION_2003: outstr = "2003" elif level_domain == DS_DOMAIN_FUNCTION_2008: outstr = "2008" elif level_domain == DS_DOMAIN_FUNCTION_2008_R2: outstr = "2008 R2" else: outstr = "higher than 2008 R2" self.message("Domain function level: (Windows) " + outstr) if min_level_dc == DS_DOMAIN_FUNCTION_2000: outstr = "2000" elif min_level_dc == DS_DOMAIN_FUNCTION_2003: outstr = "2003" elif min_level_dc == DS_DOMAIN_FUNCTION_2008: outstr = "2008" elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2: outstr = "2008 R2" else: outstr = "higher than 2008 R2" self.message("Lowest function level of a DC: (Windows) " + outstr) elif subcommand == "raise": msgs = [] if domain_level is not None: if domain_level == "2003": new_level_domain = DS_DOMAIN_FUNCTION_2003 elif domain_level == "2008": new_level_domain = DS_DOMAIN_FUNCTION_2008 elif domain_level == "2008_R2": new_level_domain = DS_DOMAIN_FUNCTION_2008_R2 if new_level_domain <= level_domain and level_domain_mixed == 0: raise CommandError("Domain function level can't be smaller than or equal to the actual one!") if new_level_domain > min_level_dc: raise CommandError("Domain function level can't be higher than the lowest function level of a DC!") # Deactivate mixed/interim domain support if level_domain_mixed != 0: # Directly on the base DN m = ldb.Message() m.dn = ldb.Dn(samdb, domain_dn) m["nTMixedDomain"] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "nTMixedDomain") samdb.modify(m) # Under partitions m = ldb.Message() m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn()) m["nTMixedDomain"] = ldb.MessageElement("0", ldb.FLAG_MOD_REPLACE, "nTMixedDomain") try: samdb.modify(m) except ldb.LdbError, (enum, emsg): if enum != ldb.ERR_UNWILLING_TO_PERFORM: raise # Directly on the base DN m = ldb.Message() m.dn = ldb.Dn(samdb, domain_dn) m["msDS-Behavior-Version"]= ldb.MessageElement( str(new_level_domain), ldb.FLAG_MOD_REPLACE, "msDS-Behavior-Version") samdb.modify(m) # Under partitions m = ldb.Message() m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn()) m["msDS-Behavior-Version"]= ldb.MessageElement( str(new_level_domain), ldb.FLAG_MOD_REPLACE, "msDS-Behavior-Version") try: samdb.modify(m) except ldb.LdbError, (enum, emsg): if enum != ldb.ERR_UNWILLING_TO_PERFORM: raise level_domain = new_level_domain msgs.append("Domain function level changed!")
class SambaDnsUpdateTests(samba.tests.BlackboxTestCase): """Blackbox test case for samba_dnsupdate.""" def setUp(self): self.server_ip = samba.tests.env_get_var_value("DNS_SERVER_IP") super(SambaDnsUpdateTests, self).setUp() try: out = self.check_output("samba_dnsupdate --verbose") self.assertTrue("Looking for DNS entry" in out, out) except samba.tests.BlackboxProcessError: pass def test_samba_dnsupate_no_change(self): try: out = self.check_output("samba_dnsupdate --verbose") except samba.tests.BlackboxProcessError as e: self.fail("Error calling samba_dnsupdate: %s" % e) self.assertTrue("No DNS updates needed" in out, out) def test_samba_dnsupate_set_ip(self): try: out = self.check_output("samba_dnsupdate --verbose --current-ip=10.0.0.1") self.assertTrue(" DNS updates and" in out, out) self.assertTrue(" DNS deletes needed" in out, out) except samba.tests.BlackboxProcessError: pass try: out = self.check_output("samba_dnsupdate --verbose --use-nsupdate --current-ip=10.0.0.1") except samba.tests.BlackboxProcessError as e: self.fail("Error calling samba_dnsupdate: %s" % e) self.assertTrue("No DNS updates needed" in out, out) try: rpc_out = self.check_output("samba_dnsupdate --verbose --use-samba-tool --rpc-server-ip=%s" % self.server_ip) except samba.tests.BlackboxProcessError as e: self.fail("Error calling samba_dnsupdate: %s" % e) self.assertTrue(" DNS updates and" in rpc_out, rpc_out) self.assertTrue(" DNS deletes needed" in rpc_out, rpc_out) out = self.check_output("samba_dnsupdate --verbose") self.assertTrue("No DNS updates needed" in out, out + rpc_out) def test_add_new_uncovered_site(self): name = 'sites' cmd = cmd_sambatool.subcommands[name] cmd.outf = StringIO() cmd.errf = StringIO() site_name = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' # Clear out any existing site cmd._run("samba-tool %s" % name, 'remove', site_name) result = cmd._run("samba-tool %s" % name, 'create', site_name) if result is not None: self.fail("Error creating new site") self.lp = samba.tests.env_loadparm() self.creds = Credentials() self.creds.guess(self.lp) self.session = system_session() uc_fn = self.lp.private_path('dns_update_cache') tmp_uc = uc_fn + '_tmp' shutil.copyfile(uc_fn, tmp_uc) self.samdb = SamDB(session_info=self.session, credentials=self.creds, lp=self.lp) m = ldb.Message() m.dn = ldb.Dn(self.samdb, 'CN=DEFAULTIPSITELINK,CN=IP,' 'CN=Inter-Site Transports,CN=Sites,{}'.format( self.samdb.get_config_basedn())) m['siteList'] = ldb.MessageElement("CN={},CN=Sites,{}".format( site_name, self.samdb.get_config_basedn()), ldb.FLAG_MOD_ADD, "siteList") dns_c = "samba_dnsupdate --verbose --use-file={}".format(tmp_uc) out = self.check_output(dns_c) self.assertFalse(site_name.lower() in out, out) self.samdb.modify(m) shutil.copyfile(uc_fn, tmp_uc) out = self.check_output(dns_c) self.assertFalse("No DNS updates needed" in out, out) self.assertTrue(site_name.lower() in out, out) result = cmd._run("samba-tool %s" % name, 'remove', site_name) if result is not None: self.fail("Error deleting site")
class SambaDnsUpdateTests(samba.tests.BlackboxTestCase): """Blackbox test case for samba_dnsupdate.""" def setUp(self): self.server_ip = samba.tests.env_get_var_value("DNS_SERVER_IP") super(SambaDnsUpdateTests, self).setUp() try: out = self.check_output("samba_dnsupdate --verbose") self.assertTrue("Looking for DNS entry" in out, out) except samba.tests.BlackboxProcessError: pass def test_samba_dnsupate_no_change(self): out = self.check_output("samba_dnsupdate --verbose") self.assertTrue("No DNS updates needed" in out, out) def test_samba_dnsupate_set_ip(self): try: out = self.check_output("samba_dnsupdate --verbose --current-ip=10.0.0.1") self.assertTrue(" DNS updates and" in out, out) self.assertTrue(" DNS deletes needed" in out, out) except samba.tests.BlackboxProcessError: pass try: out = self.check_output("samba_dnsupdate --verbose --use-nsupdate --current-ip=10.0.0.1") except samba.tests.BlackboxProcessError as e: self.fail("Error calling samba_dnsupdate: %s" % e) self.assertTrue("No DNS updates needed" in out, out) try: rpc_out = self.check_output("samba_dnsupdate --verbose --use-samba-tool --rpc-server-ip=%s" % self.server_ip) except samba.tests.BlackboxProcessError as e: self.fail("Error calling samba_dnsupdate: %s" % e) self.assertTrue(" DNS updates and" in rpc_out, rpc_out) self.assertTrue(" DNS deletes needed" in rpc_out, rpc_out) out = self.check_output("samba_dnsupdate --verbose") self.assertTrue("No DNS updates needed" in out, out + rpc_out) def test_add_new_uncovered_site(self): name = 'sites' cmd = cmd_sambatool.subcommands[name] cmd.outf = StringIO() cmd.errf = StringIO() site_name = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' # Clear out any existing site cmd._run("samba-tool %s" % name, 'remove', site_name) result = cmd._run("samba-tool %s" % name, 'create', site_name) if result is not None: self.fail("Error creating new site") self.lp = samba.tests.env_loadparm() self.creds = Credentials() self.creds.guess(self.lp) self.session = system_session() self.samdb = SamDB(session_info=self.session, credentials=self.creds, lp=self.lp) m = ldb.Message() m.dn = ldb.Dn(self.samdb, 'CN=DEFAULTIPSITELINK,CN=IP,' 'CN=Inter-Site Transports,CN=Sites,{}'.format( self.samdb.get_config_basedn())) m['siteList'] = ldb.MessageElement("CN={},CN=Sites,{}".format( site_name, self.samdb.get_config_basedn()), ldb.FLAG_MOD_ADD, "siteList") out = self.check_output("samba_dnsupdate --verbose") self.assertTrue("No DNS updates needed" in out, out) self.samdb.modify(m) out = self.check_output("samba_dnsupdate --verbose --use-samba-tool" " --rpc-server-ip={}".format(self.server_ip)) self.assertFalse("No DNS updates needed" in out, out) self.assertTrue(site_name.lower() in out, out) result = cmd._run("samba-tool %s" % name, 'remove', site_name) if result is not None: self.fail("Error deleting site")
try: # Query Partitions attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "objectSid", "Enabled", "systemFlags", "dnsRoot", "nCName", "msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"] sstr = "CN=Partitions,%s" % samdb.get_config_basedn() res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=crossRef)") # Write partitions output write_search_result(samdb, f, res) # Query cross reference container attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "fSMORoleOwner", "systemFlags", "msDS-Behavior-Version",
def samdb_to_ldif_file(samdb, dburl, lp, creds, ldif_file): """Routine to extract all objects and attributes that are relevent to the KCC algorithms from a DC database. The point of this function is to allow a programmer/debugger to extract an LDIF file with non-security relevent information from a DC database. The LDIF file can then be used to "import" via the import_ldif() function this file into a temporary abbreviated database. The KCC algorithm can then run against this abbreviated database for debug or test verification that the topology generated is computationally the same between different OSes and algorithms. :param dburl: LDAP database URL to extract info from :param ldif_file: output LDIF file name to create """ try: samdb = SamDB(url=dburl, session_info=system_session(), credentials=creds, lp=lp) except ldb.LdbError as e: (enum, estr) = e.args raise LdifError("Unable to open sam database (%s) : %s" % (dburl, estr)) if os.path.exists(ldif_file): raise LdifError("Specify a file (%s) that doesn't already exist." % ldif_file) try: f = open(ldif_file, "w") except IOError as ioerr: raise LdifError("Unable to open (%s) : %s" % (ldif_file, str(ioerr))) try: # Query Partitions attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "objectSid", "Enabled", "systemFlags", "dnsRoot", "nCName", "msDS-NC-Replica-Locations", "msDS-NC-RO-Replica-Locations"] sstr = "CN=Partitions,%s" % samdb.get_config_basedn() res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=crossRef)") # Write partitions output write_search_result(samdb, f, res) # Query cross reference container attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "fSMORoleOwner", "systemFlags", "msDS-Behavior-Version", "msDS-EnabledFeature"] sstr = "CN=Partitions,%s" % samdb.get_config_basedn() res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=crossRefContainer)") # Write cross reference container output write_search_result(samdb, f, res) # Query Sites attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "systemFlags"] sstr = "CN=Sites,%s" % samdb.get_config_basedn() sites = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=site)") # Write sites output write_search_result(samdb, f, sites) # Query NTDS Site Settings for msg in sites: sitestr = str(msg.dn) attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "interSiteTopologyGenerator", "interSiteTopologyFailover", "schedule", "options"] sstr = "CN=NTDS Site Settings,%s" % sitestr res = samdb.search(base=sstr, scope=ldb.SCOPE_BASE, attrs=attrs) # Write Site Settings output write_search_result(samdb, f, res) # Naming context list nclist = [] # Query Directory Service Agents for msg in sites: sstr = str(msg.dn) ncattrs = ["hasMasterNCs", "msDS-hasMasterNCs", "hasPartialReplicaNCs", "msDS-HasDomainNCs", "msDS-hasFullReplicaNCs", "msDS-HasInstantiatedNCs"] attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "invocationID", "options", "msDS-isRODC", "msDS-Behavior-Version"] res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs + ncattrs, expression="(objectClass=nTDSDSA)") # Spin thru all the DSAs looking for NC replicas # and build a list of all possible Naming Contexts # for subsequent retrieval below for msg in res: for k in msg.keys(): if k in ncattrs: for value in msg[k]: # Some of these have binary DNs so # use dsdb_Dn to split out relevent parts dsdn = dsdb_Dn(samdb, value) dnstr = str(dsdn.dn) if dnstr not in nclist: nclist.append(dnstr) # Write DSA output write_search_result(samdb, f, res) # Query NTDS Connections for msg in sites: sstr = str(msg.dn) attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "options", "whenCreated", "enabledConnection", "schedule", "transportType", "fromServer", "systemFlags"] res = samdb.search(base=sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=nTDSConnection)") # Write NTDS Connection output write_search_result(samdb, f, res) # Query Intersite transports attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "options", "name", "bridgeheadServerListBL", "transportAddressAttribute"] sstr = "CN=Inter-Site Transports,CN=Sites,%s" % \ samdb.get_config_basedn() res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=interSiteTransport)") # Write inter-site transport output write_search_result(samdb, f, res) # Query siteLink attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "systemFlags", "options", "schedule", "replInterval", "siteList", "cost"] sstr = "CN=Sites,%s" % \ samdb.get_config_basedn() res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=siteLink)", controls=['extended_dn:0']) # Write siteLink output write_search_result(samdb, f, res) # Query siteLinkBridge attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "siteLinkList"] sstr = "CN=Sites,%s" % samdb.get_config_basedn() res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=siteLinkBridge)") # Write siteLinkBridge output write_search_result(samdb, f, res) # Query servers containers # Needed for samdb.server_site_name() attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "systemFlags"] sstr = "CN=Sites,%s" % samdb.get_config_basedn() res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=serversContainer)") # Write servers container output write_search_result(samdb, f, res) # Query servers # Needed because some transport interfaces refer back to # attributes found in the server object. Also needed # so extended-dn will be happy with dsServiceName in rootDSE attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "systemFlags", "dNSHostName", "mailAddress"] sstr = "CN=Sites,%s" % samdb.get_config_basedn() res = samdb.search(sstr, scope=ldb.SCOPE_SUBTREE, attrs=attrs, expression="(objectClass=server)") # Write server output write_search_result(samdb, f, res) # Query Naming Context replicas attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "objectSid", "fSMORoleOwner", "msDS-Behavior-Version", "repsFrom", "repsTo"] for sstr in nclist: res = samdb.search(sstr, scope=ldb.SCOPE_BASE, attrs=attrs) # Write naming context output write_search_result(samdb, f, res) # Query rootDSE replicas attrs = ["objectClass", "objectGUID", "cn", "whenChanged", "rootDomainNamingContext", "configurationNamingContext", "schemaNamingContext", "defaultNamingContext", "dsServiceName"] sstr = "" res = samdb.search(sstr, scope=ldb.SCOPE_BASE, attrs=attrs) # Record the rootDSE object as a dn as it # would appear in the base ldb file. We have # to save it this way because we are going to # be importing as an abbreviated database. res[0].dn = ldb.Dn(samdb, "@ROOTDSE") # Write rootdse output write_search_result(samdb, f, res) except ldb.LdbError as e: (enum, estr) = e.args raise LdifError("Error processing (%s) : %s" % (sstr, estr)) f.close()
class DsdbTests(TestCase): def setUp(self): super(DsdbTests, self).setUp() self.lp = samba.tests.env_loadparm() self.creds = Credentials() self.creds.guess(self.lp) self.session = system_session() self.samdb = SamDB(session_info=self.session, credentials=self.creds, lp=self.lp) def test_get_oid_from_attrid(self): oid = self.samdb.get_oid_from_attid(591614) self.assertEquals(oid, "1.2.840.113556.1.4.1790") def test_error_replpropertymetadata(self): res = self.samdb.search(expression="cn=Administrator", scope=ldb.SCOPE_SUBTREE, attrs=["replPropertyMetaData"]) repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(res[0]["replPropertyMetaData"])) ctr = repl.ctr for o in ctr.array: # Search for Description if o.attid == 13: old_version = o.version o.version = o.version + 1 replBlob = ndr_pack(repl) msg = ldb.Message() msg.dn = res[0].dn msg["replPropertyMetaData"] = ldb.MessageElement( replBlob, ldb.FLAG_MOD_REPLACE, "replPropertyMetaData") self.assertRaises(ldb.LdbError, self.samdb.modify, msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"]) def test_error_replpropertymetadata_nochange(self): res = self.samdb.search(expression="cn=Administrator", scope=ldb.SCOPE_SUBTREE, attrs=["replPropertyMetaData"]) repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(res[0]["replPropertyMetaData"])) replBlob = ndr_pack(repl) msg = ldb.Message() msg.dn = res[0].dn msg["replPropertyMetaData"] = ldb.MessageElement( replBlob, ldb.FLAG_MOD_REPLACE, "replPropertyMetaData") self.assertRaises(ldb.LdbError, self.samdb.modify, msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"]) def test_error_replpropertymetadata_allow_sort(self): res = self.samdb.search(expression="cn=Administrator", scope=ldb.SCOPE_SUBTREE, attrs=["replPropertyMetaData"]) repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(res[0]["replPropertyMetaData"])) replBlob = ndr_pack(repl) msg = ldb.Message() msg.dn = res[0].dn msg["replPropertyMetaData"] = ldb.MessageElement( replBlob, ldb.FLAG_MOD_REPLACE, "replPropertyMetaData") self.samdb.modify(msg, [ "local_oid:1.3.6.1.4.1.7165.4.3.14:0", "local_oid:1.3.6.1.4.1.7165.4.3.25:0" ]) def test_twoatt_replpropertymetadata(self): res = self.samdb.search(expression="cn=Administrator", scope=ldb.SCOPE_SUBTREE, attrs=["replPropertyMetaData", "uSNChanged"]) repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(res[0]["replPropertyMetaData"])) ctr = repl.ctr for o in ctr.array: # Search for Description if o.attid == 13: old_version = o.version o.version = o.version + 1 o.local_usn = long(str(res[0]["uSNChanged"])) + 1 replBlob = ndr_pack(repl) msg = ldb.Message() msg.dn = res[0].dn msg["replPropertyMetaData"] = ldb.MessageElement( replBlob, ldb.FLAG_MOD_REPLACE, "replPropertyMetaData") msg["description"] = ldb.MessageElement("new val", ldb.FLAG_MOD_REPLACE, "description") self.assertRaises(ldb.LdbError, self.samdb.modify, msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"]) def test_set_replpropertymetadata(self): res = self.samdb.search(expression="cn=Administrator", scope=ldb.SCOPE_SUBTREE, attrs=["replPropertyMetaData", "uSNChanged"]) repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob, str(res[0]["replPropertyMetaData"])) ctr = repl.ctr for o in ctr.array: # Search for Description if o.attid == 13: old_version = o.version o.version = o.version + 1 o.local_usn = long(str(res[0]["uSNChanged"])) + 1 o.originating_usn = long(str(res[0]["uSNChanged"])) + 1 replBlob = ndr_pack(repl) msg = ldb.Message() msg.dn = res[0].dn msg["replPropertyMetaData"] = ldb.MessageElement( replBlob, ldb.FLAG_MOD_REPLACE, "replPropertyMetaData") self.samdb.modify(msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"]) def test_ok_get_attribute_from_attid(self): self.assertEquals(self.samdb.get_attribute_from_attid(13), "description") def test_ko_get_attribute_from_attid(self): self.assertEquals(self.samdb.get_attribute_from_attid(11979), None) def test_get_attribute_replmetadata_version(self): res = self.samdb.search(expression="cn=Administrator", scope=ldb.SCOPE_SUBTREE, attrs=["dn"]) self.assertEquals(len(res), 1) dn = str(res[0].dn) self.assertEqual( self.samdb.get_attribute_replmetadata_version(dn, "unicodePwd"), 1) def test_set_attribute_replmetadata_version(self): res = self.samdb.search(expression="cn=Administrator", scope=ldb.SCOPE_SUBTREE, attrs=["dn"]) self.assertEquals(len(res), 1) dn = str(res[0].dn) version = self.samdb.get_attribute_replmetadata_version( dn, "description") self.samdb.set_attribute_replmetadata_version(dn, "description", version + 2) self.assertEqual( self.samdb.get_attribute_replmetadata_version(dn, "description"), version + 2) def test_db_lock1(self): basedn = self.samdb.get_default_basedn() (r1, w1) = os.pipe() pid = os.fork() if pid == 0: # In the child, close the main DB, re-open just one DB del (self.samdb) gc.collect() self.samdb = SamDB(session_info=self.session, credentials=self.creds, lp=self.lp) self.samdb.transaction_start() dn = "cn=test_db_lock_user,cn=users," + str(basedn) self.samdb.add({ "dn": dn, "objectclass": "user", }) self.samdb.delete(dn) # Obtain a write lock self.samdb.transaction_prepare_commit() os.write(w1, b"prepared") time.sleep(2) # Drop the write lock self.samdb.transaction_cancel() os._exit(0) self.assertEqual(os.read(r1, 8), b"prepared") start = time.time() # We need to hold this iterator open to hold the all-record lock. res = self.samdb.search_iterator() # This should take at least 2 seconds because the transaction # has a write lock on one backend db open # Release the locks for l in res: pass end = time.time() self.assertGreater(end - start, 1.9) (got_pid, status) = os.waitpid(pid, 0) self.assertEqual(got_pid, pid) self.assertTrue(os.WIFEXITED(status)) self.assertEqual(os.WEXITSTATUS(status), 0) def test_db_lock2(self): basedn = self.samdb.get_default_basedn() (r1, w1) = os.pipe() (r2, w2) = os.pipe() pid = os.fork() if pid == 0: # In the child, close the main DB, re-open del (self.samdb) gc.collect() self.samdb = SamDB(session_info=self.session, credentials=self.creds, lp=self.lp) # We need to hold this iterator open to hold the all-record lock. res = self.samdb.search_iterator() os.write(w2, b"start") if (os.read(r1, 7) != b"started"): os._exit(1) os.write(w2, b"add") if (os.read(r1, 5) != b"added"): os._exit(2) # Wait 2 seconds to block prepare_commit() in the child. os.write(w2, b"prepare") time.sleep(2) # Release the locks for l in res: pass if (os.read(r1, 8) != b"prepared"): os._exit(3) os._exit(0) # We can start the transaction during the search # because both just grab the all-record read lock. self.assertEqual(os.read(r2, 5), b"start") self.samdb.transaction_start() os.write(w1, b"started") self.assertEqual(os.read(r2, 3), b"add") dn = "cn=test_db_lock_user,cn=users," + str(basedn) self.samdb.add({ "dn": dn, "objectclass": "user", }) self.samdb.delete(dn) os.write(w1, b"added") # Obtain a write lock, this will block until # the parent releases the read lock. self.assertEqual(os.read(r2, 7), b"prepare") start = time.time() self.samdb.transaction_prepare_commit() end = time.time() try: self.assertGreater(end - start, 1.9) except: raise finally: os.write(w1, b"prepared") # Drop the write lock self.samdb.transaction_cancel() (got_pid, status) = os.waitpid(pid, 0) self.assertEqual(got_pid, pid) self.assertTrue(os.WIFEXITED(status)) self.assertEqual(os.WEXITSTATUS(status), 0) def test_db_lock3(self): basedn = self.samdb.get_default_basedn() (r1, w1) = os.pipe() (r2, w2) = os.pipe() pid = os.fork() if pid == 0: # In the child, close the main DB, re-open del (self.samdb) gc.collect() self.samdb = SamDB(session_info=self.session, credentials=self.creds, lp=self.lp) # We need to hold this iterator open to hold the all-record lock. res = self.samdb.search_iterator() os.write(w2, b"start") if (os.read(r1, 7) != b"started"): os._exit(1) os.write(w2, b"add") if (os.read(r1, 5) != b"added"): os._exit(2) # Wait 2 seconds to block prepare_commit() in the child. os.write(w2, b"prepare") time.sleep(2) # Release the locks for l in res: pass if (os.read(r1, 8) != b"prepared"): os._exit(3) os._exit(0) # We can start the transaction during the search # because both just grab the all-record read lock. self.assertEqual(os.read(r2, 5), b"start") self.samdb.transaction_start() os.write(w1, b"started") self.assertEqual(os.read(r2, 3), b"add") # This will end up in the top level db dn = "@DSDB_LOCK_TEST" self.samdb.add({"dn": dn}) self.samdb.delete(dn) os.write(w1, b"added") # Obtain a write lock, this will block until # the child releases the read lock. self.assertEqual(os.read(r2, 7), b"prepare") start = time.time() self.samdb.transaction_prepare_commit() end = time.time() self.assertGreater(end - start, 1.9) os.write(w1, b"prepared") # Drop the write lock self.samdb.transaction_cancel() (got_pid, status) = os.waitpid(pid, 0) self.assertTrue(os.WIFEXITED(status)) self.assertEqual(os.WEXITSTATUS(status), 0) self.assertEqual(got_pid, pid) def _test_full_db_lock1(self, backend_path): (r1, w1) = os.pipe() pid = os.fork() if pid == 0: # In the child, close the main DB, re-open just one DB del (self.samdb) gc.collect() backenddb = ldb.Ldb(backend_path) backenddb.transaction_start() backenddb.add({"dn": "@DSDB_LOCK_TEST"}) backenddb.delete("@DSDB_LOCK_TEST") # Obtain a write lock backenddb.transaction_prepare_commit() os.write(w1, b"prepared") time.sleep(2) # Drop the write lock backenddb.transaction_cancel() os._exit(0) self.assertEqual(os.read(r1, 8), b"prepared") start = time.time() # We need to hold this iterator open to hold the all-record lock. res = self.samdb.search_iterator() # This should take at least 2 seconds because the transaction # has a write lock on one backend db open end = time.time() self.assertGreater(end - start, 1.9) # Release the locks for l in res: pass (got_pid, status) = os.waitpid(pid, 0) self.assertEqual(got_pid, pid) self.assertTrue(os.WIFEXITED(status)) self.assertEqual(os.WEXITSTATUS(status), 0) def test_full_db_lock1(self): basedn = self.samdb.get_default_basedn() backend_filename = "%s.ldb" % basedn.get_casefold() backend_subpath = os.path.join("sam.ldb.d", backend_filename) backend_path = self.lp.private_path(backend_subpath) self._test_full_db_lock1(backend_path) def test_full_db_lock1_config(self): basedn = self.samdb.get_config_basedn() backend_filename = "%s.ldb" % basedn.get_casefold() backend_subpath = os.path.join("sam.ldb.d", backend_filename) backend_path = self.lp.private_path(backend_subpath) self._test_full_db_lock1(backend_path) def _test_full_db_lock2(self, backend_path): (r1, w1) = os.pipe() (r2, w2) = os.pipe() pid = os.fork() if pid == 0: # In the child, close the main DB, re-open del (self.samdb) gc.collect() self.samdb = SamDB(session_info=self.session, credentials=self.creds, lp=self.lp) # We need to hold this iterator open to hold the all-record lock. res = self.samdb.search_iterator() os.write(w2, b"start") if (os.read(r1, 7) != b"started"): os._exit(1) os.write(w2, b"add") if (os.read(r1, 5) != b"added"): os._exit(2) # Wait 2 seconds to block prepare_commit() in the child. os.write(w2, b"prepare") time.sleep(2) # Release the locks for l in res: pass if (os.read(r1, 8) != b"prepared"): os._exit(3) os._exit(0) # In the parent, close the main DB, re-open just one DB del (self.samdb) gc.collect() backenddb = ldb.Ldb(backend_path) # We can start the transaction during the search # because both just grab the all-record read lock. self.assertEqual(os.read(r2, 5), b"start") backenddb.transaction_start() os.write(w1, b"started") self.assertEqual(os.read(r2, 3), b"add") backenddb.add({"dn": "@DSDB_LOCK_TEST"}) backenddb.delete("@DSDB_LOCK_TEST") os.write(w1, b"added") # Obtain a write lock, this will block until # the child releases the read lock. self.assertEqual(os.read(r2, 7), b"prepare") start = time.time() backenddb.transaction_prepare_commit() end = time.time() try: self.assertGreater(end - start, 1.9) except: raise finally: os.write(w1, b"prepared") # Drop the write lock backenddb.transaction_cancel() (got_pid, status) = os.waitpid(pid, 0) self.assertEqual(got_pid, pid) self.assertTrue(os.WIFEXITED(status)) self.assertEqual(os.WEXITSTATUS(status), 0) def test_full_db_lock2(self): basedn = self.samdb.get_default_basedn() backend_filename = "%s.ldb" % basedn.get_casefold() backend_subpath = os.path.join("sam.ldb.d", backend_filename) backend_path = self.lp.private_path(backend_subpath) self._test_full_db_lock2(backend_path) def test_full_db_lock2_config(self): basedn = self.samdb.get_config_basedn() backend_filename = "%s.ldb" % basedn.get_casefold() backend_subpath = os.path.join("sam.ldb.d", backend_filename) backend_path = self.lp.private_path(backend_subpath) self._test_full_db_lock2(backend_path) def test_no_error_on_invalid_control(self): try: res = self.samdb.search( expression="cn=Administrator", scope=ldb.SCOPE_SUBTREE, attrs=["replPropertyMetaData"], controls=[ "local_oid:%s:0" % dsdb.DSDB_CONTROL_INVALID_NOT_IMPLEMENTED ]) except ldb.LdbError as e: self.fail("Should have not raised an exception") def test_error_on_invalid_critical_control(self): try: res = self.samdb.search( expression="cn=Administrator", scope=ldb.SCOPE_SUBTREE, attrs=["replPropertyMetaData"], controls=[ "local_oid:%s:1" % dsdb.DSDB_CONTROL_INVALID_NOT_IMPLEMENTED ]) except ldb.LdbError as e: if e[0] != ldb.ERR_UNSUPPORTED_CRITICAL_EXTENSION: self.fail( "Got %s should have got ERR_UNSUPPORTED_CRITICAL_EXTENSION" % e[1])
def run(self, sambaopts=None, credopts=None, versionopts=None, server=None, targetdir=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp) net = Net(creds, lp, server=credopts.ipaddress) netbios_name = lp.get("netbios name") samdb = SamDB(session_info=system_session(), credentials=creds, lp=lp) if not server: res = samdb.search( expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"]) if (len(res) == 0): raise CommandError("Unable to search for servers") if (len(res) == 1): raise CommandError("You are the latest server in the domain") server = None for e in res: if str(e["name"]).lower() != netbios_name.lower(): server = e["dnsHostName"] break ntds_guid = samdb.get_ntds_GUID() msg = samdb.search(base=str(samdb.get_config_basedn()), scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid, attrs=['options']) if len(msg) == 0 or "options" not in msg[0]: raise CommandError("Failed to find options on %s" % ntds_guid) ntds_dn = msg[0].dn dsa_options = int(str(msg[0]['options'])) res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn), controls=["search_options:1:2"]) if len(res) != 0: raise CommandError( "Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" ) print "Using %s as partner server for the demotion" % server (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds) print "Desactivating inbound replication" nmsg = ldb.Message() nmsg.dn = msg[0].dn dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options") samdb.modify(nmsg) if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc(): print "Asking partner server %s to synchronize from us" % server for part in (samdb.get_schema_basedn(), samdb.get_config_basedn(), samdb.get_root_basedn()): try: sendDsReplicaSync(drsuapiBind, drsuapi_handle, ntds_guid, str(part), drsuapi.DRSUAPI_DRS_WRIT_REP) except drsException, e: print "Error while demoting, re-enabling inbound replication" dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL nmsg["options"] = ldb.MessageElement( str(dsa_options), ldb.FLAG_MOD_REPLACE, "options") samdb.modify(nmsg) raise CommandError( "Error while sending a DsReplicaSync for partion %s" % str(part), e)
def run(self, subcommand, H=None, forest_level=None, domain_level=None, quiet=False, credopts=None, sambaopts=None, versionopts=None): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp, fallback_machine=True) samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp) domain_dn = samdb.domain_dn() res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(), scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"]) assert len(res_forest) == 1 res_domain = samdb.search( domain_dn, scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version", "nTMixedDomain"]) assert len(res_domain) == 1 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(), scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)", attrs=["msDS-Behavior-Version"]) assert len(res_dc_s) >= 1 try: level_forest = int(res_forest[0]["msDS-Behavior-Version"][0]) level_domain = int(res_domain[0]["msDS-Behavior-Version"][0]) level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0]) min_level_dc = int( res_dc_s[0]["msDS-Behavior-Version"][0]) # Init value for msg in res_dc_s: if int(msg["msDS-Behavior-Version"][0]) < min_level_dc: min_level_dc = int(msg["msDS-Behavior-Version"][0]) if level_forest < 0 or level_domain < 0: raise CommandError( "Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!" ) if min_level_dc < 0: raise CommandError( "Lowest function level of a DC is invalid. Correct this or reprovision!" ) if level_forest > level_domain: raise CommandError( "Forest function level is higher than the domain level(s). Correct this or reprovision!" ) if level_domain > min_level_dc: raise CommandError( "Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!" ) except KeyError: raise CommandError( "Could not retrieve the actual domain, forest level and/or lowest DC function level!" ) if subcommand == "show": self.message("Domain and forest function level for domain '%s'" % domain_dn) if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0: self.message( "\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!" ) if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0: self.message( "\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!" ) if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0: self.message( "\nATTENTION: You run SAMBA 4 on a lowest function level of a DC lower than Windows 2003. This isn't supported! Please step-up or upgrade the concerning DC(s)!" ) self.message("") if level_forest == DS_DOMAIN_FUNCTION_2000: outstr = "2000" elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED: outstr = "2003 with mixed domains/interim (NT4 DC support)" elif level_forest == DS_DOMAIN_FUNCTION_2003: outstr = "2003" elif level_forest == DS_DOMAIN_FUNCTION_2008: outstr = "2008" elif level_forest == DS_DOMAIN_FUNCTION_2008_R2: outstr = "2008 R2" else: outstr = "higher than 2008 R2" self.message("Forest function level: (Windows) " + outstr) if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0: outstr = "2000 mixed (NT4 DC support)" elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0: outstr = "2000" elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED: outstr = "2003 with mixed domains/interim (NT4 DC support)" elif level_domain == DS_DOMAIN_FUNCTION_2003: outstr = "2003" elif level_domain == DS_DOMAIN_FUNCTION_2008: outstr = "2008" elif level_domain == DS_DOMAIN_FUNCTION_2008_R2: outstr = "2008 R2" else: outstr = "higher than 2008 R2" self.message("Domain function level: (Windows) " + outstr) if min_level_dc == DS_DOMAIN_FUNCTION_2000: outstr = "2000" elif min_level_dc == DS_DOMAIN_FUNCTION_2003: outstr = "2003" elif min_level_dc == DS_DOMAIN_FUNCTION_2008: outstr = "2008" elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2: outstr = "2008 R2" else: outstr = "higher than 2008 R2" self.message("Lowest function level of a DC: (Windows) " + outstr) elif subcommand == "raise": msgs = [] if domain_level is not None: if domain_level == "2003": new_level_domain = DS_DOMAIN_FUNCTION_2003 elif domain_level == "2008": new_level_domain = DS_DOMAIN_FUNCTION_2008 elif domain_level == "2008_R2": new_level_domain = DS_DOMAIN_FUNCTION_2008_R2 if new_level_domain <= level_domain and level_domain_mixed == 0: raise CommandError( "Domain function level can't be smaller than or equal to the actual one!" ) if new_level_domain > min_level_dc: raise CommandError( "Domain function level can't be higher than the lowest function level of a DC!" ) # Deactivate mixed/interim domain support if level_domain_mixed != 0: # Directly on the base DN m = ldb.Message() m.dn = ldb.Dn(samdb, domain_dn) m["nTMixedDomain"] = ldb.MessageElement( "0", ldb.FLAG_MOD_REPLACE, "nTMixedDomain") samdb.modify(m) # Under partitions m = ldb.Message() m.dn = ldb.Dn( samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % ldb.get_config_basedn()) m["nTMixedDomain"] = ldb.MessageElement( "0", ldb.FLAG_MOD_REPLACE, "nTMixedDomain") try: samdb.modify(m) except ldb.LdbError, (enum, emsg): if enum != ldb.ERR_UNWILLING_TO_PERFORM: raise # Directly on the base DN m = ldb.Message() m.dn = ldb.Dn(samdb, domain_dn) m["msDS-Behavior-Version"] = ldb.MessageElement( str(new_level_domain), ldb.FLAG_MOD_REPLACE, "msDS-Behavior-Version") samdb.modify(m) # Under partitions m = ldb.Message() m.dn = ldb.Dn( samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % ldb.get_config_basedn()) m["msDS-Behavior-Version"] = ldb.MessageElement( str(new_level_domain), ldb.FLAG_MOD_REPLACE, "msDS-Behavior-Version") try: samdb.modify(m) except ldb.LdbError, (enum, emsg): if enum != ldb.ERR_UNWILLING_TO_PERFORM: raise level_domain = new_level_domain msgs.append("Domain function level changed!")
class AuthLogPassChangeTests(samba.tests.auth_log_base.AuthLogTestBase): def setUp(self): super(AuthLogPassChangeTests, self).setUp() self.remoteAddress = os.environ["CLIENT_IP"] self.server_ip = os.environ["SERVER_IP"] host = "ldap://%s" % os.environ["SERVER"] self.ldb = SamDB(url=host, session_info=system_session(), credentials=self.get_credentials(), lp=self.get_loadparm()) print "ldb %s" % type(self.ldb) # Gets back the basedn base_dn = self.ldb.domain_dn() print "base_dn %s" % base_dn # Gets back the configuration basedn configuration_dn = self.ldb.get_config_basedn().get_linearized() # Get the old "dSHeuristics" if it was set dsheuristics = self.ldb.get_dsheuristics() # Set the "dSHeuristics" to activate the correct "userPassword" # behaviour self.ldb.set_dsheuristics("000000001") # Reset the "dSHeuristics" as they were before self.addCleanup(self.ldb.set_dsheuristics, dsheuristics) # Get the old "minPwdAge" minPwdAge = self.ldb.get_minPwdAge() # Set it temporarily to "0" self.ldb.set_minPwdAge("0") self.base_dn = self.ldb.domain_dn() # Reset the "minPwdAge" as it was before self.addCleanup(self.ldb.set_minPwdAge, minPwdAge) # (Re)adds the test user USER_NAME with password USER_PASS delete_force(self.ldb, "cn=" + USER_NAME + ",cn=users," + self.base_dn) self.ldb.add({ "dn": "cn=" + USER_NAME + ",cn=users," + self.base_dn, "objectclass": "user", "sAMAccountName": USER_NAME, "userPassword": USER_PASS }) # discard any auth log messages for the password setup self.discardMessages() def tearDown(self): super(AuthLogPassChangeTests, self).tearDown() def test_admin_change_password(self): def isLastExpectedMessage(msg): return (msg["type"] == "Authentication" and msg["Authentication"]["status"] == "NT_STATUS_OK" and msg["Authentication"]["serviceDescription"] == "SAMR Password Change" and msg["Authentication"]["authDescription"] == "samr_ChangePasswordUser3") creds = self.insta_creds(template = self.get_credentials()) lp = self.get_loadparm() net = Net(creds, lp, server=self.server_ip) password = "******" net.change_password(newpassword=password.encode('utf-8'), username=USER_NAME, oldpassword=USER_PASS) messages = self.waitForMessages(isLastExpectedMessage) print "Received %d messages" % len(messages) self.assertEquals(8, len(messages), "Did not receive the expected number of messages") def test_admin_change_password_new_password_fails_restriction(self): def isLastExpectedMessage(msg): return (msg["type"] == "Authentication" and msg["Authentication"]["status"] == "NT_STATUS_PASSWORD_RESTRICTION" and msg["Authentication"]["serviceDescription"] == "SAMR Password Change" and msg["Authentication"]["authDescription"] == "samr_ChangePasswordUser3") creds = self.insta_creds(template=self.get_credentials()) lp = self.get_loadparm() net = Net(creds, lp, server=self.server_ip) password = "******" exception_thrown = False try: net.change_password(newpassword=password.encode('utf-8'), oldpassword=USER_PASS, username=USER_NAME) except Exception, msg: exception_thrown = True self.assertEquals(True, exception_thrown, "Expected exception not thrown") messages = self.waitForMessages(isLastExpectedMessage) self.assertEquals(8, len(messages), "Did not receive the expected number of messages")