def handle(self): '''Handle the update request according to RFC2136. This method returns a tuple of the following three elements that indicate the result of the request. - Result code of the request processing, which are: UPDATE_SUCCESS Update request granted and succeeded. UPDATE_ERROR Some error happened to be reported in the response. UPDATE_DROP Error happened and no response should be sent. Except the case of UPDATE_DROP, the UpdateSession object will have created a response that is to be returned to the request client, which can be retrieved by get_message(). If it's UPDATE_DROP, subsequent call to get_message() returns None. - The name of the updated zone (isc.dns.Name object) in case of UPDATE_SUCCESS; otherwise None. - The RR class of the updated zone (isc.dns.RRClass object) in case of UPDATE_SUCCESS; otherwise None. ''' try: self._get_update_zone() # Contrary to what RFC2136 specifies, we do ACL checks before # prerequisites. It's now generally considered to be a bad # idea, and actually does harm such as information # leak. It should make more sense to prevent any security issues # by performing ACL check as early as possible. self.__check_update_acl(self.__zname, self.__zclass) self._create_diff() prereq_result = self.__check_prerequisites() if prereq_result != Rcode.NOERROR(): self.__make_response(prereq_result) return UPDATE_ERROR, 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, ClientFormatter(self.__client_addr, self.__tsig), ZoneFormatter(e.zname, e.zclass), e) # If RCODE is specified, create a corresponding resonse and return # ERROR; otherwise clear the message and return DROP. if e.rcode is not None: self.__make_response(e.rcode) 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, 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 If this method raises an exception, these members are not set. Note: This method is protected for ease of use in tests, where methods are tested that need the setup done here without calling the full handle() method. ''' # Validation: the zone section must contain exactly one question, # and it must be of type SOA. n_zones = self.__message.get_rr_count(SECTION_ZONE) if n_zones != 1: raise UpdateError('Invalid number of records in zone section: ' + str(n_zones), None, None, Rcode.FORMERR()) zrecord = self.__message.get_question()[0] if zrecord.get_type() != RRType.SOA(): raise UpdateError('update zone section contains non-SOA', None, None, Rcode.FORMERR()) # See if we're serving a primary zone specified in the zone section. zname = zrecord.get_name() zclass = zrecord.get_class() zone_type, datasrc_client = self.__zone_config.find_zone(zname, zclass) if zone_type == isc.ddns.zone_config.ZONE_PRIMARY: self.__datasrc_client = datasrc_client self.__zname = zname self.__zclass = zclass 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'. logger.debug(DBGLVL_TRACE_BASIC, LIBDDNS_UPDATE_FORWARD_FAIL, ClientFormatter(self.__client_addr, self.__tsig), ZoneFormatter(zname, zclass)) raise UpdateError('forward', zname, zclass, Rcode.NOTIMP(), True) # zone wasn't found logger.debug(DBGLVL_TRACE_BASIC, LIBDDNS_UPDATE_NOTAUTH, ClientFormatter(self.__client_addr, self.__tsig), ZoneFormatter(zname, zclass)) raise UpdateError('notauth', zname, zclass, Rcode.NOTAUTH(), True)
def __check_update_acl(self, zname, zclass): '''Apply update ACL for the zone to be updated.''' acl = self.__zone_config.get_update_acl(zname, zclass) action = acl.execute(isc.acl.dns.RequestContext( (self.__client_addr[0], self.__client_addr[1]), self.__tsig)) if action == REJECT: logger.info(LIBDDNS_UPDATE_DENIED, ClientFormatter(self.__client_addr, self.__tsig), ZoneFormatter(zname, zclass)) raise UpdateError('rejected', zname, zclass, Rcode.REFUSED(), True) if action == DROP: logger.info(LIBDDNS_UPDATE_DROPPED, ClientFormatter(self.__client_addr, self.__tsig), ZoneFormatter(zname, zclass)) raise UpdateError('dropped', zname, zclass, None, True) logger.debug(logger.DBGLVL_TRACE_BASIC, LIBDDNS_UPDATE_APPROVED, ClientFormatter(self.__client_addr, self.__tsig), ZoneFormatter(zname, zclass))