BIND 10 trac928, updated. 7018b05373f706d54a6a0fc2d8977c44292b273f [trac928] add statistics category and statistics items into some spec files (bob.spec, auth.spec, stats.spec)

BIND 10 source code commits bind10-changes at lists.isc.org
Fri Jul 22 12:42:23 UTC 2011


The branch, trac928 has been updated
  discards  26100f264a9c48aa80748256db1e94c6091ba729 (commit)
       via  7018b05373f706d54a6a0fc2d8977c44292b273f (commit)
       via  902ad260c2399b597fe22bba461481a09502b9d5 (commit)
       via  486bf91e0ecc5fbecfe637e1e75ebe373d42509b (commit)
       via  687c2d4bdc959da433c141d920820875b701c2be (commit)
       via  640a5da59304684d4fe304f2616e8dcf9f234d41 (commit)
       via  10e4a07adce6af4794166cc783eca4fed188cd42 (commit)
       via  bb79e799885427437f01e6456c03a206886ae9ff (commit)
       via  1c88cc3b00870a93c01688dd5742f5a19e0d0f76 (commit)
       via  ae79f5fe81a38b64a541adc67194404de5dc8cc5 (commit)
       via  d185c72f14dab4b4ca10dd01e6ea9b7aeb42b2df (commit)
       via  50070c824270d5da1db0b716db73b726d458e9f7 (commit)
       via  66ebc54e863f58b86c3ae65ca9f4764906c9a348 (commit)
       via  3912ef9b24104abea0e9344ff24deeed700712e3 (commit)
       via  5471e816ab36a6182b2223dea461fc8d086ed9e7 (commit)
       via  686ed44b82c009ddb63ed064d46ce44fcade5fbe (commit)
       via  f551799e8c3de59be0a6a7c5168194b93987e876 (commit)
       via  bb1028fd4f52135f4a2c8175d9bf1b90043df1cc (commit)
       via  7d85a63f7bd3ef5926b92dd8f7d9c1588cf6e286 (commit)
       via  582348ce86ac20e0bb92079e5f15ba9b05f60a66 (commit)
       via  92bf1032800f3365a5d8eb5052a2a045495ca646 (commit)
       via  ebc15cde7e0fa14a61127be51267a5ad0c430f90 (commit)
       via  df79b8d3306394ae123fb4c558f7239146e9f0d6 (commit)
       via  6d784213ea929dfa06099d7d85ed87709a7f408e (commit)
       via  e77575c3c85c7e219137b2c616ad104e5b28eb20 (commit)
       via  49f1d2d2e7f75432465ddd4acae2579c018aab33 (commit)
       via  ed9c17ed1627872d701c76336aff407d3ad5c44e (commit)
       via  b0e38303e79e2a487e37a9dcadd5f1730cdeae9e (commit)
       via  93145c09728dbfb7fe5bd77b5a3671e911c41deb (commit)
       via  1c1bd99f0add79535b62f6723d7e942661007653 (commit)
       via  1d03e4212cffa7fcf57d0f3a4fcdc1920c959e40 (commit)

This update added new revisions after undoing existing revisions.  That is
to say, the old revision is not a strict subset of the new revision.  This
situation occurs when you --force push a change and generate a repository
containing something like this:

 * -- * -- B -- O -- O -- O (26100f264a9c48aa80748256db1e94c6091ba729)
            \
             N -- N -- N (7018b05373f706d54a6a0fc2d8977c44292b273f)

When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 7018b05373f706d54a6a0fc2d8977c44292b273f
Author: Naoki Kambe <kambe at jprs.co.jp>
Date:   Wed Jul 20 09:00:53 2011 +0900

    [trac928] add statistics category and statistics items into some spec files (bob.spec, auth.spec, stats.spec)

-----------------------------------------------------------------------

Summary of changes:
 ChangeLog                                   |   13 +++
 src/bin/auth/auth_srv.cc                    |    2 +-
 src/bin/stats/stats_httpd.py.in             |   18 ++++-
 src/bin/stats/tests/b10-stats-httpd_test.py |   89 ++++++++++++++++++++
 src/bin/xfrout/tests/Makefile.am            |    2 +-
 src/bin/xfrout/tests/xfrout_test.py.in      |  118 ++++++++++++++++++++++-----
 src/bin/xfrout/xfrout.py.in                 |   98 ++++++++++++++++-------
 src/bin/xfrout/xfrout.spec.pre.in           |   26 +++++--
 src/bin/xfrout/xfrout_messages.mes          |   11 +++
 9 files changed, 316 insertions(+), 61 deletions(-)

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index fc9e8b4..8f86551 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+274.	[bug]		naokikambe
+	add unittests for functions xml_handler, xsd_handler and xsl_handler
+	respectively to make sure their behaviors are correct, regardless of
+	whether type which xml.etree.ElementTree.tostring() after Python3.2
+	returns is str or byte.
+	(Trac #1021, git 486bf91e0ecc5fbecfe637e1e75ebe373d42509b)
+
+273.    [func]		vorner
+	It is possible to specify ACL for the xfrout module. It is in the ACL
+	configuration key and has the usual ACL syntax. It currently supports
+	only the source address. Default ACL accepts everything.
+	(Trac #772, git 50070c824270d5da1db0b716db73b726d458e9f7)
+
 272.	[func]		jinmei
 	libdns++/pydnspp: TSIG signing now handles truncated DNS messages
 	(i.e. with TC bit on) with TSIG correctly.
diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index f29fd05..f96e642 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -290,7 +290,7 @@ makeErrorMessage(MessagePtr message, OutputBufferPtr buffer,
         message->toWire(renderer);
     }
     LOG_DEBUG(auth_logger, DBG_AUTH_MESSAGES, AUTH_SEND_ERROR_RESPONSE)
-              .arg(message->toText());
+              .arg(renderer.getLength()).arg(*message);
 }
 }
 
diff --git a/src/bin/stats/stats_httpd.py.in b/src/bin/stats/stats_httpd.py.in
index ea68e7d..74298cf 100755
--- a/src/bin/stats/stats_httpd.py.in
+++ b/src/bin/stats/stats_httpd.py.in
@@ -385,7 +385,14 @@ class StatsHttpd:
             annotation.append(documentation)
             element.append(annotation)
             xsd_root.append(element)
-        xsd_string = xml.etree.ElementTree.tostring(xsd_root)
+        # The coding conversion is tricky. xml..tostring() of Python 3.2
+        # returns bytes (not string) regardless of the coding, while
+        # tostring() of Python 3.1 returns a string.  To support both
+        # cases transparently, we first make sure tostring() returns
+        # bytes by specifying utf-8 and then convert the result to a
+        # plain string (code below assume it).
+        xsd_string = str(xml.etree.ElementTree.tostring(xsd_root, encoding='utf-8'),
+                         encoding='us-ascii')
         self.xsd_body = self.open_template(XSD_TEMPLATE_LOCATION).substitute(
             xsd_string=xsd_string,
             xsd_namespace=XSD_NAMESPACE
@@ -410,7 +417,14 @@ class StatsHttpd:
             tr.append(td1)
             tr.append(td2)
             xsd_root.append(tr)
-        xsl_string = xml.etree.ElementTree.tostring(xsd_root)
+        # The coding conversion is tricky. xml..tostring() of Python 3.2
+        # returns bytes (not string) regardless of the coding, while
+        # tostring() of Python 3.1 returns a string.  To support both
+        # cases transparently, we first make sure tostring() returns
+        # bytes by specifying utf-8 and then convert the result to a
+        # plain string (code below assume it).
+        xsl_string = str(xml.etree.ElementTree.tostring(xsd_root, encoding='utf-8'),
+                         encoding='us-ascii')
         self.xsl_body = self.open_template(XSL_TEMPLATE_LOCATION).substitute(
             xsl_string=xsl_string,
             xsd_namespace=XSD_NAMESPACE)
diff --git a/src/bin/stats/tests/b10-stats-httpd_test.py b/src/bin/stats/tests/b10-stats-httpd_test.py
index a7f83a5..6d72dc2 100644
--- a/src/bin/stats/tests/b10-stats-httpd_test.py
+++ b/src/bin/stats/tests/b10-stats-httpd_test.py
@@ -402,6 +402,95 @@ class TestStatsHttpd(unittest.TestCase):
             )
         self.assertEqual(ret, 1)
 
+    def test_xml_handler(self):
+        orig_get_stats_data = stats_httpd.StatsHttpd.get_stats_data
+        stats_httpd.StatsHttpd.get_stats_data = lambda x: {'foo':'bar'}
+        xml_body1 = stats_httpd.StatsHttpd().open_template(
+            stats_httpd.XML_TEMPLATE_LOCATION).substitute(
+            xml_string='<foo>bar</foo>',
+            xsd_namespace=stats_httpd.XSD_NAMESPACE,
+            xsd_url_path=stats_httpd.XSD_URL_PATH,
+            xsl_url_path=stats_httpd.XSL_URL_PATH)
+        xml_body2 = stats_httpd.StatsHttpd().xml_handler()
+        self.assertEqual(type(xml_body1), str)
+        self.assertEqual(type(xml_body2), str)
+        self.assertEqual(xml_body1, xml_body2)
+        stats_httpd.StatsHttpd.get_stats_data = lambda x: {'bar':'foo'}
+        xml_body2 = stats_httpd.StatsHttpd().xml_handler()
+        self.assertNotEqual(xml_body1, xml_body2)
+        stats_httpd.StatsHttpd.get_stats_data = orig_get_stats_data
+
+    def test_xsd_handler(self):
+        orig_get_stats_spec = stats_httpd.StatsHttpd.get_stats_spec
+        stats_httpd.StatsHttpd.get_stats_spec = lambda x: \
+            [{
+                "item_name": "foo",
+                "item_type": "string",
+                "item_optional": False,
+                "item_default": "bar",
+                "item_description": "foo is bar",
+                "item_title": "Foo"
+               }]
+        xsd_body1 = stats_httpd.StatsHttpd().open_template(
+            stats_httpd.XSD_TEMPLATE_LOCATION).substitute(
+            xsd_string='<all>' \
+                + '<element maxOccurs="1" minOccurs="1" name="foo" type="string">' \
+                + '<annotation><appinfo>Foo</appinfo>' \
+                + '<documentation>foo is bar</documentation>' \
+                + '</annotation></element></all>',
+            xsd_namespace=stats_httpd.XSD_NAMESPACE)
+        xsd_body2 = stats_httpd.StatsHttpd().xsd_handler()
+        self.assertEqual(type(xsd_body1), str)
+        self.assertEqual(type(xsd_body2), str)
+        self.assertEqual(xsd_body1, xsd_body2)
+        stats_httpd.StatsHttpd.get_stats_spec = lambda x: \
+            [{
+                "item_name": "bar",
+                "item_type": "string",
+                "item_optional": False,
+                "item_default": "foo",
+                "item_description": "bar is foo",
+                "item_title": "bar"
+               }]
+        xsd_body2 = stats_httpd.StatsHttpd().xsd_handler()
+        self.assertNotEqual(xsd_body1, xsd_body2)
+        stats_httpd.StatsHttpd.get_stats_spec = orig_get_stats_spec
+
+    def test_xsl_handler(self):
+        orig_get_stats_spec = stats_httpd.StatsHttpd.get_stats_spec
+        stats_httpd.StatsHttpd.get_stats_spec = lambda x: \
+            [{
+                "item_name": "foo",
+                "item_type": "string",
+                "item_optional": False,
+                "item_default": "bar",
+                "item_description": "foo is bar",
+                "item_title": "Foo"
+               }]
+        xsl_body1 = stats_httpd.StatsHttpd().open_template(
+            stats_httpd.XSL_TEMPLATE_LOCATION).substitute(
+            xsl_string='<xsl:template match="*"><tr>' \
+                + '<td class="title" title="foo is bar">Foo</td>' \
+                + '<td><xsl:value-of select="foo" /></td>' \
+                + '</tr></xsl:template>',
+            xsd_namespace=stats_httpd.XSD_NAMESPACE)
+        xsl_body2 = stats_httpd.StatsHttpd().xsl_handler()
+        self.assertEqual(type(xsl_body1), str)
+        self.assertEqual(type(xsl_body2), str)
+        self.assertEqual(xsl_body1, xsl_body2)
+        stats_httpd.StatsHttpd.get_stats_spec = lambda x: \
+            [{
+                "item_name": "bar",
+                "item_type": "string",
+                "item_optional": False,
+                "item_default": "foo",
+                "item_description": "bar is foo",
+                "item_title": "bar"
+               }]
+        xsl_body2 = stats_httpd.StatsHttpd().xsl_handler()
+        self.assertNotEqual(xsl_body1, xsl_body2)
+        stats_httpd.StatsHttpd.get_stats_spec = orig_get_stats_spec
+
     def test_for_without_B10_FROM_SOURCE(self):
         # just lets it go through the code without B10_FROM_SOURCE env
         # variable
diff --git a/src/bin/xfrout/tests/Makefile.am b/src/bin/xfrout/tests/Makefile.am
index 6ca2b42..99f4843 100644
--- a/src/bin/xfrout/tests/Makefile.am
+++ b/src/bin/xfrout/tests/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
 # required by loadable python modules.
 LIBRARY_PATH_PLACEHOLDER =
 if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$$$(ENV_LIBRARY_PATH)
 endif
 
 # test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
index 7ab4a58..e353a60 100644
--- a/src/bin/xfrout/tests/xfrout_test.py.in
+++ b/src/bin/xfrout/tests/xfrout_test.py.in
@@ -24,6 +24,7 @@ from pydnspp import *
 from xfrout import *
 import xfrout
 import isc.log
+import isc.acl.dns
 
 TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
 
@@ -117,8 +118,11 @@ class TestXfroutSession(unittest.TestCase):
 
     def setUp(self):
         self.sock = MySocket(socket.AF_INET,socket.SOCK_STREAM)
-        #self.log = isc.log.NSLogger('xfrout', '',  severity = 'critical', log_to_console = False )
-        self.xfrsess = MyXfroutSession(self.sock, None, Dbserver(), TSIGKeyRing())
+        self.xfrsess = MyXfroutSession(self.sock, None, Dbserver(),
+                                       TSIGKeyRing(), ('127.0.0.1', 12345),
+                                       # When not testing ACLs, simply accept
+                                       isc.acl.dns.REQUEST_LOADER.load(
+                                           [{"action": "ACCEPT"}]))
         self.mdata = bytes(b'\xd6=\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\xfc\x00\x01')
         self.soa_record = (4, 3, 'example.com.', 'com.example.', 3600, 'SOA', None, 'master.example.com. admin.example.com. 1234 3600 1800 2419200 7200')
 
@@ -138,6 +142,36 @@ class TestXfroutSession(unittest.TestCase):
         self.assertEqual(rcode.to_text(), "NOERROR")
         self.assertTrue(self.xfrsess._tsig_ctx is not None)
 
+        # ACL checks, put some ACL inside
+        self.xfrsess._acl = isc.acl.dns.REQUEST_LOADER.load([
+            {
+                "from": "127.0.0.1",
+                "action": "ACCEPT"
+            },
+            {
+                "from": "192.0.2.1",
+                "action": "DROP"
+            }
+        ])
+        # Localhost (the default in this test) is accepted
+        rcode, msg = self.xfrsess._parse_query_message(self.mdata)
+        self.assertEqual(rcode.to_text(), "NOERROR")
+        # This should be dropped completely, therefore returning None
+        self.xfrsess._remote = ('192.0.2.1', 12345)
+        rcode, msg = self.xfrsess._parse_query_message(self.mdata)
+        self.assertEqual(None, rcode)
+        # This should be refused, therefore REFUSED
+        self.xfrsess._remote = ('192.0.2.2', 12345)
+        rcode, msg = self.xfrsess._parse_query_message(self.mdata)
+        self.assertEqual(rcode.to_text(), "REFUSED")
+        # If the TSIG check fails, it should not check ACL
+        # (If it checked ACL as well, it would just drop the request)
+        self.xfrsess._remote = ('192.0.2.1', 12345)
+        self.xfrsess._tsig_key_ring = TSIGKeyRing()
+        rcode, msg = self.xfrsess._parse_query_message(request_data)
+        self.assertEqual(rcode.to_text(), "NOTAUTH")
+        self.assertTrue(self.xfrsess._tsig_ctx is not None)
+
     def test_get_query_zone_name(self):
         msg = self.getmsg()
         self.assertEqual(self.xfrsess._get_query_zone_name(msg), "example.com.")
@@ -196,20 +230,6 @@ class TestXfroutSession(unittest.TestCase):
         self.assertEqual(msg.get_rcode(), rcode)
         self.assertTrue(msg.get_header_flag(Message.HEADERFLAG_AA))
 
-    def test_reply_query_with_format_error(self):
-        msg = self.getmsg()
-        self.xfrsess._reply_query_with_format_error(msg, self.sock)
-        get_msg = self.sock.read_msg()
-        self.assertEqual(get_msg.get_rcode().to_text(), "FORMERR")
-
-        # tsig signed message
-        msg = self.getmsg()
-        self.xfrsess._tsig_ctx = self.create_mock_tsig_ctx(TSIGError.NOERROR)
-        self.xfrsess._reply_query_with_format_error(msg, self.sock)
-        get_msg = self.sock.read_msg()
-        self.assertEqual(get_msg.get_rcode().to_text(), "FORMERR")
-        self.assertTrue(self.message_has_tsig(get_msg))
-
     def test_create_rrset_from_db_record(self):
         rrset = self.xfrsess._create_rrset_from_db_record(self.soa_record)
         self.assertEqual(rrset.get_name().to_text(), "example.com.")
@@ -516,18 +536,42 @@ class MyCCSession():
 
 class MyUnixSockServer(UnixSockServer):
     def __init__(self):
-        self._lock = threading.Lock()
-        self._transfers_counter = 0
         self._shutdown_event = threading.Event()
         self._max_transfers_out = 10
         self._cc = MyCCSession()
-        #self._log = isc.log.NSLogger('xfrout', '', severity = 'critical', log_to_console = False )
+        self._common_init()
 
 class TestUnixSockServer(unittest.TestCase):
     def setUp(self):
         self.write_sock, self.read_sock = socket.socketpair()
         self.unix = MyUnixSockServer()
 
+    def test_guess_remote(self):
+        """Test we can guess the remote endpoint when we have only the
+           file descriptor. This is needed, because we get only that one
+           from auth."""
+        # We test with UDP, as it can be "connected" without other
+        # endpoint
+        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        sock.connect(('127.0.0.1', 12345))
+        self.assertEqual(('127.0.0.1', 12345),
+                         self.unix._guess_remote(sock.fileno()))
+        if socket.has_ipv6:
+            # Don't check IPv6 address on hosts not supporting them
+            sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
+            sock.connect(('::1', 12345))
+            self.assertEqual(('::1', 12345, 0, 0),
+                             self.unix._guess_remote(sock.fileno()))
+            # Try when pretending there's no IPv6 support
+            # (No need to pretend when there's really no IPv6)
+            xfrout.socket.has_ipv6 = False
+            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+            sock.connect(('127.0.0.1', 12345))
+            self.assertEqual(('127.0.0.1', 12345),
+                             self.unix._guess_remote(sock.fileno()))
+            # Return it back
+            xfrout.socket.has_ipv6 = True
+
     def test_receive_query_message(self):
         send_msg = b"\xd6=\x00\x00\x00\x01\x00"
         msg_len = struct.pack('H', socket.htons(len(send_msg)))
@@ -536,15 +580,37 @@ class TestUnixSockServer(unittest.TestCase):
         recv_msg = self.unix._receive_query_message(self.read_sock)
         self.assertEqual(recv_msg, send_msg)
 
-    def test_updata_config_data(self):
+    def check_default_ACL(self):
+        context = isc.acl.dns.RequestContext(socket.getaddrinfo("127.0.0.1",
+                                             1234, 0, socket.SOCK_DGRAM,
+                                             socket.IPPROTO_UDP,
+                                             socket.AI_NUMERICHOST)[0][4])
+        self.assertEqual(isc.acl.acl.ACCEPT, self.unix._acl.execute(context))
+
+    def check_loaded_ACL(self):
+        context = isc.acl.dns.RequestContext(socket.getaddrinfo("127.0.0.1",
+                                             1234, 0, socket.SOCK_DGRAM,
+                                             socket.IPPROTO_UDP,
+                                             socket.AI_NUMERICHOST)[0][4])
+        self.assertEqual(isc.acl.acl.ACCEPT, self.unix._acl.execute(context))
+        context = isc.acl.dns.RequestContext(socket.getaddrinfo("192.0.2.1",
+                                             1234, 0, socket.SOCK_DGRAM,
+                                             socket.IPPROTO_UDP,
+                                             socket.AI_NUMERICHOST)[0][4])
+        self.assertEqual(isc.acl.acl.REJECT, self.unix._acl.execute(context))
+
+    def test_update_config_data(self):
+        self.check_default_ACL()
         tsig_key_str = 'example.com:SFuWd/q99SzF8Yzd1QbB9g=='
         tsig_key_list = [tsig_key_str]
         bad_key_list = ['bad..example.com:SFuWd/q99SzF8Yzd1QbB9g==']
         self.unix.update_config_data({'transfers_out':10 })
         self.assertEqual(self.unix._max_transfers_out, 10)
         self.assertTrue(self.unix.tsig_key_ring is not None)
+        self.check_default_ACL()
 
-        self.unix.update_config_data({'transfers_out':9, 'tsig_key_ring':tsig_key_list})
+        self.unix.update_config_data({'transfers_out':9,
+                                      'tsig_key_ring':tsig_key_list})
         self.assertEqual(self.unix._max_transfers_out, 9)
         self.assertEqual(self.unix.tsig_key_ring.size(), 1)
         self.unix.tsig_key_ring.remove(Name("example.com."))
@@ -555,6 +621,16 @@ class TestUnixSockServer(unittest.TestCase):
         self.assertRaises(None, self.unix.update_config_data(config_data))
         self.assertEqual(self.unix.tsig_key_ring.size(), 0)
 
+        # Load the ACL
+        self.unix.update_config_data({'query_acl': [{'from': '127.0.0.1',
+                                               'action': 'ACCEPT'}]})
+        self.check_loaded_ACL()
+        # Pass a wrong data there and check it does not replace the old one
+        self.assertRaises(isc.acl.acl.LoaderError,
+                          self.unix.update_config_data,
+                          {'query_acl': ['Something bad']})
+        self.check_loaded_ACL()
+
     def test_get_db_file(self):
         self.assertEqual(self.unix.get_db_file(), "initdb.file")
 
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index b44b099..2e94369 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -48,6 +48,9 @@ except ImportError as e:
     # must keep running, so we warn about it and move forward.
     log.error(XFROUT_IMPORT, str(e))
 
+from isc.acl.acl import ACCEPT, REJECT, DROP
+from isc.acl.dns import REQUEST_LOADER
+
 isc.util.process.rename()
 
 def init_paths():
@@ -92,16 +95,16 @@ def get_rrset_len(rrset):
 
 
 class XfroutSession():
-    def __init__(self, sock_fd, request_data, server, tsig_key_ring):
-        # The initializer for the superclass may call functions
-        # that need _log to be set, so we set it first
+    def __init__(self, sock_fd, request_data, server, tsig_key_ring, remote,
+                 acl):
         self._sock_fd = sock_fd
         self._request_data = request_data
         self._server = server
-        #self._log = log
         self._tsig_key_ring = tsig_key_ring
         self._tsig_ctx = None
         self._tsig_len = 0
+        self._remote = remote
+        self._acl = acl
         self.handle()
 
     def create_tsig_ctx(self, tsig_record, tsig_key_ring):
@@ -114,7 +117,7 @@ class XfroutSession():
             self.dns_xfrout_start(self._sock_fd, self._request_data)
             #TODO, avoid catching all exceptions
         except Exception as e:
-            logger.error(XFROUT_HANDLE_QUERY_ERROR, str(e))
+            logger.error(XFROUT_HANDLE_QUERY_ERROR, e)
             pass
 
         os.close(self._sock_fd)
@@ -141,8 +144,25 @@ class XfroutSession():
             # TSIG related checks
             rcode = self._check_request_tsig(msg, mdata)
 
+            if rcode == Rcode.NOERROR():
+                # ACL checks
+                acl_result = self._acl.execute(
+                    isc.acl.dns.RequestContext(self._remote))
+                if acl_result == DROP:
+                    logger.info(XFROUT_QUERY_DROPPED,
+                                self._get_query_zone_name(msg),
+                                self._get_query_zone_class(msg),
+                                self._remote[0], self._remote[1])
+                    return None, None
+                elif acl_result == REJECT:
+                    logger.info(XFROUT_QUERY_REJECTED,
+                                self._get_query_zone_name(msg),
+                                self._get_query_zone_class(msg),
+                                self._remote[0], self._remote[1])
+                    return Rcode.REFUSED(), msg
+
         except Exception as err:
-            logger.error(XFROUT_PARSE_QUERY_ERROR, str(err))
+            logger.error(XFROUT_PARSE_QUERY_ERROR, err)
             return Rcode.FORMERR(), None
 
         return rcode, msg
@@ -183,18 +203,11 @@ class XfroutSession():
 
 
     def _reply_query_with_error_rcode(self, msg, sock_fd, rcode_):
-        msg.make_response()
-        msg.set_rcode(rcode_)
-        self._send_message(sock_fd, msg, self._tsig_ctx)
-
-
-    def _reply_query_with_format_error(self, msg, sock_fd):
-        '''query message format isn't legal.'''
         if not msg:
             return # query message is invalid. send nothing back.
 
         msg.make_response()
-        msg.set_rcode(Rcode.FORMERR())
+        msg.set_rcode(rcode_)
         self._send_message(sock_fd, msg, self._tsig_ctx)
 
     def _zone_has_soa(self, zone):
@@ -244,10 +257,13 @@ class XfroutSession():
     def dns_xfrout_start(self, sock_fd, msg_query):
         rcode_, msg = self._parse_query_message(msg_query)
         #TODO. create query message and parse header
-        if rcode_ == Rcode.NOTAUTH():
+        if rcode_ is None: # Dropped by ACL
+            return
+        elif rcode_ == Rcode.NOTAUTH() or rcode_ == Rcode.REFUSED():
             return self._reply_query_with_error_rcode(msg, sock_fd, rcode_)
         elif rcode_ != Rcode.NOERROR():
-            return self._reply_query_with_format_error(msg, sock_fd)
+            return self._reply_query_with_error_rcode(msg, sock_fd,
+                                                      Rcode.FORMERR())
 
         zone_name = self._get_query_zone_name(msg)
         zone_class_str = self._get_query_zone_class(msg)
@@ -257,7 +273,7 @@ class XfroutSession():
         if rcode_ != Rcode.NOERROR():
             logger.info(XFROUT_AXFR_TRANSFER_FAILED, zone_name,
                         zone_class_str, rcode_.to_text())
-            return self. _reply_query_with_error_rcode(msg, sock_fd, rcode_)
+            return self._reply_query_with_error_rcode(msg, sock_fd, rcode_)
 
         try:
             logger.info(XFROUT_AXFR_TRANSFER_STARTED, zone_name, zone_class_str)
@@ -375,14 +391,20 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
         self._sock_file = sock_file
         socketserver_mixin.NoPollMixIn.__init__(self)
         ThreadingUnixStreamServer.__init__(self, sock_file, handle_class)
-        self._lock = threading.Lock()
-        self._transfers_counter = 0
         self._shutdown_event = shutdown_event
         self._write_sock, self._read_sock = socket.socketpair()
-        #self._log = log
+        self._common_init()
         self.update_config_data(config_data)
         self._cc = cc
 
+    def _common_init(self):
+        self._lock = threading.Lock()
+        self._transfers_counter = 0
+        # This default value will probably get overwritten by the (same)
+        # default value from the spec file. This is here just to make
+        # sure and to make the default value in tests consistent.
+        self._acl = REQUEST_LOADER.load('[{"action": "ACCEPT"}]')
+
     def _receive_query_message(self, sock):
         ''' receive request message from sock'''
         # receive data length
@@ -465,10 +487,28 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
             t.daemon = True
         t.start()
 
+    def _guess_remote(self, sock_fd):
+        """
+           Guess remote address and port of the socket. The sock_fd must be a
+           socket
+        """
+        # This uses a trick. If the socket is IPv4 in reality and we pretend
+        # it to be IPv6, it returns IPv4 address anyway. This doesn't seem
+        # to care about the SOCK_STREAM parameter at all (which it really is,
+        # except for testing)
+        if socket.has_ipv6:
+            sock = socket.fromfd(sock_fd, socket.AF_INET6, socket.SOCK_STREAM)
+        else:
+            # To make it work even on hosts without IPv6 support
+            # (Any idea how to simulate this in test?)
+            sock = socket.fromfd(sock_fd, socket.AF_INET, socket.SOCK_STREAM)
+        return sock.getpeername()
 
     def finish_request(self, sock_fd, request_data):
         '''Finish one request by instantiating RequestHandlerClass.'''
-        self.RequestHandlerClass(sock_fd, request_data, self, self.tsig_key_ring)
+        self.RequestHandlerClass(sock_fd, request_data, self,
+                                 self.tsig_key_ring,
+                                 self._guess_remote(sock_fd), self._acl)
 
     def _remove_unused_sock_file(self, sock_file):
         '''Try to remove the socket file. If the file is being used
@@ -512,6 +552,8 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
     def update_config_data(self, new_config):
         '''Apply the new config setting of xfrout module. '''
         logger.info(XFROUT_NEW_CONFIG)
+        if 'query_acl' in new_config:
+            self._acl = REQUEST_LOADER.load(new_config['query_acl'])
         self._lock.acquire()
         self._max_transfers_out = new_config.get('transfers_out')
         self.set_tsig_key_ring(new_config.get('tsig_key_ring'))
@@ -563,16 +605,12 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
 class XfroutServer:
     def __init__(self):
         self._unix_socket_server = None
-        #self._log = None
         self._listen_sock_file = UNIX_SOCKET_FILE
         self._shutdown_event = threading.Event()
         self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
         self._config_data = self._cc.get_full_config()
         self._cc.start()
         self._cc.add_remote_config(AUTH_SPECFILE_LOCATION);
-        #self._log = isc.log.NSLogger(self._config_data.get('log_name'), self._config_data.get('log_file'),
-        #                        self._config_data.get('log_severity'), self._config_data.get('log_versions'),
-        #                        self._config_data.get('log_max_bytes'), True)
         self._start_xfr_query_listener()
         self._start_notifier()
 
@@ -601,11 +639,13 @@ class XfroutServer:
                 continue
             self._config_data[key] = new_config[key]
 
-        #if self._log:
-        #    self._log.update_config(new_config)
-
         if self._unix_socket_server:
-            self._unix_socket_server.update_config_data(self._config_data)
+            try:
+                self._unix_socket_server.update_config_data(self._config_data)
+            except Exception as e:
+                answer = create_answer(1,
+                                       "Failed to handle new configuration: " +
+                                       str(e))
 
         return answer
 
diff --git a/src/bin/xfrout/xfrout.spec.pre.in b/src/bin/xfrout/xfrout.spec.pre.in
index 2efa3d7..8ecbb0b 100644
--- a/src/bin/xfrout/xfrout.spec.pre.in
+++ b/src/bin/xfrout/xfrout.spec.pre.in
@@ -16,27 +16,27 @@
        },
        {
          "item_name": "log_file",
-    	 "item_type": "string",
+         "item_type": "string",
          "item_optional": false,
          "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/log/Xfrout.log"
        },
        {
          "item_name": "log_severity",
-    	 "item_type": "string",
+         "item_type": "string",
          "item_optional": false,
-    	 "item_default": "debug"
+         "item_default": "debug"
        },
        {
          "item_name": "log_versions",
-    	 "item_type": "integer",
+         "item_type": "integer",
          "item_optional": false,
-    	 "item_default": 5
+         "item_default": 5
        },
        {
          "item_name": "log_max_bytes",
-    	 "item_type": "integer",
+         "item_type": "integer",
          "item_optional": false,
-    	 "item_default": 1048576
+         "item_default": 1048576
        },
        {
          "item_name": "tsig_key_ring",
@@ -49,6 +49,18 @@
              "item_type": "string",
              "item_optional": true
          }
+       },
+       {
+         "item_name": "query_acl",
+         "item_type": "list",
+         "item_optional": false,
+         "item_default": [{"action": "ACCEPT"}],
+         "list_item_spec":
+         {
+             "item_name": "acl_element",
+             "item_type": "any",
+             "item_optional": true
+         }
        }
       ],
       "commands": [
diff --git a/src/bin/xfrout/xfrout_messages.mes b/src/bin/xfrout/xfrout_messages.mes
index 2dada54..19b104e 100644
--- a/src/bin/xfrout/xfrout_messages.mes
+++ b/src/bin/xfrout/xfrout_messages.mes
@@ -95,6 +95,17 @@ in the log message, but at this point no specific information other
 than that could be given. This points to incomplete exception handling
 in the code.
 
+% XFROUT_QUERY_DROPPED request to transfer %1/%2 to [%3]:%4 dropped
+The xfrout process silently dropped a request to transfer zone to given host.
+This is required by the ACLs. The %1 and %2 represent the zone name and class,
+the %3 and %4 the IP address and port of the peer requesting the transfer.
+
+% XFROUT_QUERY_REJECTED request to transfer %1/%2 to [%3]:%4 rejected
+The xfrout process rejected (by REFUSED rcode) a request to transfer zone to
+given host. This is because of ACLs. The %1 and %2 represent the zone name and
+class, the %3 and %4 the IP address and port of the peer requesting the
+transfer.
+
 % XFROUT_RECEIVE_FILE_DESCRIPTOR_ERROR error receiving the file descriptor for an XFR connection
 There was an error receiving the file descriptor for the transfer
 request. Normally, the request is received by b10-auth, and passed on




More information about the bind10-changes mailing list