|
@@ -19,6 +19,8 @@ from isc.log import *
|
|
|
from isc.ddns.logger import logger, ClientFormatter, ZoneFormatter,\
|
|
|
RRsetFormatter
|
|
|
from isc.log_messages.libddns_messages import *
|
|
|
+from isc.datasrc import ZoneFinder
|
|
|
+import isc.xfrin.diff
|
|
|
from isc.acl.acl import ACCEPT, REJECT, DROP
|
|
|
import copy
|
|
|
|
|
@@ -59,6 +61,70 @@ class UpdateError(Exception):
|
|
|
self.rcode = rcode
|
|
|
self.nolog = nolog
|
|
|
|
|
|
+def foreach_rr(rrset):
|
|
|
+ '''
|
|
|
+ Generator that creates a new RRset with one RR from
|
|
|
+ the given RRset upon each iteration, usable in calls that
|
|
|
+ need to loop over an RRset and perform an action with each
|
|
|
+ of the individual RRs in it.
|
|
|
+ Example:
|
|
|
+ for rr in foreach_rr(rrset):
|
|
|
+ print(str(rr))
|
|
|
+ '''
|
|
|
+ for rdata in rrset.get_rdata():
|
|
|
+ rr = isc.dns.RRset(rrset.get_name(),
|
|
|
+ rrset.get_class(),
|
|
|
+ rrset.get_type(),
|
|
|
+ rrset.get_ttl())
|
|
|
+ rr.add_rdata(rdata)
|
|
|
+ yield rr
|
|
|
+
|
|
|
+def convert_rrset_class(rrset, rrclass):
|
|
|
+ '''Returns a (new) rrset with the data from the given rrset,
|
|
|
+ but of the given class. Useful to convert from NONE and ANY to
|
|
|
+ a real class.
|
|
|
+ Note that the caller should be careful what to convert;
|
|
|
+ and DNS error that could happen during wire-format reading
|
|
|
+ could technically occur here, and is not caught by this helper.
|
|
|
+ '''
|
|
|
+ new_rrset = isc.dns.RRset(rrset.get_name(), rrclass,
|
|
|
+ rrset.get_type(), rrset.get_ttl())
|
|
|
+ for rdata in rrset.get_rdata():
|
|
|
+ # Rdata class is nof modifiable, and must match rrset's
|
|
|
+ # class, so we need to to some ugly conversion here.
|
|
|
+ # And we cannot use to_text() (since the class may be unknown)
|
|
|
+ wire = rdata.to_wire(bytes())
|
|
|
+ new_rrset.add_rdata(isc.dns.Rdata(rrset.get_type(), rrclass, wire))
|
|
|
+ return new_rrset
|
|
|
+
|
|
|
+def collect_rrsets(collection, rrset):
|
|
|
+ '''
|
|
|
+ Helper function to collect similar rrsets.
|
|
|
+ Collect all rrsets with the same name, class, and type
|
|
|
+ collection is the currently collected list of RRsets,
|
|
|
+ rrset is the RRset to add;
|
|
|
+ if an RRset with the same name, class and type as the
|
|
|
+ given rrset exists in the collection, its rdata fields
|
|
|
+ are added to that RRset. Otherwise, the rrset is added
|
|
|
+ to the given collection.
|
|
|
+ TTL is ignored.
|
|
|
+ This method does not check rdata contents for duplicate
|
|
|
+ values.
|
|
|
+
|
|
|
+ The collection and its rrsets are modified in-place,
|
|
|
+ this method does not return anything.
|
|
|
+ '''
|
|
|
+ found = False
|
|
|
+ for existing_rrset in collection:
|
|
|
+ if existing_rrset.get_name() == rrset.get_name() and\
|
|
|
+ existing_rrset.get_class() == rrset.get_class() and\
|
|
|
+ existing_rrset.get_type() == rrset.get_type():
|
|
|
+ for rdata in rrset.get_rdata():
|
|
|
+ existing_rrset.add_rdata(rdata)
|
|
|
+ found = True
|
|
|
+ if not found:
|
|
|
+ collection.append(rrset)
|
|
|
+
|
|
|
class UpdateSession:
|
|
|
'''Protocol handling for a single dynamic update request.
|
|
|
|
|
@@ -89,6 +155,7 @@ class UpdateSession:
|
|
|
self.__tsig = req_message.get_tsig_record()
|
|
|
self.__client_addr = client_addr
|
|
|
self.__zone_config = zone_config
|
|
|
+ self.__added_soa = None
|
|
|
|
|
|
def get_message(self):
|
|
|
'''Return the update message.
|
|
@@ -122,17 +189,18 @@ class UpdateSession:
|
|
|
|
|
|
'''
|
|
|
try:
|
|
|
- datasrc_client, zname, zclass = self.__get_update_zone()
|
|
|
- # conceptual code that would follow
|
|
|
- prereq_result = self.__check_prerequisites(datasrc_client,
|
|
|
- zname, zclass)
|
|
|
+ self.__get_update_zone()
|
|
|
+ prereq_result = self.__check_prerequisites()
|
|
|
if prereq_result != Rcode.NOERROR():
|
|
|
self.__make_response(prereq_result)
|
|
|
- return UPDATE_ERROR, zname, zclass
|
|
|
- self.__check_update_acl(zname, zclass)
|
|
|
- # self.__do_update()
|
|
|
- # self.__make_response(Rcode.NOERROR())
|
|
|
- return UPDATE_SUCCESS, zname, zclass
|
|
|
+ return UPDATE_ERROR, self.__zname, self.__zclass
|
|
|
+ self.__check_update_acl(self.__zname, self.__zclass)
|
|
|
+ update_result = self.__do_update()
|
|
|
+ if update_result != Rcode.NOERROR():
|
|
|
+ self.__make_response(update_result)
|
|
|
+ return UPDATE_ERROR, self.__zname, self.__zclass
|
|
|
+ self.__make_response(Rcode.NOERROR())
|
|
|
+ return UPDATE_SUCCESS, self.__zname, self.__zclass
|
|
|
except UpdateError as e:
|
|
|
if not e.nolog:
|
|
|
logger.debug(logger.DBGLVL_TRACE_BASIC, LIBDDNS_UPDATE_ERROR,
|
|
@@ -145,16 +213,23 @@ class UpdateSession:
|
|
|
return UPDATE_ERROR, None, None
|
|
|
self.__message = None
|
|
|
return UPDATE_DROP, None, None
|
|
|
+ except isc.datasrc.Error as e:
|
|
|
+ logger.error(LIBDDNS_DATASRC_ERROR,
|
|
|
+ ClientFormatter(self.__client_addr, self.__tsig), e)
|
|
|
+ self.__make_response(Rcode.SERVFAIL())
|
|
|
+ return UPDATE_ERROR, None, None
|
|
|
|
|
|
def __get_update_zone(self):
|
|
|
'''Parse the zone section and find the zone to be updated.
|
|
|
|
|
|
If the zone section is valid and the specified zone is found in
|
|
|
- the configuration, it returns a tuple of:
|
|
|
- - A matching data source that contains the specified zone
|
|
|
- - The zone name as a Name object
|
|
|
- - The zone class as an RRClass object
|
|
|
-
|
|
|
+ the configuration, sets private member variables for this session:
|
|
|
+ __datasrc_client: A matching data source that contains the specified
|
|
|
+ zone
|
|
|
+ __zname: The zone name as a Name object
|
|
|
+ __zclass: The zone class as an RRClass object
|
|
|
+ __finder: A ZoneFinder for this zone
|
|
|
+ If this method raises an exception, these members are not set
|
|
|
'''
|
|
|
# Validation: the zone section must contain exactly one question,
|
|
|
# and it must be of type SOA.
|
|
@@ -172,7 +247,11 @@ class UpdateSession:
|
|
|
zclass = zrecord.get_class()
|
|
|
zone_type, datasrc_client = self.__zone_config.find_zone(zname, zclass)
|
|
|
if zone_type == isc.ddns.zone_config.ZONE_PRIMARY:
|
|
|
- return datasrc_client, zname, zclass
|
|
|
+ _, self.__finder = datasrc_client.find_zone(zname)
|
|
|
+ self.__zname = zname
|
|
|
+ self.__zclass = zclass
|
|
|
+ self.__datasrc_client = datasrc_client
|
|
|
+ return
|
|
|
elif zone_type == isc.ddns.zone_config.ZONE_SECONDARY:
|
|
|
# We are a secondary server; since we don't yet support update
|
|
|
# forwarding, we return 'not implemented'.
|
|
@@ -217,7 +296,7 @@ class UpdateSession:
|
|
|
self.__message.clear_section(SECTION_ZONE)
|
|
|
self.__message.set_rcode(rcode)
|
|
|
|
|
|
- def __prereq_rrset_exists(self, datasrc_client, rrset):
|
|
|
+ def __prereq_rrset_exists(self, rrset):
|
|
|
'''Check whether an rrset with the given name and type exists. Class,
|
|
|
TTL, and Rdata (if any) of the given RRset are ignored.
|
|
|
RFC2136 Section 2.4.1.
|
|
@@ -229,22 +308,22 @@ class UpdateSession:
|
|
|
only return what the result code would be (and not read/copy
|
|
|
any actual data).
|
|
|
'''
|
|
|
- _, finder = datasrc_client.find_zone(rrset.get_name())
|
|
|
- result, _, _ = finder.find(rrset.get_name(), rrset.get_type(),
|
|
|
- finder.NO_WILDCARD | finder.FIND_GLUE_OK)
|
|
|
- return result == finder.SUCCESS
|
|
|
+ result, _, _ = self.__finder.find(rrset.get_name(), rrset.get_type(),
|
|
|
+ ZoneFinder.NO_WILDCARD |
|
|
|
+ ZoneFinder.FIND_GLUE_OK)
|
|
|
+ return result == ZoneFinder.SUCCESS
|
|
|
|
|
|
- def __prereq_rrset_exists_value(self, datasrc_client, rrset):
|
|
|
+ def __prereq_rrset_exists_value(self, rrset):
|
|
|
'''Check whether an rrset that matches name, type, and rdata(s) of the
|
|
|
given rrset exists.
|
|
|
RFC2136 Section 2.4.2
|
|
|
Returns True if the prerequisite is satisfied, False otherwise.
|
|
|
'''
|
|
|
- _, finder = datasrc_client.find_zone(rrset.get_name())
|
|
|
- result, found_rrset, _ = finder.find(rrset.get_name(), rrset.get_type(),
|
|
|
- finder.NO_WILDCARD |
|
|
|
- finder.FIND_GLUE_OK)
|
|
|
- if result == finder.SUCCESS and\
|
|
|
+ result, found_rrset, _ = self.__finder.find(rrset.get_name(),
|
|
|
+ rrset.get_type(),
|
|
|
+ ZoneFinder.NO_WILDCARD |
|
|
|
+ ZoneFinder.FIND_GLUE_OK)
|
|
|
+ if result == ZoneFinder.SUCCESS and\
|
|
|
rrset.get_name() == found_rrset.get_name() and\
|
|
|
rrset.get_type() == found_rrset.get_type():
|
|
|
# We need to match all actual RRs, unfortunately there is no
|
|
@@ -262,15 +341,15 @@ class UpdateSession:
|
|
|
return len(found_rdata) == 0
|
|
|
return False
|
|
|
|
|
|
- def __prereq_rrset_does_not_exist(self, datasrc_client, rrset):
|
|
|
+ def __prereq_rrset_does_not_exist(self, rrset):
|
|
|
'''Check whether no rrsets with the same name and type as the given
|
|
|
rrset exist.
|
|
|
RFC2136 Section 2.4.3.
|
|
|
Returns True if the prerequisite is satisfied, False otherwise.
|
|
|
'''
|
|
|
- return not self.__prereq_rrset_exists(datasrc_client, rrset)
|
|
|
+ return not self.__prereq_rrset_exists(rrset)
|
|
|
|
|
|
- def __prereq_name_in_use(self, datasrc_client, rrset):
|
|
|
+ def __prereq_name_in_use(self, rrset):
|
|
|
'''Check whether the name of the given RRset is in use (i.e. has
|
|
|
1 or more RRs).
|
|
|
RFC2136 Section 2.4.4
|
|
@@ -282,37 +361,45 @@ class UpdateSession:
|
|
|
to only return what the result code would be (and not read/copy
|
|
|
any actual data).
|
|
|
'''
|
|
|
- _, finder = datasrc_client.find_zone(rrset.get_name())
|
|
|
- result, rrsets, flags = finder.find_all(rrset.get_name(),
|
|
|
- finder.NO_WILDCARD |
|
|
|
- finder.FIND_GLUE_OK)
|
|
|
- if result == finder.SUCCESS and\
|
|
|
- (flags & finder.RESULT_WILDCARD == 0):
|
|
|
+ result, rrsets, flags = self.__finder.find_all(rrset.get_name(),
|
|
|
+ ZoneFinder.NO_WILDCARD |
|
|
|
+ ZoneFinder.FIND_GLUE_OK)
|
|
|
+ if result == ZoneFinder.SUCCESS and\
|
|
|
+ (flags & ZoneFinder.RESULT_WILDCARD == 0):
|
|
|
return True
|
|
|
return False
|
|
|
|
|
|
- def __prereq_name_not_in_use(self, datasrc_client, rrset):
|
|
|
+ def __prereq_name_not_in_use(self, rrset):
|
|
|
'''Check whether the name of the given RRset is not in use (i.e. does
|
|
|
not exist at all, or is an empty nonterminal.
|
|
|
RFC2136 Section 2.4.5.
|
|
|
Returns True if the prerequisite is satisfied, False otherwise.
|
|
|
'''
|
|
|
- return not self.__prereq_name_in_use(datasrc_client, rrset)
|
|
|
+ return not self.__prereq_name_in_use(rrset)
|
|
|
|
|
|
- def __check_prerequisites(self, datasrc_client, zname, zclass):
|
|
|
+ def __check_in_zone(self, rrset):
|
|
|
+ '''Returns true if the name of the given rrset is equal to
|
|
|
+ or a subdomain of the zname from the Zone Section.'''
|
|
|
+ relation = rrset.get_name().compare(self.__zname).get_relation()
|
|
|
+ return relation == NameComparisonResult.SUBDOMAIN or\
|
|
|
+ relation == NameComparisonResult.EQUAL
|
|
|
+
|
|
|
+ def __check_prerequisites(self):
|
|
|
'''Check the prerequisites section of the UPDATE Message.
|
|
|
RFC2136 Section 2.4.
|
|
|
Returns a dns Rcode signaling either no error (Rcode.NOERROR())
|
|
|
or that one of the prerequisites failed (any other Rcode).
|
|
|
'''
|
|
|
+
|
|
|
+ # Temporary array to store exact-match RRsets
|
|
|
+ exact_match_rrsets = []
|
|
|
+
|
|
|
for rrset in self.__message.get_section(SECTION_PREREQUISITE):
|
|
|
# First check if the name is in the zone
|
|
|
- relation = rrset.get_name().compare(zname).get_relation()
|
|
|
- if relation != NameComparisonResult.SUBDOMAIN and\
|
|
|
- relation != NameComparisonResult.EQUAL:
|
|
|
+ if not self.__check_in_zone(rrset):
|
|
|
logger.info(LIBDDNS_PREREQ_NOTZONE,
|
|
|
ClientFormatter(self.__client_addr),
|
|
|
- ZoneFormatter(zname, zclass),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
RRsetFormatter(rrset))
|
|
|
return Rcode.NOTZONE()
|
|
|
|
|
@@ -322,24 +409,23 @@ class UpdateSession:
|
|
|
rrset.get_rdata_count() != 0:
|
|
|
logger.info(LIBDDNS_PREREQ_FORMERR_ANY,
|
|
|
ClientFormatter(self.__client_addr),
|
|
|
- ZoneFormatter(zname, zclass),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
RRsetFormatter(rrset))
|
|
|
return Rcode.FORMERR()
|
|
|
elif rrset.get_type() == RRType.ANY():
|
|
|
- if not self.__prereq_name_in_use(datasrc_client,
|
|
|
- rrset):
|
|
|
+ if not self.__prereq_name_in_use(rrset):
|
|
|
rcode = Rcode.NXDOMAIN()
|
|
|
logger.info(LIBDDNS_PREREQ_NAME_IN_USE_FAILED,
|
|
|
ClientFormatter(self.__client_addr),
|
|
|
- ZoneFormatter(zname, zclass),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
RRsetFormatter(rrset), rcode)
|
|
|
return rcode
|
|
|
else:
|
|
|
- if not self.__prereq_rrset_exists(datasrc_client, rrset):
|
|
|
+ if not self.__prereq_rrset_exists(rrset):
|
|
|
rcode = Rcode.NXRRSET()
|
|
|
logger.info(LIBDDNS_PREREQ_RRSET_EXISTS_FAILED,
|
|
|
ClientFormatter(self.__client_addr),
|
|
|
- ZoneFormatter(zname, zclass),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
RRsetFormatter(rrset), rcode)
|
|
|
return rcode
|
|
|
elif rrset.get_class() == RRClass.NONE():
|
|
@@ -347,49 +433,367 @@ class UpdateSession:
|
|
|
rrset.get_rdata_count() != 0:
|
|
|
logger.info(LIBDDNS_PREREQ_FORMERR_NONE,
|
|
|
ClientFormatter(self.__client_addr),
|
|
|
- ZoneFormatter(zname, zclass),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
RRsetFormatter(rrset))
|
|
|
return Rcode.FORMERR()
|
|
|
elif rrset.get_type() == RRType.ANY():
|
|
|
- if not self.__prereq_name_not_in_use(datasrc_client,
|
|
|
- rrset):
|
|
|
+ if not self.__prereq_name_not_in_use(rrset):
|
|
|
rcode = Rcode.YXDOMAIN()
|
|
|
logger.info(LIBDDNS_PREREQ_NAME_NOT_IN_USE_FAILED,
|
|
|
ClientFormatter(self.__client_addr),
|
|
|
- ZoneFormatter(zname, zclass),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
RRsetFormatter(rrset), rcode)
|
|
|
return rcode
|
|
|
else:
|
|
|
- if not self.__prereq_rrset_does_not_exist(datasrc_client,
|
|
|
- rrset):
|
|
|
+ if not self.__prereq_rrset_does_not_exist(rrset):
|
|
|
rcode = Rcode.YXRRSET()
|
|
|
logger.info(LIBDDNS_PREREQ_RRSET_DOES_NOT_EXIST_FAILED,
|
|
|
ClientFormatter(self.__client_addr),
|
|
|
- ZoneFormatter(zname, zclass),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
RRsetFormatter(rrset), rcode)
|
|
|
return rcode
|
|
|
- elif rrset.get_class() == zclass:
|
|
|
+ elif rrset.get_class() == self.__zclass:
|
|
|
if rrset.get_ttl().get_value() != 0:
|
|
|
logger.info(LIBDDNS_PREREQ_FORMERR,
|
|
|
ClientFormatter(self.__client_addr),
|
|
|
- ZoneFormatter(zname, zclass),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
RRsetFormatter(rrset))
|
|
|
return Rcode.FORMERR()
|
|
|
else:
|
|
|
- if not self.__prereq_rrset_exists_value(datasrc_client,
|
|
|
- rrset):
|
|
|
- rcode = Rcode.NXRRSET()
|
|
|
- logger.info(LIBDDNS_PREREQ_RRSET_EXISTS_VAL_FAILED,
|
|
|
- ClientFormatter(self.__client_addr),
|
|
|
- ZoneFormatter(zname, zclass),
|
|
|
- RRsetFormatter(rrset), rcode)
|
|
|
- return rcode
|
|
|
+ collect_rrsets(exact_match_rrsets, rrset)
|
|
|
else:
|
|
|
logger.info(LIBDDNS_PREREQ_FORMERR_CLASS,
|
|
|
ClientFormatter(self.__client_addr),
|
|
|
- ZoneFormatter(zname, zclass),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
RRsetFormatter(rrset))
|
|
|
return Rcode.FORMERR()
|
|
|
|
|
|
+ for collected_rrset in exact_match_rrsets:
|
|
|
+ if not self.__prereq_rrset_exists_value(collected_rrset):
|
|
|
+ rcode = Rcode.NXRRSET()
|
|
|
+ logger.info(LIBDDNS_PREREQ_RRSET_EXISTS_VAL_FAILED,
|
|
|
+ ClientFormatter(self.__client_addr),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
+ RRsetFormatter(collected_rrset), rcode)
|
|
|
+ return rcode
|
|
|
+
|
|
|
# All prerequisites are satisfied
|
|
|
return Rcode.NOERROR()
|
|
|
+
|
|
|
+ def __set_soa_rrset(self, rrset):
|
|
|
+ '''Sets the given rrset to the member __added_soa (which
|
|
|
+ is used by __do_update for updating the SOA record'''
|
|
|
+ self.__added_soa = rrset
|
|
|
+
|
|
|
+ def __do_prescan(self):
|
|
|
+ '''Perform the prescan as defined in RFC2136 section 3.4.1.
|
|
|
+ This method has a side-effect; it sets self._new_soa if
|
|
|
+ it encounters the addition of a SOA record in the update
|
|
|
+ list (so serial can be checked by update later, etc.).
|
|
|
+ It puts the added SOA in self.__added_soa.
|
|
|
+ '''
|
|
|
+ for rrset in self.__message.get_section(SECTION_UPDATE):
|
|
|
+ if not self.__check_in_zone(rrset):
|
|
|
+ logger.info(LIBDDNS_UPDATE_NOTZONE,
|
|
|
+ ClientFormatter(self.__client_addr),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
+ RRsetFormatter(rrset))
|
|
|
+ return Rcode.NOTZONE()
|
|
|
+ if rrset.get_class() == self.__zclass:
|
|
|
+ # In fact, all metatypes are in a specific range,
|
|
|
+ # so one check can test TKEY to ANY
|
|
|
+ # (some value check is needed anyway, since we do
|
|
|
+ # not have defined RRtypes for MAILA and MAILB)
|
|
|
+ if rrset.get_type().get_code() >= 249:
|
|
|
+ logger.info(LIBDDNS_UPDATE_ADD_BAD_TYPE,
|
|
|
+ ClientFormatter(self.__client_addr),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
+ RRsetFormatter(rrset))
|
|
|
+ return Rcode.FORMERR()
|
|
|
+ if rrset.get_type() == RRType.SOA():
|
|
|
+ # In case there's multiple soa records in the update
|
|
|
+ # somehow, just take the last
|
|
|
+ for rr in foreach_rr(rrset):
|
|
|
+ self.__set_soa_rrset(rr)
|
|
|
+ elif rrset.get_class() == RRClass.ANY():
|
|
|
+ if rrset.get_ttl().get_value() != 0:
|
|
|
+ logger.info(LIBDDNS_UPDATE_DELETE_NONZERO_TTL,
|
|
|
+ ClientFormatter(self.__client_addr),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
+ RRsetFormatter(rrset))
|
|
|
+ return Rcode.FORMERR()
|
|
|
+ if rrset.get_rdata_count() > 0:
|
|
|
+ logger.info(LIBDDNS_UPDATE_DELETE_RRSET_NOT_EMPTY,
|
|
|
+ ClientFormatter(self.__client_addr),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
+ RRsetFormatter(rrset))
|
|
|
+ return Rcode.FORMERR()
|
|
|
+ if rrset.get_type().get_code() >= 249 and\
|
|
|
+ rrset.get_type().get_code() <= 254:
|
|
|
+ logger.info(LIBDDNS_UPDATE_DELETE_BAD_TYPE,
|
|
|
+ ClientFormatter(self.__client_addr),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
+ RRsetFormatter(rrset))
|
|
|
+ return Rcode.FORMERR()
|
|
|
+ elif rrset.get_class() == RRClass.NONE():
|
|
|
+ if rrset.get_ttl().get_value() != 0:
|
|
|
+ logger.info(LIBDDNS_UPDATE_DELETE_RR_NONZERO_TTL,
|
|
|
+ ClientFormatter(self.__client_addr),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
+ RRsetFormatter(rrset))
|
|
|
+ return Rcode.FORMERR()
|
|
|
+ if rrset.get_type().get_code() >= 249:
|
|
|
+ logger.info(LIBDDNS_UPDATE_DELETE_RR_BAD_TYPE,
|
|
|
+ ClientFormatter(self.__client_addr),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
+ RRsetFormatter(rrset))
|
|
|
+ return Rcode.FORMERR()
|
|
|
+ else:
|
|
|
+ logger.info(LIBDDNS_UPDATE_BAD_CLASS,
|
|
|
+ ClientFormatter(self.__client_addr),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
+ RRsetFormatter(rrset))
|
|
|
+ return Rcode.FORMERR()
|
|
|
+ return Rcode.NOERROR()
|
|
|
+
|
|
|
+ def __do_update_add_single_rr(self, diff, rr, existing_rrset):
|
|
|
+ '''Helper for __do_update_add_rrs_to_rrset: only add the
|
|
|
+ rr if it is not present yet
|
|
|
+ (note that rr here should already be a single-rr rrset)
|
|
|
+ '''
|
|
|
+ if existing_rrset is None:
|
|
|
+ diff.add_data(rr)
|
|
|
+ else:
|
|
|
+ rr_rdata = rr.get_rdata()[0]
|
|
|
+ if not rr_rdata in existing_rrset.get_rdata():
|
|
|
+ diff.add_data(rr)
|
|
|
+
|
|
|
+ def __do_update_add_rrs_to_rrset(self, diff, rrset):
|
|
|
+ '''Add the rrs from the given rrset to the diff.
|
|
|
+ There is handling for a number of special cases mentioned
|
|
|
+ in RFC2136;
|
|
|
+ - If the addition is a CNAME, but existing data at its
|
|
|
+ name is not, the addition is ignored, and vice versa.
|
|
|
+ - If it is a CNAME, and existing data is too, it is
|
|
|
+ replaced (existing data is deleted)
|
|
|
+ An additional restriction is that SOA data is ignored as
|
|
|
+ well (it is handled separately by the __do_update method).
|
|
|
+
|
|
|
+ Note that in the (near) future, this method may have
|
|
|
+ addition special-cases processing.
|
|
|
+ '''
|
|
|
+ # For a number of cases, we may need to remove data in the zone
|
|
|
+ # (note; SOA is handled separately by __do_update, so that one
|
|
|
+ # is explicitely ignored here)
|
|
|
+ if rrset.get_type() == RRType.SOA():
|
|
|
+ return
|
|
|
+ result, orig_rrset, _ = self.__finder.find(rrset.get_name(),
|
|
|
+ rrset.get_type(),
|
|
|
+ ZoneFinder.NO_WILDCARD |
|
|
|
+ ZoneFinder.FIND_GLUE_OK)
|
|
|
+ if result == self.__finder.CNAME:
|
|
|
+ # Ignore non-cname rrs that try to update CNAME records
|
|
|
+ # (if rrset itself is a CNAME, the finder result would be
|
|
|
+ # SUCCESS, see next case)
|
|
|
+ return
|
|
|
+ elif result == ZoneFinder.SUCCESS:
|
|
|
+ # if update is cname, and zone rr is not, ignore
|
|
|
+ if rrset.get_type() == RRType.CNAME():
|
|
|
+ # Remove original CNAME record (the new one
|
|
|
+ # is added below)
|
|
|
+ diff.delete_data(orig_rrset)
|
|
|
+ # We do not have WKS support at this time, but if there
|
|
|
+ # are special Update equality rules such as for WKS, and
|
|
|
+ # we do have support for the type, this is where the check
|
|
|
+ # (and potential delete) would go.
|
|
|
+ elif result == ZoneFinder.NXRRSET:
|
|
|
+ # There is data present, but not for this type.
|
|
|
+ # If this type is CNAME, ignore the update
|
|
|
+ if rrset.get_type() == RRType.CNAME():
|
|
|
+ return
|
|
|
+ for rr in foreach_rr(rrset):
|
|
|
+ self.__do_update_add_single_rr(diff, rr, orig_rrset)
|
|
|
+
|
|
|
+ def __do_update_delete_rrset(self, diff, rrset):
|
|
|
+ '''Deletes the rrset with the name and type of the given
|
|
|
+ rrset from the zone data (by putting all existing data
|
|
|
+ in the given diff as delete statements).
|
|
|
+ Special cases: if the delete statement is for the
|
|
|
+ zone's apex, and the type is either SOA or NS, it
|
|
|
+ is ignored.'''
|
|
|
+ result, to_delete, _ = self.__finder.find(rrset.get_name(),
|
|
|
+ rrset.get_type(),
|
|
|
+ ZoneFinder.NO_WILDCARD |
|
|
|
+ ZoneFinder.FIND_GLUE_OK)
|
|
|
+ if result == ZoneFinder.SUCCESS:
|
|
|
+ if to_delete.get_name() == self.__zname and\
|
|
|
+ (to_delete.get_type() == RRType.SOA() or\
|
|
|
+ to_delete.get_type() == RRType.NS()):
|
|
|
+ # ignore
|
|
|
+ return
|
|
|
+ for rr in foreach_rr(to_delete):
|
|
|
+ diff.delete_data(rr)
|
|
|
+
|
|
|
+ def __ns_deleter_helper(self, diff, rrset):
|
|
|
+ '''Special case helper for deleting NS resource records
|
|
|
+ at the zone apex. In that scenario, the last NS record
|
|
|
+ may never be removed (and any action that would do so
|
|
|
+ should be ignored).
|
|
|
+ '''
|
|
|
+ # NOTE: This method is currently bad: it WILL delete all
|
|
|
+ # NS rrsets in a number of cases.
|
|
|
+ # We need an extension to our diff.py to handle this correctly
|
|
|
+ # (see ticket #2016)
|
|
|
+ # The related test is currently disabled. When this is fixed,
|
|
|
+ # enable that test again.
|
|
|
+ result, orig_rrset, _ = self.__finder.find(rrset.get_name(),
|
|
|
+ rrset.get_type(),
|
|
|
+ ZoneFinder.NO_WILDCARD |
|
|
|
+ ZoneFinder.FIND_GLUE_OK)
|
|
|
+ # Even a real rrset comparison wouldn't help here...
|
|
|
+ # The goal is to make sure that after deletion of the
|
|
|
+ # given rrset, at least 1 NS record is left (at the apex).
|
|
|
+ # So we make a (shallow) copy of the existing rrset,
|
|
|
+ # and for each rdata in the to_delete set, we check if it wouldn't
|
|
|
+ # delete the last one. If it would, that specific one is ignored.
|
|
|
+ # If it would not, the rdata is removed from the temporary list
|
|
|
+ orig_rrset_rdata = copy.copy(orig_rrset.get_rdata())
|
|
|
+ for rdata in rrset.get_rdata():
|
|
|
+ if len(orig_rrset_rdata) == 1 and rdata == orig_rrset_rdata[0]:
|
|
|
+ # ignore
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ # create an individual RRset for deletion
|
|
|
+ to_delete = isc.dns.RRset(rrset.get_name(),
|
|
|
+ rrset.get_class(),
|
|
|
+ rrset.get_type(),
|
|
|
+ rrset.get_ttl())
|
|
|
+ to_delete.add_rdata(rdata)
|
|
|
+ orig_rrset_rdata.remove(rdata)
|
|
|
+ diff.delete_data(to_delete)
|
|
|
+
|
|
|
+ def __do_update_delete_name(self, diff, rrset):
|
|
|
+ '''Delete all data at the name of the given rrset,
|
|
|
+ by adding all data found by find_all as delete statements
|
|
|
+ to the given diff.
|
|
|
+ Special case: if the name is the zone's apex, SOA and
|
|
|
+ NS records are kept.
|
|
|
+ '''
|
|
|
+ result, rrsets, flags = self.__finder.find_all(rrset.get_name(),
|
|
|
+ ZoneFinder.NO_WILDCARD |
|
|
|
+ ZoneFinder.FIND_GLUE_OK)
|
|
|
+ if result == ZoneFinder.SUCCESS and\
|
|
|
+ (flags & ZoneFinder.RESULT_WILDCARD == 0):
|
|
|
+ for to_delete in rrsets:
|
|
|
+ # if name == self.__zname and type is soa or ns, don't delete!
|
|
|
+ if to_delete.get_name() == self.__zname and\
|
|
|
+ (to_delete.get_type() == RRType.SOA() or
|
|
|
+ to_delete.get_type() == RRType.NS()):
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ for rr in foreach_rr(to_delete):
|
|
|
+ diff.delete_data(rr)
|
|
|
+
|
|
|
+ def __do_update_delete_rrs_from_rrset(self, diff, rrset):
|
|
|
+ '''Deletes all resource records in the given rrset from the
|
|
|
+ zone. Resource records that do not exist are ignored.
|
|
|
+ If the rrset if of type SOA, it is ignored.
|
|
|
+ Uses the __ns_deleter_helper if the rrset's name is the
|
|
|
+ zone's apex, and the type is NS.
|
|
|
+ '''
|
|
|
+ # Delete all rrs in the rrset, except if name=self.__zname and type=soa, or
|
|
|
+ # type = ns and there is only one left (...)
|
|
|
+
|
|
|
+ # The delete does not want class NONE, we would not have gotten here
|
|
|
+ # if it wasn't, but now is a good time to change it to the zclass.
|
|
|
+ to_delete = convert_rrset_class(rrset, self.__zclass)
|
|
|
+
|
|
|
+ if rrset.get_name() == self.__zname:
|
|
|
+ if rrset.get_type() == RRType.SOA():
|
|
|
+ # ignore
|
|
|
+ return
|
|
|
+ elif rrset.get_type() == RRType.NS():
|
|
|
+ # hmm. okay. annoying. There must be at least one left,
|
|
|
+ # delegate to helper method
|
|
|
+ self.__ns_deleter_helper(diff, to_delete)
|
|
|
+ return
|
|
|
+ for rr in foreach_rr(to_delete):
|
|
|
+ diff.delete_data(rr)
|
|
|
+
|
|
|
+ def __update_soa(self, diff):
|
|
|
+ '''Checks the member value __added_soa, and depending on
|
|
|
+ whether it has been set and what its value is, creates
|
|
|
+ a new SOA if necessary.
|
|
|
+ Then removes the original SOA and adds the new one,
|
|
|
+ by adding the needed operations to the given diff.'''
|
|
|
+ # Get the existing SOA
|
|
|
+ # if a new soa was specified, add that one, otherwise, do the
|
|
|
+ # serial magic and add the newly created one
|
|
|
+
|
|
|
+ # get it from DS and to increment and stuff
|
|
|
+ result, old_soa, _ = self.__finder.find(self.__zname, RRType.SOA(),
|
|
|
+ ZoneFinder.NO_WILDCARD |
|
|
|
+ ZoneFinder.FIND_GLUE_OK)
|
|
|
+
|
|
|
+ if self.__added_soa is not None:
|
|
|
+ new_soa = self.__added_soa
|
|
|
+ # serial check goes here
|
|
|
+ else:
|
|
|
+ new_soa = old_soa
|
|
|
+ # increment goes here
|
|
|
+
|
|
|
+ diff.delete_data(old_soa)
|
|
|
+ diff.add_data(new_soa)
|
|
|
+
|
|
|
+ def __do_update(self):
|
|
|
+ '''Scan, check, and execute the Update section in the
|
|
|
+ DDNS Update message.
|
|
|
+ Returns an Rcode to signal the result (NOERROR upon success,
|
|
|
+ any error result otherwise).
|
|
|
+ '''
|
|
|
+ # prescan
|
|
|
+ prescan_result = self.__do_prescan()
|
|
|
+ if prescan_result != Rcode.NOERROR():
|
|
|
+ return prescan_result
|
|
|
+
|
|
|
+ # update
|
|
|
+ try:
|
|
|
+ # create an ixfr-out-friendly diff structure to work on
|
|
|
+ diff = isc.xfrin.diff.Diff(self.__datasrc_client, self.__zname,
|
|
|
+ journaling=True, single_update_mode=True)
|
|
|
+
|
|
|
+ # Do special handling for SOA first
|
|
|
+ self.__update_soa(diff)
|
|
|
+
|
|
|
+ # Algorithm from RFC2136 Section 3.4
|
|
|
+ # Note that this works on full rrsets, not individual RRs.
|
|
|
+ # Some checks might be easier with individual RRs, but only if we
|
|
|
+ # would use the ZoneUpdater directly (so we can query the
|
|
|
+ # 'zone-as-it-would-be-so-far'. However, due to the current use
|
|
|
+ # of the Diff class, this is not the case, and therefore it
|
|
|
+ # is easier to work with full rrsets for the most parts
|
|
|
+ # (less lookups needed; conversion to individual rrs is
|
|
|
+ # the same effort whether it is done here or in the several
|
|
|
+ # do_update statements)
|
|
|
+ for rrset in self.__message.get_section(SECTION_UPDATE):
|
|
|
+ if rrset.get_class() == self.__zclass:
|
|
|
+ self.__do_update_add_rrs_to_rrset(diff, rrset)
|
|
|
+ elif rrset.get_class() == RRClass.ANY():
|
|
|
+ if rrset.get_type() == RRType.ANY():
|
|
|
+ self.__do_update_delete_name(diff, rrset)
|
|
|
+ else:
|
|
|
+ self.__do_update_delete_rrset(diff, rrset)
|
|
|
+ elif rrset.get_class() == RRClass.NONE():
|
|
|
+ self.__do_update_delete_rrs_from_rrset(diff, rrset)
|
|
|
+
|
|
|
+ diff.commit()
|
|
|
+ return Rcode.NOERROR()
|
|
|
+ except isc.datasrc.Error as dse:
|
|
|
+ logger.info(LIBDDNS_UPDATE_DATASRC_ERROR, dse)
|
|
|
+ return Rcode.SERVFAIL()
|
|
|
+ except Exception as uce:
|
|
|
+ logger.error(LIBDDNS_UPDATE_UNCAUGHT_EXCEPTION,
|
|
|
+ ClientFormatter(self.__client_addr),
|
|
|
+ ZoneFormatter(self.__zname, self.__zclass),
|
|
|
+ uce)
|
|
|
+ return Rcode.SERVFAIL()
|