Esempio n. 1
0
 def __do_send(self):
     while True:
         data = self.__send_buffer.get_data(self.__send_marker)
         if data is None:
             # send complete; clear the internal buffer for next possible
             # send.
             logger.debug(logger.DBGLVL_TRACE_DETAIL,
                          PYSERVER_COMMON_DNS_TCP_SEND_DONE,
                          ClientFormatter(self.__remote_addr),
                          self.__send_marker)
             self.__send_buffer = None
             self.__send_marker = 0
             return self.SEND_DONE
         try:
             cc = self.__sock.send(data)
         except socket.error as ex:
             total_len = self.__send_buffer.get_total_len()
             if ex.errno == errno.EAGAIN:
                 logger.debug(logger.DBGLVL_TRACE_DETAIL,
                              PYSERVER_COMMON_DNS_TCP_SEND_PENDING,
                              ClientFormatter(self.__remote_addr),
                              self.__send_marker, total_len)
                 return self.SENDING
             logger.warn(PYSERVER_COMMON_DNS_TCP_SEND_FAILED,
                         ClientFormatter(self.__remote_addr),
                         self.__send_marker, total_len, ex)
             self.__sock.close()
             self.__sock = None
             return self.CLOSED
         self.__send_marker += cc
Esempio n. 2
0
    def handle(self):
        '''Handle the update request according to RFC2136.

        This method returns a tuple of the following 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 (bundy.dns.Name object) in case of
          UPDATE_SUCCESS; otherwise None.
        - The RR class of the updated zone (bundy.dns.RRClass object) in case
          of UPDATE_SUCCESS; otherwise None.
        - The name of the used data source associated with DataSourceClient
          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, None
            update_result = self.__do_update()
            if update_result != Rcode.NOERROR:
                self.__make_response(update_result)
                return UPDATE_ERROR, self.__zname, self.__zclass, None
            self.__make_response(Rcode.NOERROR)
            datasrc_name = self.__datasrc_client.get_datasource_name()
            return UPDATE_SUCCESS, self.__zname, self.__zclass, datasrc_name
        except UpdateError as e:
            if not e.nolog:
                logger.debug(logger.DBGLVL_TRACE_BASIC,
                             LIBDDNS_UPDATE_PROCESSING_FAILED,
                             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, None
            self.__message = None
            return UPDATE_DROP, None, None, None
        except bundy.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, None
Esempio n. 3
0
    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 == bundy.ddns.zone_config.ZONE_PRIMARY:
            self.__datasrc_client = datasrc_client
            self.__zname = zname
            self.__zclass = zclass
            return
        elif zone_type == bundy.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)
Esempio n. 4
0
    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:
            # Do special handling for SOA first
            self.__update_soa()

            # 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(rrset)
                elif rrset.get_class() == RRClass.ANY:
                    if rrset.get_type() == RRType.ANY:
                        self.__do_update_delete_name(rrset)
                    else:
                        self.__do_update_delete_rrset(rrset)
                elif rrset.get_class() == RRClass.NONE:
                    self.__do_update_delete_rrs_from_rrset(rrset)

            if not check_zone(
                    self.__zname, self.__zclass,
                    self.__diff.get_rrset_collection(),
                (self.__validate_error, self.__validate_warning)):
                raise UpdateError('Validation of the new zone failed',
                                  self.__zname, self.__zclass, Rcode.REFUSED)
            self.__diff.commit()
            return Rcode.NOERROR
        except UpdateError:
            # Propagate UpdateError exceptions (don't catch them in the
            # blocks below)
            raise
        except bundy.datasrc.Error as dse:
            logger.info(LIBDDNS_UPDATE_DATASRC_COMMIT_FAILED, 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
Esempio n. 5
0
 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(
         bundy.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))
Esempio n. 6
0
 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
Esempio n. 7
0
    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
            if not self.__check_in_zone(rrset):
                logger.info(LIBDDNS_PREREQ_NOTZONE,
                            ClientFormatter(self.__client_addr),
                            ZoneFormatter(self.__zname, self.__zclass),
                            RRsetFormatter(rrset))
                return Rcode.NOTZONE

            # Algorithm taken from RFC2136 Section 3.2
            if rrset.get_class() == RRClass.ANY:
                if rrset.get_ttl().get_value() != 0 or\
                   rrset.get_rdata_count() != 0:
                    logger.info(LIBDDNS_PREREQ_FORMERR_ANY,
                                ClientFormatter(self.__client_addr),
                                ZoneFormatter(self.__zname, self.__zclass),
                                RRsetFormatter(rrset))
                    return Rcode.FORMERR
                elif rrset.get_type() == RRType.ANY:
                    if not self.__prereq_name_in_use(rrset):
                        rcode = Rcode.NXDOMAIN
                        logger.info(LIBDDNS_PREREQ_NAME_IN_USE_FAILED,
                                    ClientFormatter(self.__client_addr),
                                    ZoneFormatter(self.__zname, self.__zclass),
                                    RRsetFormatter(rrset), rcode)
                        return rcode
                else:
                    if not self.__prereq_rrset_exists(rrset):
                        rcode = Rcode.NXRRSET
                        logger.info(LIBDDNS_PREREQ_RRSET_EXISTS_FAILED,
                                    ClientFormatter(self.__client_addr),
                                    ZoneFormatter(self.__zname, self.__zclass),
                                    RRsetFormatter(rrset), rcode)
                        return rcode
            elif rrset.get_class() == RRClass.NONE:
                if rrset.get_ttl().get_value() != 0 or\
                   rrset.get_rdata_count() != 0:
                    logger.info(LIBDDNS_PREREQ_FORMERR_NONE,
                                ClientFormatter(self.__client_addr),
                                ZoneFormatter(self.__zname, self.__zclass),
                                RRsetFormatter(rrset))
                    return Rcode.FORMERR
                elif rrset.get_type() == RRType.ANY:
                    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(self.__zname, self.__zclass),
                                    RRsetFormatter(rrset), rcode)
                        return rcode
                else:
                    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(self.__zname, self.__zclass),
                                    RRsetFormatter(rrset), rcode)
                        return rcode
            elif rrset.get_class() == self.__zclass:
                if rrset.get_ttl().get_value() != 0:
                    logger.info(LIBDDNS_PREREQ_FORMERR,
                                ClientFormatter(self.__client_addr),
                                ZoneFormatter(self.__zname, self.__zclass),
                                RRsetFormatter(rrset))
                    return Rcode.FORMERR
                else:
                    collect_rrsets(exact_match_rrsets, rrset)
            else:
                logger.info(LIBDDNS_PREREQ_FORMERR_CLASS,
                            ClientFormatter(self.__client_addr),
                            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