Beispiel #1
0
    def setUp(self):
        super(DrsRodcTestCase, self).setUp()
        self.base_dn = self.ldb_dc1.get_default_basedn()

        self.ou = samba.tests.create_test_ou(self.ldb_dc1, "test_drs_rodc")
        self.allowed_group = "CN=Allowed RODC Password Replication Group,CN=Users,%s" % self.base_dn

        self.site = self.ldb_dc1.server_site_name()
        self.rodc_name = "TESTRODCDRS%s" % random.randint(1, 10000000)
        self.rodc_pass = "******"
        self.computer_dn = "CN=%s,OU=Domain Controllers,%s" % (self.rodc_name, self.base_dn)

        self.rodc_ctx = DCJoinContext(server=self.ldb_dc1.host_dns_name(),
                                      creds=self.get_credentials(),
                                      lp=self.get_loadparm(), site=self.site,
                                      netbios_name=self.rodc_name,
                                      targetdir=None, domain=None,
                                      machinepass=self.rodc_pass)
        self._create_rodc(self.rodc_ctx)
        self.rodc_ctx.create_tmp_samdb()
        self.tmp_samdb = self.rodc_ctx.tmp_samdb

        rodc_creds = Credentials()
        rodc_creds.guess(self.rodc_ctx.lp)
        rodc_creds.set_username(self.rodc_name + '$')
        rodc_creds.set_password(self.rodc_pass)
        self.rodc_creds = rodc_creds

        (self.drs, self.drs_handle) = self._ds_bind(self.dnsname_dc1)
        (self.rodc_drs, self.rodc_drs_handle) = self._ds_bind(self.dnsname_dc1, rodc_creds)
Beispiel #2
0
    def setUp(self):
        self.server = samba.tests.env_get_var_value("SERVER")
        self.server_ip = samba.tests.env_get_var_value("SERVER_IP")
        super(JoinTestCase, self).setUp()
        self.lp = samba.tests.env_loadparm()
        self.creds = self.get_credentials()
        self.netbios_name = "jointest1"
        logger = get_logger()

        self.join_ctx = DCJoinContext(server=self.server,
                                      creds=self.creds,
                                      lp=self.get_loadparm(),
                                      netbios_name=self.netbios_name,
                                      targetdir=self.tempdir,
                                      domain=None,
                                      logger=logger,
                                      dns_backend="SAMBA_INTERNAL")
        self.join_ctx.userAccountControl = (
            samba.dsdb.UF_SERVER_TRUST_ACCOUNT
            | samba.dsdb.UF_TRUSTED_FOR_DELEGATION)

        self.join_ctx.replica_flags |= (
            drsuapi.DRSUAPI_DRS_WRIT_REP
            | drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
        self.join_ctx.domain_replica_flags = self.join_ctx.replica_flags
        self.join_ctx.secure_channel_type = misc.SEC_CHAN_BDC

        self.join_ctx.cleanup_old_join()

        self.join_ctx.force_all_ips = True

        self.join_ctx.do_join()
Beispiel #3
0
class DrsRodcTestCase(drs_base.DrsBaseTestCase):
    """Intended as a semi-black box test case for replication involving
       an RODC."""

    def setUp(self):
        super(DrsRodcTestCase, self).setUp()
        self.base_dn = self.ldb_dc1.get_default_basedn()

        self.ou = samba.tests.create_test_ou(self.ldb_dc1, "test_drs_rodc")
        self.allowed_group = "CN=Allowed RODC Password Replication Group,CN=Users,%s" % self.base_dn

        self.site = self.ldb_dc1.server_site_name()
        self.rodc_name = "TESTRODCDRS%s" % random.randint(1, 10000000)
        self.rodc_pass = "******"
        self.computer_dn = "CN=%s,OU=Domain Controllers,%s" % (self.rodc_name, self.base_dn)

        self.rodc_ctx = DCJoinContext(server=self.ldb_dc1.host_dns_name(),
                                      creds=self.get_credentials(),
                                      lp=self.get_loadparm(), site=self.site,
                                      netbios_name=self.rodc_name,
                                      targetdir=None, domain=None,
                                      machinepass=self.rodc_pass)
        self._create_rodc(self.rodc_ctx)
        self.rodc_ctx.create_tmp_samdb()
        self.tmp_samdb = self.rodc_ctx.tmp_samdb

        rodc_creds = Credentials()
        rodc_creds.guess(self.rodc_ctx.lp)
        rodc_creds.set_username(self.rodc_name + '$')
        rodc_creds.set_password(self.rodc_pass)
        self.rodc_creds = rodc_creds

        (self.drs, self.drs_handle) = self._ds_bind(self.dnsname_dc1)
        (self.rodc_drs, self.rodc_drs_handle) = self._ds_bind(self.dnsname_dc1, rodc_creds)

    def tearDown(self):
        self.rodc_ctx.cleanup_old_join()
        super(DrsRodcTestCase, self).tearDown()

    def test_admin_repl_secrets(self):
        """
        When a secret attribute is set to be replicated to an RODC with the
        admin credentials, it should always replicate regardless of whether
        or not it's in the Allowed RODC Password Replication Group.
        """
        rand = random.randint(1, 10000000)
        expected_user_attributes = [drsuapi.DRSUAPI_ATTID_lmPwdHistory,
                                    drsuapi.DRSUAPI_ATTID_supplementalCredentials,
                                    drsuapi.DRSUAPI_ATTID_ntPwdHistory,
                                    drsuapi.DRSUAPI_ATTID_unicodePwd,
                                    drsuapi.DRSUAPI_ATTID_dBCSPwd]

        user_name = "test_rodcA_%s" % rand
        user_dn = "CN=%s,%s" % (user_name, self.ou)
        self.ldb_dc1.add({
            "dn": user_dn,
            "objectclass": "user",
            "sAMAccountName": user_name
        })

        # Store some secret on this user
        self.ldb_dc1.setpassword("(sAMAccountName=%s)" % user_name, 'penguin12#', False, user_name)

        req10 = self._getnc_req10(dest_dsa=str(self.rodc_ctx.ntds_guid),
                                  invocation_id=self.ldb_dc1.get_invocation_id(),
                                  nc_dn_str=user_dn,
                                  exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET,
                                  partial_attribute_set=drs_get_rodc_partial_attribute_set(self.ldb_dc1, self.tmp_samdb),
                                  max_objects=133,
                                  replica_flags=0)
        (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 10, req10)

        # Check that the user has been added to msDSRevealedUsers
        self._assert_in_revealed_users(user_dn, expected_user_attributes)

    def test_rodc_repl_secrets(self):
        """
        When a secret attribute is set to be replicated to an RODC with
        the RODC account credentials, it should not replicate if it's in
        the Allowed RODC Password Replication Group. Once it is added to
        the group, it should replicate.
        """
        rand = random.randint(1, 10000000)
        expected_user_attributes = [drsuapi.DRSUAPI_ATTID_lmPwdHistory,
                                    drsuapi.DRSUAPI_ATTID_supplementalCredentials,
                                    drsuapi.DRSUAPI_ATTID_ntPwdHistory,
                                    drsuapi.DRSUAPI_ATTID_unicodePwd,
                                    drsuapi.DRSUAPI_ATTID_dBCSPwd]

        user_name = "test_rodcB_%s" % rand
        user_dn = "CN=%s,%s" % (user_name, self.ou)
        self.ldb_dc1.add({
            "dn": user_dn,
            "objectclass": "user",
            "sAMAccountName": user_name
        })

        # Store some secret on this user
        self.ldb_dc1.setpassword("(sAMAccountName=%s)" % user_name, 'penguin12#', False, user_name)

        req10 = self._getnc_req10(dest_dsa=str(self.rodc_ctx.ntds_guid),
                                  invocation_id=self.ldb_dc1.get_invocation_id(),
                                  nc_dn_str=user_dn,
                                  exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET,
                                  partial_attribute_set=drs_get_rodc_partial_attribute_set(self.ldb_dc1, self.tmp_samdb),
                                  max_objects=133,
                                  replica_flags=0)

        try:
            (level, ctr) = self.rodc_drs.DsGetNCChanges(self.rodc_drs_handle, 10, req10)
            self.fail("Successfully replicated secrets to an RODC that shouldn't have been replicated.")
        except WERRORError as e:
            (enum, estr) = e.args
            self.assertEqual(enum, 8630)  # ERROR_DS_DRA_SECRETS_DENIED

        # send the same request again and we should get the same response
        try:
            (level, ctr) = self.rodc_drs.DsGetNCChanges(self.rodc_drs_handle, 10, req10)
            self.fail("Successfully replicated secrets to an RODC that shouldn't have been replicated.")
        except WERRORError as e1:
            (enum, estr) = e1.args
            self.assertEqual(enum, 8630)  # ERROR_DS_DRA_SECRETS_DENIED

        # Retry with Administrator credentials, ignores password replication groups
        (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 10, req10)

        # Check that the user has been added to msDSRevealedUsers
        self._assert_in_revealed_users(user_dn, expected_user_attributes)

    def test_rodc_repl_secrets_follow_on_req(self):
        """
        Checks that an RODC can't subvert an existing (valid) GetNCChanges
        request to reveal secrets it shouldn't have access to.
        """

        # send an acceptable request that will match as many GUIDs as possible.
        # Here we set the SPECIAL_SECRET_PROCESSING flag so that the request gets accepted.
        # (On the server, this builds up the getnc_state->guids array)
        req8 = self._exop_req8(dest_dsa=str(self.rodc_ctx.ntds_guid),
                               invocation_id=self.ldb_dc1.get_invocation_id(),
                               nc_dn_str=self.ldb_dc1.domain_dn(),
                               exop=drsuapi.DRSUAPI_EXOP_NONE,
                               max_objects=1,
                               replica_flags=drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING)
        (level, ctr) = self.rodc_drs.DsGetNCChanges(self.rodc_drs_handle, 8, req8)

        # Get the next replication chunk, but set REPL_SECRET this time. This
        # is following on the the previous accepted request, but we've changed
        # exop to now request secrets. This request should fail
        try:
            req8 = self._exop_req8(dest_dsa=str(self.rodc_ctx.ntds_guid),
                                   invocation_id=self.ldb_dc1.get_invocation_id(),
                                   nc_dn_str=self.ldb_dc1.domain_dn(),
                                   exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET)
            req8.highwatermark = ctr.new_highwatermark

            (level, ctr) = self.rodc_drs.DsGetNCChanges(self.rodc_drs_handle, 8, req8)

            self.fail("Successfully replicated secrets to an RODC that shouldn't have been replicated.")
        except RuntimeError as e2:
            (enum, estr) = e2.args
            pass

    def test_msDSRevealedUsers_admin(self):
        """
        When a secret attribute is to be replicated to an RODC, the contents
        of the attribute should be added to the msDSRevealedUsers attribute
        of the computer object corresponding to the RODC.
        """

        rand = random.randint(1, 10000000)
        expected_user_attributes = [drsuapi.DRSUAPI_ATTID_lmPwdHistory,
                                    drsuapi.DRSUAPI_ATTID_supplementalCredentials,
                                    drsuapi.DRSUAPI_ATTID_ntPwdHistory,
                                    drsuapi.DRSUAPI_ATTID_unicodePwd,
                                    drsuapi.DRSUAPI_ATTID_dBCSPwd]

        # Add a user on DC1, add it to allowed password replication
        # group, and replicate to RODC with EXOP_REPL_SECRETS
        user_name = "test_rodcC_%s" % rand
        password = "******"
        user_dn = "CN=%s,%s" % (user_name, self.ou)
        self.ldb_dc1.add({
            "dn": user_dn,
            "objectclass": "user",
            "sAMAccountName": user_name
        })

        # Store some secret on this user
        self.ldb_dc1.setpassword("(sAMAccountName=%s)" % user_name, password, False, user_name)

        self.ldb_dc1.add_remove_group_members("Allowed RODC Password Replication Group",
                                              [user_name],
                                              add_members_operation=True)

        req10 = self._getnc_req10(dest_dsa=str(self.rodc_ctx.ntds_guid),
                                  invocation_id=self.ldb_dc1.get_invocation_id(),
                                  nc_dn_str=user_dn,
                                  exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET,
                                  partial_attribute_set=drs_get_rodc_partial_attribute_set(self.ldb_dc1, self.tmp_samdb),
                                  max_objects=133,
                                  replica_flags=0)
        (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 10, req10)

        # Check that the user has been added to msDSRevealedUsers
        (packed_attrs_1, unpacked_attrs_1) = self._assert_in_revealed_users(user_dn, expected_user_attributes)

        # Change the user's password on DC1
        self.ldb_dc1.setpassword("(sAMAccountName=%s)" % user_name, password + "1", False, user_name)

        (packed_attrs_2, unpacked_attrs_2) = self._assert_in_revealed_users(user_dn, expected_user_attributes)
        self._assert_attrlist_equals(unpacked_attrs_1, unpacked_attrs_2)

        # Replicate to RODC again with EXOP_REPL_SECRETS
        req10 = self._getnc_req10(dest_dsa=str(self.rodc_ctx.ntds_guid),
                                  invocation_id=self.ldb_dc1.get_invocation_id(),
                                  nc_dn_str=user_dn,
                                  exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET,
                                  partial_attribute_set=drs_get_rodc_partial_attribute_set(self.ldb_dc1, self.tmp_samdb),
                                  max_objects=133,
                                  replica_flags=0)
        (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 10, req10)

        # This is important for Windows, because the entry won't have been
        # updated in time if we don't have it. Even with this sleep, it only
        # passes some of the time...
        time.sleep(5)

        # Check that the entry in msDSRevealedUsers has been updated
        (packed_attrs_3, unpacked_attrs_3) = self._assert_in_revealed_users(user_dn, expected_user_attributes)
        self._assert_attrlist_changed(unpacked_attrs_2, unpacked_attrs_3, expected_user_attributes)

        # We should be able to delete the user
        self.ldb_dc1.deleteuser(user_name)

        res = self.ldb_dc1.search(scope=ldb.SCOPE_BASE, base=self.computer_dn,
                                  attrs=["msDS-RevealedUsers"])
        self.assertFalse("msDS-RevealedUsers" in res[0])

    def test_msDSRevealedUsers(self):
        """
        When a secret attribute is to be replicated to an RODC, the contents
        of the attribute should be added to the msDSRevealedUsers attribute
        of the computer object corresponding to the RODC.
        """

        rand = random.randint(1, 10000000)
        expected_user_attributes = [drsuapi.DRSUAPI_ATTID_lmPwdHistory,
                                    drsuapi.DRSUAPI_ATTID_supplementalCredentials,
                                    drsuapi.DRSUAPI_ATTID_ntPwdHistory,
                                    drsuapi.DRSUAPI_ATTID_unicodePwd,
                                    drsuapi.DRSUAPI_ATTID_dBCSPwd]

        # Add a user on DC1, add it to allowed password replication
        # group, and replicate to RODC with EXOP_REPL_SECRETS
        user_name = "test_rodcD_%s" % rand
        password = "******"
        user_dn = "CN=%s,%s" % (user_name, self.ou)
        self.ldb_dc1.add({
            "dn": user_dn,
            "objectclass": "user",
            "sAMAccountName": user_name
        })

        # Store some secret on this user
        self.ldb_dc1.setpassword("(sAMAccountName=%s)" % user_name, password, False, user_name)

        self.ldb_dc1.add_remove_group_members("Allowed RODC Password Replication Group",
                                              [user_name],
                                              add_members_operation=True)

        req10 = self._getnc_req10(dest_dsa=str(self.rodc_ctx.ntds_guid),
                                  invocation_id=self.ldb_dc1.get_invocation_id(),
                                  nc_dn_str=user_dn,
                                  exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET,
                                  partial_attribute_set=drs_get_rodc_partial_attribute_set(self.ldb_dc1, self.tmp_samdb),
                                  max_objects=133,
                                  replica_flags=0)
        (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 10, req10)

        # Check that the user has been added to msDSRevealedUsers
        (packed_attrs_1, unpacked_attrs_1) = self._assert_in_revealed_users(user_dn, expected_user_attributes)

        # Change the user's password on DC1
        self.ldb_dc1.setpassword("(sAMAccountName=%s)" % user_name, password + "1", False, user_name)

        (packed_attrs_2, unpacked_attrs_2) = self._assert_in_revealed_users(user_dn, expected_user_attributes)
        self._assert_attrlist_equals(unpacked_attrs_1, unpacked_attrs_2)

        # Replicate to RODC again with EXOP_REPL_SECRETS
        req10 = self._getnc_req10(dest_dsa=str(self.rodc_ctx.ntds_guid),
                                  invocation_id=self.ldb_dc1.get_invocation_id(),
                                  nc_dn_str=user_dn,
                                  exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET,
                                  partial_attribute_set=drs_get_rodc_partial_attribute_set(self.ldb_dc1, self.tmp_samdb),
                                  max_objects=133,
                                  replica_flags=0)
        (level, ctr) = self.rodc_drs.DsGetNCChanges(self.rodc_drs_handle, 10, req10)

        # This is important for Windows, because the entry won't have been
        # updated in time if we don't have it. Even with this sleep, it only
        # passes some of the time...
        time.sleep(5)

        # Check that the entry in msDSRevealedUsers has been updated
        (packed_attrs_3, unpacked_attrs_3) = self._assert_in_revealed_users(user_dn, expected_user_attributes)
        self._assert_attrlist_changed(unpacked_attrs_2, unpacked_attrs_3, expected_user_attributes)

        # We should be able to delete the user
        self.ldb_dc1.deleteuser(user_name)

        res = self.ldb_dc1.search(scope=ldb.SCOPE_BASE, base=self.computer_dn,
                                  attrs=["msDS-RevealedUsers"])
        self.assertFalse("msDS-RevealedUsers" in res[0])

    def test_msDSRevealedUsers_pas(self):
        """
        If we provide a Partial Attribute Set when replicating to an RODC,
        we should ignore it and replicate all of the secret attributes anyway
        msDSRevealedUsers attribute.
        """
        rand = random.randint(1, 10000000)
        expected_user_attributes = [drsuapi.DRSUAPI_ATTID_lmPwdHistory,
                                    drsuapi.DRSUAPI_ATTID_supplementalCredentials,
                                    drsuapi.DRSUAPI_ATTID_ntPwdHistory,
                                    drsuapi.DRSUAPI_ATTID_unicodePwd,
                                    drsuapi.DRSUAPI_ATTID_dBCSPwd]
        pas_exceptions = [drsuapi.DRSUAPI_ATTID_lmPwdHistory,
                          drsuapi.DRSUAPI_ATTID_supplementalCredentials,
                          drsuapi.DRSUAPI_ATTID_ntPwdHistory,
                          drsuapi.DRSUAPI_ATTID_dBCSPwd]

        # Add a user on DC1, add it to allowed password replication
        # group, and replicate to RODC with EXOP_REPL_SECRETS
        user_name = "test_rodcE_%s" % rand
        password = "******"
        user_dn = "CN=%s,%s" % (user_name, self.ou)
        self.ldb_dc1.add({
            "dn": user_dn,
            "objectclass": "user",
            "sAMAccountName": user_name
        })

        # Store some secret on this user
        self.ldb_dc1.setpassword("(sAMAccountName=%s)" % user_name, password, False, user_name)

        self.ldb_dc1.add_remove_group_members("Allowed RODC Password Replication Group",
                                              [user_name],
                                              add_members_operation=True)

        pas = drs_get_rodc_partial_attribute_set(self.ldb_dc1, self.tmp_samdb, exceptions=pas_exceptions)
        req10 = self._getnc_req10(dest_dsa=str(self.rodc_ctx.ntds_guid),
                                  invocation_id=self.ldb_dc1.get_invocation_id(),
                                  nc_dn_str=user_dn,
                                  exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET,
                                  partial_attribute_set=pas,
                                  max_objects=133,
                                  replica_flags=0)
        (level, ctr) = self.drs.DsGetNCChanges(self.drs_handle, 10, req10)

        # Make sure that we still replicate the secrets
        for attribute in ctr.first_object.object.attribute_ctr.attributes:
            if attribute.attid in pas_exceptions:
                pas_exceptions.remove(attribute.attid)
        for attribute in pas_exceptions:
            self.fail("%d was not replicated even though the partial attribute set should be ignored."
                      % attribute)

        # Check that the user has been added to msDSRevealedUsers
        (packed_attrs_1, unpacked_attrs_1) = self._assert_in_revealed_users(user_dn, expected_user_attributes)

    def test_msDSRevealedUsers_using_other_RODC(self):
        """
        Ensure that the machine account is tied to the destination DSA.
        """
        # Create a new identical RODC with just the first letter missing
        other_rodc_name = self.rodc_name[1:]
        other_rodc_ctx = DCJoinContext(server=self.ldb_dc1.host_dns_name(),
                                       creds=self.get_credentials(),
                                       lp=self.get_loadparm(), site=self.site,
                                       netbios_name=other_rodc_name,
                                       targetdir=None, domain=None,
                                       machinepass=self.rodc_pass)
        self._create_rodc(other_rodc_ctx)

        other_rodc_creds = Credentials()
        other_rodc_creds.guess(other_rodc_ctx.lp)
        other_rodc_creds.set_username(other_rodc_name + '$')
        other_rodc_creds.set_password(self.rodc_pass)

        (other_rodc_drs, other_rodc_drs_handle) = self._ds_bind(self.dnsname_dc1, other_rodc_creds)

        rand = random.randint(1, 10000000)
        expected_user_attributes = [drsuapi.DRSUAPI_ATTID_lmPwdHistory,
                                    drsuapi.DRSUAPI_ATTID_supplementalCredentials,
                                    drsuapi.DRSUAPI_ATTID_ntPwdHistory,
                                    drsuapi.DRSUAPI_ATTID_unicodePwd,
                                    drsuapi.DRSUAPI_ATTID_dBCSPwd]

        user_name = "test_rodcF_%s" % rand
        user_dn = "CN=%s,%s" % (user_name, self.ou)
        self.ldb_dc1.add({
            "dn": user_dn,
            "objectclass": "user",
            "sAMAccountName": user_name
        })

        # Store some secret on this user
        self.ldb_dc1.setpassword("(sAMAccountName=%s)" % user_name, 'penguin12#', False, user_name)
        self.ldb_dc1.add_remove_group_members("Allowed RODC Password Replication Group",
                                              [user_name],
                                              add_members_operation=True)

        req10 = self._getnc_req10(dest_dsa=str(other_rodc_ctx.ntds_guid),
                                  invocation_id=self.ldb_dc1.get_invocation_id(),
                                  nc_dn_str=user_dn,
                                  exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET,
                                  partial_attribute_set=drs_get_rodc_partial_attribute_set(self.ldb_dc1, self.tmp_samdb),
                                  max_objects=133,
                                  replica_flags=0)

        try:
            (level, ctr) = self.rodc_drs.DsGetNCChanges(self.rodc_drs_handle, 10, req10)
            self.fail("Successfully replicated secrets to an RODC that shouldn't have been replicated.")
        except WERRORError as e3:
            (enum, estr) = e3.args
            self.assertEqual(enum, 8630)  # ERROR_DS_DRA_SECRETS_DENIED

        req10 = self._getnc_req10(dest_dsa=str(self.rodc_ctx.ntds_guid),
                                  invocation_id=self.ldb_dc1.get_invocation_id(),
                                  nc_dn_str=user_dn,
                                  exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET,
                                  partial_attribute_set=drs_get_rodc_partial_attribute_set(self.ldb_dc1, self.tmp_samdb),
                                  max_objects=133,
                                  replica_flags=0)

        try:
            (level, ctr) = other_rodc_drs.DsGetNCChanges(other_rodc_drs_handle, 10, req10)
            self.fail("Successfully replicated secrets to an RODC that shouldn't have been replicated.")
        except WERRORError as e4:
            (enum, estr) = e4.args
            self.assertEqual(enum, 8630)  # ERROR_DS_DRA_SECRETS_DENIED

    def test_msDSRevealedUsers_local_deny_allow(self):
        """
        Ensure that the deny trumps allow, and we can modify these
        attributes directly instead of the global groups.

        This may fail on Windows due to tokenGroup calculation caching.
        """
        rand = random.randint(1, 10000000)
        expected_user_attributes = [drsuapi.DRSUAPI_ATTID_lmPwdHistory,
                                    drsuapi.DRSUAPI_ATTID_supplementalCredentials,
                                    drsuapi.DRSUAPI_ATTID_ntPwdHistory,
                                    drsuapi.DRSUAPI_ATTID_unicodePwd,
                                    drsuapi.DRSUAPI_ATTID_dBCSPwd]

        # Add a user on DC1, add it to allowed password replication
        # group, and replicate to RODC with EXOP_REPL_SECRETS
        user_name = "test_rodcF_%s" % rand
        password = "******"
        user_dn = "CN=%s,%s" % (user_name, self.ou)
        self.ldb_dc1.add({
            "dn": user_dn,
            "objectclass": "user",
            "sAMAccountName": user_name
        })

        # Store some secret on this user
        self.ldb_dc1.setpassword("(sAMAccountName=%s)" % user_name, password, False, user_name)

        req10 = self._getnc_req10(dest_dsa=str(self.rodc_ctx.ntds_guid),
                                  invocation_id=self.ldb_dc1.get_invocation_id(),
                                  nc_dn_str=user_dn,
                                  exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET,
                                  partial_attribute_set=drs_get_rodc_partial_attribute_set(self.ldb_dc1, self.tmp_samdb),
                                  max_objects=133,
                                  replica_flags=0)

        m = ldb.Message()
        m.dn = ldb.Dn(self.ldb_dc1, self.computer_dn)

        m["msDS-RevealOnDemandGroup"] = \
            ldb.MessageElement(user_dn, ldb.FLAG_MOD_ADD,
                               "msDS-RevealOnDemandGroup")
        self.ldb_dc1.modify(m)

        # In local allow, should be success
        try:
            (level, ctr) = self.rodc_drs.DsGetNCChanges(self.rodc_drs_handle, 10, req10)
        except:
            self.fail("Should have succeeded when in local allow group")

        self._assert_in_revealed_users(user_dn, expected_user_attributes)

        (self.rodc_drs, self.rodc_drs_handle) = self._ds_bind(self.dnsname_dc1, self.rodc_creds)

        m = ldb.Message()
        m.dn = ldb.Dn(self.ldb_dc1, self.computer_dn)

        m["msDS-NeverRevealGroup"] = \
            ldb.MessageElement(user_dn, ldb.FLAG_MOD_ADD,
                               "msDS-NeverRevealGroup")
        self.ldb_dc1.modify(m)

        # In local allow and deny, should be failure
        try:
            (level, ctr) = self.rodc_drs.DsGetNCChanges(self.rodc_drs_handle, 10, req10)
            self.fail("Successfully replicated secrets to an RODC that shouldn't have been replicated.")
        except WERRORError as e5:
            (enum, estr) = e5.args
            self.assertEqual(enum, 8630)  # ERROR_DS_DRA_SECRETS_DENIED

        m = ldb.Message()
        m.dn = ldb.Dn(self.ldb_dc1, self.computer_dn)

        m["msDS-RevealOnDemandGroup"] = \
            ldb.MessageElement(user_dn, ldb.FLAG_MOD_DELETE,
                               "msDS-RevealOnDemandGroup")
        self.ldb_dc1.modify(m)

        # In local deny, should be failure
        (self.rodc_drs, self.rodc_drs_handle) = self._ds_bind(self.dnsname_dc1, self.rodc_creds)
        try:
            (level, ctr) = self.rodc_drs.DsGetNCChanges(self.rodc_drs_handle, 10, req10)
            self.fail("Successfully replicated secrets to an RODC that shouldn't have been replicated.")
        except WERRORError as e6:
            (enum, estr) = e6.args
            self.assertEqual(enum, 8630)  # ERROR_DS_DRA_SECRETS_DENIED

    def _assert_in_revealed_users(self, user_dn, attrlist):
        res = self.ldb_dc1.search(scope=ldb.SCOPE_BASE, base=self.computer_dn,
                                  attrs=["msDS-RevealedUsers"])
        revealed_users = res[0]["msDS-RevealedUsers"]
        actual_attrids = []
        packed_attrs = []
        unpacked_attrs = []
        for attribute in revealed_users:
            attribute = attribute.decode('utf8')
            dsdb_dn = dsdb_Dn(self.ldb_dc1, attribute)
            metadata = ndr_unpack(drsblobs.replPropertyMetaData1, dsdb_dn.get_bytes())
            if user_dn in attribute:
                unpacked_attrs.append(metadata)
                packed_attrs.append(dsdb_dn.get_bytes())
                actual_attrids.append(metadata.attid)

        self.assertEqual(sorted(actual_attrids), sorted(attrlist))

        return (packed_attrs, unpacked_attrs)

    def _assert_attrlist_equals(self, list_1, list_2):
        return self._assert_attrlist_changed(list_1, list_2, [], num_changes=0, expected_new_usn=False)

    def _assert_attrlist_changed(self, list_1, list_2, changed_attributes, num_changes=1, expected_new_usn=True):
        for i in range(len(list_2)):
            self.assertEqual(list_1[i].attid, list_2[i].attid)
            self.assertEqual(list_1[i].originating_invocation_id, list_2[i].originating_invocation_id)
            self.assertEqual(list_1[i].version + num_changes, list_2[i].version)

            if expected_new_usn:
                self.assertTrue(list_1[i].originating_usn < list_2[i].originating_usn)
                self.assertTrue(list_1[i].local_usn < list_2[i].local_usn)
            else:
                self.assertEqual(list_1[i].originating_usn, list_2[i].originating_usn)
                self.assertEqual(list_1[i].local_usn, list_2[i].local_usn)

            if list_1[i].attid in changed_attributes:
                # We do the changes too quickly, so unless we put sleeps
                # inbetween calls, these remain the same. Checking the USNs
                # is enough.
                pass
                #self.assertTrue(list_1[i].originating_change_time < list_2[i].originating_change_time)
            else:
                self.assertEqual(list_1[i].originating_change_time, list_2[i].originating_change_time)

    def _create_rodc(self, ctx):
        ctx.nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
        ctx.full_nc_list = [ctx.base_dn, ctx.config_dn, ctx.schema_dn]
        ctx.krbtgt_dn = "CN=krbtgt_%s,CN=Users,%s" % (ctx.myname, ctx.base_dn)

        ctx.never_reveal_sid = ["<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_DENY),
                                "<SID=%s>" % security.SID_BUILTIN_ADMINISTRATORS,
                                "<SID=%s>" % security.SID_BUILTIN_SERVER_OPERATORS,
                                "<SID=%s>" % security.SID_BUILTIN_BACKUP_OPERATORS,
                                "<SID=%s>" % security.SID_BUILTIN_ACCOUNT_OPERATORS]
        ctx.reveal_sid = "<SID=%s-%s>" % (ctx.domsid, security.DOMAIN_RID_RODC_ALLOW)

        mysid = ctx.get_mysid()
        admin_dn = "<SID=%s>" % mysid
        ctx.managedby = admin_dn

        ctx.userAccountControl = (samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
                                  samba.dsdb.UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION |
                                  samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT)

        ctx.connection_dn = "CN=RODC Connection (FRS),%s" % ctx.ntds_dn
        ctx.secure_channel_type = misc.SEC_CHAN_RODC
        ctx.RODC = True
        ctx.replica_flags = (drsuapi.DRSUAPI_DRS_INIT_SYNC |
                             drsuapi.DRSUAPI_DRS_PER_SYNC |
                             drsuapi.DRSUAPI_DRS_GET_ANC |
                             drsuapi.DRSUAPI_DRS_NEVER_SYNCED |
                             drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING)

        ctx.join_add_objects()
Beispiel #4
0
    def test_msDSRevealedUsers_using_other_RODC(self):
        """
        Ensure that the machine account is tied to the destination DSA.
        """
        # Create a new identical RODC with just the first letter missing
        other_rodc_name = self.rodc_name[1:]
        other_rodc_ctx = DCJoinContext(server=self.ldb_dc1.host_dns_name(),
                                       creds=self.get_credentials(),
                                       lp=self.get_loadparm(), site=self.site,
                                       netbios_name=other_rodc_name,
                                       targetdir=None, domain=None,
                                       machinepass=self.rodc_pass)
        self._create_rodc(other_rodc_ctx)

        other_rodc_creds = Credentials()
        other_rodc_creds.guess(other_rodc_ctx.lp)
        other_rodc_creds.set_username(other_rodc_name + '$')
        other_rodc_creds.set_password(self.rodc_pass)

        (other_rodc_drs, other_rodc_drs_handle) = self._ds_bind(self.dnsname_dc1, other_rodc_creds)

        rand = random.randint(1, 10000000)
        expected_user_attributes = [drsuapi.DRSUAPI_ATTID_lmPwdHistory,
                                    drsuapi.DRSUAPI_ATTID_supplementalCredentials,
                                    drsuapi.DRSUAPI_ATTID_ntPwdHistory,
                                    drsuapi.DRSUAPI_ATTID_unicodePwd,
                                    drsuapi.DRSUAPI_ATTID_dBCSPwd]

        user_name = "test_rodcF_%s" % rand
        user_dn = "CN=%s,%s" % (user_name, self.ou)
        self.ldb_dc1.add({
            "dn": user_dn,
            "objectclass": "user",
            "sAMAccountName": user_name
        })

        # Store some secret on this user
        self.ldb_dc1.setpassword("(sAMAccountName=%s)" % user_name, 'penguin12#', False, user_name)
        self.ldb_dc1.add_remove_group_members("Allowed RODC Password Replication Group",
                                              [user_name],
                                              add_members_operation=True)

        req10 = self._getnc_req10(dest_dsa=str(other_rodc_ctx.ntds_guid),
                                  invocation_id=self.ldb_dc1.get_invocation_id(),
                                  nc_dn_str=user_dn,
                                  exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET,
                                  partial_attribute_set=drs_get_rodc_partial_attribute_set(self.ldb_dc1, self.tmp_samdb),
                                  max_objects=133,
                                  replica_flags=0)

        try:
            (level, ctr) = self.rodc_drs.DsGetNCChanges(self.rodc_drs_handle, 10, req10)
            self.fail("Successfully replicated secrets to an RODC that shouldn't have been replicated.")
        except WERRORError as e3:
            (enum, estr) = e3.args
            self.assertEqual(enum, 8630)  # ERROR_DS_DRA_SECRETS_DENIED

        req10 = self._getnc_req10(dest_dsa=str(self.rodc_ctx.ntds_guid),
                                  invocation_id=self.ldb_dc1.get_invocation_id(),
                                  nc_dn_str=user_dn,
                                  exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET,
                                  partial_attribute_set=drs_get_rodc_partial_attribute_set(self.ldb_dc1, self.tmp_samdb),
                                  max_objects=133,
                                  replica_flags=0)

        try:
            (level, ctr) = other_rodc_drs.DsGetNCChanges(other_rodc_drs_handle, 10, req10)
            self.fail("Successfully replicated secrets to an RODC that shouldn't have been replicated.")
        except WERRORError as e4:
            (enum, estr) = e4.args
            self.assertEqual(enum, 8630)  # ERROR_DS_DRA_SECRETS_DENIED
Beispiel #5
0
    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.")
Beispiel #6
0
class JoinTestCase(DNSTKeyTest):
    def setUp(self):
        self.server = samba.tests.env_get_var_value("SERVER")
        self.server_ip = samba.tests.env_get_var_value("SERVER_IP")
        super(JoinTestCase, self).setUp()
        self.lp = samba.tests.env_loadparm()
        self.creds = self.get_credentials()
        self.netbios_name = "jointest1"
        logger = get_logger()

        self.join_ctx = DCJoinContext(server=self.server,
                                      creds=self.creds,
                                      lp=self.get_loadparm(),
                                      netbios_name=self.netbios_name,
                                      targetdir=self.tempdir,
                                      domain=None,
                                      logger=logger,
                                      dns_backend="SAMBA_INTERNAL")
        self.join_ctx.userAccountControl = (
            samba.dsdb.UF_SERVER_TRUST_ACCOUNT
            | samba.dsdb.UF_TRUSTED_FOR_DELEGATION)

        self.join_ctx.replica_flags |= (
            drsuapi.DRSUAPI_DRS_WRIT_REP
            | drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS)
        self.join_ctx.domain_replica_flags = self.join_ctx.replica_flags
        self.join_ctx.secure_channel_type = misc.SEC_CHAN_BDC

        self.join_ctx.cleanup_old_join()

        self.join_ctx.force_all_ips = True

        self.join_ctx.do_join()

    def tearDown(self):
        try:
            paths = self.join_ctx.paths
        except AttributeError:
            paths = None

        if paths is not None:
            shutil.rmtree(paths.private_dir)
            shutil.rmtree(paths.state_dir)
            shutil.rmtree(os.path.join(self.tempdir, "etc"))
            shutil.rmtree(os.path.join(self.tempdir, "msg.lock"))
            os.unlink(os.path.join(self.tempdir, "names.tdb"))
            shutil.rmtree(os.path.join(self.tempdir, "bind-dns"))

        self.join_ctx.cleanup_old_join(force=True)

        super(JoinTestCase, self).tearDown()

    def test_join_makes_records(self):
        "create a query packet containing one query record via TCP"
        p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
        questions = []

        name = self.join_ctx.dnshostname
        q = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN)
        questions.append(q)

        # Get expected IPs
        IPs = samba.interface_ips(self.lp)

        self.finish_name_packet(p, questions)
        (response,
         response_packet) = self.dns_transaction_tcp(p, host=self.server_ip)
        self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
        self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
        self.assertEquals(response.ancount, len(IPs))

        questions = []
        name = "%s._msdcs.%s" % (self.join_ctx.ntds_guid,
                                 self.join_ctx.dnsforest)
        q = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN)
        questions.append(q)

        self.finish_name_packet(p, questions)
        (response,
         response_packet) = self.dns_transaction_tcp(p, host=self.server_ip)
        self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
        self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)

        self.assertEquals(response.ancount, 1 + len(IPs))
        self.assertEquals(response.answers[0].rr_type, dns.DNS_QTYPE_CNAME)
        self.assertEquals(response.answers[0].rdata, self.join_ctx.dnshostname)
        self.assertEquals(response.answers[1].rr_type, dns.DNS_QTYPE_A)

    def test_join_records_can_update(self):
        dc_creds = Credentials()
        dc_creds.guess(self.join_ctx.lp)
        dc_creds.set_machine_account(self.join_ctx.lp)

        self.tkey_trans(creds=dc_creds)

        p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
        q = self.make_name_question(self.join_ctx.dnsdomain, dns.DNS_QTYPE_SOA,
                                    dns.DNS_QCLASS_IN)
        questions = []
        questions.append(q)
        self.finish_name_packet(p, questions)

        updates = []
        # Delete the old expected IPs
        IPs = samba.interface_ips(self.lp)
        for IP in IPs[1:]:
            if ":" in IP:
                r = dns.res_rec()
                r.name = self.join_ctx.dnshostname
                r.rr_type = dns.DNS_QTYPE_AAAA
                r.rr_class = dns.DNS_QCLASS_NONE
                r.ttl = 0
                r.length = 0xffff
                rdata = IP
            else:
                r = dns.res_rec()
                r.name = self.join_ctx.dnshostname
                r.rr_type = dns.DNS_QTYPE_A
                r.rr_class = dns.DNS_QCLASS_NONE
                r.ttl = 0
                r.length = 0xffff
                rdata = IP

            r.rdata = rdata
            updates.append(r)

        p.nscount = len(updates)
        p.nsrecs = updates

        mac = self.sign_packet(p, self.key_name)
        (response, response_p) = self.dns_transaction_udp(p, self.server_ip)
        self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
        self.verify_packet(response, response_p, mac)

        p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
        questions = []

        name = self.join_ctx.dnshostname
        q = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN)
        questions.append(q)

        self.finish_name_packet(p, questions)
        (response,
         response_packet) = self.dns_transaction_tcp(p, host=self.server_ip)
        self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
        self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
        self.assertEquals(response.ancount, 1)