From: JINMEI Tatuya Date: Wed, 30 May 2012 22:50:17 +0000 (-0700) Subject: [master] Merge branch 'trac1458' with fixing conflicts. X-Git-Tag: trac2351_base~226^2~64^2^2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f89b4428468decd82132b0e0a02f2dc171fd690c;p=thirdparty%2Fkea.git [master] Merge branch 'trac1458' with fixing conflicts. --- f89b4428468decd82132b0e0a02f2dc171fd690c diff --cc src/lib/python/isc/ddns/libddns_messages.mes index 190a6bf10b,27ab475b03..12c3a8c76d --- a/src/lib/python/isc/ddns/libddns_messages.mes +++ b/src/lib/python/isc/ddns/libddns_messages.mes @@@ -15,84 -15,16 +15,100 @@@ # No namespace declaration - these constants go in the global namespace # of the libddns_messages python module. +% LIBDDNS_PREREQ_FORMERR update client %1 for zone %2: Format error in prerequisite (%3). Non-zero TTL. +The prerequisite with the given name, class and type is not well-formed. +The specific prerequisite is shown. In this case, it has a non-zero TTL value. +A FORMERR error response is sent to the client. + +% LIBDDNS_PREREQ_FORMERR_ANY update client %1 for zone %2: Format error in prerequisite (%3). Non-zero TTL or rdata found. +The prerequisite with the given name, class and type is not well-formed. +The specific prerequisite is shown. In this case, it either has a non-zero +TTL value, or has rdata fields. A FORMERR error response is sent to the client. + +% LIBDDNS_PREREQ_FORMERR_CLASS update client %1 for zone %2: Format error in prerequisite (%3). Bad class. +The prerequisite with the given name, class and type is not well-formed. +The specific prerequisite is shown. In this case, the class of the +prerequisite should either match the class of the zone in the Zone Section, +or it should be ANY or NONE, and it is not. A FORMERR error response is sent +to the client. + +% LIBDDNS_PREREQ_FORMERR_NONE update client %1 for zone %2: Format error in prerequisite (%3). Non-zero TTL or rdata found. +The prerequisite with the given name, class and type is not well-formed. +The specific prerequisite is shown. In this case, it either has a non-zero +TTL value, or has rdata fields. A FORMERR error response is sent to the client. + +% LIBDDNS_PREREQ_NAME_IN_USE_FAILED update client %1 for zone %2: 'Name is in use' prerequisite not satisfied (%3), rcode: %4 +A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that +was not satisfied is shown. The client is sent an error response with the +given rcode. +In this case, the specific prerequisite is 'Name is in use'. From RFC2136: +Name is in use. At least one RR with a specified NAME (in +the zone and class specified by the Zone Section) must exist. +Note that this prerequisite is NOT satisfied by empty +nonterminals. + +% LIBDDNS_PREREQ_NAME_NOT_IN_USE_FAILED update client %1 for zone %2: 'Name is not in use' (%3) prerequisite not satisfied, rcode: %4 +A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that +was not satisfied is shown. The client is sent an error response with the +given rcode. +In this case, the specific prerequisite is 'Name is not in use'. +From RFC2136: +Name is not in use. No RR of any type is owned by a +specified NAME. Note that this prerequisite IS satisfied by +empty nonterminals. + +% LIBDDNS_PREREQ_NOTZONE update client %1 for zone %2: prerequisite not in zone (%3) +A DNS UPDATE prerequisite has a name that does not appear to be inside +the zone specified in the Zone section of the UPDATE message. +The specific prerequisite is shown. A NOTZONE error response is sent to +the client. + +% LIBDDNS_PREREQ_RRSET_DOES_NOT_EXIST_FAILED update client %1 for zone %2: 'RRset does not exist' (%3) prerequisite not satisfied, rcode: %4 +A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that +was not satisfied is shown. The client is sent an error response with the +given rcode. +In this case, the specific prerequisite is 'RRset does not exist'. +From RFC2136: +RRset does not exist. No RRs with a specified NAME and TYPE +(in the zone and class denoted by the Zone Section) can exist. + +% LIBDDNS_PREREQ_RRSET_EXISTS_FAILED update client %1 for zone %2: 'RRset exists (value independent)' (%3) prerequisite not satisfied, rcode: %4 +A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that +was not satisfied is shown. The client is sent an error response with the +given rcode. +In this case, the specific prerequisite is 'RRset exists (value independent)'. +From RFC2136: +RRset exists (value dependent). A set of RRs with a +specified NAME and TYPE exists and has the same members +with the same RDATAs as the RRset specified here in this +Section. + +% LIBDDNS_PREREQ_RRSET_EXISTS_VAL_FAILED update client %1 for zone %2: 'RRset exists (value dependent)' (%3) prerequisite not satisfied, rcode: %4 +A DNS UPDATE prerequisite was not satisfied. The specific prerequisite that +was not satisfied is shown. The client is sent an error response with the +given rcode. +In this case, the specific prerequisite is 'RRset exists (value dependent)'. +From RFC2136: +RRset exists (value independent). At least one RR with a +specified NAME and TYPE (in the zone and class specified by +the Zone Section) must exist. + + % LIBDDNS_UPDATE_APPROVED update client %1 for zone %2 approved + Debug message. An update request was approved in terms of the zone's + update ACL. + ++% LIBDDNS_UPDATE_DENIED update client %1 for zone %2 denied ++Informational message. An update request was denied because it was ++rejected by the zone's update ACL. When this library is used by ++b10-ddns, the server will respond to the request with an RCODE of ++REFUSED as described in Section 3.3 of RFC2136. ++ + % LIBDDNS_UPDATE_DROPPED update client %1 for zone %2 dropped + Informational message. An update request was denied because it was + rejected by the zone's update ACL. When this library is used by + b10-ddns, the server will then completely ignore the request; no + response will be sent. + % LIBDDNS_UPDATE_ERROR update client %1 for zone %2: %3 Debug message. An error is found in processing a dynamic update request. This log message is used for general errors that are not @@@ -109,18 -41,17 +125,18 @@@ will simply return a response with an R The client's address and the zone name/class are logged. % LIBDDNS_UPDATE_NOTAUTH update client %1 for zone %2: not authoritative for update zone - Debug message. An update request for a zone for which the receiving - server doesn't have authority. In theory this is an unexpected event, - but there are client implementations that could send update requests - carelessly, so it may not necessarily be so uncommon in practice. If - possible, you may want to check the implementation or configuration of - those clients to suppress the requests. As specified in Section 3.1 - of RFC2136, the receiving server will return a response with an RCODE - of NOTAUTH. + Debug message. An update request was received for a zone for which + the receiving server doesn't have authority. In theory this is an + unexpected event, but there are client implementations that could send + update requests carelessly, so it may not necessarily be so uncommon + in practice. If possible, you may want to check the implementation or + configuration of those clients to suppress the requests. As specified + in Section 3.1 of RFC2136, the receiving server will return a response + with an RCODE of NOTAUTH. -% LIBDDNS_UPDATE_DENIED update client %1 for zone %2 denied -Informational message. An update request was denied because it was -rejected by the zone's update ACL. When this library is used by -b10-ddns, the server will respond to the request with an RCODE of -REFUSED as described in Section 3.3 of RFC2136. +% LIBDDNS_UPDATE_PREREQUISITE_FAILED prerequisite failed in update update client %1 for zone %2: result code %3 +The handling of the prerequisite section (RFC2136 Section 3.2) found +that one of the prerequisites was not satisfied. The result code +should give more information on what prerequisite type failed. +If the result code is FORMERR, the prerequisite section was not well-formed. +An error response with the given result code is sent back to the client. diff --cc src/lib/python/isc/ddns/logger.py index be163a8b3c,656a6df833..0f95bd7273 --- a/src/lib/python/isc/ddns/logger.py +++ b/src/lib/python/isc/ddns/logger.py @@@ -37,18 -39,12 +39,19 @@@ class ClientFormatter message is suppressed because of its log level (which is often the case for debug messages). + Note: this optimization comes with the cost of instantiating the + formatter object itself. It's not really clear which overhead is + heavier, and we may conclude it's actually better to just generate + the strings unconditionally. Alternatively, we can make the stored + address of this object replaceable so that this object can be reused. + Right now this is an open issue. + """ - def __init__(self, addr): + def __init__(self, addr, tsig_record=None): self.__addr = addr + self.__tsig_record = tsig_record - def __str__(self): + def __format_addr(self): if len(self.__addr) == 2: return self.__addr[0] + ':' + str(self.__addr[1]) elif len(self.__addr) == 4: diff --cc src/lib/python/isc/ddns/session.py index 51da0e9fb4,4e4255f0a3..6a4079ddb5 --- a/src/lib/python/isc/ddns/session.py +++ b/src/lib/python/isc/ddns/session.py @@@ -16,10 -16,9 +16,11 @@@ from isc.dns import * import isc.ddns.zone_config from isc.log import * -from isc.ddns.logger import logger, ClientFormatter, ZoneFormatter +from isc.ddns.logger import logger, ClientFormatter, ZoneFormatter,\ + RRsetFormatter from isc.log_messages.libddns_messages import * + from isc.acl.acl import ACCEPT, REJECT, DROP +import copy # Result codes for UpdateSession.handle() UPDATE_SUCCESS = 0 @@@ -125,12 -122,8 +124,12 @@@ class UpdateSession try: datasrc_client, zname, zclass = self.__get_update_zone() # conceptual code that would follow - # self.__check_prerequisites() + prereq_result = self.__check_prerequisites(datasrc_client, + zname, zclass) + if prereq_result != Rcode.NOERROR(): + self.__make_response(prereq_result) + return UPDATE_ERROR, zname, zclass - # self.__check_update_acl() + self.__check_update_acl(zname, zclass) # self.__do_update() # self.__make_response(Rcode.NOERROR()) return UPDATE_SUCCESS, zname, zclass diff --cc src/lib/python/isc/ddns/tests/session_tests.py index e5b85d7b9c,192c025dd0..7f1548045e --- a/src/lib/python/isc/ddns/tests/session_tests.py +++ b/src/lib/python/isc/ddns/tests/session_tests.py @@@ -35,37 -35,47 +35,50 @@@ TEST_RRCLASS = RRClass.IN( TEST_ZONE_RECORD = Question(TEST_ZONE_NAME, TEST_RRCLASS, UPDATE_RRTYPE) TEST_CLIENT6 = ('2001:db8::1', 53, 0, 0) TEST_CLIENT4 = ('192.0.2.1', 53) + # TSIG key for tests when needed. The key name is TEST_ZONE_NAME. + TEST_TSIG_KEY = TSIGKey("example.org:SFuWd/q99SzF8Yzd1QbB9g==") - def create_update_msg(zones=[TEST_ZONE_RECORD], prerequisites=[]): -def create_update_msg(zones=[TEST_ZONE_RECORD], tsig_key=None): ++def create_update_msg(zones=[TEST_ZONE_RECORD], prerequisites=[], ++ tsig_key=None): msg = Message(Message.RENDER) msg.set_qid(5353) # arbitrary chosen msg.set_opcode(Opcode.UPDATE()) msg.set_rcode(Rcode.NOERROR()) for z in zones: msg.add_question(z) + for p in prerequisites: + msg.add_rrset(SECTION_PREREQUISITE, p) renderer = MessageRenderer() - msg.to_wire(renderer) + if tsig_key is not None: + msg.to_wire(renderer, TSIGContext(tsig_key)) + else: + msg.to_wire(renderer) # re-read the created data in the parse mode msg.clear(Message.PARSE) msg.from_wire(renderer.get_data()) - return renderer.get_data(), msg + return msg - class SessionTest(unittest.TestCase): - '''Session tests''' + class SesseionTestBase(unittest.TestCase): + '''Base class for all sesion related tests. + + It just initializes common test parameters in its setUp() and defines + some common utility method(s). + + ''' def setUp(self): shutil.copyfile(READ_ZONE_DB_FILE, WRITE_ZONE_DB_FILE) - self.__datasrc_client = DataSourceClient("sqlite3", - WRITE_ZONE_DB_CONFIG) - self.__update_msgdata, self.__update_msg = create_update_msg() - self.__session = UpdateSession(self.__update_msg, - self.__update_msgdata, TEST_CLIENT4, - ZoneConfig([], TEST_RRCLASS, - self.__datasrc_client)) + self._datasrc_client = DataSourceClient("sqlite3", + WRITE_ZONE_DB_CONFIG) + self._update_msg = create_update_msg() - acl_map = {(TEST_ZONE_NAME, TEST_RRCLASS): - REQUEST_LOADER.load([{"action": "ACCEPT"}])} ++ self._acl_map = {(TEST_ZONE_NAME, TEST_RRCLASS): ++ REQUEST_LOADER.load([{"action": "ACCEPT"}])} + self._session = UpdateSession(self._update_msg, TEST_CLIENT4, - ZoneConfig([], TEST_RRCLASS, - self._datasrc_client, - acl_map)) ++ ZoneConfig([], TEST_RRCLASS, ++ self._datasrc_client, ++ self._acl_map)) def check_response(self, msg, expected_rcode): '''Perform common checks on update resposne message.''' @@@ -150,455 -157,60 +160,509 @@@ class SessionTest(SesseionTestBase) # zone class doesn't match self.check_notauth(Name('example.org'), RRClass.CH()) + def __prereq_helper(self, method, expected, rrset): - '''Calls the given method with self.__datasrc_client ++ '''Calls the given method with self._datasrc_client + and the given rrset, and compares the return value. + Function does not do much but makes the code look nicer''' - self.assertEqual(expected, method(self.__datasrc_client, rrset)) ++ self.assertEqual(expected, method(self._datasrc_client, rrset)) + + def __check_prerequisite_exists_combined(self, method, rrclass, expected): + '''shared code for the checks for the very similar (but reversed + in behaviour) methods __prereq_rrset_exists and + __prereq_rrset_does_not_exist. + For rrset_exists, rrclass should be ANY, for rrset_does_not_exist, + it should be NONE. + ''' + # Basic existence checks + # www.example.org should have an A, but not an MX + rrset = isc.dns.RRset(isc.dns.Name("www.example.org"), + rrclass, isc.dns.RRType.A(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, expected, rrset) + rrset = isc.dns.RRset(isc.dns.Name("www.example.org"), + rrclass, isc.dns.RRType.MX(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, not expected, rrset) + + # example.org should have an MX, but not an A + rrset = isc.dns.RRset(isc.dns.Name("example.org"), + rrclass, isc.dns.RRType.MX(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, expected, rrset) + rrset = isc.dns.RRset(isc.dns.Name("example.org"), + rrclass, isc.dns.RRType.A(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, not expected, rrset) + + # Also check the case where the name does not even exist + rrset = isc.dns.RRset(isc.dns.Name("doesnotexist.example.org"), + rrclass, isc.dns.RRType.A(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, not expected, rrset) + + # Wildcard expansion should not be applied, but literal matches + # should work + rrset = isc.dns.RRset(isc.dns.Name("foo.wildcard.example.org"), + rrclass, isc.dns.RRType.A(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, not expected, rrset) + + rrset = isc.dns.RRset(isc.dns.Name("*.wildcard.example.org"), + rrclass, isc.dns.RRType.A(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, expected, rrset) + + # Likewise, CNAME directly should match, but what it points to should + # not + rrset = isc.dns.RRset(isc.dns.Name("cname.example.org"), + rrclass, isc.dns.RRType.A(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, not expected, rrset) + + rrset = isc.dns.RRset(isc.dns.Name("cname.example.org"), + rrclass, isc.dns.RRType.CNAME(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, expected, rrset) + + # And also make sure a delegation (itself) is not treated as existing + # data + rrset = isc.dns.RRset(isc.dns.Name("foo.sub.example.org"), + rrclass, isc.dns.RRType.A(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, not expected, rrset) + # But the delegation data itself should match + rrset = isc.dns.RRset(isc.dns.Name("sub.example.org"), + rrclass, isc.dns.RRType.NS(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, expected, rrset) + # As should glue + rrset = isc.dns.RRset(isc.dns.Name("ns.sub.example.org"), + rrclass, isc.dns.RRType.A(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, expected, rrset) + + def test_check_prerequisite_exists(self): - method = self.__session._UpdateSession__prereq_rrset_exists ++ method = self._session._UpdateSession__prereq_rrset_exists + self.__check_prerequisite_exists_combined(method, + isc.dns.RRClass.ANY(), + True) + + def test_check_prerequisite_does_not_exist(self): - method = self.__session._UpdateSession__prereq_rrset_does_not_exist ++ method = self._session._UpdateSession__prereq_rrset_does_not_exist + self.__check_prerequisite_exists_combined(method, + isc.dns.RRClass.NONE(), + False) + + def test_check_prerequisite_exists_value(self): - method = self.__session._UpdateSession__prereq_rrset_exists_value ++ method = self._session._UpdateSession__prereq_rrset_exists_value + + rrset = isc.dns.RRset(isc.dns.Name("www.example.org"), + isc.dns.RRClass.IN(), isc.dns.RRType.A(), + isc.dns.RRTTL(0)) + # empty one should not match + self.__prereq_helper(method, False, rrset) + + # When the rdata is added, it should match + rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(), + isc.dns.RRClass.IN(), + "192.0.2.1")) + self.__prereq_helper(method, True, rrset) + + # But adding more should not + rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(), + isc.dns.RRClass.IN(), + "192.0.2.2")) + self.__prereq_helper(method, False, rrset) + + # Also test one with more than one RR + rrset = isc.dns.RRset(isc.dns.Name("example.org"), + isc.dns.RRClass.IN(), isc.dns.RRType.NS(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, False, rrset) + rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(), + isc.dns.RRClass.IN(), + "ns1.example.org.")) + self.__prereq_helper(method, False, rrset) + rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(), + isc.dns.RRClass.IN(), + "ns2.example.org.")) + self.__prereq_helper(method, False, rrset) + rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(), + isc.dns.RRClass.IN(), + "ns3.example.org.")) + self.__prereq_helper(method, True, rrset) + rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(), + isc.dns.RRClass.IN(), + "ns4.example.org.")) + self.__prereq_helper(method, False, rrset) + + # Repeat that, but try a different order of Rdata addition + rrset = isc.dns.RRset(isc.dns.Name("example.org"), + isc.dns.RRClass.IN(), isc.dns.RRType.NS(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, False, rrset) + rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(), + isc.dns.RRClass.IN(), + "ns3.example.org.")) + self.__prereq_helper(method, False, rrset) + rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(), + isc.dns.RRClass.IN(), + "ns2.example.org.")) + self.__prereq_helper(method, False, rrset) + rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(), + isc.dns.RRClass.IN(), + "ns1.example.org.")) + self.__prereq_helper(method, True, rrset) + rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(), + isc.dns.RRClass.IN(), + "ns4.example.org.")) + self.__prereq_helper(method, False, rrset) + + # and test one where the name does not even exist + rrset = isc.dns.RRset(isc.dns.Name("doesnotexist.example.org"), + isc.dns.RRClass.IN(), isc.dns.RRType.A(), + isc.dns.RRTTL(0)) + rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(), + isc.dns.RRClass.IN(), + "192.0.2.1")) + self.__prereq_helper(method, False, rrset) + + def __check_prerequisite_name_in_use_combined(self, method, rrclass, + expected): + '''shared code for the checks for the very similar (but reversed + in behaviour) methods __prereq_name_in_use and + __prereq_name_not_in_use + ''' + rrset = isc.dns.RRset(isc.dns.Name("example.org"), + rrclass, isc.dns.RRType.ANY(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, expected, rrset) + + rrset = isc.dns.RRset(isc.dns.Name("www.example.org"), + rrclass, isc.dns.RRType.ANY(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, expected, rrset) + + rrset = isc.dns.RRset(isc.dns.Name("doesnotexist.example.org"), + rrclass, isc.dns.RRType.ANY(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, not expected, rrset) + + rrset = isc.dns.RRset(isc.dns.Name("belowdelegation.sub.example.org"), + rrclass, isc.dns.RRType.ANY(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, not expected, rrset) + + rrset = isc.dns.RRset(isc.dns.Name("foo.wildcard.example.org"), + rrclass, isc.dns.RRType.ANY(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, not expected, rrset) + + # empty nonterminal should not match + rrset = isc.dns.RRset(isc.dns.Name("nonterminal.example.org"), + rrclass, isc.dns.RRType.ANY(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, not expected, rrset) + rrset = isc.dns.RRset(isc.dns.Name("empty.nonterminal.example.org"), + rrclass, isc.dns.RRType.ANY(), + isc.dns.RRTTL(0)) + self.__prereq_helper(method, expected, rrset) + + def test_check_prerequisite_name_in_use(self): - method = self.__session._UpdateSession__prereq_name_in_use ++ method = self._session._UpdateSession__prereq_name_in_use + self.__check_prerequisite_name_in_use_combined(method, + isc.dns.RRClass.ANY(), + True) + + def test_check_prerequisite_name_not_in_use(self): - method = self.__session._UpdateSession__prereq_name_not_in_use ++ method = self._session._UpdateSession__prereq_name_not_in_use + self.__check_prerequisite_name_in_use_combined(method, + isc.dns.RRClass.NONE(), + False) + + def check_prerequisite_result(self, expected, prerequisites): + '''Helper method for checking the result of a prerequisite check; + creates an update session, and fills it with the list of rrsets + from 'prerequisites'. Then checks if __check_prerequisites() + returns the Rcode specified in 'expected'.''' - msg_data, msg = create_update_msg([TEST_ZONE_RECORD], - prerequisites) - zconfig = ZoneConfig([], TEST_RRCLASS, self.__datasrc_client) - session = UpdateSession(msg, msg_data, TEST_CLIENT4, zconfig) ++ msg = create_update_msg([TEST_ZONE_RECORD], prerequisites) ++ zconfig = ZoneConfig([], TEST_RRCLASS, self._datasrc_client, ++ self._acl_map) ++ session = UpdateSession(msg, TEST_CLIENT4, zconfig) + # compare the to_text output of the rcodes (nicer error messages) + # This call itself should also be done by handle(), + # but just for better failures, it is first called on its own + self.assertEqual(expected.to_text(), - session._UpdateSession__check_prerequisites(self.__datasrc_client, ++ session._UpdateSession__check_prerequisites(self._datasrc_client, + TEST_ZONE_NAME, + TEST_RRCLASS).to_text()) + # Now see if handle finds the same result + (result, _, _) = session.handle() + self.assertEqual(expected, + session._UpdateSession__message.get_rcode()) + # And that the result looks right + if expected == Rcode.NOERROR(): + self.assertEqual(UPDATE_SUCCESS, result) + else: + self.assertEqual(UPDATE_ERROR, result) + + def test_check_prerequisites(self): + # This test checks if the actual prerequisite-type-specific + # methods are called. + # It does test all types of prerequisites, but it does not test + # every possible result for those types (those are tested above, + # in the specific prerequisite type tests) + + # Let's first define a number of prereq's that should succeed + rrset_exists_yes = isc.dns.RRset(isc.dns.Name("example.org"), + isc.dns.RRClass.ANY(), + isc.dns.RRType.SOA(), + isc.dns.RRTTL(0)) + + rrset_exists_value_yes = isc.dns.RRset(isc.dns.Name("www.example.org"), + isc.dns.RRClass.IN(), + isc.dns.RRType.A(), + isc.dns.RRTTL(0)) + rrset_exists_value_yes.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(), + isc.dns.RRClass.IN(), + "192.0.2.1")) + + rrset_does_not_exist_yes = isc.dns.RRset(isc.dns.Name("foo.example.org"), + isc.dns.RRClass.NONE(), + isc.dns.RRType.SOA(), + isc.dns.RRTTL(0)) + + name_in_use_yes = isc.dns.RRset(isc.dns.Name("www.example.org"), + isc.dns.RRClass.ANY(), + isc.dns.RRType.ANY(), + isc.dns.RRTTL(0)) + + name_not_in_use_yes = isc.dns.RRset(isc.dns.Name("foo.example.org"), + isc.dns.RRClass.NONE(), + isc.dns.RRType.ANY(), + isc.dns.RRTTL(0)) + + rrset_exists_value_1 = isc.dns.RRset(isc.dns.Name("example.org"), + isc.dns.RRClass.IN(), + isc.dns.RRType.NS(), + isc.dns.RRTTL(0)) + rrset_exists_value_1.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(), + isc.dns.RRClass.IN(), + "ns1.example.org")) + rrset_exists_value_2 = isc.dns.RRset(isc.dns.Name("example.org"), + isc.dns.RRClass.IN(), + isc.dns.RRType.NS(), + isc.dns.RRTTL(0)) + rrset_exists_value_2.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(), + isc.dns.RRClass.IN(), + "ns2.example.org")) + rrset_exists_value_3 = isc.dns.RRset(isc.dns.Name("example.org"), + isc.dns.RRClass.IN(), + isc.dns.RRType.NS(), + isc.dns.RRTTL(0)) + rrset_exists_value_3.add_rdata(isc.dns.Rdata(isc.dns.RRType.NS(), + isc.dns.RRClass.IN(), + "ns3.example.org")) + + # and a number that should not + rrset_exists_no = isc.dns.RRset(isc.dns.Name("foo.example.org"), + isc.dns.RRClass.ANY(), + isc.dns.RRType.SOA(), + isc.dns.RRTTL(0)) + + + rrset_exists_value_no = isc.dns.RRset(isc.dns.Name("www.example.org"), + isc.dns.RRClass.IN(), + isc.dns.RRType.A(), + isc.dns.RRTTL(0)) + rrset_exists_value_no.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(), + isc.dns.RRClass.IN(), + "192.0.2.2")) + + rrset_does_not_exist_no = isc.dns.RRset(isc.dns.Name("example.org"), + isc.dns.RRClass.NONE(), + isc.dns.RRType.SOA(), + isc.dns.RRTTL(0)) + + name_in_use_no = isc.dns.RRset(isc.dns.Name("foo.example.org"), + isc.dns.RRClass.ANY(), + isc.dns.RRType.ANY(), + isc.dns.RRTTL(0)) + + name_not_in_use_no = isc.dns.RRset(isc.dns.Name("www.example.org"), + isc.dns.RRClass.NONE(), + isc.dns.RRType.ANY(), + isc.dns.RRTTL(0)) + + # Create an UPDATE with all 5 'yes' prereqs - data, update = create_update_msg([TEST_ZONE_RECORD], - [ - rrset_exists_yes, - rrset_does_not_exist_yes, - name_in_use_yes, - name_not_in_use_yes, - rrset_exists_value_yes, - ]) ++ create_update_msg([TEST_ZONE_RECORD], ++ [rrset_exists_yes, ++ rrset_does_not_exist_yes, ++ name_in_use_yes, ++ name_not_in_use_yes, ++ rrset_exists_value_yes, ++ ]) ++ + # check 'no' result codes + self.check_prerequisite_result(Rcode.NXRRSET(), + [ rrset_exists_no ]) + self.check_prerequisite_result(Rcode.NXRRSET(), + [ rrset_exists_value_no ]) + self.check_prerequisite_result(Rcode.YXRRSET(), + [ rrset_does_not_exist_no ]) + self.check_prerequisite_result(Rcode.NXDOMAIN(), + [ name_in_use_no ]) + self.check_prerequisite_result(Rcode.YXDOMAIN(), + [ name_not_in_use_no ]) + + # the 'yes' codes should result in ok + self.check_prerequisite_result(Rcode.NOERROR(), + [ rrset_exists_yes, + rrset_exists_value_yes, + rrset_does_not_exist_yes, + name_in_use_yes, + name_not_in_use_yes, + rrset_exists_value_1, + rrset_exists_value_2, + rrset_exists_value_3]) + + # try out a permutation, note that one rrset is split up, + # and the order of the RRs should not matter + self.check_prerequisite_result(Rcode.NOERROR(), + [ rrset_exists_value_3, + rrset_exists_yes, + rrset_exists_value_2, + name_in_use_yes, + rrset_exists_value_1]) + + # Should fail on the first error, even if most of the + # prerequisites are ok + self.check_prerequisite_result(Rcode.NXDOMAIN(), + [ rrset_exists_value_3, + rrset_exists_yes, + rrset_exists_value_2, + name_in_use_yes, + name_in_use_no, + rrset_exists_value_1]) + + def test_prerequisite_notzone(self): + rrset = isc.dns.RRset(isc.dns.Name("some.other.zone."), + isc.dns.RRClass.ANY(), + isc.dns.RRType.SOA(), + isc.dns.RRTTL(0)) + self.check_prerequisite_result(Rcode.NOTZONE(), [ rrset ]) + + def test_prerequisites_formerr(self): + # test for form errors in the prerequisite section + + # Class ANY, non-zero TTL + rrset = isc.dns.RRset(isc.dns.Name("example.org"), + isc.dns.RRClass.ANY(), + isc.dns.RRType.SOA(), + isc.dns.RRTTL(1)) + self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ]) + + # Class ANY, but with rdata + rrset = isc.dns.RRset(isc.dns.Name("example.org"), + isc.dns.RRClass.ANY(), + isc.dns.RRType.A(), + isc.dns.RRTTL(0)) + rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(), + isc.dns.RRClass.ANY(), + "\# 04 00 00 00 00")) + self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ]) + + # Class NONE, non-zero TTL + rrset = isc.dns.RRset(isc.dns.Name("example.org"), + isc.dns.RRClass.NONE(), + isc.dns.RRType.SOA(), + isc.dns.RRTTL(1)) + self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ]) + + # Class NONE, but with rdata + rrset = isc.dns.RRset(isc.dns.Name("example.org"), + isc.dns.RRClass.NONE(), + isc.dns.RRType.A(), + isc.dns.RRTTL(0)) + rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(), + isc.dns.RRClass.NONE(), + "\# 04 00 00 00 00")) + self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ]) + + # Matching class and type, but non-zero TTL + rrset = isc.dns.RRset(isc.dns.Name("www.example.org"), + isc.dns.RRClass.IN(), + isc.dns.RRType.A(), + isc.dns.RRTTL(1)) + rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.A(), + isc.dns.RRClass.IN(), + "192.0.2.1")) + self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ]) + + # Completely different class + rrset = isc.dns.RRset(isc.dns.Name("example.org"), + isc.dns.RRClass.CH(), + isc.dns.RRType.TXT(), + isc.dns.RRTTL(0)) + rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.TXT(), + isc.dns.RRClass.CH(), + "foo")) + self.check_prerequisite_result(Rcode.FORMERR(), [ rrset ]) + + class SessionACLTest(SesseionTestBase): + '''ACL related tests for update session.''' + def test_update_acl_check(self): + '''Test for various ACL checks. + + Note that accepted cases are covered in the basic tests. + + ''' + # create a separate session, with default (empty) ACL map. + session = UpdateSession(self._update_msg, + TEST_CLIENT4, ZoneConfig([], TEST_RRCLASS, + self._datasrc_client)) + # then the request should be rejected. + self.assertEqual((UPDATE_ERROR, None, None), session.handle()) + + # recreate the request message, and test with an ACL that would result + # in 'DROP'. get_message() should return None. + msg = create_update_msg() + acl_map = {(TEST_ZONE_NAME, TEST_RRCLASS): + REQUEST_LOADER.load([{"action": "DROP", "from": + TEST_CLIENT4[0]}])} + session = UpdateSession(msg, TEST_CLIENT4, + ZoneConfig([], TEST_RRCLASS, + self._datasrc_client, acl_map)) + self.assertEqual((UPDATE_DROP, None, None), session.handle()) + self.assertEqual(None, session.get_message()) + + def test_update_tsigacl_check(self): + '''Test for various ACL checks using TSIG.''' + # This ACL will accept requests from TEST_CLIENT4 (any port) *and* + # has TSIG signed by TEST_ZONE_NAME; all others will be rejected. + acl_map = {(TEST_ZONE_NAME, TEST_RRCLASS): + REQUEST_LOADER.load([{"action": "ACCEPT", + "from": TEST_CLIENT4[0], + "key": TEST_ZONE_NAME.to_text()}])} + + # If the message doesn't contain TSIG, it doesn't match the ACCEPT + # ACL entry, and the request should be rejected. + session = UpdateSession(self._update_msg, + TEST_CLIENT4, ZoneConfig([], TEST_RRCLASS, + self._datasrc_client, + acl_map)) + self.assertEqual((UPDATE_ERROR, None, None), session.handle()) + self.check_response(session.get_message(), Rcode.REFUSED()) + + # If the message contains TSIG, it should match the ACCEPT + # ACL entry, and the request should be granted. + session = UpdateSession(create_update_msg(tsig_key=TEST_TSIG_KEY), + TEST_CLIENT4, ZoneConfig([], TEST_RRCLASS, + self._datasrc_client, + acl_map)) + self.assertEqual((UPDATE_SUCCESS, TEST_ZONE_NAME, TEST_RRCLASS), + session.handle()) + if __name__ == "__main__": isc.log.init("bind10") isc.log.resetUnitTestRootLogger()