Fix for https://bugzilla.samba.org/show_bug.cgi?id=9634
authorJulien ROPÉ <jrope@linagora.com>
Fri, 23 Nov 2018 14:56:59 +0000 (15:56 +0100)
committerJeremy Allison <jra@samba.org>
Fri, 11 Jun 2021 19:28:10 +0000 (19:28 +0000)
Add an option to smb.conf to list authorized zone transfer clients.
Implement restriction in dlz_bind9 module to allow transfers only to selected IPs.
Deny zone transfer by default in dlz_bind9.

Adds test for the restriction in DNZ zone transfer clients.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=9634

Signed-off-by: Julien ROPÉ <jrope@linagora.com>
Reviewed-by: Jeremy Allison <jra@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Autobuild-User(master): Jeremy Allison <jra@samba.org>
Autobuild-Date(master): Fri Jun 11 19:28:10 UTC 2021 on sn-devel-184

WHATSNEW.txt
docs-xml/smbdotconf/domain/dnszonetransferclientsallow.xml [new file with mode: 0644]
docs-xml/smbdotconf/domain/dnszonetransferclientsdeny.xml [new file with mode: 0644]
source4/dns_server/dlz_bind9.c
source4/torture/dns/dlz_bind9.c

index 1e407da422e93d975d39a27dcda21ef69ab2c55f..b28722c6f922bd3f3af298713427eb7ff1eb5aba 100644 (file)
@@ -36,6 +36,15 @@ See also GPG_AA99442FB680B620_replaces_6F33915B6568B7EA.txt
 
 NEW FEATURES/CHANGES
 ====================
+- bind DLZ: Added the ability to set allow/deny lists for zone
+  transfer clients.
+  Up to now, any client could use a DNS zone transfer request
+  to the bind server, and get an answer from Samba.
+  Now the default behaviour will be to deny those request.
+  Two new options have been added to manage the list of
+  authorized/denied clients for zone transfer requests.
+  In order to be accepted, the request must be issued by a client
+  that is in the allow list and NOT in the deny list.
 
 
 
diff --git a/docs-xml/smbdotconf/domain/dnszonetransferclientsallow.xml b/docs-xml/smbdotconf/domain/dnszonetransferclientsallow.xml
new file mode 100644 (file)
index 0000000..902b608
--- /dev/null
@@ -0,0 +1,26 @@
+<samba:parameter name="dns zone transfer clients allow"
+                 context="G"
+                 type="cmdlist"
+                 xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
+<description>
+       <para>This option specifies the list IPs authorized to ask for dns zone
+           transfer from bind DLZ module.
+       </para>
+
+       <para>The IP list is comma and space separated and specified in the same
+           syntax as used in <smbconfoption name="hosts allow"/>, specifically
+           including IP address, IP prefixes and IP address masks.
+       </para>
+
+       <para>As this is a DNS server option, hostnames are naturally not permitted.
+       </para>
+
+       <para>The default behaviour is to deny any request.
+              A request will be authorized only if the emitting client is identified
+              in this list, and not in <smbconfoption name="dns zone transfer clients deny"/>
+       </para>
+</description>
+
+<value type="default"></value>
+<value type="example">192.168.0.1</value>
+</samba:parameter>
diff --git a/docs-xml/smbdotconf/domain/dnszonetransferclientsdeny.xml b/docs-xml/smbdotconf/domain/dnszonetransferclientsdeny.xml
new file mode 100644 (file)
index 0000000..f88b15b
--- /dev/null
@@ -0,0 +1,26 @@
+<samba:parameter name="dns zone transfer clients deny"
+                 context="G"
+                 type="cmdlist"
+                 xmlns:samba="http://www.samba.org/samba/DTD/samba-doc">
+<description>
+       <para>This option specifies the list IPs denied to ask for dns zone
+           transfer from bind DLZ module.
+       </para>
+
+       <para>The IP list is comma and space separated and specified in the same
+           syntax as used in <smbconfoption name="hosts allow"/>, specifically
+           including IP address, IP prefixes and IP address masks.
+       </para>
+
+       <para>As this is a DNS server option, hostnames are naturally not permitted.
+       </para>
+
+       <para>If a client identified in this list sends a zone transfer request, it will always
+              be denied, even if they are in <smbconfoption name="dns zone transfer clients allow"/>.
+             This allows the definition of sepcific denied clients within an authorized subnet.
+       </para>
+</description>
+
+<value type="default"></value>
+<value type="example">192.168.0.1</value>
+</samba:parameter>
index 78f69a4d635faadd02885d1dad86af00edd72967..a9946041206c5c638957d6f14d362f26c3538aa9 100644 (file)
@@ -40,6 +40,7 @@
 #include "dlz_minimal.h"
 #include "dnsserver_common.h"
 #include "lib/util/smb_strtox.h"
+#include "lib/util/access.h"
 
 #undef strcasecmp
 
@@ -1085,10 +1086,60 @@ _PUBLIC_ isc_result_t dlz_lookup(const char *zone, const char *name,
  */
 _PUBLIC_ isc_result_t dlz_allowzonexfr(void *dbdata, const char *name, const char *client)
 {
-       /* just say yes for all our zones for now */
        struct dlz_bind9_data *state = talloc_get_type(
                dbdata, struct dlz_bind9_data);
-       return b9_find_zone_dn(state, name, NULL, NULL);
+       isc_result_t ret;
+       const char **authorized_clients, **denied_clients;
+       const char *cname="";
+
+       /* check that the zone is known */
+       ret = b9_find_zone_dn(state, name, NULL, NULL);
+       if (ret != ISC_R_SUCCESS) {
+               return ret;
+       }
+
+       /* default is to deny all transfers */
+
+       authorized_clients = lpcfg_dns_zone_transfer_clients_allow(state->lp);
+       denied_clients = lpcfg_dns_zone_transfer_clients_deny(state->lp);
+
+       /* The logic of allow_access() when both allow and deny lists are given
+        * does not match our expectation here: it would allow clients thar are
+        * neither allowed nor denied.
+        * Here, we want to deny clients by default.
+        * Using the allow_access() function is still useful as it takes care of
+        * parsing IP adresses and subnets in a consistent way with other options
+        * from smb.conf.
+        *
+        * We will then check the deny list first, then the allow list, so that
+        * we accept only clients that are explicitely allowed AND not explicitely
+        * denied.
+        */
+       if ((authorized_clients == NULL) && (denied_clients == NULL)) {
+               /* No "allow" or "deny" lists given. Deny by default. */
+               return ISC_R_NOPERM;
+       }
+
+       if (denied_clients != NULL) {
+               bool ok = allow_access(denied_clients, NULL, cname, client);
+               if (!ok) {
+                       /* client on deny list. Deny. */
+                       return ISC_R_NOPERM;
+               }
+       }
+
+       if (authorized_clients != NULL) {
+               bool ok = allow_access(NULL, authorized_clients, cname, client);
+               if (ok) {
+                       /*
+                        * client is not on deny list and is on allow list.
+                        * This is the only place we should return "allow".
+                        */
+                       return ISC_R_SUCCESS;
+               }
+       }
+       /* We shouldn't get here, but deny by default. */
+       return ISC_R_NOPERM;
 }
 
 /*
index 1e1f1e8bef9a718f0f3dfa17871ad0d648e2affc..9ec2702dc1dd2ab1856243ed810f4cc480a27800 100644 (file)
@@ -1200,6 +1200,76 @@ cancel_version:
        return ret;
 }
 
+/*
+ * Test zone transfer requests restrictions
+ *
+ * 1: test that zone transfer is denied by default
+ * 2: with an authorized list of IPs set in smb.conf, test that zone transfer
+ *    is accepted only for selected IPs.
+ */
+static bool test_dlz_bind9_allowzonexfr(struct torture_context *tctx)
+{
+       void *dbdata;
+       const char *argv[] = {
+               "samba_dlz",
+               "-H",
+               test_dlz_bind9_binddns_dir(tctx, "dns/sam.ldb"),
+               NULL
+       };
+       isc_result_t ret;
+       dns_dlzdb_t *dlzdb = NULL;
+       bool ok;
+
+       tctx_static = tctx;
+       torture_assert_int_equal(tctx, dlz_create("samba_dlz", 3, argv, &dbdata,
+                                                 "log", dlz_bind9_log_wrapper,
+                                                 "writeable_zone", dlz_bind9_writeable_zone_hook,
+                                                 "putrr", dlz_bind9_putrr_hook,
+                                                 "putnamedrr", dlz_bind9_putnamedrr_hook,
+                                                 NULL),
+                                ISC_R_SUCCESS,
+                                "Failed to create samba_dlz");
+
+       torture_assert_int_equal(tctx, dlz_configure((void*)tctx, dlzdb, dbdata),
+                                                    ISC_R_SUCCESS,
+                                            "Failed to configure samba_dlz");
+
+    /* Ask for zone transfer with no specific config => expect denied */
+    ret = dlz_allowzonexfr(dbdata, lpcfg_dnsdomain(tctx->lp_ctx), "127.0.0.1");
+    torture_assert_int_equal(tctx, ret, ISC_R_NOPERM,
+                            "Zone transfer accepted with default settings");
+
+    /* Ask for zone transfer with authorizations set */
+    ok = lpcfg_set_option(tctx->lp_ctx, "dns zone transfer clients allow=127.0.0.1,1234:5678::1,192.168.0.");
+    torture_assert(tctx, ok, "Failed to set dns zone transfer clients allow option.");
+
+    ok = lpcfg_set_option(tctx->lp_ctx, "dns zone transfer clients deny=192.168.0.2");
+    torture_assert(tctx, ok, "Failed to set dns zone transfer clients deny option.");
+
+    ret = dlz_allowzonexfr(dbdata, lpcfg_dnsdomain(tctx->lp_ctx), "127.0.0.1");
+    torture_assert_int_equal(tctx, ret, ISC_R_SUCCESS,
+                            "Zone transfer refused for authorized IPv4 address");
+
+    ret = dlz_allowzonexfr(dbdata, lpcfg_dnsdomain(tctx->lp_ctx), "1234:5678::1");
+    torture_assert_int_equal(tctx, ret, ISC_R_SUCCESS,
+                             "Zone transfer refused for authorized IPv6 address.");
+
+    ret = dlz_allowzonexfr(dbdata, lpcfg_dnsdomain(tctx->lp_ctx), "10.0.0.1");
+    torture_assert_int_equal(tctx, ret, ISC_R_NOPERM,
+                            "Zone transfer accepted for unauthorized IP");
+
+    ret = dlz_allowzonexfr(dbdata, lpcfg_dnsdomain(tctx->lp_ctx), "192.168.0.1");
+    torture_assert_int_equal(tctx, ret, ISC_R_SUCCESS,
+                             "Zone transfer refused for address in authorized IPv4 subnet.");
+
+    ret = dlz_allowzonexfr(dbdata, lpcfg_dnsdomain(tctx->lp_ctx), "192.168.0.2");
+    torture_assert_int_equal(tctx, ret, ISC_R_NOPERM,
+                            "Zone transfer allowed for denied client.");
+
+    dlz_destroy(dbdata);
+    return true;
+}
+
 static struct torture_suite *dlz_bind9_suite(TALLOC_CTX *ctx)
 {
        struct torture_suite *suite = torture_suite_create(ctx, "dlz_bind9");
@@ -1221,6 +1291,7 @@ static struct torture_suite *dlz_bind9_suite(TALLOC_CTX *ctx)
        torture_suite_add_simple_test(suite, "lookup", test_dlz_bind9_lookup);
        torture_suite_add_simple_test(suite, "zonedump", test_dlz_bind9_zonedump);
        torture_suite_add_simple_test(suite, "update01", test_dlz_bind9_update01);
+       torture_suite_add_simple_test(suite, "allowzonexfr", test_dlz_bind9_allowzonexfr);
        return suite;
 }