diff --git a/NEWS b/NEWS index 891675e4e949b728fbe8131ce197cb86855b6399..e6bc4a35ec57cc67c931f436a889df44c130ec3f 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,11 @@ +Knot Resolver 5.4.3 (2021-mm-dd) +================================ + +Bugfixes +-------- +- policy.rpz: improve logs, fix origin detection in files without $ORIGIN + + Knot Resolver 5.4.2 (2021-10-13) ================================ diff --git a/modules/policy/policy.lua b/modules/policy/policy.lua index 41320adf85ad177f5cc96d984b351d7446b91263..1a33a180326e78796108ca9460cb9ec7a3e16d4c 100644 --- a/modules/policy/policy.lua +++ b/modules/policy/policy.lua @@ -404,13 +404,18 @@ local function rpz_parse(action, path) local rrtype_bad = { [kres.type.DNAME] = true, [kres.type.NS] = false, - [kres.type.SOA] = false, [kres.type.DNSKEY] = true, [kres.type.DS] = true, [kres.type.RRSIG] = true, [kres.type.NSEC] = true, [kres.type.NSEC3] = true, } + + -- We generally don't know what zone should be in the file; we try to detect it. + -- Fortunately, it's typical that SOA is the first record, even required for AXFR. + local origin_soa = nil + local warned_soa, warned_bailiwick + local parser = require('zonefile').new() local ok, errstr = parser:open(path) if not ok then @@ -419,7 +424,8 @@ local function rpz_parse(action, path) while true do ok, errstr = parser:parse() if errstr then - log_info(ffi.C.LOG_GRP_POLICY, 'RPZ %s:%d: %s', path, tonumber(parser.line_counter), errstr) + log_warn(ffi.C.LOG_GRP_POLICY, 'RPZ %s:%d: %s', + path, tonumber(parser.line_counter), errstr) end if not ok then break end @@ -427,26 +433,47 @@ local function rpz_parse(action, path) local rdata = ffi.string(parser.r_data, parser.r_data_length) ffi.C.knot_dname_to_lower(full_name) - local prefix_labels = ffi.C.knot_dname_in_bailiwick(full_name, parser.zone_origin) + local origin = origin_soa or parser.zone_origin + local prefix_labels = ffi.C.knot_dname_in_bailiwick(full_name, origin) if prefix_labels < 0 then - log_info(ffi.C.LOG_GRP_POLICY, 'RPZ %s:%d: RR owner "%s" outside the zone (ignored)', - path, tonumber(parser.line_counter), kres.dname2str(full_name)) + if not warned_bailiwick then + warned_bailiwick = true + log_warn(ffi.C.LOG_GRP_POLICY, + 'RPZ %s:%d: RR owner "%s" outside the zone (ignored; reported once per file)', + path, tonumber(parser.line_counter), kres.dname2str(full_name)) + end goto continue end - local bytes = ffi.C.knot_dname_size(full_name) - ffi.C.knot_dname_size(parser.zone_origin) + local bytes = ffi.C.knot_dname_size(full_name) - ffi.C.knot_dname_size(origin) local name = ffi.string(full_name, bytes) .. '\0' if parser.r_type == kres.type.CNAME then if action_map[rdata] then rules[name] = action_map[rdata] else - log_info(ffi.C.LOG_GRP_POLICY, 'RPZ %s:%d: CNAME with custom target in RPZ is not supported yet (ignored)', + log_warn(ffi.C.LOG_GRP_POLICY, + 'RPZ %s:%d: CNAME with custom target in RPZ is not supported yet (ignored)', path, tonumber(parser.line_counter)) end else if #name then local is_bad = rrtype_bad[parser.r_type] + + if parser.r_type == kres.type.SOA then + if origin_soa == nil then + origin_soa = ffi.gc(ffi.C.knot_dname_copy(parser.r_owner, nil), ffi.C.free) + goto continue -- we don't want to modify `new_actions` + else + is_bad = true -- maybe provide more info, but it seems rare + end + elseif origin_soa == nil and not warned_soa then + warned_soa = true + log_warn(ffi.C.LOG_GRP_POLICY, + 'RPZ %s:%d warning: SOA missing as the first record', + path, tonumber(parser.line_counter)) + end + if is_bad == true or (is_bad == false and prefix_labels ~= 0) then log_warn(ffi.C.LOG_GRP_POLICY, 'RPZ %s:%d warning: RR type %s is not allowed in RPZ (ignored)', path, tonumber(parser.line_counter), kres.tostring.type[parser.r_type]) diff --git a/modules/policy/policy.rpz.test.lua b/modules/policy/policy.rpz.test.lua index 047b27f5cd1fb069da3754a97413e03e40f41607..70ef9fb6f7a6cbbc513ea48c3868de04bfc7ca99 100644 --- a/modules/policy/policy.rpz.test.lua +++ b/modules/policy/policy.rpz.test.lua @@ -44,13 +44,22 @@ local function test_rpz() {'2001:db8::2', '2001:db8::1'}) end +local function test_rpz_soa() + check_answer('"CNAME ." return NXDOMAIN (SOA origin)', + 'nxdomain-fqdn.', kres.type.A, kres.rcode.NXDOMAIN) + check_answer('"CNAME *." return NODATA (SOA origin)', + 'nodata-fqdn.', kres.type.A, kres.rcode.NOERROR, {}) +end + net.ipv4 = false net.ipv6 = false prepare_cache() policy.add(policy.rpz(policy.DENY, 'policy.test.rpz')) +policy.add(policy.rpz(policy.DENY, 'policy.test.rpz.soa')) return { test_rpz, + test_rpz_soa, } diff --git a/modules/policy/policy.test.rpz b/modules/policy/policy.test.rpz index 80b7106f40170b44e2773e1ed62def6181d7c124..d962e9fcc982c53df7e0fc6ffaefaf4b020af46e 100644 --- a/modules/policy/policy.test.rpz +++ b/modules/policy/policy.test.rpz @@ -9,9 +9,9 @@ nodata CNAME *. rpzdrop CNAME rpz-drop. rpzpassthru CNAME rpz-passthru. rra A 192.168.5.5 -rra-zonename-suffix A 192.168.6.6 -testdomain.rra.testdomain. A 192.168.7.7 -CaSe.SeNSiTiVe A 192.168.8.8 +rra-zonename-suffix A 192.168.6.6 +testdomain.rra.testdomain. A 192.168.7.7 +CaSe.SeNSiTiVe A 192.168.8.8 two.records AAAA 2001:db8::2 two.records AAAA 2001:db8::1 diff --git a/modules/policy/policy.test.rpz.soa b/modules/policy/policy.test.rpz.soa new file mode 100644 index 0000000000000000000000000000000000000000..ad18aa49c9f8e99f48e62472231c37e7036e7945 --- /dev/null +++ b/modules/policy/policy.test.rpz.soa @@ -0,0 +1,5 @@ +test2domain. SOA nonexistent.test2domain. test2domain. 1 12h 15m 3w 2h + NS nonexistent.test2domain. + +nxdomain-fqdn.test2domain. CNAME . +nodata-fqdn.test2domain. CNAME *.