Browse Source

[2911] more refactoring: unify xfr command handling into a single method.

JINMEI Tatuya 12 years ago
parent
commit
f714c4ca5a
1 changed files with 62 additions and 46 deletions
  1. 62 46
      src/bin/xfrin/xfrin.py.in

+ 62 - 46
src/bin/xfrin/xfrin.py.in

@@ -1518,67 +1518,69 @@ class Xfrin:
                 continue
             th.join()
 
-    def __handle_xfr_by_zonemgr(self, args):
-        # Xfrin receives the refresh/notify command from zone manager.
-        # notify command maybe has the parameters which
-        # specify the notifyfrom address and port, according to
-        # RFC1996, zone transfer should starts first from the
-        # notifyfrom, but this is a 'TODO' item for now.
-        # (using the value now, while we can only set one master
-        # address, would be a security hole. Once we add the ability
-        # to have multiple master addresses,
-        # we should check if it matches one of them, and then use it.)
-        (zone_name, rrclass) = self._parse_zone_name_and_class(args)
-        zone_str = format_zone_str(zone_name, rrclass)
-        zone_info = self._get_zone_info(zone_name, rrclass)
-        notify_addr = self._parse_master_and_port(args, zone_name, rrclass)
+    def __validate_notify_addr(self, notify_addr, zone_str, zone_info):
+        """Validate notify source as a destination for xfr source.
+
+        This is called from __handle_xfr_command in case xfr is triggered
+        by ZoneMgr either due to incoming Notify or periodic refresh event.
+
+        """
         if zone_info is None:
             # TODO what to do? no info known about zone. defaults?
             errmsg = "Got notification to retransfer unknown zone " + zone_str
             logger.info(XFRIN_RETRANSFER_UNKNOWN_ZONE, zone_str)
-            answer = create_answer(1, errmsg)
+            return create_answer(1, errmsg)
         else:
-            request_type = RRType.AXFR
-            if zone_info.use_ixfr:
-                request_type = RRType.IXFR
             master_addr = zone_info.get_master_addr_info()
-            if (notify_addr[0] == master_addr[0] and
-                notify_addr[2] == master_addr[2]):
-                ret = self.xfrin_start(zone_name, rrclass, self._get_db_file(),
-                                       master_addr, zone_info.get_tsig_key(),
-                                       request_type, True)
-                answer = create_answer(ret[0], ret[1])
-            else:
+            if (notify_addr[0] != master_addr[0] or
+                notify_addr[2] != master_addr[2]):
                 notify_addr_str = format_addrinfo(notify_addr)
                 master_addr_str = format_addrinfo(master_addr)
                 errmsg = "Got notification for " + zone_str\
                     + "from unknown address: " + notify_addr_str;
                 logger.info(XFRIN_NOTIFY_UNKNOWN_MASTER, zone_str,
                             notify_addr_str, master_addr_str)
-                answer = create_answer(1, errmsg)
-        return answer
+                return create_answer(1, errmsg)
+
+        # Notified address is okay
+        return None
 
-    def __handle_xfr_by_user(self, command, args):
-        # Xfrin receives the retransfer/refresh from cmdctl(sent by
-        # bindctl). If the command has specified master address, do
-        # transfer from the master address, or else do transfer from
-        # the configured masters.
+    def __handle_xfr_command(self, args, arg_db, check_soa, addr_validator):
+        """Common subroutine for handling transfer commands.
+
+        This helper method unifies both cases of transfer command from
+        ZoneMgr or from a user.  Depending on who invokes the transfer,
+        details of validation and parameter selection slightly vary.
+        These conditions are passed through parameters and handled in the
+        unified code of this method accordingly.
+
+        If this is from the ZoneMgr due to incoming notify, zone transfer
+        should start from the notify's source address as long as it's
+        configured as a master address, according to RFC1996.  The current
+        implementation conforms to it in a limited way: we can only set one
+        master address. Once we add the ability to have multiple master
+        addresses, we should check if it matches one of them, and then use it.
+
+        In case of transfer command from the user, if the command specifies
+        the master address, use that one; otherwise try to use a configured
+        master address for the zone.
+
+        """
         (zone_name, rrclass) = self._parse_zone_name_and_class(args)
         master_addr = self._parse_master_and_port(args, zone_name, rrclass)
         zone_info = self._get_zone_info(zone_name, rrclass)
-        tsig_key = None
         request_type = RRType.AXFR
-        if zone_info:
-            tsig_key = zone_info.get_tsig_key()
-            if zone_info.use_ixfr:
-                request_type = RRType.IXFR
-        db_file = args.get('db_file') or self._get_db_file()
+        if zone_info is not None and zone_info.use_ixfr:
+            request_type = RRType.IXFR
+        tsig_key = None if zone_info is None else zone_info.get_tsig_key()
+        db_file = arg_db or self._get_db_file()
+        zone_str = format_zone_str(zone_name, rrclass) # for logging
+        answer = addr_validator(master_addr, zone_str, zone_info)
+        if answer is not None:
+            return answer
         ret = self.xfrin_start(zone_name, rrclass, db_file, master_addr,
-                               tsig_key, request_type,
-                               (False if command == 'retransfer' else True))
-        answer = create_answer(ret[0], ret[1])
-
-        return answer
+                               tsig_key, request_type, check_soa)
+        return create_answer(ret[0], ret[1])
 
     def command_handler(self, command, args):
         logger.debug(DBG_XFRIN_TRACE, XFRIN_RECEIVED_COMMAND, command)
@@ -1587,9 +1589,22 @@ class Xfrin:
             if command == 'shutdown':
                 self._shutdown_event.set()
             elif command == 'notify' or command == REFRESH_FROM_ZONEMGR:
-                answer = self.__handle_xfr_by_zonemgr(args)
+                # refresh/notify command from zone manager.
+                # The address has to be validated, db_file is local only,
+                # and always perform SOA check.
+                addr_validator = \
+                    lambda x, y, z: self.__validate_notify_addr(x, y, z)
+                answer = self.__handle_xfr_command(args, None, True,
+                                                   addr_validator)
             elif command == 'retransfer' or command == 'refresh':
-                answer = self.__handle_xfr_by_user(command, args)
+                # retransfer/refresh from cmdctl (sent by bindctl).
+                # No need for address validation, db_file may be specified
+                # with the command, and whether to do SOA check depends on
+                # type of command.
+                check_soa = False if command == 'retransfer' else True
+                answer = self.__handle_xfr_command(args, args.get('db_file'),
+                                                   check_soa,
+                                                   lambda x, y, z: None)
             # return statistics data to the stats daemon
             elif command == "getstats":
                 # The log level is here set to debug in order to avoid
@@ -1610,7 +1625,8 @@ class Xfrin:
         if zone_name_str is None:
             raise XfrinException('zone name should be provided')
 
-        return (_check_zone_name(zone_name_str), _check_zone_class(args.get('zone_class')))
+        return (_check_zone_name(zone_name_str),
+                _check_zone_class(args.get('zone_class')))
 
     def _parse_master_and_port(self, args, zone_name, zone_class):
         """